vendor/pimcore/pimcore/lib/Tool/Glossary/Processor.php line 78

Open in your IDE?
  1. <?php
  2. /**
  3.  * Pimcore
  4.  *
  5.  * This source file is available under two different licenses:
  6.  * - GNU General Public License version 3 (GPLv3)
  7.  * - Pimcore Commercial License (PCL)
  8.  * Full copyright and license information is available in
  9.  * LICENSE.md which is distributed with this source code.
  10.  *
  11.  *  @copyright  Copyright (c) Pimcore GmbH (http://www.pimcore.org)
  12.  *  @license    http://www.pimcore.org/license     GPLv3 and PCL
  13.  */
  14. namespace Pimcore\Tool\Glossary;
  15. use Pimcore\Cache;
  16. use Pimcore\Http\Request\Resolver\DocumentResolver;
  17. use Pimcore\Http\Request\Resolver\EditmodeResolver;
  18. use Pimcore\Http\RequestHelper;
  19. use Pimcore\Model\Document;
  20. use Pimcore\Model\Glossary;
  21. use Pimcore\Model\Site;
  22. use Pimcore\Tool\DomCrawler;
  23. /**
  24.  * @internal
  25.  */
  26. class Processor
  27. {
  28.     /**
  29.      * @var RequestHelper
  30.      */
  31.     private $requestHelper;
  32.     /**
  33.      * @var EditmodeResolver
  34.      */
  35.     private $editmodeResolver;
  36.     /**
  37.      * @var DocumentResolver
  38.      */
  39.     private $documentResolver;
  40.     /**
  41.      * @var array
  42.      */
  43.     private $blockedTags = [
  44.         'a''script''style''code''pre''textarea''acronym',
  45.         'abbr''option''h1''h2''h3''h4''h5''h6',
  46.     ];
  47.     /**
  48.      * @param RequestHelper $requestHelper
  49.      * @param EditmodeResolver $editmodeResolver
  50.      * @param DocumentResolver $documentResolver
  51.      */
  52.     public function __construct(
  53.         RequestHelper $requestHelper,
  54.         EditmodeResolver $editmodeResolver,
  55.         DocumentResolver $documentResolver
  56.     ) {
  57.         $this->requestHelper $requestHelper;
  58.         $this->editmodeResolver $editmodeResolver;
  59.         $this->documentResolver $documentResolver;
  60.     }
  61.     /**
  62.      * Process glossary entries in content string
  63.      *
  64.      * @param string $content
  65.      * @param array $options
  66.      *
  67.      * @return string
  68.      */
  69.     public function process(string $content, array $options): string
  70.     {
  71.         $data $this->getData();
  72.         if (empty($data)) {
  73.             return $content;
  74.         }
  75.         $options array_merge([
  76.             'limit' => -1,
  77.         ], $options);
  78.         if ($this->editmodeResolver->isEditmode()) {
  79.             return $content;
  80.         }
  81.         // why not using a simple str_ireplace(array(), array(), $subject) ?
  82.         // because if you want to replace the terms "Donec vitae" and "Donec" you will get nested links, so the content
  83.         // of the html must be reloaded every search term to ensure that there is no replacement within a blocked tag
  84.         $html = new DomCrawler($content);
  85.         $es $html->filterXPath('//*[normalize-space(text())]');
  86.         $tmpData = [
  87.             'search' => [],
  88.             'replace' => [],
  89.         ];
  90.         // get initial document from request (requested document, if it was a "document" request)
  91.         $currentDocument $this->documentResolver->getDocument();
  92.         $currentUri $this->requestHelper->getMainRequest()->getRequestUri();
  93.         foreach ($data as $entry) {
  94.             if ($currentDocument && $currentDocument instanceof Document) {
  95.                 // check if the current document is the target link (id check)
  96.                 if ($entry['linkType'] == 'internal' && $currentDocument->getId() == $entry['linkTarget']) {
  97.                     continue;
  98.                 }
  99.                 // check if the current document is the target link (path check)
  100.                 if ($currentDocument->getFullPath() == rtrim($entry['linkTarget'], ' /')) {
  101.                     continue;
  102.                 }
  103.             }
  104.             // check if the current URI is the target link (path check)
  105.             if ($currentUri == rtrim($entry['linkTarget'], ' /')) {
  106.                 continue;
  107.             }
  108.             $tmpData['search'][] = $entry['search'];
  109.             $tmpData['replace'][] = $entry['replace'];
  110.         }
  111.         $data $tmpData;
  112.         $data['count'] = array_fill(0count($data['search']), 0);
  113.         $es->each(function ($parentNode$i) use ($options$data) {
  114.             /** @var DomCrawler|null $parentNode */
  115.             $text $parentNode->html();
  116.             if (
  117.                 $parentNode instanceof DomCrawler &&
  118.                 !in_array((string)$parentNode->nodeName(), $this->blockedTags) &&
  119.                 strlen(trim($text))
  120.             ) {
  121.                 $originalText $text;
  122.                 if ($options['limit'] < 0) {
  123.                     $text preg_replace($data['search'], $data['replace'], $text);
  124.                 } else {
  125.                     foreach ($data['search'] as $index => $search) {
  126.                         if ($data['count'][$index] < $options['limit']) {
  127.                             $limit $options['limit'] - $data['count'][$index];
  128.                             $text preg_replace($search$data['replace'][$index], $text$limit$count);
  129.                             $data['count'][$index] += $count;
  130.                         }
  131.                     }
  132.                 }
  133.                 if ($originalText !== $text) {
  134.                     $domNode $parentNode->getNode(0);
  135.                     $fragment $domNode->ownerDocument->createDocumentFragment();
  136.                     $fragment->appendXML($text);
  137.                     $clone $domNode->cloneNode();
  138.                     $clone->appendChild($fragment);
  139.                     $domNode->parentNode->replaceChild($clone$domNode);
  140.                 }
  141.             }
  142.         });
  143.         $result $html->html();
  144.         $html->clear();
  145.         unset($html);
  146.         return $result;
  147.     }
  148.     /**
  149.      * @return array
  150.      */
  151.     private function getData(): array
  152.     {
  153.         $locale $this->requestHelper->getMainRequest()->getLocale();
  154.         if (!$locale) {
  155.             return [];
  156.         }
  157.         $siteId '';
  158.         if (Site::isSiteRequest()) {
  159.             $siteId Site::getCurrentSite()->getId();
  160.         }
  161.         $cacheKey 'glossary_' $locale '_' $siteId;
  162.         if (Cache\Runtime::isRegistered($cacheKey)) {
  163.             return Cache\Runtime::get($cacheKey);
  164.         }
  165.         if (!$data Cache::load($cacheKey)) {
  166.             $list = new Glossary\Listing();
  167.             $list->setCondition("(language = ? OR language IS NULL OR language = '') AND (site = ? OR site IS NULL OR site = '')", [$locale$siteId]);
  168.             $list->setOrderKey('LENGTH(`text`)'false);
  169.             $list->setOrder('DESC');
  170.             $data $list->getDataArray();
  171.             $data $this->prepareData($data);
  172.             Cache::save($data$cacheKey, ['glossary'], null995);
  173.             Cache\Runtime::set($cacheKey$data);
  174.         }
  175.         return $data;
  176.     }
  177.     /**
  178.      * @param array $data
  179.      *
  180.      * @return array
  181.      */
  182.     private function prepareData(array $data): array
  183.     {
  184.         $mappedData = [];
  185.         // fix htmlentities issues
  186.         $tmpData = [];
  187.         foreach ($data as $d) {
  188.             if ($d['text'] != htmlentities($d['text'], null'UTF-8')) {
  189.                 $td $d;
  190.                 $td['text'] = htmlentities($d['text'], null'UTF-8');
  191.                 $tmpData[] = $td;
  192.             }
  193.             $tmpData[] = $d;
  194.         }
  195.         $data $tmpData;
  196.         // prepare data
  197.         foreach ($data as $d) {
  198.             if (!($d['link'] || $d['abbr'])) {
  199.                 continue;
  200.             }
  201.             $r $d['text'];
  202.             if ($d['abbr']) {
  203.                 $r '<abbr class="pimcore_glossary" title="' $d['abbr'] . '">' $r '</abbr>';
  204.             }
  205.             $linkType '';
  206.             $linkTarget '';
  207.             if ($d['link']) {
  208.                 $linkType 'external';
  209.                 $linkTarget $d['link'];
  210.                 if ((int)$d['link']) {
  211.                     if ($doc Document::getById($d['link'])) {
  212.                         $d['link'] = $doc->getFullPath();
  213.                         $linkType 'internal';
  214.                         $linkTarget $doc->getId();
  215.                     }
  216.                 }
  217.                 $r '<a class="pimcore_glossary" href="' $d['link'] . '">' $r '</a>';
  218.             }
  219.             // add PCRE delimiter and modifiers
  220.             if ($d['exactmatch']) {
  221.                 $d['text'] = '/<a.*\/a>(*SKIP)(*FAIL)|(?<!\w)' preg_quote($d['text'], '/') . '(?!\w)/';
  222.             } else {
  223.                 $d['text'] = '/<a.*\/a>(*SKIP)(*FAIL)|' preg_quote($d['text'], '/') . '/';
  224.             }
  225.             if (!$d['casesensitive']) {
  226.                 $d['text'] .= 'i';
  227.             }
  228.             $mappedData[] = [
  229.                 'replace' => $r,
  230.                 'search' => $d['text'],
  231.                 'linkType' => $linkType,
  232.                 'linkTarget' => $linkTarget,
  233.             ];
  234.         }
  235.         return $mappedData;
  236.     }
  237. }