<?php
/*
* This file is part of Sulu.
*
* (c) Sulu GmbH
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Sulu\Component\Content\Repository;
use Jackalope\Query\QOM\PropertyValue;
use Jackalope\Query\Row;
use PHPCR\ItemNotFoundException;
use PHPCR\Query\QOM\QueryObjectModelConstantsInterface;
use PHPCR\Query\QOM\QueryObjectModelFactoryInterface;
use PHPCR\SessionInterface;
use PHPCR\Util\PathHelper;
use PHPCR\Util\QOM\QueryBuilder;
use Sulu\Bundle\SecurityBundle\System\SystemStoreInterface;
use Sulu\Component\Content\Compat\LocalizationFinderInterface;
use Sulu\Component\Content\Compat\Structure;
use Sulu\Component\Content\Compat\StructureManagerInterface;
use Sulu\Component\Content\Compat\StructureType;
use Sulu\Component\Content\Document\Behavior\SecurityBehavior;
use Sulu\Component\Content\Document\RedirectType;
use Sulu\Component\Content\Document\Subscriber\SecuritySubscriber;
use Sulu\Component\Content\Document\WorkflowStage;
use Sulu\Component\Content\Repository\Mapping\MappingInterface;
use Sulu\Component\DocumentManager\PropertyEncoder;
use Sulu\Component\Localization\Localization;
use Sulu\Component\PHPCR\SessionManager\SessionManagerInterface;
use Sulu\Component\Security\Authentication\UserInterface;
use Sulu\Component\Security\Authorization\AccessControl\DescendantProviderInterface;
use Sulu\Component\Util\SuluNodeHelper;
use Sulu\Component\Webspace\Manager\WebspaceManagerInterface;
/**
* Content repository which query content with sql2 statements.
*/
class ContentRepository implements ContentRepositoryInterface, DescendantProviderInterface
{
private static $nonFallbackProperties = [
'uuid',
'state',
'order',
'created',
'creator',
'changed',
'changer',
'published',
'shadowOn',
'shadowBase',
];
/**
* @var SessionManagerInterface
*/
private $sessionManager;
/**
* @var PropertyEncoder
*/
private $propertyEncoder;
/**
* @var WebspaceManagerInterface
*/
private $webspaceManager;
/**
* @var SessionInterface
*/
private $session;
/**
* @var QueryObjectModelFactoryInterface
*/
private $qomFactory;
/**
* @var LocalizationFinderInterface
*/
private $localizationFinder;
/**
* @var StructureManagerInterface
*/
private $structureManager;
/**
* @var SuluNodeHelper
*/
private $nodeHelper;
/**
* @var array
*/
private $permissions;
/**
* @var SystemStoreInterface
*/
private $systemStore;
public function __construct(
SessionManagerInterface $sessionManager,
PropertyEncoder $propertyEncoder,
WebspaceManagerInterface $webspaceManager,
LocalizationFinderInterface $localizationFinder,
StructureManagerInterface $structureManager,
SuluNodeHelper $nodeHelper,
SystemStoreInterface $systemStore,
array $permissions
) {
$this->sessionManager = $sessionManager;
$this->propertyEncoder = $propertyEncoder;
$this->webspaceManager = $webspaceManager;
$this->localizationFinder = $localizationFinder;
$this->structureManager = $structureManager;
$this->nodeHelper = $nodeHelper;
$this->systemStore = $systemStore;
$this->permissions = $permissions;
$this->session = $sessionManager->getSession();
$this->qomFactory = $this->session->getWorkspace()->getQueryManager()->getQOMFactory();
}
/**
* Find content by uuid.
*
* @param string $uuid
* @param string $locale
* @param string $webspaceKey
* @param MappingInterface $mapping Includes array of property names
* @param UserInterface $user
*
* @return Content|null
*/
public function find($uuid, $locale, $webspaceKey, MappingInterface $mapping, UserInterface $user = null)
{
$locales = $this->getLocalesByWebspaceKey($webspaceKey);
$queryBuilder = $this->getQueryBuilder($locale, $locales, $user);
$queryBuilder->where(
$this->qomFactory->comparison(
new PropertyValue('node', 'jcr:uuid'),
'=',
$this->qomFactory->literal($uuid)
)
);
$this->appendMapping($queryBuilder, $mapping, $locale, $locales);
$queryResult = $queryBuilder->execute();
$rows = \iterator_to_array($queryResult->getRows());
if (1 !== \count($rows)) {
throw new ItemNotFoundException();
}
$resultPermissions = $this->resolveResultPermissions($rows, $user);
$permissions = empty($resultPermissions) ? [] : \current($resultPermissions);
return $this->resolveContent(\current($rows), $locale, $locales, $mapping, $user, $permissions);
}
public function findByParentUuid(
$uuid,
$locale,
$webspaceKey,
MappingInterface $mapping,
UserInterface $user = null
) {
$path = $this->resolvePathByUuid($uuid);
if (!$webspaceKey) {
// TODO find a better solution than this (e.g. reuse logic from DocumentInspector and preferably in the PageController)
$webspaceKey = \explode('/', $path)[2];
}
$locales = $this->getLocalesByWebspaceKey($webspaceKey);
$queryBuilder = $this->getQueryBuilder($locale, $locales, $user);
$queryBuilder->where($this->qomFactory->childNode('node', $path));
$this->appendMapping($queryBuilder, $mapping, $locale, $locales);
return $this->resolveQueryBuilder($queryBuilder, $locale, $locales, $mapping, $user);
}
public function findByWebspaceRoot($locale, $webspaceKey, MappingInterface $mapping, UserInterface $user = null)
{
$locales = $this->getLocalesByWebspaceKey($webspaceKey);
$queryBuilder = $this->getQueryBuilder($locale, $locales, $user);
$queryBuilder->where(
$this->qomFactory->childNode('node', $this->sessionManager->getContentPath($webspaceKey))
);
$this->appendMapping($queryBuilder, $mapping, $locale, $locales);
return $this->resolveQueryBuilder($queryBuilder, $locale, $locales, $mapping, $user);
}
public function findParentsWithSiblingsByUuid(
$uuid,
$locale,
$webspaceKey,
MappingInterface $mapping,
UserInterface $user = null
) {
$path = $this->resolvePathByUuid($uuid);
if (empty($webspaceKey)) {
$webspaceKey = $this->nodeHelper->extractWebspaceFromPath($path);
}
$contentPath = $this->sessionManager->getContentPath($webspaceKey);
$locales = $this->getLocalesByWebspaceKey($webspaceKey);
$queryBuilder = $this->getQueryBuilder($locale, $locales, $user)
->orderBy($this->qomFactory->propertyValue('node', 'jcr:path'))
->where($this->qomFactory->childNode('node', $path));
while (PathHelper::getPathDepth($path) > PathHelper::getPathDepth($contentPath)) {
$path = PathHelper::getParentPath($path);
$queryBuilder->orWhere($this->qomFactory->childNode('node', $path));
}
$mapping->addProperties(['order']);
$this->appendMapping($queryBuilder, $mapping, $locale, $locales);
$result = $this->resolveQueryBuilder($queryBuilder, $locale, $locales, $mapping, $user);
return $this->generateTreeByPath($result, $uuid);
}
public function findByPaths(
array $paths,
$locale,
MappingInterface $mapping,
UserInterface $user = null
) {
$locales = $this->getLocales();
$queryBuilder = $this->getQueryBuilder($locale, $locales, $user);
foreach ($paths as $path) {
$queryBuilder->orWhere(
$this->qomFactory->sameNode('node', $path)
);
}
$this->appendMapping($queryBuilder, $mapping, $locale, $locales);
return $this->resolveQueryBuilder($queryBuilder, $locale, $locales, $mapping, $user);
}
public function findByUuids(
array $uuids,
$locale,
MappingInterface $mapping,
UserInterface $user = null
) {
if (0 === \count($uuids)) {
return [];
}
$locales = $this->getLocales();
$queryBuilder = $this->getQueryBuilder($locale, $locales, $user);
foreach ($uuids as $uuid) {
$queryBuilder->orWhere(
$this->qomFactory->comparison(
$queryBuilder->qomf()->propertyValue('node', 'jcr:uuid'),
QueryObjectModelConstantsInterface::JCR_OPERATOR_EQUAL_TO,
$queryBuilder->qomf()->literal($uuid)
)
);
}
$this->appendMapping($queryBuilder, $mapping, $locale, $locales);
$result = $this->resolveQueryBuilder($queryBuilder, $locale, $locales, $mapping, $user);
\usort($result, function($a, $b) use ($uuids) {
return \array_search($a->getId(), $uuids) < \array_search($b->getId(), $uuids) ? -1 : 1;
});
return $result;
}
public function findAll($locale, $webspaceKey, MappingInterface $mapping, UserInterface $user = null)
{
$contentPath = $this->sessionManager->getContentPath($webspaceKey);
$locales = $this->getLocalesByWebspaceKey($webspaceKey);
$queryBuilder = $this->getQueryBuilder($locale, $locales, $user)
->where($this->qomFactory->descendantNode('node', $contentPath))
->orWhere($this->qomFactory->sameNode('node', $contentPath));
$this->appendMapping($queryBuilder, $mapping, $locale, $locales);
return $this->resolveQueryBuilder($queryBuilder, $locale, $locales, $mapping, $user);
}
public function findAllByPortal($locale, $portalKey, MappingInterface $mapping, UserInterface $user = null)
{
$webspaceKey = $this->webspaceManager->findPortalByKey($portalKey)->getWebspace()->getKey();
$contentPath = $this->sessionManager->getContentPath($webspaceKey);
$locales = $this->getLocalesByPortalKey($portalKey);
$queryBuilder = $this->getQueryBuilder($locale, $locales, $user)
->where($this->qomFactory->descendantNode('node', $contentPath))
->orWhere($this->qomFactory->sameNode('node', $contentPath));
$this->appendMapping($queryBuilder, $mapping, $locale, $locales);
return $this->resolveQueryBuilder($queryBuilder, $locale, $locales, $mapping, $user);
}
public function findDescendantIdsById($id)
{
$queryBuilder = $this->getQueryBuilder();
$queryBuilder->where(
$this->qomFactory->comparison(
new PropertyValue('node', 'jcr:uuid'),
'=',
$this->qomFactory->literal($id)
)
);
$result = \iterator_to_array($queryBuilder->execute());
if (0 === \count($result)) {
return [];
}
$path = $result[0]->getPath();
$descendantQueryBuilder = $this->getQueryBuilder()
->where($this->qomFactory->descendantNode('node', $path));
return \array_map(
function(Row $row) {
return $row->getNode()->getIdentifier();
},
\iterator_to_array($descendantQueryBuilder->execute())
);
}
/**
* Generates a content-tree with paths of given content array.
*
* @param Content[] $contents
*
* @return Content[]
*/
private function generateTreeByPath(array $contents, $uuid)
{
$childrenByPath = [];
foreach ($contents as $content) {
$path = PathHelper::getParentPath($content->getPath());
if (!isset($childrenByPath[$path])) {
$childrenByPath[$path] = [];
}
$order = $content['order'];
while (isset($childrenByPath[$path][$order])) {
++$order;
}
$childrenByPath[$path][$order] = $content;
}
foreach ($contents as $content) {
if (!isset($childrenByPath[$content->getPath()])) {
if ($content->getId() === $uuid) {
$content->setChildren([]);
}
continue;
}
\ksort($childrenByPath[$content->getPath()]);
$content->setChildren(\array_values($childrenByPath[$content->getPath()]));
}
if (!\array_key_exists('/', $childrenByPath) || !\is_array($childrenByPath['/'])) {
return [];
}
\ksort($childrenByPath['/']);
return \array_values($childrenByPath['/']);
}
/**
* Resolve path for node with given uuid.
*
* @param string $uuid
*
* @return string
*
* @throws ItemNotFoundException
*/
private function resolvePathByUuid($uuid)
{
$queryBuilder = new QueryBuilder($this->qomFactory);
$queryBuilder
->select('node', 'jcr:uuid', 'uuid')
->from($this->qomFactory->selector('node', 'nt:unstructured'))
->where(
$this->qomFactory->comparison(
$this->qomFactory->propertyValue('node', 'jcr:uuid'),
'=',
$this->qomFactory->literal($uuid)
)
);
$rows = $queryBuilder->execute();
if (1 !== \count(\iterator_to_array($rows->getRows()))) {
throw new ItemNotFoundException();
}
return $rows->getRows()->current()->getPath();
}
/**
* Resolves query results to content.
*
* @param string $locale
* @param UserInterface $user
*
* @return Content[]
*/
private function resolveQueryBuilder(
QueryBuilder $queryBuilder,
$locale,
$locales,
MappingInterface $mapping,
UserInterface $user = null
) {
$result = \iterator_to_array($queryBuilder->execute());
$permissions = $this->resolveResultPermissions($result, $user);
return \array_values(
\array_filter(
\array_map(
function(Row $row, $index) use ($mapping, $locale, $locales, $user, $permissions) {
return $this->resolveContent(
$row,
$locale,
$locales,
$mapping,
$user,
$permissions[$index] ?? []
);
},
$result,
\array_keys($result)
)
)
);
}
private function resolveResultPermissions(array $result, UserInterface $user = null)
{
$permissions = [];
foreach ($result as $index => $row) {
$permissions[$index] = [];
$jsonPermission = $row->getValue(SecuritySubscriber::SECURITY_PERMISSION_PROPERTY);
if (!$jsonPermission) {
continue;
}
$rowPermissions = \json_decode($jsonPermission, true);
foreach ($rowPermissions as $roleId => $rolePermissions) {
foreach ($this->permissions as $permissionKey => $permission) {
$permissions[$index][$roleId][$permissionKey] = false;
}
foreach ($rolePermissions as $rolePermission) {
$permissions[$index][$roleId][$rolePermission] = true;
}
}
}
return $permissions;
}
/**
* Returns QueryBuilder with basic select and where statements.
*
* @param string $locale
* @param string[] $locales
* @param UserInterface $user
*
* @return QueryBuilder
*/
private function getQueryBuilder($locale = null, $locales = [], UserInterface $user = null)
{
$queryBuilder = new QueryBuilder($this->qomFactory);
$queryBuilder
->select('node', 'jcr:uuid', 'uuid')
->addSelect('node', $this->getPropertyName('nodeType', $locale), 'nodeType')
->addSelect('node', $this->getPropertyName('internal_link', $locale), 'internalLink')
->addSelect('node', $this->getPropertyName('state', $locale), 'state')
->addSelect('node', $this->getPropertyName('shadow-on', $locale), 'shadowOn')
->addSelect('node', $this->getPropertyName('shadow-base', $locale), 'shadowBase')
->addSelect('node', $this->propertyEncoder->systemName('order'), 'order')
->from($this->qomFactory->selector('node', 'nt:unstructured'))
->orderBy($this->qomFactory->propertyValue('node', 'sulu:order'));
$this->appendSingleMapping($queryBuilder, 'template', $locales);
$this->appendSingleMapping($queryBuilder, 'shadow-on', $locales);
$this->appendSingleMapping($queryBuilder, 'state', $locales);
$queryBuilder->addSelect(
'node',
SecuritySubscriber::SECURITY_PERMISSION_PROPERTY,
SecuritySubscriber::SECURITY_PERMISSION_PROPERTY
);
return $queryBuilder;
}
private function getPropertyName($propertyName, $locale)
{
if ($locale) {
return $this->propertyEncoder->localizedContentName($propertyName, $locale);
}
return $this->propertyEncoder->contentName($propertyName);
}
/**
* Returns array of locales for given webspace key.
*
* @param string $webspaceKey
*
* @return string[]
*/
private function getLocalesByWebspaceKey($webspaceKey)
{
$webspace = $this->webspaceManager->findWebspaceByKey($webspaceKey);
return \array_map(
function(Localization $localization) {
return $localization->getLocale();
},
$webspace->getAllLocalizations()
);
}
/**
* Returns array of locales for given portal key.
*
* @param string $portalKey
*
* @return string[]
*/
private function getLocalesByPortalKey($portalKey)
{
$portal = $this->webspaceManager->findPortalByKey($portalKey);
return \array_map(
function(Localization $localization) {
return $localization->getLocale();
},
$portal->getLocalizations()
);
}
/**
* Returns array of locales for webspaces.
*
* @return string[]
*/
private function getLocales()
{
return $this->webspaceManager->getAllLocales();
}
/**
* Append mapping selects to given query-builder.
*
* @param MappingInterface $mapping Includes array of property names
* @param string $locale
* @param string[] $locales
*/
private function appendMapping(QueryBuilder $queryBuilder, MappingInterface $mapping, $locale, $locales)
{
if ($mapping->onlyPublished()) {
$queryBuilder->andWhere(
$this->qomFactory->comparison(
$this->qomFactory->propertyValue(
'node',
$this->propertyEncoder->localizedSystemName('state', $locale)
),
'=',
$this->qomFactory->literal(WorkflowStage::PUBLISHED)
)
);
}
$properties = $mapping->getProperties();
foreach ($properties as $propertyName) {
$this->appendSingleMapping($queryBuilder, $propertyName, $locales);
}
if ($mapping->resolveUrl()) {
$this->appendUrlMapping($queryBuilder, $locales);
}
}
/**
* Append mapping selects for a single property to given query-builder.
*
* @param string $propertyName
* @param string[] $locales
*/
private function appendSingleMapping(QueryBuilder $queryBuilder, $propertyName, $locales)
{
foreach ($locales as $locale) {
$alias = \sprintf('%s%s', $locale, \str_replace('-', '_', \ucfirst($propertyName)));
$queryBuilder->addSelect(
'node',
$this->propertyEncoder->localizedContentName($propertyName, $locale),
$alias
);
}
}
/**
* Append mapping for url to given query-builder.
*
* @param string[] $locales
*/
private function appendUrlMapping(QueryBuilder $queryBuilder, $locales)
{
$structures = $this->structureManager->getStructures(Structure::TYPE_PAGE);
$urlNames = [];
foreach ($structures as $structure) {
if (!$structure->hasTag('sulu.rlp')) {
continue;
}
$propertyName = $structure->getPropertyByTagName('sulu.rlp')->getName();
if (!\in_array($propertyName, $urlNames)) {
$this->appendSingleMapping($queryBuilder, $propertyName, $locales);
$urlNames[] = $propertyName;
}
}
}
/**
* Resolve a single result row to a content object.
*
* @param string $locale
* @param string $locales
*
* @return Content|null
*/
private function resolveContent(
Row $row,
$locale,
$locales,
MappingInterface $mapping,
UserInterface $user = null,
array $permissions
) {
$webspaceKey = $this->nodeHelper->extractWebspaceFromPath($row->getPath());
$originalLocale = $locale;
$availableLocales = $this->resolveAvailableLocales($row);
$ghostLocale = $this->localizationFinder->findAvailableLocale(
$webspaceKey,
$availableLocales,
$locale
);
if (null === $ghostLocale) {
$ghostLocale = \reset($availableLocales);
}
$type = null;
if ($row->getValue('shadowOn')) {
if (!$mapping->shouldHydrateShadow()) {
return null;
}
$type = StructureType::getShadow($row->getValue('shadowBase'));
} elseif (null !== $ghostLocale && $ghostLocale !== $originalLocale) {
if (!$mapping->shouldHydrateGhost()) {
return null;
}
$locale = $ghostLocale;
$type = StructureType::getGhost($locale);
}
if (
RedirectType::INTERNAL === $row->getValue('nodeType')
&& $mapping->followInternalLink()
&& '' !== $row->getValue('internalLink')
&& $row->getValue('internalLink') !== $row->getValue('uuid')
) {
// TODO collect all internal link contents and query once
return $this->resolveInternalLinkContent($row, $locale, $webspaceKey, $mapping, $type, $user);
}
$shadowBase = null;
if ($row->getValue('shadowOn')) {
$shadowBase = $row->getValue('shadowBase');
}
$data = [];
foreach ($mapping->getProperties() as $item) {
$data[$item] = $this->resolveProperty($row, $item, $locale, $shadowBase);
}
$content = new Content(
$originalLocale,
$webspaceKey,
$row->getValue('uuid'),
$this->resolvePath($row, $webspaceKey),
$row->getValue('state'),
$row->getValue('nodeType'),
$this->resolveHasChildren($row), $this->resolveProperty($row, 'template', $locale, $shadowBase),
$data,
$permissions,
$type
);
$content->setRow($row);
if (!$content->getTemplate() || !$this->structureManager->getStructure($content->getTemplate())) {
$content->setBrokenTemplate();
}
if ($mapping->resolveUrl()) {
$url = $this->resolveUrl($row, $locale);
/** @var array<string, string|null> $urls */
$urls = [];
\array_walk(
$locales,
/** @var array<string, string|null> $urls */
function($locale) use (&$urls, $row) {
$urls[$locale] = $this->resolveUrl($row, $locale);
}
);
$content->setUrl($url);
$content->setUrls($urls);
}
if ($mapping->resolveConcreteLocales()) {
$locales = $this->resolveAvailableLocales($row);
$content->setContentLocales($locales);
}
return $content;
}
/**
* Resolves all available localizations for given row.
*
* @return string[]
*/
private function resolveAvailableLocales(Row $row)
{
$locales = [];
foreach ($row->getValues() as $key => $value) {
if (\preg_match('/^node.([a-zA-Z_]*?)Template/', $key, $matches) && '' !== $value
&& !$row->getValue(\sprintf('node.%sShadow_on', $matches[1]))
) {
$locales[] = $matches[1];
}
}
return $locales;
}
/**
* Resolve a single result row which is an internal link to a content object.
*
* @param string $locale
* @param string $webspaceKey
* @param MappingInterface $mapping Includes array of property names
* @param StructureType $type
* @param UserInterface $user
*
* @return Content|null
*/
public function resolveInternalLinkContent(
Row $row,
$locale,
$webspaceKey,
MappingInterface $mapping,
StructureType $type = null,
UserInterface $user = null
) {
$linkedContent = $this->find($row->getValue('internalLink'), $locale, $webspaceKey, $mapping);
if (null === $linkedContent) {
return null;
}
$data = $linkedContent->getData();
// return value of source node instead of link destination for title and non-fallback-properties
$sourceNodeValueProperties = self::$nonFallbackProperties;
$sourceNodeValueProperties[] = 'title';
$properties = \array_intersect($sourceNodeValueProperties, \array_keys($data));
foreach ($properties as $property) {
$data[$property] = $this->resolveProperty($row, $property, $locale);
}
$resultPermissions = $this->resolveResultPermissions([$row], $user);
$permissions = empty($resultPermissions) ? [] : \current($resultPermissions);
$content = new Content(
$locale,
$webspaceKey,
$row->getValue('uuid'),
$this->resolvePath($row, $webspaceKey),
$row->getValue('state'),
$row->getValue('nodeType'),
$this->resolveHasChildren($row), $this->resolveProperty($row, 'template', $locale),
$data,
$permissions,
$type
);
if ($mapping->resolveUrl()) {
$content->setUrl($linkedContent->getUrl());
$content->setUrls($linkedContent->getUrls());
}
if (!$content->getTemplate() || !$this->structureManager->getStructure($content->getTemplate())) {
$content->setBrokenTemplate();
}
return $content;
}
/**
* Resolve a property and follow shadow locale if it has one.
*
* @param string $name
* @param string $locale
* @param string $shadowLocale
*/
private function resolveProperty(Row $row, $name, $locale, $shadowLocale = null)
{
if (\array_key_exists(\sprintf('node.%s', $name), $row->getValues())) {
return $row->getValue($name);
}
if (null !== $shadowLocale && !\in_array($name, self::$nonFallbackProperties)) {
$locale = $shadowLocale;
}
$name = \sprintf('%s%s', $locale, \str_replace('-', '_', \ucfirst($name)));
try {
return $row->getValue($name);
} catch (ItemNotFoundException $e) {
// the default value of a non existing property in jackalope is an empty string
return '';
}
}
/**
* Resolve url property.
*
* @param string $locale
*
* @return string|null
*/
private function resolveUrl(Row $row, $locale)
{
if (WorkflowStage::PUBLISHED !== $this->resolveProperty($row, $locale . 'State', $locale)) {
return null;
}
$template = $this->resolveProperty($row, 'template', $locale);
if (empty($template)) {
return null;
}
$structure = $this->structureManager->getStructure($template);
if (!$structure || !$structure->hasTag('sulu.rlp')) {
return null;
}
$propertyName = $structure->getPropertyByTagName('sulu.rlp')->getName();
return $this->resolveProperty($row, $propertyName, $locale);
}
/**
* Resolves path for given row.
*
* @param string $webspaceKey
*
* @return string
*/
private function resolvePath(Row $row, $webspaceKey)
{
return '/' . \ltrim(\str_replace($this->sessionManager->getContentPath($webspaceKey), '', $row->getPath()), '/');
}
/**
* Resolve property has-children with given node.
*
* @return bool
*/
private function resolveHasChildren(Row $row)
{
$queryBuilder = new QueryBuilder($this->qomFactory);
$queryBuilder
->select('node', 'jcr:uuid', 'uuid')
->from($this->qomFactory->selector('node', 'nt:unstructured'))
->where($this->qomFactory->childNode('node', $row->getPath()))
->setMaxResults(1);
$result = $queryBuilder->execute();
return \count(\iterator_to_array($result->getRows())) > 0;
}
public function supportsDescendantType(string $type): bool
{
try {
$class = new \ReflectionClass($type);
} catch (\ReflectionException $e) {
// in case the class does not exist there is no support
return false;
}
return $class->implementsInterface(SecurityBehavior::class);
}
}