vendor/sulu/sulu/src/Sulu/Component/Content/Document/Subscriber/StructureSubscriber.php line 376

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\Document\Subscriber;
  11. use PHPCR\NodeInterface;
  12. use Sulu\Bundle\DocumentManagerBundle\Bridge\DocumentInspector;
  13. use Sulu\Component\Content\Compat\Structure\LegacyPropertyFactory;
  14. use Sulu\Component\Content\ContentTypeManagerInterface;
  15. use Sulu\Component\Content\Document\Behavior\LocalizedStructureBehavior;
  16. use Sulu\Component\Content\Document\Behavior\StructureBehavior;
  17. use Sulu\Component\Content\Document\LocalizationState;
  18. use Sulu\Component\Content\Document\Structure\ManagedStructure;
  19. use Sulu\Component\Content\Document\Structure\Structure;
  20. use Sulu\Component\Content\Document\Structure\StructureInterface;
  21. use Sulu\Component\Content\Document\Subscriber\PHPCR\SuluNode;
  22. use Sulu\Component\Content\Exception\MandatoryPropertyException;
  23. use Sulu\Component\DocumentManager\Event\AbstractMappingEvent;
  24. use Sulu\Component\DocumentManager\Event\ConfigureOptionsEvent;
  25. use Sulu\Component\DocumentManager\Event\PersistEvent;
  26. use Sulu\Component\DocumentManager\Events;
  27. use Sulu\Component\DocumentManager\PropertyEncoder;
  28. use Sulu\Component\Webspace\Manager\WebspaceManagerInterface;
  29. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  30. class StructureSubscriber implements EventSubscriberInterface
  31. {
  32.     public const STRUCTURE_TYPE_FIELD 'template';
  33.     /**
  34.      * @var ContentTypeManagerInterface
  35.      */
  36.     private $contentTypeManager;
  37.     /**
  38.      * @var DocumentInspector
  39.      */
  40.     private $inspector;
  41.     /**
  42.      * @var LegacyPropertyFactory
  43.      */
  44.     private $legacyPropertyFactory;
  45.     /**
  46.      * @var PropertyEncoder
  47.      */
  48.     private $encoder;
  49.     /**
  50.      * @var WebspaceManagerInterface
  51.      */
  52.     private $webspaceManager;
  53.     /**
  54.      * @var array
  55.      */
  56.     private $defaultTypes;
  57.     /**
  58.      * @param array $defaultTypes
  59.      */
  60.     public function __construct(
  61.         PropertyEncoder $encoder,
  62.         ContentTypeManagerInterface $contentTypeManager,
  63.         DocumentInspector $inspector,
  64.         LegacyPropertyFactory $legacyPropertyFactory,
  65.         WebspaceManagerInterface $webspaceManager,
  66.         $defaultTypes
  67.     ) {
  68.         $this->encoder $encoder;
  69.         $this->contentTypeManager $contentTypeManager;
  70.         $this->inspector $inspector;
  71.         $this->legacyPropertyFactory $legacyPropertyFactory;
  72.         $this->webspaceManager $webspaceManager;
  73.         $this->defaultTypes $defaultTypes;
  74.     }
  75.     public static function getSubscribedEvents()
  76.     {
  77.         return [
  78.             Events::PERSIST => [
  79.                 // persist should happen before content is mapped
  80.                 ['saveStructureData'0],
  81.                 // staged properties must be commited before title subscriber
  82.                 ['handlePersistStagedProperties'50],
  83.                 // setting the structure should happen very early
  84.                 ['handlePersistStructureType'100],
  85.             ],
  86.             Events::PUBLISH => 'saveStructureData',
  87.             // hydrate should happen afterwards
  88.             Events::HYDRATE => ['handleHydrate'0],
  89.             Events::CONFIGURE_OPTIONS => 'configureOptions',
  90.         ];
  91.     }
  92.     public function configureOptions(ConfigureOptionsEvent $event)
  93.     {
  94.         $options $event->getOptions();
  95.         $options->setDefaults(
  96.             [
  97.                 'load_ghost_content' => true,
  98.                 'clear_missing_content' => false,
  99.                 'ignore_required' => false,
  100.                 'structure_type' => null,
  101.             ]
  102.         );
  103.         $options->setAllowedTypes('load_ghost_content''bool');
  104.         $options->setAllowedTypes('clear_missing_content''bool');
  105.         $options->setAllowedTypes('ignore_required''bool');
  106.     }
  107.     /**
  108.      * Set the structure type early so that subsequent subscribers operate
  109.      * upon the correct structure type.
  110.      */
  111.     public function handlePersistStructureType(PersistEvent $event)
  112.     {
  113.         $document $event->getDocument();
  114.         if (!$this->supportsBehavior($document)) {
  115.             return;
  116.         }
  117.         $structureMetadata $this->inspector->getStructureMetadata($document);
  118.         $structure $document->getStructure();
  119.         if ($structure instanceof ManagedStructure) {
  120.             $structure->setStructureMetadata($structureMetadata);
  121.         }
  122.     }
  123.     /**
  124.      * Commit the properties, which are only staged on the structure yet.
  125.      */
  126.     public function handlePersistStagedProperties(PersistEvent $event)
  127.     {
  128.         $document $event->getDocument();
  129.         if (!$this->supportsBehavior($document)) {
  130.             return;
  131.         }
  132.         $document->getStructure()->commitStagedData($event->getOption('clear_missing_content'));
  133.     }
  134.     public function handleHydrate(AbstractMappingEvent $event)
  135.     {
  136.         $document $event->getDocument();
  137.         if (!$this->supportsBehavior($document)) {
  138.             return;
  139.         }
  140.         $rehydrate $event->getOption('rehydrate');
  141.         $structureType $this->getStructureType($event$document$rehydrate);
  142.         $document->setStructureType($structureType);
  143.         if (false === $event->getOption('load_ghost_content'false)) {
  144.             if (LocalizationState::GHOST === $this->inspector->getLocalizationState($document)) {
  145.                 $structureType null;
  146.             }
  147.         }
  148.         $structure $this->getStructure($document$structureType$rehydrate);
  149.         // Set the property container
  150.         $event->getAccessor()->set(
  151.             'structure',
  152.             $structure
  153.         );
  154.     }
  155.     public function saveStructureData(AbstractMappingEvent $event)
  156.     {
  157.         // Set the structure type
  158.         $document $event->getDocument();
  159.         if (!$this->supportsBehavior($document)) {
  160.             return;
  161.         }
  162.         if (!$document->getStructureType()) {
  163.             return;
  164.         }
  165.         if (!$event->getLocale()) {
  166.             return;
  167.         }
  168.         $node $event->getNode();
  169.         $locale $event->getLocale();
  170.         $options $event->getOptions();
  171.         $this->mapContentToNode($document$node$locale$options['ignore_required']);
  172.         $node->setProperty(
  173.             $this->getStructureTypePropertyName($document$locale),
  174.             $document->getStructureType()
  175.         );
  176.     }
  177.     /**
  178.      * @param bool $rehydrate
  179.      *
  180.      * @return string
  181.      */
  182.     private function getStructureType(AbstractMappingEvent $eventStructureBehavior $document$rehydrate)
  183.     {
  184.         $structureType $event->getOption('structure_type');
  185.         if ($structureType) {
  186.             return $structureType;
  187.         }
  188.         $node $event->getNode();
  189.         $propertyName $this->getStructureTypePropertyName($document$event->getLocale());
  190.         $structureType $node->getPropertyValueWithDefault($propertyNamenull);
  191.         if (!$structureType && $rehydrate) {
  192.             return $this->getDefaultStructureType($document);
  193.         }
  194.         return $structureType;
  195.     }
  196.     /**
  197.      * Return the default structure for the given StructureBehavior implementing document.
  198.      *
  199.      * @return string
  200.      */
  201.     private function getDefaultStructureType(StructureBehavior $document)
  202.     {
  203.         $alias $this->inspector->getMetadata($document)->getAlias();
  204.         $webspace $this->webspaceManager->findWebspaceByKey($this->inspector->getWebspace($document));
  205.         if (!$webspace) {
  206.             return $this->getDefaultStructureTypeFromConfig($alias);
  207.         }
  208.         return $webspace->getDefaultTemplate($alias);
  209.     }
  210.     /**
  211.      * Returns configured "default_type".
  212.      *
  213.      * @param string $alias
  214.      *
  215.      * @return string
  216.      */
  217.     private function getDefaultStructureTypeFromConfig($alias)
  218.     {
  219.         if (!\array_key_exists($alias$this->defaultTypes)) {
  220.             return;
  221.         }
  222.         return $this->defaultTypes[$alias];
  223.     }
  224.     private function supportsBehavior($document)
  225.     {
  226.         return $document instanceof StructureBehavior;
  227.     }
  228.     private function getStructureTypePropertyName($document$locale)
  229.     {
  230.         if ($document instanceof LocalizedStructureBehavior) {
  231.             return $this->encoder->localizedSystemName(self::STRUCTURE_TYPE_FIELD$locale);
  232.         }
  233.         // TODO: This is the wrong namespace, it should be the system namespcae, but we do this for initial BC
  234.         return $this->encoder->contentName(self::STRUCTURE_TYPE_FIELD);
  235.     }
  236.     /**
  237.      * @return ManagedStructure
  238.      */
  239.     private function createStructure($document)
  240.     {
  241.         return new ManagedStructure(
  242.             $this->contentTypeManager,
  243.             $this->legacyPropertyFactory,
  244.             $this->inspector,
  245.             $document
  246.         );
  247.     }
  248.     /**
  249.      * Map to the content properties to the node using the content types.
  250.      *
  251.      * @param string $locale
  252.      * @param bool $ignoreRequired
  253.      *
  254.      * @throws MandatoryPropertyException
  255.      * @throws \RuntimeException
  256.      */
  257.     private function mapContentToNode($documentNodeInterface $node$locale$ignoreRequired)
  258.     {
  259.         $structure $document->getStructure();
  260.         $webspaceName $this->inspector->getWebspace($document);
  261.         $metadata $this->inspector->getStructureMetadata($document);
  262.         if (!$metadata) {
  263.             throw new \RuntimeException(
  264.                 \sprintf(
  265.                     'Metadata for Structure Type "%s" was not found, does the file "%s.xml" exists?',
  266.                     $document->getStructureType(),
  267.                     $document->getStructureType()
  268.                 )
  269.             );
  270.         }
  271.         foreach ($metadata->getProperties() as $propertyName => $structureProperty) {
  272.             if (TitleSubscriber::PROPERTY_NAME === $propertyName) {
  273.                 continue;
  274.             }
  275.             $realProperty $structure->getProperty($propertyName);
  276.             $value $realProperty->getValue();
  277.             if (false === $ignoreRequired && $structureProperty->isRequired() && null === $value) {
  278.                 throw new MandatoryPropertyException(
  279.                     \sprintf(
  280.                         'Property "%s" in structure "%s" is required but no value was given. Loaded from "%s"',
  281.                         $propertyName,
  282.                         $metadata->getName(),
  283.                         $metadata->getResource()
  284.                     )
  285.                 );
  286.             }
  287.             $contentTypeName $structureProperty->getType();
  288.             $contentType $this->contentTypeManager->get($contentTypeName);
  289.             // TODO: Only write if the property has been modified.
  290.             $legacyProperty $this->legacyPropertyFactory->createTranslatedProperty($structureProperty$locale);
  291.             $legacyProperty->setValue($value);
  292.             $contentType->write(
  293.                 new SuluNode($node),
  294.                 $legacyProperty,
  295.                 null,
  296.                 $webspaceName,
  297.                 $locale,
  298.                 null
  299.             );
  300.         }
  301.     }
  302.     /**
  303.      * Return the a structure for the document.
  304.      *
  305.      * - If the Structure already exists on the document, use that.
  306.      * - If the Structure type is given, then create a ManagedStructure - this
  307.      *   means that the structure is already persisted on the node and it has data.
  308.      * - If none of the above applies then create a new, empty, Structure.
  309.      *
  310.      * @param object $document
  311.      * @param string $structureType
  312.      * @param bool $rehydrate
  313.      *
  314.      * @return StructureInterface
  315.      */
  316.     private function getStructure($document$structureType$rehydrate)
  317.     {
  318.         if ($structureType) {
  319.             return $this->createStructure($document);
  320.         }
  321.         if (!$rehydrate && $document->getStructure()) {
  322.             return $document->getStructure();
  323.         }
  324.         return new Structure();
  325.     }
  326. }