vendor/sulu/sulu/src/Sulu/Component/Content/Repository/ContentRepository.php line 431

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of Sulu.
  4.  *
  5.  * (c) Sulu GmbH
  6.  *
  7.  * This source file is subject to the MIT license that is bundled
  8.  * with this source code in the file LICENSE.
  9.  */
  10. namespace Sulu\Component\Content\Repository;
  11. use Jackalope\Query\QOM\PropertyValue;
  12. use Jackalope\Query\Row;
  13. use PHPCR\ItemNotFoundException;
  14. use PHPCR\Query\QOM\QueryObjectModelConstantsInterface;
  15. use PHPCR\Query\QOM\QueryObjectModelFactoryInterface;
  16. use PHPCR\SessionInterface;
  17. use PHPCR\Util\PathHelper;
  18. use PHPCR\Util\QOM\QueryBuilder;
  19. use Sulu\Bundle\SecurityBundle\System\SystemStoreInterface;
  20. use Sulu\Component\Content\Compat\LocalizationFinderInterface;
  21. use Sulu\Component\Content\Compat\Structure;
  22. use Sulu\Component\Content\Compat\StructureManagerInterface;
  23. use Sulu\Component\Content\Compat\StructureType;
  24. use Sulu\Component\Content\Document\Behavior\SecurityBehavior;
  25. use Sulu\Component\Content\Document\RedirectType;
  26. use Sulu\Component\Content\Document\Subscriber\SecuritySubscriber;
  27. use Sulu\Component\Content\Document\WorkflowStage;
  28. use Sulu\Component\Content\Repository\Mapping\MappingInterface;
  29. use Sulu\Component\DocumentManager\PropertyEncoder;
  30. use Sulu\Component\Localization\Localization;
  31. use Sulu\Component\PHPCR\SessionManager\SessionManagerInterface;
  32. use Sulu\Component\Security\Authentication\UserInterface;
  33. use Sulu\Component\Security\Authorization\AccessControl\DescendantProviderInterface;
  34. use Sulu\Component\Util\SuluNodeHelper;
  35. use Sulu\Component\Webspace\Manager\WebspaceManagerInterface;
  36. /**
  37.  * Content repository which query content with sql2 statements.
  38.  */
  39. class ContentRepository implements ContentRepositoryInterfaceDescendantProviderInterface
  40. {
  41.     private static $nonFallbackProperties = [
  42.         'uuid',
  43.         'state',
  44.         'order',
  45.         'created',
  46.         'creator',
  47.         'changed',
  48.         'changer',
  49.         'published',
  50.         'shadowOn',
  51.         'shadowBase',
  52.     ];
  53.     /**
  54.      * @var SessionManagerInterface
  55.      */
  56.     private $sessionManager;
  57.     /**
  58.      * @var PropertyEncoder
  59.      */
  60.     private $propertyEncoder;
  61.     /**
  62.      * @var WebspaceManagerInterface
  63.      */
  64.     private $webspaceManager;
  65.     /**
  66.      * @var SessionInterface
  67.      */
  68.     private $session;
  69.     /**
  70.      * @var QueryObjectModelFactoryInterface
  71.      */
  72.     private $qomFactory;
  73.     /**
  74.      * @var LocalizationFinderInterface
  75.      */
  76.     private $localizationFinder;
  77.     /**
  78.      * @var StructureManagerInterface
  79.      */
  80.     private $structureManager;
  81.     /**
  82.      * @var SuluNodeHelper
  83.      */
  84.     private $nodeHelper;
  85.     /**
  86.      * @var array
  87.      */
  88.     private $permissions;
  89.     /**
  90.      * @var SystemStoreInterface
  91.      */
  92.     private $systemStore;
  93.     public function __construct(
  94.         SessionManagerInterface $sessionManager,
  95.         PropertyEncoder $propertyEncoder,
  96.         WebspaceManagerInterface $webspaceManager,
  97.         LocalizationFinderInterface $localizationFinder,
  98.         StructureManagerInterface $structureManager,
  99.         SuluNodeHelper $nodeHelper,
  100.         SystemStoreInterface $systemStore,
  101.         array $permissions
  102.     ) {
  103.         $this->sessionManager $sessionManager;
  104.         $this->propertyEncoder $propertyEncoder;
  105.         $this->webspaceManager $webspaceManager;
  106.         $this->localizationFinder $localizationFinder;
  107.         $this->structureManager $structureManager;
  108.         $this->nodeHelper $nodeHelper;
  109.         $this->systemStore $systemStore;
  110.         $this->permissions $permissions;
  111.         $this->session $sessionManager->getSession();
  112.         $this->qomFactory $this->session->getWorkspace()->getQueryManager()->getQOMFactory();
  113.     }
  114.     public function find($uuid$locale$webspaceKeyMappingInterface $mappingUserInterface $user null)
  115.     {
  116.         $locales $this->getLocalesByWebspaceKey($webspaceKey);
  117.         $queryBuilder $this->getQueryBuilder($locale$locales$user);
  118.         $queryBuilder->where(
  119.             $this->qomFactory->comparison(
  120.                 new PropertyValue('node''jcr:uuid'),
  121.                 '=',
  122.                 $this->qomFactory->literal($uuid)
  123.             )
  124.         );
  125.         $this->appendMapping($queryBuilder$mapping$locale$locales);
  126.         $queryResult $queryBuilder->execute();
  127.         $rows \iterator_to_array($queryResult->getRows());
  128.         if (!== \count($rows)) {
  129.             throw new ItemNotFoundException();
  130.         }
  131.         $resultPermissions $this->resolveResultPermissions($rows$user);
  132.         $permissions = empty($resultPermissions) ? [] : \current($resultPermissions);
  133.         return $this->resolveContent(\current($rows), $locale$locales$mapping$user$permissions);
  134.     }
  135.     public function findByParentUuid(
  136.         $uuid,
  137.         $locale,
  138.         $webspaceKey,
  139.         MappingInterface $mapping,
  140.         UserInterface $user null
  141.     ) {
  142.         $path $this->resolvePathByUuid($uuid);
  143.         if (!$webspaceKey) {
  144.             // TODO find a better solution than this (e.g. reuse logic from DocumentInspector and preferably in the PageController)
  145.             $webspaceKey \explode('/'$path)[2];
  146.         }
  147.         $locales $this->getLocalesByWebspaceKey($webspaceKey);
  148.         $queryBuilder $this->getQueryBuilder($locale$locales$user);
  149.         $queryBuilder->where($this->qomFactory->childNode('node'$path));
  150.         $this->appendMapping($queryBuilder$mapping$locale$locales);
  151.         return $this->resolveQueryBuilder($queryBuilder$locale$locales$mapping$user);
  152.     }
  153.     public function findByWebspaceRoot($locale$webspaceKeyMappingInterface $mappingUserInterface $user null)
  154.     {
  155.         $locales $this->getLocalesByWebspaceKey($webspaceKey);
  156.         $queryBuilder $this->getQueryBuilder($locale$locales$user);
  157.         $queryBuilder->where(
  158.             $this->qomFactory->childNode('node'$this->sessionManager->getContentPath($webspaceKey))
  159.         );
  160.         $this->appendMapping($queryBuilder$mapping$locale$locales);
  161.         return $this->resolveQueryBuilder($queryBuilder$locale$locales$mapping$user);
  162.     }
  163.     public function findParentsWithSiblingsByUuid(
  164.         $uuid,
  165.         $locale,
  166.         $webspaceKey,
  167.         MappingInterface $mapping,
  168.         UserInterface $user null
  169.     ) {
  170.         $path $this->resolvePathByUuid($uuid);
  171.         if (empty($webspaceKey)) {
  172.             $webspaceKey $this->nodeHelper->extractWebspaceFromPath($path);
  173.         }
  174.         $contentPath $this->sessionManager->getContentPath($webspaceKey);
  175.         $locales $this->getLocalesByWebspaceKey($webspaceKey);
  176.         $queryBuilder $this->getQueryBuilder($locale$locales$user)
  177.             ->orderBy($this->qomFactory->propertyValue('node''jcr:path'))
  178.             ->where($this->qomFactory->childNode('node'$path));
  179.         while (PathHelper::getPathDepth($path) > PathHelper::getPathDepth($contentPath)) {
  180.             $path PathHelper::getParentPath($path);
  181.             $queryBuilder->orWhere($this->qomFactory->childNode('node'$path));
  182.         }
  183.         $mapping->addProperties(['order']);
  184.         $this->appendMapping($queryBuilder$mapping$locale$locales);
  185.         $result $this->resolveQueryBuilder($queryBuilder$locale$locales$mapping$user);
  186.         return $this->generateTreeByPath($result$uuid);
  187.     }
  188.     public function findByPaths(
  189.         array $paths,
  190.         $locale,
  191.         MappingInterface $mapping,
  192.         UserInterface $user null
  193.     ) {
  194.         $locales $this->getLocales();
  195.         $queryBuilder $this->getQueryBuilder($locale$locales$user);
  196.         foreach ($paths as $path) {
  197.             $queryBuilder->orWhere(
  198.                 $this->qomFactory->sameNode('node'$path)
  199.             );
  200.         }
  201.         $this->appendMapping($queryBuilder$mapping$locale$locales);
  202.         return $this->resolveQueryBuilder($queryBuilder$locale$locales$mapping$user);
  203.     }
  204.     public function findByUuids(
  205.         array $uuids,
  206.         $locale,
  207.         MappingInterface $mapping,
  208.         UserInterface $user null
  209.     ) {
  210.         if (=== \count($uuids)) {
  211.             return [];
  212.         }
  213.         $locales $this->getLocales();
  214.         $queryBuilder $this->getQueryBuilder($locale$locales$user);
  215.         foreach ($uuids as $uuid) {
  216.             $queryBuilder->orWhere(
  217.                 $this->qomFactory->comparison(
  218.                     $queryBuilder->qomf()->propertyValue('node''jcr:uuid'),
  219.                     QueryObjectModelConstantsInterface::JCR_OPERATOR_EQUAL_TO,
  220.                     $queryBuilder->qomf()->literal($uuid)
  221.                 )
  222.             );
  223.         }
  224.         $this->appendMapping($queryBuilder$mapping$locale$locales);
  225.         $result $this->resolveQueryBuilder($queryBuilder$locale$locales$mapping$user);
  226.         \usort($result, function($a$b) use ($uuids) {
  227.             return \array_search($a->getId(), $uuids) < \array_search($b->getId(), $uuids) ? -1;
  228.         });
  229.         return $result;
  230.     }
  231.     public function findAll($locale$webspaceKeyMappingInterface $mappingUserInterface $user null)
  232.     {
  233.         $contentPath $this->sessionManager->getContentPath($webspaceKey);
  234.         $locales $this->getLocalesByWebspaceKey($webspaceKey);
  235.         $queryBuilder $this->getQueryBuilder($locale$locales$user)
  236.             ->where($this->qomFactory->descendantNode('node'$contentPath))
  237.             ->orWhere($this->qomFactory->sameNode('node'$contentPath));
  238.         $this->appendMapping($queryBuilder$mapping$locale$locales);
  239.         return $this->resolveQueryBuilder($queryBuilder$locale$locales$mapping$user);
  240.     }
  241.     public function findAllByPortal($locale$portalKeyMappingInterface $mappingUserInterface $user null)
  242.     {
  243.         $webspaceKey $this->webspaceManager->findPortalByKey($portalKey)->getWebspace()->getKey();
  244.         $contentPath $this->sessionManager->getContentPath($webspaceKey);
  245.         $locales $this->getLocalesByPortalKey($portalKey);
  246.         $queryBuilder $this->getQueryBuilder($locale$locales$user)
  247.             ->where($this->qomFactory->descendantNode('node'$contentPath))
  248.             ->orWhere($this->qomFactory->sameNode('node'$contentPath));
  249.         $this->appendMapping($queryBuilder$mapping$locale$locales);
  250.         return $this->resolveQueryBuilder($queryBuilder$locale$locales$mapping$user);
  251.     }
  252.     public function findDescendantIdsById($id)
  253.     {
  254.         $queryBuilder $this->getQueryBuilder();
  255.         $queryBuilder->where(
  256.             $this->qomFactory->comparison(
  257.                 new PropertyValue('node''jcr:uuid'),
  258.                 '=',
  259.                 $this->qomFactory->literal($id)
  260.             )
  261.         );
  262.         $result \iterator_to_array($queryBuilder->execute());
  263.         if (=== \count($result)) {
  264.             return [];
  265.         }
  266.         $path $result[0]->getPath();
  267.         $descendantQueryBuilder $this->getQueryBuilder()
  268.             ->where($this->qomFactory->descendantNode('node'$path));
  269.         return \array_map(
  270.             function(Row $row) {
  271.                 return $row->getNode()->getIdentifier();
  272.             },
  273.             \iterator_to_array($descendantQueryBuilder->execute())
  274.         );
  275.     }
  276.     /**
  277.      * Generates a content-tree with paths of given content array.
  278.      *
  279.      * @param Content[] $contents
  280.      *
  281.      * @return Content[]
  282.      */
  283.     private function generateTreeByPath(array $contents$uuid)
  284.     {
  285.         $childrenByPath = [];
  286.         foreach ($contents as $content) {
  287.             $path PathHelper::getParentPath($content->getPath());
  288.             if (!isset($childrenByPath[$path])) {
  289.                 $childrenByPath[$path] = [];
  290.             }
  291.             $order $content['order'];
  292.             while (isset($childrenByPath[$path][$order])) {
  293.                 ++$order;
  294.             }
  295.             $childrenByPath[$path][$order] = $content;
  296.         }
  297.         foreach ($contents as $content) {
  298.             if (!isset($childrenByPath[$content->getPath()])) {
  299.                 if ($content->getId() === $uuid) {
  300.                     $content->setChildren([]);
  301.                 }
  302.                 continue;
  303.             }
  304.             \ksort($childrenByPath[$content->getPath()]);
  305.             $content->setChildren(\array_values($childrenByPath[$content->getPath()]));
  306.         }
  307.         if (!\array_key_exists('/'$childrenByPath) || !\is_array($childrenByPath['/'])) {
  308.             return [];
  309.         }
  310.         \ksort($childrenByPath['/']);
  311.         return \array_values($childrenByPath['/']);
  312.     }
  313.     /**
  314.      * Resolve path for node with given uuid.
  315.      *
  316.      * @param string $uuid
  317.      *
  318.      * @return string
  319.      *
  320.      * @throws ItemNotFoundException
  321.      */
  322.     private function resolvePathByUuid($uuid)
  323.     {
  324.         $queryBuilder = new QueryBuilder($this->qomFactory);
  325.         $queryBuilder
  326.             ->select('node''jcr:uuid''uuid')
  327.             ->from($this->qomFactory->selector('node''nt:unstructured'))
  328.             ->where(
  329.                 $this->qomFactory->comparison(
  330.                     $this->qomFactory->propertyValue('node''jcr:uuid'),
  331.                     '=',
  332.                     $this->qomFactory->literal($uuid)
  333.                 )
  334.             );
  335.         $rows $queryBuilder->execute();
  336.         if (!== \count(\iterator_to_array($rows->getRows()))) {
  337.             throw new ItemNotFoundException();
  338.         }
  339.         return $rows->getRows()->current()->getPath();
  340.     }
  341.     /**
  342.      * Resolves query results to content.
  343.      *
  344.      * @param string $locale
  345.      * @param UserInterface $user
  346.      *
  347.      * @return Content[]
  348.      */
  349.     private function resolveQueryBuilder(
  350.         QueryBuilder $queryBuilder,
  351.         $locale,
  352.         $locales,
  353.         MappingInterface $mapping,
  354.         UserInterface $user null
  355.     ) {
  356.         $result \iterator_to_array($queryBuilder->execute());
  357.         $permissions $this->resolveResultPermissions($result$user);
  358.         return \array_values(
  359.             \array_filter(
  360.                 \array_map(
  361.                     function(Row $row$index) use ($mapping$locale$locales$user$permissions) {
  362.                         return $this->resolveContent(
  363.                             $row,
  364.                             $locale,
  365.                             $locales,
  366.                             $mapping,
  367.                             $user,
  368.                             $permissions[$index] ?? []
  369.                         );
  370.                     },
  371.                     $result,
  372.                     \array_keys($result)
  373.                 )
  374.             )
  375.         );
  376.     }
  377.     private function resolveResultPermissions(array $resultUserInterface $user null)
  378.     {
  379.         $permissions = [];
  380.         foreach ($result as $index => $row) {
  381.             $permissions[$index] = [];
  382.             $jsonPermission $row->getValue(SecuritySubscriber::SECURITY_PERMISSION_PROPERTY);
  383.             if (!$jsonPermission) {
  384.                 continue;
  385.             }
  386.             $rowPermissions \json_decode($jsonPermissiontrue);
  387.             foreach ($rowPermissions as $roleId => $rolePermissions) {
  388.                 foreach ($this->permissions as $permissionKey => $permission) {
  389.                     $permissions[$index][$roleId][$permissionKey] = false;
  390.                 }
  391.                 foreach ($rolePermissions as $rolePermission) {
  392.                     $permissions[$index][$roleId][$rolePermission] = true;
  393.                 }
  394.             }
  395.         }
  396.         return $permissions;
  397.     }
  398.     /**
  399.      * Returns QueryBuilder with basic select and where statements.
  400.      *
  401.      * @param string $locale
  402.      * @param string[] $locales
  403.      * @param UserInterface $user
  404.      *
  405.      * @return QueryBuilder
  406.      */
  407.     private function getQueryBuilder($locale null$locales = [], UserInterface $user null)
  408.     {
  409.         $queryBuilder = new QueryBuilder($this->qomFactory);
  410.         $queryBuilder
  411.             ->select('node''jcr:uuid''uuid')
  412.             ->addSelect('node'$this->getPropertyName('nodeType'$locale), 'nodeType')
  413.             ->addSelect('node'$this->getPropertyName('internal_link'$locale), 'internalLink')
  414.             ->addSelect('node'$this->getPropertyName('state'$locale), 'state')
  415.             ->addSelect('node'$this->getPropertyName('shadow-on'$locale), 'shadowOn')
  416.             ->addSelect('node'$this->getPropertyName('shadow-base'$locale), 'shadowBase')
  417.             ->addSelect('node'$this->propertyEncoder->systemName('order'), 'order')
  418.             ->from($this->qomFactory->selector('node''nt:unstructured'))
  419.             ->orderBy($this->qomFactory->propertyValue('node''sulu:order'));
  420.         $this->appendSingleMapping($queryBuilder'template'$locales);
  421.         $this->appendSingleMapping($queryBuilder'shadow-on'$locales);
  422.         $this->appendSingleMapping($queryBuilder'state'$locales);
  423.         $queryBuilder->addSelect(
  424.             'node',
  425.             SecuritySubscriber::SECURITY_PERMISSION_PROPERTY,
  426.             SecuritySubscriber::SECURITY_PERMISSION_PROPERTY
  427.         );
  428.         return $queryBuilder;
  429.     }
  430.     private function getPropertyName($propertyName$locale)
  431.     {
  432.         if ($locale) {
  433.             return $this->propertyEncoder->localizedContentName($propertyName$locale);
  434.         }
  435.         return $this->propertyEncoder->contentName($propertyName);
  436.     }
  437.     /**
  438.      * Returns array of locales for given webspace key.
  439.      *
  440.      * @param string $webspaceKey
  441.      *
  442.      * @return string[]
  443.      */
  444.     private function getLocalesByWebspaceKey($webspaceKey)
  445.     {
  446.         $webspace $this->webspaceManager->findWebspaceByKey($webspaceKey);
  447.         return \array_map(
  448.             function(Localization $localization) {
  449.                 return $localization->getLocale();
  450.             },
  451.             $webspace->getAllLocalizations()
  452.         );
  453.     }
  454.     /**
  455.      * Returns array of locales for given portal key.
  456.      *
  457.      * @param string $portalKey
  458.      *
  459.      * @return string[]
  460.      */
  461.     private function getLocalesByPortalKey($portalKey)
  462.     {
  463.         $portal $this->webspaceManager->findPortalByKey($portalKey);
  464.         return \array_map(
  465.             function(Localization $localization) {
  466.                 return $localization->getLocale();
  467.             },
  468.             $portal->getLocalizations()
  469.         );
  470.     }
  471.     /**
  472.      * Returns array of locales for webspaces.
  473.      *
  474.      * @return string[]
  475.      */
  476.     private function getLocales()
  477.     {
  478.         return $this->webspaceManager->getAllLocales();
  479.     }
  480.     /**
  481.      * Append mapping selects to given query-builder.
  482.      *
  483.      * @param MappingInterface $mapping Includes array of property names
  484.      * @param string $locale
  485.      * @param string[] $locales
  486.      */
  487.     private function appendMapping(QueryBuilder $queryBuilderMappingInterface $mapping$locale$locales)
  488.     {
  489.         if ($mapping->onlyPublished()) {
  490.             $queryBuilder->andWhere(
  491.                 $this->qomFactory->comparison(
  492.                     $this->qomFactory->propertyValue(
  493.                         'node',
  494.                         $this->propertyEncoder->localizedSystemName('state'$locale)
  495.                     ),
  496.                     '=',
  497.                     $this->qomFactory->literal(WorkflowStage::PUBLISHED)
  498.                 )
  499.             );
  500.         }
  501.         $properties $mapping->getProperties();
  502.         foreach ($properties as $propertyName) {
  503.             $this->appendSingleMapping($queryBuilder$propertyName$locales);
  504.         }
  505.         if ($mapping->resolveUrl()) {
  506.             $this->appendUrlMapping($queryBuilder$locales);
  507.         }
  508.     }
  509.     /**
  510.      * Append mapping selects for a single property to given query-builder.
  511.      *
  512.      * @param string $propertyName
  513.      * @param string[] $locales
  514.      */
  515.     private function appendSingleMapping(QueryBuilder $queryBuilder$propertyName$locales)
  516.     {
  517.         foreach ($locales as $locale) {
  518.             $alias \sprintf('%s%s'$locale\str_replace('-''_'\ucfirst($propertyName)));
  519.             $queryBuilder->addSelect(
  520.                 'node',
  521.                 $this->propertyEncoder->localizedContentName($propertyName$locale),
  522.                 $alias
  523.             );
  524.         }
  525.     }
  526.     /**
  527.      * Append mapping for url to given query-builder.
  528.      *
  529.      * @param string[] $locales
  530.      */
  531.     private function appendUrlMapping(QueryBuilder $queryBuilder$locales)
  532.     {
  533.         $structures $this->structureManager->getStructures(Structure::TYPE_PAGE);
  534.         $urlNames = [];
  535.         foreach ($structures as $structure) {
  536.             if (!$structure->hasTag('sulu.rlp')) {
  537.                 continue;
  538.             }
  539.             $propertyName $structure->getPropertyByTagName('sulu.rlp')->getName();
  540.             if (!\in_array($propertyName$urlNames)) {
  541.                 $this->appendSingleMapping($queryBuilder$propertyName$locales);
  542.                 $urlNames[] = $propertyName;
  543.             }
  544.         }
  545.     }
  546.     /**
  547.      * Resolve a single result row to a content object.
  548.      *
  549.      * @param string $locale
  550.      * @param string $locales
  551.      *
  552.      * @return Content
  553.      */
  554.     private function resolveContent(
  555.         Row $row,
  556.         $locale,
  557.         $locales,
  558.         MappingInterface $mapping,
  559.         UserInterface $user null,
  560.         array $permissions
  561.     ) {
  562.         $webspaceKey $this->nodeHelper->extractWebspaceFromPath($row->getPath());
  563.         $originalLocale $locale;
  564.         $availableLocales $this->resolveAvailableLocales($row);
  565.         $ghostLocale $this->localizationFinder->findAvailableLocale(
  566.             $webspaceKey,
  567.             $availableLocales,
  568.             $locale
  569.         );
  570.         if (null === $ghostLocale) {
  571.             $ghostLocale \reset($availableLocales);
  572.         }
  573.         $type null;
  574.         if ($row->getValue('shadowOn')) {
  575.             if (!$mapping->shouldHydrateShadow()) {
  576.                 return;
  577.             }
  578.             $type StructureType::getShadow($row->getValue('shadowBase'));
  579.         } elseif (null !== $ghostLocale && $ghostLocale !== $originalLocale) {
  580.             if (!$mapping->shouldHydrateGhost()) {
  581.                 return;
  582.             }
  583.             $locale $ghostLocale;
  584.             $type StructureType::getGhost($locale);
  585.         }
  586.         if (
  587.             RedirectType::INTERNAL === $row->getValue('nodeType')
  588.             && $mapping->followInternalLink()
  589.             && '' !== $row->getValue('internalLink')
  590.             && $row->getValue('internalLink') !== $row->getValue('uuid')
  591.         ) {
  592.             // TODO collect all internal link contents and query once
  593.             return $this->resolveInternalLinkContent($row$locale$webspaceKey$mapping$type$user);
  594.         }
  595.         $shadowBase null;
  596.         if ($row->getValue('shadowOn')) {
  597.             $shadowBase $row->getValue('shadowBase');
  598.         }
  599.         $data = [];
  600.         foreach ($mapping->getProperties() as $item) {
  601.             $data[$item] = $this->resolveProperty($row$item$locale$shadowBase);
  602.         }
  603.         $content = new Content(
  604.             $originalLocale,
  605.             $webspaceKey,
  606.             $row->getValue('uuid'),
  607.             $this->resolvePath($row$webspaceKey),
  608.             $row->getValue('state'),
  609.             $row->getValue('nodeType'),
  610.             $this->resolveHasChildren($row), $this->resolveProperty($row'template'$locale$shadowBase),
  611.             $data,
  612.             $permissions,
  613.             $type
  614.         );
  615.         $content->setRow($row);
  616.         if (!$content->getTemplate() || !$this->structureManager->getStructure($content->getTemplate())) {
  617.             $content->setBrokenTemplate();
  618.         }
  619.         if ($mapping->resolveUrl()) {
  620.             $url $this->resolveUrl($row$locale);
  621.             /** @var array<string, string|null> $urls */
  622.             $urls = [];
  623.             \array_walk(
  624.                 $locales,
  625.                 /** @var array<string, string|null> $urls */
  626.                 function($locale) use (&$urls$row) {
  627.                     $urls[$locale] = $this->resolveUrl($row$locale);
  628.                 }
  629.             );
  630.             $content->setUrl($url);
  631.             $content->setUrls($urls);
  632.         }
  633.         if ($mapping->resolveConcreteLocales()) {
  634.             $locales $this->resolveAvailableLocales($row);
  635.             $content->setContentLocales($locales);
  636.         }
  637.         return $content;
  638.     }
  639.     /**
  640.      * Resolves all available localizations for given row.
  641.      *
  642.      * @return string[]
  643.      */
  644.     private function resolveAvailableLocales(Row $row)
  645.     {
  646.         $locales = [];
  647.         foreach ($row->getValues() as $key => $value) {
  648.             if (\preg_match('/^node.([a-zA-Z_]*?)Template/'$key$matches) && '' !== $value
  649.                 && !$row->getValue(\sprintf('node.%sShadow_on'$matches[1]))
  650.             ) {
  651.                 $locales[] = $matches[1];
  652.             }
  653.         }
  654.         return $locales;
  655.     }
  656.     /**
  657.      * Resolve a single result row which is an internal link to a content object.
  658.      *
  659.      * @param string $locale
  660.      * @param string $webspaceKey
  661.      * @param MappingInterface $mapping Includes array of property names
  662.      * @param StructureType $type
  663.      * @param UserInterface $user
  664.      *
  665.      * @return Content
  666.      */
  667.     public function resolveInternalLinkContent(
  668.         Row $row,
  669.         $locale,
  670.         $webspaceKey,
  671.         MappingInterface $mapping,
  672.         StructureType $type null,
  673.         UserInterface $user null
  674.     ) {
  675.         $linkedContent $this->find($row->getValue('internalLink'), $locale$webspaceKey$mapping);
  676.         $data $linkedContent->getData();
  677.         // return value of source node instead of link destination for title and non-fallback-properties
  678.         $sourceNodeValueProperties self::$nonFallbackProperties;
  679.         $sourceNodeValueProperties[] = 'title';
  680.         $properties \array_intersect($sourceNodeValueProperties\array_keys($data));
  681.         foreach ($properties as $property) {
  682.             $data[$property] = $this->resolveProperty($row$property$locale);
  683.         }
  684.         $resultPermissions $this->resolveResultPermissions([$row], $user);
  685.         $permissions = empty($resultPermissions) ? [] : \current($resultPermissions);
  686.         $content = new Content(
  687.             $locale,
  688.             $webspaceKey,
  689.             $row->getValue('uuid'),
  690.             $this->resolvePath($row$webspaceKey),
  691.             $row->getValue('state'),
  692.             $row->getValue('nodeType'),
  693.             $this->resolveHasChildren($row), $this->resolveProperty($row'template'$locale),
  694.             $data,
  695.             $permissions,
  696.             $type
  697.         );
  698.         if (!$content->getTemplate() || !$this->structureManager->getStructure($content->getTemplate())) {
  699.             $content->setBrokenTemplate();
  700.         }
  701.         return $content;
  702.     }
  703.     /**
  704.      * Resolve a property and follow shadow locale if it has one.
  705.      *
  706.      * @param string $name
  707.      * @param string $locale
  708.      * @param string $shadowLocale
  709.      */
  710.     private function resolveProperty(Row $row$name$locale$shadowLocale null)
  711.     {
  712.         if (\array_key_exists(\sprintf('node.%s'$name), $row->getValues())) {
  713.             return $row->getValue($name);
  714.         }
  715.         if (null !== $shadowLocale && !\in_array($nameself::$nonFallbackProperties)) {
  716.             $locale $shadowLocale;
  717.         }
  718.         $name \sprintf('%s%s'$locale\str_replace('-''_'\ucfirst($name)));
  719.         try {
  720.             return $row->getValue($name);
  721.         } catch (ItemNotFoundException $e) {
  722.             // the default value of a non existing property in jackalope is an empty string
  723.             return '';
  724.         }
  725.     }
  726.     /**
  727.      * Resolve url property.
  728.      *
  729.      * @param string $locale
  730.      *
  731.      * @return string|null
  732.      */
  733.     private function resolveUrl(Row $row$locale)
  734.     {
  735.         if (WorkflowStage::PUBLISHED !== $this->resolveProperty($row$locale 'State'$locale)) {
  736.             return null;
  737.         }
  738.         $template $this->resolveProperty($row'template'$locale);
  739.         if (empty($template)) {
  740.             return null;
  741.         }
  742.         $structure $this->structureManager->getStructure($template);
  743.         if (!$structure || !$structure->hasTag('sulu.rlp')) {
  744.             return null;
  745.         }
  746.         $propertyName $structure->getPropertyByTagName('sulu.rlp')->getName();
  747.         return $this->resolveProperty($row$propertyName$locale);
  748.     }
  749.     /**
  750.      * Resolves path for given row.
  751.      *
  752.      * @param string $webspaceKey
  753.      *
  754.      * @return string
  755.      */
  756.     private function resolvePath(Row $row$webspaceKey)
  757.     {
  758.         return '/' \ltrim(\str_replace($this->sessionManager->getContentPath($webspaceKey), ''$row->getPath()), '/');
  759.     }
  760.     /**
  761.      * Resolve property has-children with given node.
  762.      *
  763.      * @return bool
  764.      */
  765.     private function resolveHasChildren(Row $row)
  766.     {
  767.         $queryBuilder = new QueryBuilder($this->qomFactory);
  768.         $queryBuilder
  769.             ->select('node''jcr:uuid''uuid')
  770.             ->from($this->qomFactory->selector('node''nt:unstructured'))
  771.             ->where($this->qomFactory->childNode('node'$row->getPath()))
  772.             ->setMaxResults(1);
  773.         $result $queryBuilder->execute();
  774.         return \count(\iterator_to_array($result->getRows())) > 0;
  775.     }
  776.     public function supportsDescendantType(string $type): bool
  777.     {
  778.         try {
  779.             $class = new \ReflectionClass($type);
  780.         } catch (\ReflectionException $e) {
  781.             // in case the class does not exist there is no support
  782.             return false;
  783.         }
  784.         return $class->implementsInterface(SecurityBehavior::class);
  785.     }
  786. }