<?php declare(strict_types=1);
namespace Webcellent\InternalLinks\Subscriber;
use Shopware\Core\Content\Cms\CmsPageEntity;
use Shopware\Core\Content\Cms\Events\CmsPageLoadedEvent;
use Shopware\Core\Content\Cms\SalesChannel\Struct\TextStruct;
use Shopware\Core\Content\Cms\SalesChannel\Struct\ProductDescriptionReviewsStruct;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Webcellent\InternalLinks\Entity\InternalLinksEntity;
class StorefrontSubscriber implements EventSubscriberInterface {
private EntityRepository $linkRepository;
public function __construct(EntityRepository $linkRepository) {
$this->linkRepository = $linkRepository;
}
public static function getSubscribedEvents(): array {
return [
CmsPageLoadedEvent::class => 'onPageLoadedEvent'
];
}
public function onPageLoadedEvent(CmsPageLoadedEvent $event) {
$cmsCollection = $event->getResult();
$salesChannel = $event->getSalesChannelContext()->getSalesChannelId();
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('salesChannelId', $salesChannel))->addSorting(new FieldSorting('updatedAt', FieldSorting::ASCENDING));
$internalLinks = $this->linkRepository->search($criteria, $event->getContext());
/**
* Convert $internalLinks to array,
* sort it by keyword length so longer keywords are processed first
*/
$internalLinks = iterator_to_array($internalLinks);
usort($internalLinks, function($link1, $link2) {
return strlen($link2->getKeyword()) >= strlen($link1->getKeyword());
});
$alreadyReplaced = array();
/** @var CmsPageEntity $cmsEntity */
foreach ($cmsCollection as $cmsEntity) {
$sections = $cmsEntity->getSections();
foreach ($sections as $section) {
$blocks = $section->getBlocks();
foreach ($blocks as $block) {
$slots = $block->getSlots();
foreach ($slots as $slot) {
$data = $slot->getData();
if ($data instanceof TextStruct) {
$content = $data->getContent();
/** @var InternalLinksEntity $keyword */
foreach ($internalLinks as $internalLink) {
$keyword = strtolower($internalLink->getKeyword());
$linkInHeadings = $internalLink->getLinkInHeadings();
$currentURLs = [
((array_key_exists('HTTPS', $_SERVER) && $_SERVER['HTTPS'] == 'on') ? 'https://' : 'http://') . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'],
str_replace('www.', '', ((array_key_exists('HTTPS', $_SERVER) && $_SERVER['HTTPS'] == 'on') ? 'https://' : 'http://') . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']),
$_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'],
str_replace('www.', '', $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'])
];
// skip internal link if currently on to be linked page
if (in_array($internalLink->getLink(), $currentURLs)) {
continue;
}
$limit = $internalLink->getMaxLinks();
if (!is_int($limit) || $limit < -1) {
$limit = 1;
}
if ($limit <= 0) {
$limit = 10000;
}
if (!in_array($keyword, $alreadyReplaced)) {
$content = preg_replace('/\b(' . $keyword . ')(?![^<]*<\/a>)' . ($linkInHeadings ? '' : '(?![^<]*<\/h1>)(?![^<]*<\/h2>)(?![^<]*<\/h3>)(?![^<]*<\/h4>)') . '\b/i', $this->generateReplacement($internalLink, "$1"), $content, $limit, $count);
if ($count >= $limit) {
$alreadyReplaced[] = $keyword;
}
}
}
$content = str_replace(['&wctlt;', '&wctgt;'], ['<', '>'], $content);
$data->setContent($content);
} elseif ($data instanceof ProductDescriptionReviewsStruct) {
$product = $data->getProduct();
$content = $product->getTranslation('description');
foreach ($internalLinks as $internalLink) {
$keyword = strtolower($internalLink->getKeyword());
$linkInHeadings = $internalLink->getLinkInHeadings();
$currentURLs = [
((array_key_exists('HTTPS', $_SERVER) && $_SERVER['HTTPS'] == 'on') ? 'https://' : 'http://') . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'],
str_replace('www.', '', ((array_key_exists('HTTPS', $_SERVER) && $_SERVER['HTTPS'] == 'on') ? 'https://' : 'http://') . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']),
$_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'],
str_replace('www.', '', $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'])
];
// skip internal link if currently on to be linked page
if (in_array($internalLink->getLink(), $currentURLs)) {
continue;
}
$limit = $internalLink->getMaxLinks();
if (!is_int($limit) || $limit < -1) {
$limit = 1;
}
if ($limit <= 0) {
$limit = 10000;
}
$content = preg_replace('/\b(' . $keyword . ')(?![^<]*<\/a>)' . ($linkInHeadings ? '' : '(?![^<]*<\/h1>)(?![^<]*<\/h2>)(?![^<]*<\/h3>)(?![^<]*<\/h4>)') . '\b/i', $this->generateReplacement($internalLink, "$1"), $content, $limit, $count);
if ($count >= $limit) {
$alreadyReplaced[] = $keyword;
}
}
$content = str_replace(['&wctlt;', '&wctgt;'], ['<', '>'], $content);
$product->setDescription($content);
$product->addTranslated('description', $content);
$data->setProduct($product);
}
}
}
}
}
}
private function generateReplacement(InternalLinksEntity $internalLink, string $originalContent) {
$tag = '&wctlt;a href="' . $internalLink->getLink() . '" id=' . $internalLink->getId() . ' webcellent-internal-link=true class="internal-link ' . $internalLink->getClasses() . '"';
if ($internalLink->isOpenInNewTab()) {
$tag .= ' target="_blank" rel="noreferrer"';
}
if ($internalLink->getTitle()) {
$tag .= ' title="' . $internalLink->getTitle() . '"&wctgt;' . $originalContent . '&wctlt;/a&wctgt;';
} else {
$tag .= '&wctgt;' . $originalContent . '&wctlt;/a&wctgt;';
}
return $tag;
}
}