vendor/sulu/headless-bundle/Content/StructureResolver.php line 199

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4.  * This file is part of Sulu.
  5.  *
  6.  * (c) Sulu GmbH
  7.  *
  8.  * This source file is subject to the MIT license that is bundled
  9.  * with this source code in the file LICENSE.
  10.  */
  11. namespace Sulu\Bundle\HeadlessBundle\Content;
  12. use Sulu\Bundle\DocumentManagerBundle\Bridge\DocumentInspector;
  13. use Sulu\Bundle\PageBundle\Document\BasePageDocument;
  14. use Sulu\Bundle\WebsiteBundle\ReferenceStore\ReferenceStoreNotExistsException;
  15. use Sulu\Bundle\WebsiteBundle\ReferenceStore\ReferenceStorePoolInterface;
  16. use Sulu\Component\Content\Compat\PropertyInterface;
  17. use Sulu\Component\Content\Compat\Structure\StructureBridge;
  18. use Sulu\Component\Content\Compat\StructureInterface;
  19. use Sulu\Component\Content\Compat\StructureManagerInterface;
  20. use Sulu\Component\Content\Document\Behavior\RedirectTypeBehavior;
  21. use Sulu\Component\Content\Document\Behavior\StructureBehavior;
  22. use Sulu\Component\Content\Document\Extension\ExtensionContainer;
  23. use Sulu\Component\Content\Document\RedirectType;
  24. use Sulu\Component\Content\Metadata\StructureMetadata;
  25. class StructureResolver implements StructureResolverInterface
  26. {
  27.     /**
  28.      * @var ContentResolverInterface
  29.      */
  30.     private $contentResolver;
  31.     /**
  32.      * @var StructureManagerInterface
  33.      */
  34.     private $structureManager;
  35.     /**
  36.      * @var DocumentInspector
  37.      */
  38.     private $documentInspector;
  39.     /**
  40.      * @var ReferenceStorePoolInterface
  41.      */
  42.     private $referenceStorePool;
  43.     public function __construct(
  44.         ContentResolverInterface $contentResolver,
  45.         StructureManagerInterface $structureManager,
  46.         DocumentInspector $documentInspector,
  47.         ReferenceStorePoolInterface $referenceStorePool
  48.     ) {
  49.         $this->contentResolver $contentResolver;
  50.         $this->structureManager $structureManager;
  51.         $this->documentInspector $documentInspector;
  52.         $this->referenceStorePool $referenceStorePool;
  53.     }
  54.     /**
  55.      * @param StructureBridge $structure
  56.      */
  57.     public function resolve(
  58.         StructureInterface $structure,
  59.         string $locale,
  60.         bool $includeExtension true
  61.     ): array {
  62.         $requestedStructure $structure;
  63.         $targetStructure $this->getTargetStructure($requestedStructure);
  64.         $data $this->getStructureData($targetStructure$requestedStructure);
  65.         if ($includeExtension) {
  66.             $data['extension'] = $this->resolveExtensionData(
  67.                 $this->getExtensionData($targetStructure),
  68.                 $locale,
  69.                 ['webspaceKey' => $targetStructure->getWebspaceKey()]
  70.             );
  71.         }
  72.         foreach ($this->getProperties($targetStructure$requestedStructure) as $property) {
  73.             $contentView $this->contentResolver->resolve(
  74.                 $property->getValue(),
  75.                 $property,
  76.                 $locale,
  77.                 ['webspaceKey' => $property->getStructure()->getWebspaceKey()]
  78.             );
  79.             $data['content'][$property->getName()] = $contentView->getContent();
  80.             $data['view'][$property->getName()] = $contentView->getView();
  81.         }
  82.         return $data;
  83.     }
  84.     /**
  85.      * @param StructureBridge $structure
  86.      */
  87.     public function resolveProperties(
  88.         StructureInterface $structure,
  89.         array $propertyMap,
  90.         string $locale,
  91.         bool $includeExtension false
  92.     ): array {
  93.         $requestedStructure $structure;
  94.         $targetStructure $this->getTargetStructure($requestedStructure);
  95.         $data $this->getStructureData($targetStructure$requestedStructure);
  96.         $unresolvedExtensionData $this->getExtensionData($targetStructure);
  97.         $attributes = ['webspaceKey' => $targetStructure->getWebspaceKey()];
  98.         $excerptStructure $this->structureManager->getStructure('excerpt');
  99.         if ($includeExtension) {
  100.             $data['extension'] = $this->resolveExtensionData($unresolvedExtensionData$locale$attributes);
  101.         }
  102.         foreach ($propertyMap as $targetProperty => $sourceProperty) {
  103.             if (!\is_string($targetProperty)) {
  104.                 $targetProperty $sourceProperty;
  105.             }
  106.             // the '.' is used to separate the extension name from the property name.
  107.             if (false !== \strpos($sourceProperty'.')) {
  108.                 [$extensionName$propertyName] = \explode('.'$sourceProperty);
  109.                 if (!isset($unresolvedExtensionData[$extensionName][$propertyName])) {
  110.                     continue;
  111.                 }
  112.                 $contentView = new ContentView($unresolvedExtensionData[$extensionName][$propertyName]);
  113.                 if ('excerpt' === $extensionName) {
  114.                     $contentView $this->resolveProperty(
  115.                         $excerptStructure,
  116.                         $propertyName,
  117.                         $locale,
  118.                         $attributes,
  119.                         $contentView->getContent()
  120.                     );
  121.                 }
  122.             } else {
  123.                 $property $this->getProperty($sourceProperty$targetStructure$requestedStructure);
  124.                 if (null === $property) {
  125.                     continue;
  126.                 }
  127.                 $contentView $this->resolveProperty(
  128.                     $property->getStructure(),
  129.                     $sourceProperty,
  130.                     $locale,
  131.                     ['webspaceKey' => $property->getStructure()->getWebspaceKey()],
  132.                     $property->getValue()
  133.                 );
  134.             }
  135.             $data['content'][$targetProperty] = $contentView->getContent();
  136.             $data['view'][$targetProperty] = $contentView->getView();
  137.         }
  138.         return $data;
  139.     }
  140.     /**
  141.      * @param StructureBridge $targetStructure
  142.      * @param StructureBridge $requestedStructure
  143.      */
  144.     private function getProperty(
  145.         string $name,
  146.         StructureInterface $targetStructure,
  147.         StructureInterface $requestedStructure
  148.     ): ?PropertyInterface {
  149.         if ('title' === $name && $requestedStructure->hasProperty('title')) {
  150.             return $requestedStructure->getProperty('title');
  151.         }
  152.         if ($targetStructure->hasProperty($name)) {
  153.             return $targetStructure->getProperty($name);
  154.         }
  155.         return null;
  156.     }
  157.     /**
  158.      * @param StructureBridge $targetStructure
  159.      * @param StructureBridge $requestedStructure
  160.      *
  161.      * @return array<string, PropertyInterface>
  162.      */
  163.     private function getProperties(StructureInterface $targetStructureStructureInterface $requestedStructure): array
  164.     {
  165.         $properties = [];
  166.         foreach ($targetStructure->getProperties(true) as $property) {
  167.             $property $this->getProperty(
  168.                 $property->getName(),
  169.                 $targetStructure,
  170.                 $requestedStructure
  171.             );
  172.             if (null !== $property) {
  173.                 $properties[$property->getName()] = $property;
  174.             }
  175.         }
  176.         return $properties;
  177.     }
  178.     /**
  179.      * @param StructureBridge $targetStructure
  180.      * @param StructureBridge $requestedStructure
  181.      *
  182.      * @return mixed[]
  183.      */
  184.     private function getStructureData(
  185.         StructureInterface $targetStructure,
  186.         StructureInterface $requestedStructure
  187.     ): array {
  188.         $targetDocument $targetStructure->getDocument();
  189.         $requestedDocument $requestedStructure->getDocument();
  190.         /** @var string|null $templateKey */
  191.         $templateKey null;
  192.         if (\method_exists($targetDocument'getStructureType')) {
  193.             $templateKey $targetDocument->getStructureType();
  194.         }
  195.         /** @var int|null $author */
  196.         $author null;
  197.         if (\method_exists($targetDocument'getAuthor')) {
  198.             $author $targetDocument->getAuthor();
  199.         }
  200.         /** @var \DateTimeInterface|null $authored */
  201.         $authored null;
  202.         if (\method_exists($targetDocument'getAuthored')) {
  203.             /** @var \DateTimeInterface|null $authored typehint in sulu is wrong */
  204.             $authored $targetDocument->getAuthored();
  205.         }
  206.         /** @var int|null $changer */
  207.         $changer null;
  208.         if (\method_exists($requestedDocument'getChanger')) {
  209.             $changer $requestedDocument->getChanger();
  210.         }
  211.         /** @var \DateTimeInterface|null $changed */
  212.         $changed null;
  213.         if (\method_exists($requestedDocument'getChanged')) {
  214.             $changed $requestedDocument->getChanged();
  215.         }
  216.         /** @var int|null $creator */
  217.         $creator null;
  218.         if (\method_exists($requestedDocument'getCreator')) {
  219.             $creator $requestedDocument->getCreator();
  220.         }
  221.         /** @var \DateTimeInterface|null $created */
  222.         $created null;
  223.         if (\method_exists($requestedDocument'getCreated')) {
  224.             $created $requestedDocument->getCreated();
  225.         }
  226.         $templateType $this->getTemplateType($targetStructure$targetDocument);
  227.         $this->addToReferenceStore($targetStructure->getUuid(), $templateType);
  228.         $this->addToReferenceStore(
  229.             $requestedStructure->getUuid(),
  230.             $this->getTemplateType($requestedStructure$requestedDocument)
  231.         );
  232.         return [
  233.             'id' => $requestedStructure->getUuid(),
  234.             'nodeType' => $requestedStructure->getNodeType(),
  235.             'type' => $templateType,
  236.             'template' => $templateKey,
  237.             'content' => [],
  238.             'view' => [],
  239.             'author' => $author,
  240.             'authored' => $authored $authored->format(\DateTimeImmutable::ISO8601) : null,
  241.             'changer' => $changer,
  242.             'changed' => $changed $changed->format(\DateTimeImmutable::ISO8601) : null,
  243.             'creator' => $creator,
  244.             'created' => $created $created->format(\DateTimeImmutable::ISO8601) : null,
  245.         ];
  246.     }
  247.     /**
  248.      * @param StructureBridge $structure
  249.      *
  250.      * @return mixed[]
  251.      */
  252.     private function getExtensionData(StructureInterface $structure): array
  253.     {
  254.         /** @var BasePageDocument $document */
  255.         $document $structure->getDocument();
  256.         /** @var ExtensionContainer|mixed[] $extensionData */
  257.         $extensionData = [];
  258.         if (\method_exists($document'getExtensionsData')) {
  259.             $extensionData $document->getExtensionsData();
  260.         }
  261.         if ($extensionData instanceof ExtensionContainer) {
  262.             $extensionData $extensionData->toArray();
  263.         }
  264.         return $extensionData;
  265.     }
  266.     /**
  267.      * @param mixed[] $data
  268.      * @param mixed[] $attributes
  269.      *
  270.      * @return mixed[]
  271.      */
  272.     private function resolveExtensionData(array $datastring $locale, array $attributes): array
  273.     {
  274.         $excerptStructure $this->structureManager->getStructure('excerpt');
  275.         $unresolvedExcerptData $data['excerpt'] ?? [];
  276.         $resolvedExcerptData = [];
  277.         foreach ($excerptStructure->getProperties(true) as $property) {
  278.             $resolvedExcerptData[$property->getName()] = $this->resolveProperty(
  279.                 $excerptStructure,
  280.                 $property->getName(),
  281.                 $locale,
  282.                 $attributes,
  283.                 $unresolvedExcerptData[$property->getName()] ?? null
  284.             )->getContent();
  285.         }
  286.         $data['excerpt'] = $resolvedExcerptData;
  287.         return $data;
  288.     }
  289.     /**
  290.      * @param mixed $value
  291.      * @param mixed[] $attributes
  292.      */
  293.     private function resolveProperty(
  294.         StructureInterface $structure,
  295.         string $name,
  296.         string $locale,
  297.         array $attributes,
  298.         $value
  299.     ): ContentView {
  300.         $property $structure->getProperty($name);
  301.         $property->setValue($value);
  302.         return $this->contentResolver->resolve(
  303.             $value,
  304.             $property,
  305.             $locale,
  306.             $attributes
  307.         );
  308.     }
  309.     private function addToReferenceStore(string $uuidstring $alias): void
  310.     {
  311.         if ('page' === $alias) {
  312.             // unfortunately the reference store for pages was not adjusted and still uses content as alias
  313.             $alias 'content';
  314.         }
  315.         try {
  316.             $referenceStore $this->referenceStorePool->getStore($alias);
  317.         } catch (ReferenceStoreNotExistsException $e) {
  318.             // @ignoreException do nothing when reference store was not found
  319.             return;
  320.         }
  321.         $referenceStore->add($uuid);
  322.     }
  323.     private function getTemplateType(StructureInterface $structureobject $document): string
  324.     {
  325.         $structureContent null;
  326.         if (\method_exists($structure'getContent')) {
  327.             $structureContent $structure->getContent();
  328.         }
  329.         if (\is_object($structureContent) && \method_exists($structureContent'getTemplateType')) {
  330.             // determine type for structure that is implemented based on the SuluContentBundle
  331.             return $structureContent->getTemplateType();
  332.         }
  333.         if ($document instanceof StructureBehavior) {
  334.             // determine type for structure that is implemented in the SuluPageBundle or the SuluArticleBundle
  335.             $templateType $this->documentInspector->getMetadata($document)->getAlias();
  336.             if ('home' === $templateType) {
  337.                 return 'page';
  338.             }
  339.             return $templateType;
  340.         }
  341.         return 'unknown';
  342.     }
  343.     /**
  344.      * @param StructureBridge $structure
  345.      *
  346.      * @return StructureBridge
  347.      */
  348.     private function getTargetStructure(StructureInterface $structure): StructureInterface
  349.     {
  350.         $document $structure->getDocument();
  351.         if (!$document instanceof RedirectTypeBehavior) {
  352.             return $structure;
  353.         }
  354.         while ($document instanceof RedirectTypeBehavior && RedirectType::INTERNAL === $document->getRedirectType()) {
  355.             $redirectTargetDocument $document->getRedirectTarget();
  356.             if ($redirectTargetDocument instanceof StructureBehavior) {
  357.                 $document $redirectTargetDocument;
  358.             }
  359.         }
  360.         if ($document !== $structure->getDocument() && $document instanceof StructureBehavior) {
  361.             return $this->documentToStructure($document);
  362.         }
  363.         return $structure;
  364.     }
  365.     /**
  366.      * @see PageRouteDefaultsProvider::documentToStructure()
  367.      *
  368.      * @return StructureBridge
  369.      */
  370.     private function documentToStructure(StructureBehavior $document): StructureInterface
  371.     {
  372.         /** @var StructureMetadata $structure */
  373.         $structure $this->documentInspector->getStructureMetadata($document);
  374.         $documentAlias $this->documentInspector->getMetadata($document)->getAlias();
  375.         /** @var StructureBridge $structureBridge */
  376.         $structureBridge $this->structureManager->wrapStructure($documentAlias$structure);
  377.         $structureBridge->setDocument($document);
  378.         return $structureBridge;
  379.     }
  380. }