Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <?php
- declare(strict_types=1);
- namespace Vendor\Extension\Routing;
- use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
- use Doctrine\DBAL\FetchMode;
- use Vendor\Extension\Service\Api\Client;
- use Vendor\Extension\Service\Api\Entity;
- use TYPO3\CMS\Core\Database\ConnectionPool;
- use TYPO3\CMS\Core\DataHandling\SlugHelper;
- use TYPO3\CMS\Core\Log\Logger;
- use TYPO3\CMS\Core\Log\LogManager;
- use TYPO3\CMS\Core\Routing\Aspect\PersistedMappableAspectInterface;
- use TYPO3\CMS\Core\Site\SiteLanguageAwareTrait;
- use TYPO3\CMS\Core\Utility\GeneralUtility;
- class ApiAspect implements PersistedMappableAspectInterface
- {
- use SiteLanguageAwareTrait;
- const TABLE_SLUGS = "tx_extension_api_slugs";
- const TABLE_REDIRECTS = "tx_extension_api_slugs_redirects";
- /**
- * The time we wait and dont check if a slug has changed
- * This will increase performance because we avoid a lot of I/O
- */
- const DONT_CHECK_FOR = 60 * 60 * 24 * 7;
- /**
- * @var ConnectionPool
- */
- protected $connectionPool;
- /**
- * @var Logger
- */
- protected $logger;
- public function __construct()
- {
- $this->connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
- $this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(get_class());
- }
- /**
- * @param string $value
- * @return string|null
- * @throws
- */
- public function generate(string $value): ?string
- {
- $language = $this->getSiteLanguage()->getTwoLetterIsoCode();
- $q = $this->connectionPool->getQueryBuilderForTable(static::TABLE_SLUGS);
- $statement = $q
- ->select("*")
- ->from(static::TABLE_SLUGS)
- ->where(
- $q->expr()->andX(
- $q->expr()->eq('uuid', $q->createNamedParameter($value)),
- $q->expr()->eq('language', $q->createNamedParameter($language))
- )
- )
- ->setMaxResults(1)
- ->execute();
- $slug = null;
- if ($statement->rowCount() === 1) {
- $row = $statement->fetch(FetchMode::ASSOCIATIVE);
- $slug = $row["slug"];
- if (time() - static::DONT_CHECK_FOR > (int) $row["tstamp"]) {
- $newSlug = $this->generateSlugFromData($this->getData($value), $value);
- if ($this->needsUpdate($slug, $newSlug)) {
- // seems like we need a new slug because the name changed
- $this->updateSlug($value, $language, $slug, $newSlug);
- return $newSlug;
- } else {
- // update timestamp
- $this->touchSlug($value, $language, $slug);
- }
- }
- return $slug;
- } else {
- $slug = $this->generateSlugFromData($this->getData($value), $value);
- $this->insertNewSlug($value, $language, $slug);
- }
- return $slug;
- }
- /**
- * @param string $value
- * @return string|null
- */
- public function resolve(string $value): ?string
- {
- $q = $this->connectionPool->getQueryBuilderForTable(static::TABLE_SLUGS);
- $language = "de";
- if ($this->getSiteLanguage()) {
- $language = $this->getSiteLanguage()->getTwoLetterIsoCode();
- }
- $statement = $q->select("uuid")->from(static::TABLE_SLUGS)->where(
- $q->expr()->andX(
- $q->expr()->eq("slug", $q->createNamedParameter($value)),
- $q->expr()->eq("language", $q->createNamedParameter($language)),
- )
- )->execute();
- if ($statement->rowCount() > 0) {
- $row = $statement->fetch(FetchMode::ASSOCIATIVE);
- return $row["uuid"];
- } else {
- $r = $this->connectionPool->getQueryBuilderForTable(static::TABLE_REDIRECTS);
- $statement = $r->select("uuid")->from(static::TABLE_REDIRECTS)->where(
- $r->expr()->andX(
- $r->expr()->eq("slug", $r->createNamedParameter($value)),
- $r->expr()->eq("language", $r->createNamedParameter($language)),
- )
- )->execute();
- if ($statement->rowCount() > 0) {
- $row = $statement->fetch(FetchMode::ASSOCIATIVE);
- $uuid = $row["uuid"];
- $statement = $q->select("*")->from(static::TABLE_SLUGS)->where(
- $q->expr()->andX(
- $q->expr()->eq("uuid", $q->createNamedParameter($uuid)),
- $q->expr()->eq("language", $q->createNamedParameter($language)),
- )
- )->execute();
- if ($statement->rowCount() > 0) {
- $row = $statement->fetch(FetchMode::ASSOCIATIVE);
- $this->sendRedirect($value, $row["slug"]);
- } else {
- // log the error and return the uuid so the content is delivered as it should
- $this->logger->error(
- "Can not find correct url for redirected uuid",
- [
- "uuid" => $uuid,
- "slug" => $value,
- "language" => $language,
- ]
- );
- return $uuid;
- }
- } else {
- return null;
- }
- }
- }
- protected function getData(string $uuid): ?Entity
- {
- return Client::getInstance()->get($uuid);
- }
- protected function needsUpdate(string $currentSlug, string $newSlug): bool
- {
- if ($newSlug === $currentSlug) {
- // its the same, no update
- return false;
- } elseif (strlen($newSlug) > strlen($currentSlug)) {
- // the new slug is longer, so it must be updated
- return true;
- } elseif (strlen($newSlug) < strlen($currentSlug) && strpos($currentSlug, $newSlug) === 0) {
- // the new slug is shorter but at the beginning of the old slug, so it might be an appendix (-1 or -2...)
- $diff = strlen($currentSlug) - strlen($newSlug);
- $remaining = substr($currentSlug, $diff * -1);
- if (preg_match("/^\-\d{1,3}$/", $remaining)) {
- // so it is really the case, that we deal with an appendix, so nothing to do for now
- return false;
- }
- // something else changed, update required
- return true;
- }
- // dont change by default
- return false;
- }
- protected function sendRedirect(string $currentSlug, string $newSlug): void
- {
- // @todo can this be solved by some typo3 api?
- $currentUri = $_SERVER['REQUEST_URI'];
- $newUrl = str_replace($currentSlug, $newSlug, $currentUri);
- header("Location: $newUrl", true, 301);
- exit;
- }
- protected function updateSlug(string $uuid, string $language, string $slug, string $newSlug): void
- {
- $q = $this->connectionPool->getQueryBuilderForTable(static::TABLE_REDIRECTS);
- $q->insert(static::TABLE_REDIRECTS)->values([
- "uuid" => $uuid,
- "language" => $language,
- "slug" => $slug,
- "crdate" => (string) time(),
- ])->execute();
- $q = $this->connectionPool->getQueryBuilderForTable(static::TABLE_SLUGS);
- $q->delete(static::TABLE_SLUGS)->where(
- $q->expr()->andX(
- $q->expr()->eq("slug", $q->createNamedParameter($slug)),
- $q->expr()->eq("language", $q->createNamedParameter($language)),
- )
- )->execute();
- $this->insertNewSlug($uuid, $language, $newSlug);
- }
- protected function insertNewSlug(string $uuid, string $language, string $slug): void
- {
- $q = $this->connectionPool->getQueryBuilderForTable(static::TABLE_SLUGS);
- $try = 0;
- while ($try < 100) {
- try {
- $insertSlug = $try === 0 ? $slug : "{$slug}-{$try}";
- $q->insert(static::TABLE_SLUGS)->values([
- "uuid" => $uuid,
- "language" => $language,
- "slug" => $insertSlug,
- "tstamp" => (string) time(),
- ])->execute();
- if ($try > 0) {
- $this->logger->info("slug collision for uuid $uuid, adding slug with appendix $try");
- }
- break;
- } catch (UniqueConstraintViolationException $error) {
- $try += 1;
- }
- }
- }
- protected function touchSlug(string $uuid, string $language, string $slug): void
- {
- $q = $this->connectionPool->getQueryBuilderForTable(static::TABLE_SLUGS);
- $q->update(static::TABLE_SLUGS)
- ->set("tstamp", (string) time())
- ->where($q->expr()->andX(
- $q->expr()->eq("uuid", $q->createNamedParameter($uuid)),
- $q->expr()->eq("language", $q->createNamedParameter($language)),
- $q->expr()->eq("slug", $q->createNamedParameter($slug)),
- ))->execute();
- }
- protected function generateSlugFromData(Entity $data, string $uuid): string
- {
- $parts = [
- $data['contentType'] ?? null,
- $data['name'] ?? $uuid,
- ];
- $helper = new SlugHelper("", "", []);
- $input = implode("-", array_filter($parts));
- $input = str_replace("/", "-", $input);
- return $helper->sanitize($input);
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement