vendor/sulu/sulu/src/Sulu/Bundle/PageBundle/Command/WebspaceCopyCommand.php line 293

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\Bundle\PageBundle\Command;
  11. use Sulu\Bundle\DocumentManagerBundle\Bridge\DocumentInspector;
  12. use Sulu\Bundle\MarkupBundle\Markup\HtmlTagExtractor;
  13. use Sulu\Bundle\MarkupBundle\Markup\TagMatchGroup;
  14. use Sulu\Bundle\PageBundle\Document\BasePageDocument;
  15. use Sulu\Bundle\PageBundle\Document\HomeDocument;
  16. use Sulu\Bundle\PageBundle\Document\PageDocument;
  17. use Sulu\Bundle\PageBundle\EventListener\PageRemoveSubscriber;
  18. use Sulu\Component\Content\Compat\PropertyParameter;
  19. use Sulu\Component\Content\Document\LocalizationState;
  20. use Sulu\Component\Content\Document\RedirectType;
  21. use Sulu\Component\Content\Document\WorkflowStage;
  22. use Sulu\Component\Content\Metadata\BlockMetadata;
  23. use Sulu\Component\Content\Metadata\ItemMetadata;
  24. use Sulu\Component\Content\Metadata\PropertyMetadata;
  25. use Sulu\Component\DocumentManager\DocumentManagerInterface;
  26. use Sulu\Component\DocumentManager\Exception\DocumentNotFoundException;
  27. use Sulu\Component\PHPCR\SessionManager\SessionManagerInterface;
  28. use Symfony\Component\Console\Command\Command;
  29. use Symfony\Component\Console\Input\InputArgument;
  30. use Symfony\Component\Console\Input\InputInterface;
  31. use Symfony\Component\Console\Output\OutputInterface;
  32. use Symfony\Component\Console\Style\SymfonyStyle;
  33. class WebspaceCopyCommand extends Command
  34. {
  35.     protected static $defaultName 'sulu:webspaces:copy';
  36.     /**
  37.      * @var SymfonyStyle
  38.      */
  39.     private $io;
  40.     /**
  41.      * @var OutputInterface
  42.      */
  43.     private $output;
  44.     /**
  45.      * @var DocumentManagerInterface
  46.      */
  47.     private $documentManager;
  48.     /**
  49.      * @var SessionManagerInterface
  50.      */
  51.     private $sessionManager;
  52.     /**
  53.      * @var DocumentInspector
  54.      */
  55.     private $documentInspector;
  56.     /**
  57.      * @var HtmlTagExtractor
  58.      */
  59.     private $htmlTagExtractor;
  60.     /**
  61.      * @var string
  62.      */
  63.     protected $webspaceKeySource;
  64.     /**
  65.      * @var string
  66.      */
  67.     protected $webspaceKeyDestination;
  68.     public function __construct(
  69.         DocumentManagerInterface $documentManager,
  70.         SessionManagerInterface $sessionManager,
  71.         DocumentInspector $documentInspector,
  72.         HtmlTagExtractor $htmlTagExtractor
  73.     ) {
  74.         parent::__construct();
  75.         $this->documentManager $documentManager;
  76.         $this->sessionManager $sessionManager;
  77.         $this->documentInspector $documentInspector;
  78.         $this->htmlTagExtractor $htmlTagExtractor;
  79.     }
  80.     protected function configure()
  81.     {
  82.         $this->addArgument('source-webspace'InputArgument::REQUIRED)
  83.             ->addArgument('source-locale'InputArgument::REQUIRED)
  84.             ->addArgument('destination-webspace'InputArgument::REQUIRED)
  85.             ->addArgument('destination-locale'InputArgument::REQUIRED)
  86.             ->addOption('clear-destination-webspace')
  87.             ->setDescription(
  88.                 'Copies a given webspace with given locale to a destination webspace with a destination locale.'
  89.             );
  90.     }
  91.     protected function execute(InputInterface $inputOutputInterface $output): int
  92.     {
  93.         $this->io = new SymfonyStyle($input$output);
  94.         $webspaceKeySource $input->getArgument('source-webspace');
  95.         $localesSource \explode(','$input->getArgument('source-locale'));
  96.         $webspaceKeyDestination $input->getArgument('destination-webspace');
  97.         $localesDestination \explode(','$input->getArgument('destination-locale'));
  98.         if (\count($localesSource) !== \count($localesDestination)) {
  99.             $output->writeln([
  100.                 '<error>Aborted!</error>',
  101.                 '<comment>Provide correct source and destination locales</comment>',
  102.             ]);
  103.             return -1;
  104.         }
  105.         $localesPairs = [];
  106.         for ($i 0$i \count($localesSource); ++$i) {
  107.             $localesPairs[] = $localesSource[$i] . ' => ' $localesDestination[$i];
  108.         }
  109.         $output->writeln([
  110.             '<info>Webspace Copy</info>',
  111.             '<info>==============================</info>',
  112.             '',
  113.             '<info>Options</info>',
  114.             '------------------------------',
  115.             'Webspace: ' $webspaceKeySource ' => ' $webspaceKeyDestination,
  116.             'Locales: ' \implode(', '$localesPairs),
  117.             '------------------------------',
  118.             '',
  119.         ]);
  120.         $output->writeln([
  121.             '<info>==============================</info>',
  122.             '<info>ATTENTION</info>',
  123.             '<info>The whole destination webspace (' $webspaceKeyDestination ') will be deleted!</info>',
  124.             '<info>==============================</info>',
  125.             '',
  126.         ]);
  127.         if (true !== $input->getOption('clear-destination-webspace')) {
  128.             $output->writeln([
  129.                 '<error>==============================',
  130.                 '<error>Aborted!</error>',
  131.                 '<error>This command currently does not work if there is already data in the webspace. You can run the command with --clear-destination-webspace to remove all the content from the destination webspace.</error>',
  132.                 '<error>==============================</error>',
  133.             ]);
  134.             return -1;
  135.         }
  136.         $this->webspaceKeySource $webspaceKeySource;
  137.         $this->webspaceKeyDestination $webspaceKeyDestination;
  138.         $this->output $output;
  139.         $this->output->writeln([
  140.             '==============================',
  141.             '1. Clear destination webspace',
  142.             '------------------------------',
  143.         ]);
  144.         $this->clearDestinationWebspace();
  145.         $this->output->writeln([
  146.             '------------------------------',
  147.             '',
  148.         ]);
  149.         $this->output->writeln([
  150.             '==============================',
  151.             '2. Copy pages to destination webspace',
  152.         ]);
  153.         for ($i 0$i \count($localesSource); ++$i) {
  154.             $this->copyWebspace(
  155.                 $localesSource[$i],
  156.                 $localesDestination[$i]
  157.             );
  158.         }
  159.         $this->output->writeln([
  160.             '==============================',
  161.             '3. Copy redirects and structure',
  162.         ]);
  163.         for ($i 0$i \count($localesSource); ++$i) {
  164.             $this->copyRedirectsAndStructure(
  165.                 $localesSource[$i],
  166.                 $localesDestination[$i]
  167.             );
  168.         }
  169.         $this->output->writeln('<info>Done</info>');
  170.         return 0;
  171.     }
  172.     /**
  173.      * Removes all pages from given webspace.
  174.      */
  175.     protected function clearDestinationWebspace()
  176.     {
  177.         $homeDocument $this->documentManager->find(
  178.             $this->sessionManager->getContentPath($this->webspaceKeyDestination)
  179.         );
  180.         foreach ($homeDocument->getChildren() as $child) {
  181.             $this->output->writeln('<info>Processing: </info>' $child->getPath());
  182.             $this->documentManager->remove($child, [
  183.                 PageRemoveSubscriber::FORCE_REMOVE_CHILDREN_OPTION => true,
  184.             ]);
  185.             $this->documentManager->flush();
  186.         }
  187.     }
  188.     /**
  189.      * Copies a given webspace with given locale to a destination webspace with a destination locale.
  190.      *
  191.      * @param string $localeSource
  192.      * @param string $localeDestination
  193.      */
  194.     protected function copyWebspace($localeSource$localeDestination)
  195.     {
  196.         $this->output->writeln([
  197.             '------------------------------',
  198.             '<info>Webspace: </info>' $this->webspaceKeySource ' => ' $this->webspaceKeyDestination,
  199.             '<info>Locale: </info>' $localeSource ' => ' $localeDestination,
  200.             '------------------------------',
  201.         ]);
  202.         /** @var HomeDocument $homeDocumentSource */
  203.         $homeDocumentSource $this->documentManager->find(
  204.             $this->sessionManager->getContentPath($this->webspaceKeySource),
  205.             $localeSource
  206.         );
  207.         // Generate all needed page documents.
  208.         $this->recursiveCopy(
  209.             $homeDocumentSource,
  210.             null,
  211.             $localeDestination
  212.         );
  213.         $this->output->writeln([
  214.             '------------------------------',
  215.             '',
  216.         ]);
  217.     }
  218.     /**
  219.      * Copies a given webspace with given locale to a destination webspace with a destination locale.
  220.      *
  221.      * @param string $localeSource
  222.      * @param string $localeDestination
  223.      */
  224.     protected function copyRedirectsAndStructure(
  225.         $localeSource,
  226.         $localeDestination
  227.     ) {
  228.         $this->output->writeln([
  229.             '------------------------------',
  230.             '<info>Webspace: </info>' $this->webspaceKeySource ' => ' $this->webspaceKeyDestination,
  231.             '<info>Locale: </info>' $localeSource ' => ' $localeDestination,
  232.             '------------------------------',
  233.         ]);
  234.         /** @var HomeDocument $homeDocumentSource */
  235.         $homeDocumentSource $this->documentManager->find(
  236.             $this->sessionManager->getContentPath($this->webspaceKeySource),
  237.             $localeSource
  238.         );
  239.         // Generate redirect and structure.
  240.         $this->recursiveCopyRedirectsAndStructure(
  241.             $homeDocumentSource,
  242.             $localeDestination
  243.         );
  244.         $this->output->writeln([
  245.             '------------------------------',
  246.             '',
  247.         ]);
  248.     }
  249.     /**
  250.      * @param string $localeDestination
  251.      */
  252.     protected function recursiveCopy(
  253.         BasePageDocument $documentSource,
  254.         ?BasePageDocument $parentDocumentDestination null,
  255.         $localeDestination
  256.     ) {
  257.         if (LocalizationState::GHOST === $this->documentInspector->getLocalizationState($documentSource)) {
  258.             $this->io->warning('Can not copy ghost page and its possible children: ' $documentSource->getPath());
  259.             return;
  260.         }
  261.         $newPath \str_replace(
  262.             $this->sessionManager->getContentPath($this->webspaceKeySource),
  263.             $this->sessionManager->getContentPath($this->webspaceKeyDestination),
  264.             $documentSource->getPath()
  265.         );
  266.         $this->output->writeln('<info>Processing: </info>' $documentSource->getPath() . ' => ' $newPath);
  267.         /* @var BasePageDocument $documentDestination */
  268.         try {
  269.             $documentDestination $this->documentManager->find($newPath$localeDestination);
  270.         } catch (DocumentNotFoundException $exception) {
  271.             $documentDestination $this->documentManager->create('page');
  272.         }
  273.         // Set data.
  274.         $documentDestination->setTitle($documentSource->getTitle());
  275.         $documentDestination->setStructureType($documentSource->getStructureType());
  276.         $documentDestination->setWorkflowStage(WorkflowStage::TEST);
  277.         $documentDestination->setExtensionsData($documentSource->getExtensionsData());
  278.         $documentDestination->setResourceSegment($documentSource->getResourceSegment());
  279.         $documentDestination->setPermissions($documentSource->getPermissions());
  280.         $documentDestination->setSuluOrder($documentSource->getSuluOrder());
  281.         $documentDestination->setNavigationContexts($documentSource->getNavigationContexts());
  282.         $documentDestination->setShadowLocaleEnabled($documentSource->isShadowLocaleEnabled());
  283.         $documentDestination->setShadowLocale($documentSource->getShadowLocale());
  284.         // Set parent.
  285.         if ($documentDestination instanceof PageDocument) {
  286.             $documentDestination->setParent($parentDocumentDestination);
  287.         }
  288.         $this->saveDocument($documentDestination$localeDestination$newPath);
  289.         foreach ($documentSource->getChildren() as $child) {
  290.             $this->recursiveCopy(
  291.                 $child,
  292.                 $documentDestination,
  293.                 $localeDestination
  294.             );
  295.         }
  296.     }
  297.     /**
  298.      * @param string $localeDestination
  299.      */
  300.     protected function recursiveCopyRedirectsAndStructure(
  301.         BasePageDocument $documentSource,
  302.         $localeDestination
  303.     ) {
  304.         if (LocalizationState::LOCALIZED === $this->documentInspector->getLocalizationState($documentSource)) {
  305.             $newPath \str_replace(
  306.                 $this->sessionManager->getContentPath($this->webspaceKeySource),
  307.                 $this->sessionManager->getContentPath($this->webspaceKeyDestination),
  308.                 $documentSource->getPath()
  309.             );
  310.             $this->output->writeln('<info>Processing: </info>' $documentSource->getPath() . ' => ' $newPath);
  311.             try {
  312.                 /** @var PageDocument $documentDestination */
  313.                 $documentDestination $this->documentManager->find($newPath$localeDestination);
  314.                 // Copy the redirects and correct the target.
  315.                 switch ($documentSource->getRedirectType()) {
  316.                     case RedirectType::INTERNAL:
  317.                         $newPathTarget \str_replace(
  318.                             $this->sessionManager->getContentPath($this->webspaceKeySource),
  319.                             $this->sessionManager->getContentPath($this->webspaceKeyDestination),
  320.                             $documentSource->getRedirectTarget()->getPath()
  321.                         );
  322.                         $documentDestination->setRedirectType(RedirectType::INTERNAL);
  323.                         $documentDestination->setRedirectTarget(
  324.                             $this->documentManager->find($newPathTarget$localeDestination)
  325.                         );
  326.                         break;
  327.                     case RedirectType::EXTERNAL:
  328.                         $documentDestination->setRedirectType(RedirectType::EXTERNAL);
  329.                         $documentDestination->setRedirectExternal($documentDestination->getRedirectExternal());
  330.                         break;
  331.                 }
  332.                 // Copy the structure and correct the target of references.
  333.                 $newStructure $documentSource->getStructure()->toArray();
  334.                 $metadata $this->documentInspector->getStructureMetadata($documentSource);
  335.                 foreach ($metadata->getProperties() as $property) {
  336.                     $this->processContentType(
  337.                         $property,
  338.                         $newStructure,
  339.                         $documentSource->getLocale(),
  340.                         $localeDestination
  341.                     );
  342.                 }
  343.                 $documentDestination->getStructure()->bind($newStructure);
  344.                 // Save new document.
  345.                 $this->saveDocument($documentDestination$localeDestination);
  346.             } catch (DocumentNotFoundException $e) {
  347.                 // Do nothing.
  348.             }
  349.         }
  350.         foreach ($documentSource->getChildren() as $child) {
  351.             $this->recursiveCopyRedirectsAndStructure(
  352.                 $child,
  353.                 $localeDestination
  354.             );
  355.         }
  356.     }
  357.     /**
  358.      * @param string $localeSource
  359.      * @param string $localeDestination
  360.      */
  361.     protected function processContentType(
  362.         ItemMetadata $property,
  363.         array &$structureArray,
  364.         $localeSource,
  365.         $localeDestination
  366.     ) {
  367.         switch ($property->getType()) {
  368.             case 'smart_content':
  369.                 $this->updateSmartContentStructure(
  370.                     $structureArray,
  371.                     $property,
  372.                     $localeSource,
  373.                     $localeDestination
  374.                 );
  375.                 break;
  376.             case 'text_editor':
  377.                 $this->updateHtmlSuluLinks(
  378.                     $structureArray,
  379.                     $property,
  380.                     $localeSource,
  381.                     $localeDestination
  382.                 );
  383.                 break;
  384.             case 'page_selection':
  385.                 $this->updatePageSelection(
  386.                     $structureArray,
  387.                     $property,
  388.                     $localeSource,
  389.                     $localeDestination
  390.                 );
  391.                 break;
  392.             case 'single_page_selection':
  393.                 $this->updateSinglePageSelection(
  394.                     $structureArray,
  395.                     $property,
  396.                     $localeSource,
  397.                     $localeDestination
  398.                 );
  399.                 break;
  400.             case 'block':
  401.                 $this->updateBlocksStructure(
  402.                     $structureArray,
  403.                     $property,
  404.                     $localeSource,
  405.                     $localeDestination
  406.                 );
  407.                 break;
  408.             case 'teaser_selection':
  409.                 $this->updateTeaserSelection(
  410.                     $structureArray,
  411.                     $property,
  412.                     $localeSource,
  413.                     $localeDestination
  414.                 );
  415.                 break;
  416.         }
  417.     }
  418.     /**
  419.      * Process content type block.
  420.      *
  421.      * @param string $localeSource
  422.      * @param string $localeDestination
  423.      */
  424.     protected function updateBlocksStructure(
  425.         array &$structureArray,
  426.         BlockMetadata $property,
  427.         $localeSource,
  428.         $localeDestination
  429.     ) {
  430.         if (!\array_key_exists($property->getName(), $structureArray) || !$structureArray[$property->getName()]) {
  431.             return;
  432.         }
  433.         foreach ($structureArray[$property->getName()] as &$structure) {
  434.             /** @var ItemMetadata $component */
  435.             $component $property->getComponentByName($structure['type']);
  436.             /** @var PropertyMetadata $child */
  437.             foreach ($component->getChildren() as $child) {
  438.                 if ($structure[$child->getName()]) {
  439.                     $this->processContentType(
  440.                         $child,
  441.                         $structure,
  442.                         $localeSource,
  443.                         $localeDestination
  444.                     );
  445.                 }
  446.             }
  447.         }
  448.     }
  449.     /**
  450.      * Updates the smart content structure when the property `dataSource` is set and the target is in the same webspace.
  451.      *
  452.      * @param string $localeSource
  453.      * @param string $localeDestination
  454.      */
  455.     protected function updateSmartContentStructure(
  456.         array &$structureArray,
  457.         PropertyMetadata $property,
  458.         $localeSource,
  459.         $localeDestination
  460.     ) {
  461.         /** @var PropertyParameter $parameter */
  462.         foreach ($property->getParameters() as $parameter) {
  463.             if (!\array_key_exists($property->getName(), $structureArray)) {
  464.                 continue;
  465.             }
  466.             if ('provider' !== $parameter['name'] || 'content' !== $parameter['value']) {
  467.                 continue;
  468.             }
  469.             if (!\array_key_exists('dataSource'$structureArray[$property->getName()])) {
  470.                 continue;
  471.             }
  472.             $targetDocumentDestination $this->getTargetDocumentDestination(
  473.                 $structureArray[$property->getName()]['dataSource'],
  474.                 $localeSource,
  475.                 $localeDestination
  476.             );
  477.             if (!$targetDocumentDestination) {
  478.                 continue;
  479.             }
  480.             $structureArray[$property->getName()]['dataSource'] = $targetDocumentDestination->getUuid();
  481.         }
  482.     }
  483.     /**
  484.      * Updates references in structure for content type `teaser_selection`.
  485.      *
  486.      * @param string $localeSource
  487.      * @param string $localeDestination
  488.      */
  489.     protected function updateTeaserSelection(
  490.         array &$structureArray,
  491.         PropertyMetadata $property,
  492.         $localeSource,
  493.         $localeDestination
  494.     ) {
  495.         if (!isset($structureArray[$property->getName()]['items'])) {
  496.             return;
  497.         }
  498.         foreach ($structureArray[$property->getName()]['items'] as $key => $teaserItem) {
  499.             if ('content' !== $teaserItem['type']) {
  500.                 continue;
  501.             }
  502.             $targetDocumentDestination $this->getTargetDocumentDestination(
  503.                 $teaserItem['id'],
  504.                 $localeSource,
  505.                 $localeDestination
  506.             );
  507.             if (!$targetDocumentDestination) {
  508.                 continue;
  509.             }
  510.             $structureArray[$property->getName()]['items'][$key]['id'] = $targetDocumentDestination->getUuid();
  511.         }
  512.     }
  513.     /**
  514.      * @param string $localeSource
  515.      * @param string $localeDestination
  516.      */
  517.     protected function updateHtmlSuluLinks(
  518.         array &$structureArray,
  519.         PropertyMetadata $property,
  520.         $localeSource,
  521.         $localeDestination
  522.     ) {
  523.         if (!\array_key_exists($property->getName(), $structureArray) || !$structureArray[$property->getName()]) {
  524.             return;
  525.         }
  526.         if (!\strpos($structureArray[$property->getName()], 'sulu-link')) {
  527.             return;
  528.         }
  529.         /** @var TagMatchGroup[] $tagMatchGroups */
  530.         $tagMatchGroups $this->htmlTagExtractor->extract($structureArray[$property->getName()]);
  531.         foreach ($tagMatchGroups as $tagMatchGroup) {
  532.             if ('sulu' === $tagMatchGroup->getNamespace() && 'link' === $tagMatchGroup->getTagName()) {
  533.                 foreach ($tagMatchGroup->getTags() as $tag) {
  534.                     if ('page' !== $tag['provider']) {
  535.                         continue;
  536.                     }
  537.                     $targetUuid $tag['href'];
  538.                     $targetDocumentDestination $this->getTargetDocumentDestination(
  539.                         $targetUuid,
  540.                         $localeSource,
  541.                         $localeDestination
  542.                     );
  543.                     if (!$targetDocumentDestination) {
  544.                         continue;
  545.                     }
  546.                     $structureArray[$property->getName()] = \str_replace(
  547.                         $targetUuid,
  548.                         $targetDocumentDestination->getUuid(),
  549.                         $structureArray[$property->getName()]
  550.                     );
  551.                 }
  552.             }
  553.         }
  554.     }
  555.     /**
  556.      * Updates references in structure for content type `page_selection`.
  557.      *
  558.      * @param string $localeSource
  559.      * @param string $localeDestination
  560.      */
  561.     protected function updatePageSelection(
  562.         array &$structureArray,
  563.         PropertyMetadata $property,
  564.         $localeSource,
  565.         $localeDestination
  566.     ) {
  567.         if (!\array_key_exists($property->getName(), $structureArray) || !$structureArray[$property->getName()]) {
  568.             return;
  569.         }
  570.         foreach ($structureArray[$property->getName()] as $key => $value) {
  571.             $targetDocumentDestination $this->getTargetDocumentDestination(
  572.                 $value,
  573.                 $localeSource,
  574.                 $localeDestination
  575.             );
  576.             if (!$targetDocumentDestination) {
  577.                 continue;
  578.             }
  579.             $structureArray[$property->getName()][$key] = $targetDocumentDestination->getUuid();
  580.         }
  581.     }
  582.     /**
  583.      * Updates references in structure for content type `single_page_selection`.
  584.      *
  585.      * @param string $localeSource
  586.      * @param string $localeDestination
  587.      */
  588.     protected function updateSinglePageSelection(
  589.         array &$structureArray,
  590.         PropertyMetadata $property,
  591.         $localeSource,
  592.         $localeDestination
  593.     ) {
  594.         if (!\array_key_exists($property->getName(), $structureArray) || !$structureArray[$property->getName()]) {
  595.             return;
  596.         }
  597.         $targetDocumentDestination $this->getTargetDocumentDestination(
  598.             $structureArray[$property->getName()],
  599.             $localeSource,
  600.             $localeDestination
  601.         );
  602.         if (!$targetDocumentDestination) {
  603.             return;
  604.         }
  605.         $structureArray[$property->getName()] = $targetDocumentDestination->getUuid();
  606.     }
  607.     /**
  608.      * @param string $uuid
  609.      * @param string $localeSource
  610.      * @param string $localeDestination
  611.      *
  612.      * @return null|BasePageDocument
  613.      */
  614.     protected function getTargetDocumentDestination(
  615.         $uuid,
  616.         $localeSource,
  617.         $localeDestination
  618.     ) {
  619.         /** @var BasePageDocument $targetDocumentSource */
  620.         $targetDocumentSource $this->documentManager->find($uuid$localeSource);
  621.         if ($this->webspaceKeySource !== $targetDocumentSource->getWebspaceName()) {
  622.             return null;
  623.         }
  624.         $newPathTarget \str_replace(
  625.             $this->sessionManager->getContentPath($this->webspaceKeySource),
  626.             $this->sessionManager->getContentPath($this->webspaceKeyDestination),
  627.             $targetDocumentSource->getPath()
  628.         );
  629.         $targetDocument null;
  630.         try {
  631.             $targetDocument $this->documentManager->find($newPathTarget$localeDestination);
  632.         } catch (DocumentNotFoundException $e) {
  633.             return null;
  634.         }
  635.         return $targetDocument;
  636.     }
  637.     /**
  638.      * @param string $locale
  639.      * @param string|null $path
  640.      */
  641.     protected function saveDocument(BasePageDocument $document$locale$path null)
  642.     {
  643.         $persistOptions = [];
  644.         if ($path) {
  645.             $persistOptions['path'] = $path;
  646.         }
  647.         $this->documentManager->persist($document$locale$persistOptions);
  648.         $this->documentManager->flush();
  649.     }
  650. }