vendor/store.shopware.com/h1webblog/src/Seo/SeoUrlRoute/SeoUrlUpdateListener.php line 168

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace H1web\Blog\Seo\SeoUrlRoute;
  3. use Cocur\Slugify\SlugifyInterface;
  4. use Doctrine\DBAL\Connection;
  5. use Shopware\Core\Content\Seo\SeoUrl\SeoUrlCollection;
  6. use Shopware\Core\Content\Seo\SeoUrlUpdater;
  7. use Shopware\Core\Framework\Context;
  8. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
  9. use H1web\Blog\Blog\Events\BlogIndexerEvent;
  10. use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenEvent;
  11. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  12. use Shopware\Core\Framework\DataAbstractionLayer\Search\EntitySearchResult;
  13. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  14. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsAnyFilter;
  15. use Shopware\Core\Framework\Uuid\Uuid;
  16. use Shopware\Core\System\Tag\TagCollection;
  17. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  18. class SeoUrlUpdateListener implements EventSubscriberInterface
  19. {
  20.     /**
  21.      * @var SeoUrlUpdater
  22.      */
  23.     private $seoUrlUpdater;
  24.     /**
  25.      * @var SlugifyInterface
  26.      */
  27.     private $slugify;
  28.     /**
  29.      * @var EntityRepository
  30.      */
  31.     private $seoUrlRepository;
  32.     /**
  33.      * @var EntityRepository
  34.      */
  35.     private $tagRepository;
  36.     /**
  37.      * @var Connection
  38.      */
  39.     private $connection;
  40.     /**
  41.      * @var string
  42.      */
  43.     public const TAG_OVERVIEW_PREFIX 'blog/tag/';
  44.     /**
  45.      * SeoUrlUpdateListener constructor.
  46.      * @param SeoUrlUpdater $seoUrlUpdater
  47.      * @param SlugifyInterface $slugify
  48.      * @param EntityRepository $seoUrlRepository
  49.      * @param EntityRepository $tagRepository
  50.      * @param Connection $connection
  51.      */
  52.     public function __construct(
  53.         SeoUrlUpdater $seoUrlUpdater,
  54.         SlugifyInterface $slugify,
  55.         EntityRepository $seoUrlRepository,
  56.         EntityRepository $tagRepository,
  57.         Connection $connection
  58.     )
  59.     {
  60.         $this->seoUrlUpdater $seoUrlUpdater;
  61.         $this->slugify $slugify;
  62.         $this->seoUrlRepository $seoUrlRepository;
  63.         $this->tagRepository $tagRepository;
  64.         $this->connection $connection;
  65.     }
  66.     /**
  67.      * @return string[]
  68.      */
  69.     public static function getSubscribedEvents()
  70.     {
  71.         return [
  72.             BlogIndexerEvent::class => 'updateBlogUrls',
  73.             'tag.written' => 'updateBlogTagUrl',
  74.             'tag.deleted' => 'deleteBlogTagUrl',
  75.             'sales_channel.written' => 'onSalesChannelWritten'
  76.         ];
  77.     }
  78.     /**
  79.      * @param EntityWrittenEvent $event
  80.      */
  81.     public function onSalesChannelWritten(EntityWrittenEvent $event): void
  82.     {
  83.         $this->updateTagOverviewSeoUrls($event->getWriteResults()[0]->getPrimaryKey(), $event->getContext());
  84.     }
  85.     /**
  86.      * @param EntityWrittenEvent $event
  87.      */
  88.     public function updateBlogTagUrl(EntityWrittenEvent $event)
  89.     {
  90.         $ids $event->getIds();
  91.         /* @var TagCollection $tags */
  92.         $tags $this->tagRepository->search(
  93.             new Criteria($ids),
  94.             $event->getContext()
  95.         );
  96.         $this->createTagOverviewUrls($tags);
  97.     }
  98.     /**
  99.      * @param EntitySearchResult $tags
  100.      */
  101.     public function createTagOverviewUrls(EntitySearchResult $tags)
  102.     {
  103.         foreach ($tags as $tag) {
  104.             $slug $this->slugify->slugify($tag->getName());
  105.             $url $this::TAG_OVERVIEW_PREFIX $slug;
  106.             // Find existing urls for this tag
  107.             $criteria = new Criteria();
  108.             $criteria->addFilter(new EqualsFilter('foreignKey'$tag->getId()));
  109.             $existingUrls $this->seoUrlRepository->search(
  110.                 $criteria,
  111.                 $tags->getContext()
  112.             );
  113.             $createNew true;
  114.             $updates = [];
  115.             foreach ($existingUrls as $existingUrl) {
  116.                 $updates[] = [
  117.                     'id' => $existingUrl->getId(),
  118.                     'isCanonical' => false,
  119.                 ];
  120.                 if ($existingUrl->getSeoPathInfo() == $url) {
  121.                     // Do not create a new url if it is already present
  122.                     $createNew false;
  123.                 }
  124.             }
  125.             if ($createNew) {
  126.                 foreach ($this->connection->fetchAll("SELECT DISTINCT HEX(language_id) FROM sales_channel_language") as $language) {
  127.                     $language strtolower(current($language));
  128.                     $this->seoUrlRepository->create(
  129.                         [$this->prepareSeoUrlData($language$tag->getId(), $url)],
  130.                         $tags->getContext()
  131.                     );
  132.                 }
  133.                 if (sizeof($updates) > 0) {
  134.                     // Set all old urls to non canonical
  135.                     $this->seoUrlRepository->update(
  136.                         $updates,
  137.                         $tags->getContext()
  138.                     );
  139.                 }
  140.             }
  141.         }
  142.     }
  143.     /**
  144.      * We have to go through the seo_urls since SW
  145.      * Doesn't autoremove associated tags/entities in on a delete
  146.      * Tag urls are created by the blog module so go through them on delete
  147.      *
  148.      * @param EntityWrittenEvent $event
  149.      */
  150.     public function deleteBlogTagUrl(EntityWrittenEvent $event)
  151.     {
  152.         $criteria = new Criteria();
  153.         // Search By fk field since no real FK is assigned for tags in the seo_url table
  154.         // Name should be more in line of foreign_id
  155.         $criteria->addFilter(new EqualsAnyFilter('foreignKey'$event->getIds()));
  156.         $existingUrls $this->seoUrlRepository->searchIds(
  157.             $criteria,
  158.             $event->getContext()
  159.         );
  160.         if ($existingUrls->getTotal() > 0) {
  161.             // Remap because getIds throws errors and SW doesn't expect an id list on delete
  162.             $this->seoUrlRepository->delete(
  163.                 array_map(function ($id) {
  164.                     return ['id' => $id];
  165.                 }, $existingUrls->getIds()),
  166.                 $event->getContext()
  167.             );
  168.         }
  169.     }
  170.     /**
  171.      * @param BlogIndexerEvent $event
  172.      */
  173.     public function updateBlogUrls(BlogIndexerEvent $event): void
  174.     {
  175.         $this->seoUrlUpdater->update(BlogPageSeoUrlRoute::ROUTE_NAME$event->getIds());
  176.     }
  177.     private function updateTagOverviewSeoUrls(string $salesChannelIdContext $context): void
  178.     {
  179.         /** @var TagCollection $tags */
  180.         $tags $this->tagRepository->search((new Criteria())->addAssociation('blogs'), $context)
  181.             ->getEntities()
  182.             ->filter(static function ($tag) {
  183.                 return $tag->getExtension('blogs')->count();
  184.             });
  185.         $languageIds $this->getSalesChannelLanguageIds($salesChannelId);
  186.         $seoUrls $this->getExistingSeoUrls($languageIds$tags$context);
  187.         $blogTagSeoUrls = [];
  188.         foreach ($tags as $tag) {
  189.             foreach ($languageIds as $languageId) {
  190.                 $seoUrl $seoUrls->filter(static function ($seoUrl) use ($tag$languageId) {
  191.                     return $seoUrl->getForeignKey() === $tag->getId() && $seoUrl->getLanguageId() === $languageId;
  192.                 });
  193.                 if ($seoUrl->count()) {
  194.                     continue;
  195.                 }
  196.                 $slug $this->slugify->slugify($tag->getName());
  197.                 $url $this::TAG_OVERVIEW_PREFIX $slug;
  198.                 $blogTagSeoUrls[] = $this->prepareSeoUrlData($languageId$tag->getId(), $url);
  199.             }
  200.         }
  201.         if (!empty($blogTagSeoUrls)) {
  202.             $this->seoUrlRepository->create($blogTagSeoUrls$context);
  203.         }
  204.     }
  205.     private function getSalesChannelLanguageIds(string $salesChannelId): array
  206.     {
  207.         $languageIds $this->connection->fetchAllNumeric(
  208.             'SELECT lower(HEX(language_id)) as languageId from sales_channel_language
  209.                 where sales_channel_id=:salesChannelId',
  210.             ['salesChannelId' => Uuid::fromHexToBytes($salesChannelId)],
  211.         );
  212.         return array_map(static function ($languageIdArray) {
  213.             return array_shift($languageIdArray);
  214.         }, $languageIds);
  215.     }
  216.     private function getExistingSeoUrls(
  217.         array $languageIds,
  218.         TagCollection $tags,
  219.         Context $context
  220.     ): SeoUrlCollection {
  221.         /** @var SeoUrlCollection $seoUrls */
  222.         $seoUrls $this->seoUrlRepository->search(
  223.             (new Criteria())
  224.                 ->addFilter(new EqualsFilter('routeName''frontend.h1webblog.tag_overview'))
  225.                 ->addFilter(new EqualsAnyFilter('foreignKey'$tags->getIds()))
  226.                 ->addFilter(new EqualsAnyFilter('languageId'$languageIds)),
  227.             $context,
  228.         )->getEntities();
  229.         return $seoUrls;
  230.     }
  231.     private function prepareSeoUrlData(string $languageIdstring $tagIdstring $url): array
  232.     {
  233.         return [
  234.             'languageId' => $languageId,
  235.             'foreignKey' => $tagId,
  236.             'routeName' => 'frontend.h1webblog.tag_overview',
  237.             'pathInfo' => '/h1webblog/tag/' $tagId,
  238.             'seoPathInfo' => $url,
  239.             'isCanonical' => true,
  240.             'isModified' => false,
  241.             'isDeleted' => false,
  242.         ];
  243.     }
  244. }