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

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