vendor/sulu/sulu/src/Sulu/Component/DocumentManager/Subscriber/Behavior/VersionSubscriber.php line 62

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\DocumentManager\Subscriber\Behavior;
  11. use Jackalope\Version\VersionManager;
  12. use PHPCR\NodeInterface;
  13. use PHPCR\SessionInterface;
  14. use PHPCR\Version\VersionException;
  15. use Sulu\Component\DocumentManager\Behavior\VersionBehavior;
  16. use Sulu\Component\DocumentManager\Event\AbstractMappingEvent;
  17. use Sulu\Component\DocumentManager\Event\HydrateEvent;
  18. use Sulu\Component\DocumentManager\Event\PersistEvent;
  19. use Sulu\Component\DocumentManager\Event\PublishEvent;
  20. use Sulu\Component\DocumentManager\Event\RestoreEvent;
  21. use Sulu\Component\DocumentManager\Events;
  22. use Sulu\Component\DocumentManager\Exception\VersionNotFoundException;
  23. use Sulu\Component\DocumentManager\PropertyEncoder;
  24. use Sulu\Component\DocumentManager\Version;
  25. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  26. /**
  27.  * This subscriber is responsible for creating versions of a document.
  28.  */
  29. class VersionSubscriber implements EventSubscriberInterface
  30. {
  31.     public const VERSION_PROPERTY 'sulu:versions';
  32.     /**
  33.      * @var SessionInterface
  34.      */
  35.     private $defaultSession;
  36.     /**
  37.      * @var PropertyEncoder
  38.      */
  39.     private $propertyEncoder;
  40.     /**
  41.      * @var VersionManager
  42.      */
  43.     private $versionManager;
  44.     /**
  45.      * @var string[]
  46.      */
  47.     private $checkoutUuids = [];
  48.     /**
  49.      * @var string[]
  50.      */
  51.     private $checkpointUuids = [];
  52.     public function __construct(SessionInterface $defaultSessionPropertyEncoder $propertyEncoder)
  53.     {
  54.         $this->defaultSession $defaultSession;
  55.         $this->propertyEncoder $propertyEncoder;
  56.         $this->versionManager $defaultSession->getWorkspace()->getVersionManager();
  57.     }
  58.     public static function getSubscribedEvents()
  59.     {
  60.         return [
  61.             Events::PERSIST => [
  62.                 ['setVersionMixin'468],
  63.                 ['rememberCheckoutUuids', -512],
  64.             ],
  65.             Events::PUBLISH => [
  66.                 ['setVersionMixin'468],
  67.                 ['rememberCreateVersion'],
  68.             ],
  69.             Events::HYDRATE => 'setVersionsOnDocument',
  70.             Events::FLUSH => 'applyVersionOperations',
  71.             Events::RESTORE => 'restoreProperties',
  72.         ];
  73.     }
  74.     /**
  75.      * Sets the versionable mixin on the node if it is a versionable document.
  76.      */
  77.     public function setVersionMixin(AbstractMappingEvent $event)
  78.     {
  79.         if (!$this->support($event->getDocument())) {
  80.             return;
  81.         }
  82.         $event->getNode()->addMixin('mix:versionable');
  83.     }
  84.     /**
  85.      * Sets the version information set on the node to the document.
  86.      */
  87.     public function setVersionsOnDocument(HydrateEvent $event)
  88.     {
  89.         $document $event->getDocument();
  90.         if (!$this->support($document)) {
  91.             return;
  92.         }
  93.         $node $event->getNode();
  94.         $versions = [];
  95.         $versionProperty $node->getPropertyValueWithDefault(static::VERSION_PROPERTY, []);
  96.         foreach ($versionProperty as $version) {
  97.             $versionInformation = \json_decode($version);
  98.             $versions[] = new Version(
  99.                 $versionInformation->version,
  100.                 $versionInformation->locale,
  101.                 $versionInformation->author,
  102.                 new \DateTime($versionInformation->authored)
  103.             );
  104.         }
  105.         $document->setVersions($versions);
  106.     }
  107.     /**
  108.      * Remember which uuids need to be checked out after everything has been saved.
  109.      */
  110.     public function rememberCheckoutUuids(PersistEvent $event)
  111.     {
  112.         if (!$this->support($event->getDocument())) {
  113.             return;
  114.         }
  115.         $this->checkoutUuids[] = $event->getNode()->getIdentifier();
  116.     }
  117.     /**
  118.      * Remember for which uuids a new version has to be created.
  119.      */
  120.     public function rememberCreateVersion(PublishEvent $event)
  121.     {
  122.         $document $event->getDocument();
  123.         if (!$this->support($document)) {
  124.             return;
  125.         }
  126.         $this->checkpointUuids[] = [
  127.             'uuid' => $event->getNode()->getIdentifier(),
  128.             'locale' => $document->getLocale(),
  129.             'author' => $event->getOption('user'),
  130.         ];
  131.     }
  132.     /**
  133.      * Apply all the operations which have been remembered after the flush.
  134.      */
  135.     public function applyVersionOperations()
  136.     {
  137.         foreach ($this->checkoutUuids as $uuid) {
  138.             $node $this->defaultSession->getNodeByIdentifier($uuid);
  139.             $path $node->getPath();
  140.             if (!$this->versionManager->isCheckedOut($path)) {
  141.                 $this->versionManager->checkout($path);
  142.             }
  143.         }
  144.         $this->checkoutUuids = [];
  145.         /** @var NodeInterface[] $nodes */
  146.         $nodes = [];
  147.         $nodeVersions = [];
  148.         foreach ($this->checkpointUuids as $versionInformation) {
  149.             $node $this->defaultSession->getNodeByIdentifier($versionInformation['uuid']);
  150.             $path $node->getPath();
  151.             $version $this->versionManager->checkpoint($path);
  152.             if (!\array_key_exists($path$nodes)) {
  153.                 $nodes[$path] = $this->defaultSession->getNode($path);
  154.             }
  155.             $versions $nodes[$path]->getPropertyValueWithDefault(static::VERSION_PROPERTY, []);
  156.             if (!\array_key_exists($path$nodeVersions)) {
  157.                 $nodeVersions[$path] = $versions;
  158.             }
  159.             $nodeVersions[$path][] = \json_encode(
  160.                 [
  161.                     'locale' => $versionInformation['locale'],
  162.                     'version' => $version->getName(),
  163.                     'author' => $versionInformation['author'],
  164.                     'authored' => \date('c'),
  165.                 ]
  166.             );
  167.         }
  168.         foreach ($nodes as $path => $node) {
  169.             $node->setProperty(static::VERSION_PROPERTY$nodeVersions[$path]);
  170.         }
  171.         $this->defaultSession->save();
  172.         $this->checkpointUuids = [];
  173.     }
  174.     /**
  175.      * Restore the properties of the old version.
  176.      *
  177.      * @throws VersionNotFoundException
  178.      */
  179.     public function restoreProperties(RestoreEvent $event)
  180.     {
  181.         if (!$this->support($event->getDocument())) {
  182.             $event->stopPropagation();
  183.             return;
  184.         }
  185.         $contentPropertyPrefix $this->propertyEncoder->localizedContentName(''$event->getLocale());
  186.         $systemPropertyPrefix $this->propertyEncoder->localizedSystemName(''$event->getLocale());
  187.         $node $event->getNode();
  188.         try {
  189.             $version $this->versionManager->getVersionHistory($node->getPath())->getVersion($event->getVersion());
  190.             $frozenNode $version->getFrozenNode();
  191.             $this->restoreNodeProperties($node$frozenNode$contentPropertyPrefix$systemPropertyPrefix);
  192.         } catch (VersionException $exception) {
  193.             throw new VersionNotFoundException($event->getDocument(), $event->getVersion());
  194.         }
  195.     }
  196.     /**
  197.      * Restore given node with properties given from frozen-node.
  198.      * Will be called recursive.
  199.      *
  200.      * @param string $contentPropertyPrefix
  201.      * @param string $systemPropertyPrefix
  202.      */
  203.     private function restoreNodeProperties(
  204.         NodeInterface $node,
  205.         NodeInterface $frozenNode,
  206.         $contentPropertyPrefix,
  207.         $systemPropertyPrefix
  208.     ) {
  209.         // remove the properties for the given language, so that values being added since the last version are removed
  210.         foreach ($node->getProperties() as $property) {
  211.             if ($this->isRestoreProperty($property->getName(), $contentPropertyPrefix$systemPropertyPrefix)) {
  212.                 $property->remove();
  213.             }
  214.         }
  215.         // set all the properties from the saved version to the node
  216.         foreach ($frozenNode->getPropertiesValues() as $name => $value) {
  217.             if ($this->isRestoreProperty($name$contentPropertyPrefix$systemPropertyPrefix)) {
  218.                 $node->setProperty($name$value);
  219.             }
  220.         }
  221.     }
  222.     /**
  223.      * @param string $propertyName
  224.      * @param string $contentPrefix
  225.      * @param string $systemPrefix
  226.      *
  227.      * @return bool
  228.      */
  229.     private function isRestoreProperty($propertyName$contentPrefix$systemPrefix)
  230.     {
  231.         // return all localized and non-translatable properties
  232.         // non-translatable properties can be recognized by their missing namespace, therfore the check for the colon
  233.         if (=== \strpos($propertyName$contentPrefix)
  234.             || === \strpos($propertyName$systemPrefix)
  235.             || false === \strpos($propertyName':')
  236.         ) {
  237.             return true;
  238.         }
  239.         return false;
  240.     }
  241.     /**
  242.      * Determines if the given document supports versioning.
  243.      *
  244.      * @param object $document
  245.      *
  246.      * @return bool
  247.      */
  248.     private function support($document)
  249.     {
  250.         return $document instanceof VersionBehavior;
  251.     }
  252. }