vendor/w-vision/data-definitions/src/DataDefinitionsBundle/Importer/Importer.php line 186

Open in your IDE?
  1. <?php
  2. /**
  3.  * Data Definitions.
  4.  *
  5.  * LICENSE
  6.  *
  7.  * This source file is subject to the GNU General Public License version 3 (GPLv3)
  8.  * For the full copyright and license information, please view the LICENSE.md and gpl-3.0.txt
  9.  * files that are distributed with this source code.
  10.  *
  11.  * @copyright  Copyright (c) 2016-2019 w-vision AG (https://www.w-vision.ch)
  12.  * @license    https://github.com/w-vision/DataDefinitions/blob/master/gpl-3.0.txt GNU General Public License version 3 (GPLv3)
  13.  */
  14. declare(strict_types=1);
  15. namespace Wvision\Bundle\DataDefinitionsBundle\Importer;
  16. use CoreShop\Component\Registry\ServiceRegistryInterface;
  17. use Countable;
  18. use InvalidArgumentException;
  19. use Pimcore;
  20. use Pimcore\File;
  21. use Pimcore\Mail;
  22. use Pimcore\Model\DataObject\ClassDefinition;
  23. use Pimcore\Model\DataObject\Concrete;
  24. use Pimcore\Model\DataObject\Service;
  25. use Pimcore\Model\Document;
  26. use Pimcore\Model\Factory;
  27. use Pimcore\Model\Version;
  28. use Psr\Log\LoggerAwareInterface;
  29. use Psr\Log\LoggerInterface;
  30. use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
  31. use Throwable;
  32. use Wvision\Bundle\DataDefinitionsBundle\Context\ContextFactoryInterface;
  33. use Wvision\Bundle\DataDefinitionsBundle\Event\EventDispatcherInterface;
  34. use Wvision\Bundle\DataDefinitionsBundle\Exception\DoNotSetException;
  35. use Wvision\Bundle\DataDefinitionsBundle\Exception\UnexpectedValueException;
  36. use Wvision\Bundle\DataDefinitionsBundle\Filter\FilterInterface;
  37. use Wvision\Bundle\DataDefinitionsBundle\Interpreter\InterpreterInterface;
  38. use Wvision\Bundle\DataDefinitionsBundle\Loader\LoaderInterface;
  39. use Wvision\Bundle\DataDefinitionsBundle\Model\ImportDefinitionInterface;
  40. use Wvision\Bundle\DataDefinitionsBundle\Model\ImportMapping;
  41. use Wvision\Bundle\DataDefinitionsBundle\Model\ParamsAwareInterface;
  42. use Wvision\Bundle\DataDefinitionsBundle\Persister\PersisterInterface;
  43. use Wvision\Bundle\DataDefinitionsBundle\Provider\ImportDataSet;
  44. use Wvision\Bundle\DataDefinitionsBundle\Provider\ImportDataSetInterface;
  45. use Wvision\Bundle\DataDefinitionsBundle\Provider\ImportProviderInterface;
  46. use Wvision\Bundle\DataDefinitionsBundle\Runner\ImportStartFinishRunnerInterface;
  47. use Wvision\Bundle\DataDefinitionsBundle\Runner\RunnerInterface;
  48. use Wvision\Bundle\DataDefinitionsBundle\Runner\SaveRunnerInterface;
  49. use Wvision\Bundle\DataDefinitionsBundle\Runner\SetterRunnerInterface;
  50. use Wvision\Bundle\DataDefinitionsBundle\Setter\SetterInterface;
  51. final class Importer implements ImporterInterface
  52. {
  53.     private bool $shouldStop false;
  54.     public function __construct(
  55.         private ServiceRegistryInterface $providerRegistry,
  56.         private ServiceRegistryInterface $filterRegistry,
  57.         private ServiceRegistryInterface $runnerRegistry,
  58.         private ServiceRegistryInterface $interpreterRegistry,
  59.         private ServiceRegistryInterface $setterRegistry,
  60.         private ServiceRegistryInterface $cleanerRegistry,
  61.         private ServiceRegistryInterface $loaderRegistry,
  62.         private ServiceRegistryInterface $persisterRegistry,
  63.         private EventDispatcherInterface $eventDispatcher,
  64.         private ContextFactoryInterface $contextFactory,
  65.         private LoggerInterface $logger,
  66.         private Factory $modelFactory,
  67.         private ExpressionLanguage $expressionLanguage
  68.     ) {
  69.     }
  70.     public function doImport(ImportDefinitionInterface $definition$params): array
  71.     {
  72.         $filter null;
  73.         if ($definition->getCreateVersion()) {
  74.             Version::enable();
  75.         } else {
  76.             Version::disable();
  77.         }
  78.         $filterType $definition->getFilter();
  79.         if ($filterType) {
  80.             /**
  81.              * @var FilterInterface $filter
  82.              */
  83.             $filter $this->filterRegistry->get($filterType);
  84.         }
  85.         /** @var ImportDataSetInterface|array $data */
  86.         $data $this->getData($definition$params);
  87.         $runner null;
  88.         $runnerContext $this->contextFactory->createRunnerContext($definition$paramsnull$datanull);
  89.         if ($definition->getRunner()) {
  90.             /**
  91.              * @var RunnerInterface $runner
  92.              */
  93.             $runner $this->runnerRegistry->get($definition->getRunner());
  94.         }
  95.         if ((\is_countable($data) || $data instanceof Countable) && ($count \count($data)) > 0) {
  96.             $this->eventDispatcher->dispatch($definition'data_definitions.import.total'$count$params);
  97.         }
  98.         if ($runner instanceof ImportStartFinishRunnerInterface) {
  99.             $runner->startImport($runnerContext);
  100.         }
  101.         [$objectIds$exceptions] = $this->runImport($definition$params$filter$runner$data);
  102.         if ($runner instanceof ImportStartFinishRunnerInterface) {
  103.             $runner->finishImport($runnerContext);
  104.         }
  105.         $cleanerType $definition->getCleaner();
  106.         if ($cleanerType) {
  107.             $cleaner $this->cleanerRegistry->get($cleanerType);
  108.             $this->logger->info(sprintf('Running Cleaner "%s"'$cleanerType));
  109.             $this->eventDispatcher->dispatch(
  110.                 $definition,
  111.                 'data_definitions.import.status',
  112.                 sprintf('Running Cleaner "%s"'$cleanerType)
  113.             );
  114.             if ($cleaner instanceof ParamsAwareInterface) {
  115.                 $cleaner->setParams($params);
  116.             }
  117.             if ($cleaner instanceof LoggerAwareInterface) {
  118.                 $cleaner->setLogger($this->logger);
  119.             }
  120.             $cleaner->cleanup($definition$objectIds);
  121.             $this->logger->info(sprintf('Finished Cleaner "%s"'$cleanerType));
  122.             $this->eventDispatcher->dispatch(
  123.                 $definition,
  124.                 'data_definitions.import.status',
  125.                 sprintf('Finished Cleaner "%s"'$cleanerType)
  126.             );
  127.         }
  128.         if (count($exceptions) > 0) {
  129.             $this->processFailedImport($definition$params$objectIds$exceptions);
  130.         } else {
  131.             $this->processSuccessfullImport($definition$params$objectIds$exceptions);
  132.         }
  133.         $this->eventDispatcher->dispatch($definition'data_definitions.import.finished'''$params);
  134.         return $objectIds;
  135.     }
  136.     public function processSuccessfullImport(ImportDefinitionInterface $definition$params$objectIds$exceptions)
  137.     {
  138.         $this->sendDocument(
  139.             $definition,
  140.             Document::getById($definition->getSuccessNotificationDocument()),
  141.             $objectIds,
  142.             $exceptions
  143.         );
  144.         $this->eventDispatcher->dispatch($definition'data_definitions.import.success'$params);
  145.     }
  146.     public function processFailedImport(ImportDefinitionInterface $definition$params$objectIds$exceptions)
  147.     {
  148.         $this->sendDocument(
  149.             $definition,
  150.             Document::getById($definition->getFailureNotificationDocument()),
  151.             $objectIds,
  152.             $exceptions
  153.         );
  154.         $this->eventDispatcher->dispatch($definition'data_definitions.import.failure'$params);
  155.     }
  156.     public function stop(): void
  157.     {
  158.         $this->shouldStop true;
  159.     }
  160.     private function sendDocument(
  161.         ImportDefinitionInterface $definition,
  162.         ?Document $document,
  163.         array $objectIds,
  164.         array $exceptions
  165.     ) {
  166.         if ($document instanceof Document) {
  167.             $params = [
  168.                 'exceptions' => $exceptions,
  169.                 'objectIds' => $objectIds,
  170.                 'className' => $definition->getClass(),
  171.                 'countObjects' => count($objectIds),
  172.                 'countExceptions' => count($exceptions),
  173.                 'name' => $definition->getName(),
  174.                 'provider' => $definition->getProvider(),
  175.             ];
  176.             if ($document instanceof Document\Email) {
  177.                 $mail = new Mail();
  178.                 $mail->setDocument($document);
  179.                 $mail->setParams($params);
  180.                 $mail->send();
  181.             }
  182.         }
  183.     }
  184.     private function getData(ImportDefinitionInterface $definition, array $params)
  185.     {
  186.         /** @var ImportProviderInterface $provider */
  187.         $provider $this->providerRegistry->get($definition->getProvider());
  188.         return $provider->getData($definition->getConfiguration(), $definition$params);
  189.     }
  190.     private function runImport(
  191.         ImportDefinitionInterface $definition,
  192.         array $params,
  193.         FilterInterface $filter null,
  194.         RunnerInterface $runner null,
  195.         ImportDataSetInterface $dataSet null,
  196.     ): array {
  197.         if (null === $dataSet) {
  198.             $dataSet = new ImportDataSet(new \EmptyIterator());
  199.         }
  200.         $count 0;
  201.         $countToClean 50;
  202.         $objectIds = [];
  203.         $exceptions = [];
  204.         foreach ($dataSet as $row) {
  205.             if ($row === null) {
  206.                 continue;
  207.             }
  208.             try {
  209.                 $object $this->importRow(
  210.                     $definition,
  211.                     $row,
  212.                     $dataSet,
  213.                     array_merge($params, ['row' => $count]),
  214.                     $filter,
  215.                     $runner
  216.                 );
  217.                 if ($object instanceof Concrete) {
  218.                     $objectIds[] = $object->getId();
  219.                 }
  220.             } catch (Throwable $ex) {
  221.                 $this->logger->error($ex);
  222.                 $exceptions[] = $ex;
  223.                 $this->eventDispatcher->dispatch(
  224.                     $definition,
  225.                     'data_definitions.import.failure',
  226.                     sprintf('Error: %s'$ex->getMessage()),
  227.                     $params
  228.                 );
  229.                 if ($definition->getStopOnException()) {
  230.                     throw $ex;
  231.                 }
  232.             } finally {
  233.                 if (($count 1) % $countToClean === 0) {
  234.                     Pimcore::collectGarbage();
  235.                     $this->logger->info('Clean Garbage');
  236.                     $this->eventDispatcher->dispatch(
  237.                         $definition,
  238.                         'data_definitions.import.status',
  239.                         'Collect Garbage',
  240.                         $params
  241.                     );
  242.                 }
  243.                 $count++;
  244.             }
  245.             $this->eventDispatcher->dispatch($definition'data_definitions.import.progress'''$params);
  246.             if ($this->shouldStop) {
  247.                 $this->eventDispatcher->dispatch(
  248.                     $definition,
  249.                     'data_definitions.import.status',
  250.                     'Process has been stopped.'
  251.                 );
  252.                 return [$objectIds$exceptions];
  253.             }
  254.         }
  255.         return [$objectIds$exceptions];
  256.     }
  257.     private function importRow(
  258.         ImportDefinitionInterface $definition,
  259.         array $data,
  260.         ImportDataSetInterface $dataSet,
  261.         array $params,
  262.         FilterInterface $filter null,
  263.         RunnerInterface $runner null,
  264.     ): ?Concrete {
  265.         $object $this->getObject($definition$data$dataSet$params);
  266.         if (null !== $object && !$object->getId()) {
  267.             if ($definition->getSkipNewObjects()) {
  268.                 $this->eventDispatcher->dispatch(
  269.                     $definition,
  270.                     'data_definitions.import.status',
  271.                     'Ignoring new Object',
  272.                     $params
  273.                 );
  274.                 return null;
  275.             }
  276.         } else {
  277.             if ($definition->getSkipExistingObjects()) {
  278.                 $this->eventDispatcher->dispatch(
  279.                     $definition,
  280.                     'data_definitions.import.status',
  281.                     'Ignoring existing Object',
  282.                     $params
  283.                 );
  284.                 return null;
  285.             }
  286.         }
  287.         if ($filter instanceof FilterInterface) {
  288.             if ($filter instanceof LoggerAwareInterface) {
  289.                 $filter->setLogger($this->logger);
  290.             }
  291.             $context $this->contextFactory->createFilterContext($definition$params$data$dataSet$object);
  292.             if (!$filter->filter($context)) {
  293.                 $this->eventDispatcher->dispatch(
  294.                     $definition,
  295.                     'data_definitions.import.status',
  296.                     'Filtered Object',
  297.                     $params
  298.                 );
  299.                 return null;
  300.             }
  301.         }
  302.         $this->eventDispatcher->dispatch(
  303.             $definition,
  304.             'data_definitions.import.status',
  305.             sprintf('Import Object %s', ($object->getId() ? $object->getFullPath() : 'new')),
  306.             $params
  307.         );
  308.         $this->eventDispatcher->dispatch(
  309.             $definition,
  310.             'data_definitions.import.object.start',
  311.             $object,
  312.             $params
  313.         );
  314.         $runnerContext $this->contextFactory->createRunnerContext($definition$params$data$dataSet$object);
  315.         if ($runner instanceof RunnerInterface) {
  316.             if ($runner instanceof LoggerAwareInterface) {
  317.                 $runner->setLogger($this->logger);
  318.             }
  319.             $runner->preRun($runnerContext);
  320.         }
  321.         $this->logger->info(sprintf('Imported Object: %s'$object->getRealFullPath()));
  322.         /** @var ImportMapping $mapItem */
  323.         foreach ($definition->getMapping() as $mapItem) {
  324.             $value null;
  325.             if (array_key_exists($mapItem->getFromColumn(), $data) || $mapItem->getFromColumn() === "custom") {
  326.                 $value $data[$mapItem->getFromColumn()] ?? null;
  327.                 $this->setObjectValue($object$mapItem$value$data$dataSet$definition$params$runner);
  328.             }
  329.         }
  330.         $shouldSave true;
  331.         if ($runner instanceof SaveRunnerInterface) {
  332.             if ($runner instanceof LoggerAwareInterface) {
  333.                 $runner->setLogger($this->logger);
  334.             }
  335.             $shouldSave $runner->shouldSaveObject($runnerContext);
  336.         }
  337.         if ($shouldSave) {
  338.             $params['versionNote'] = sprintf('%s - %s'$definition->getId(), $definition->getName());
  339.             $object->setUserModification($params['userId'] ?? 0);
  340.             $object->setOmitMandatoryCheck($definition->getOmitMandatoryCheck());
  341.             $this->saveObject($object$definition$params);
  342.             $this->eventDispatcher->dispatch(
  343.                 $definition,
  344.                 'data_definitions.import.status',
  345.                 sprintf('Imported Object %s'$object->getFullPath()),
  346.                 $params
  347.             );
  348.         } else {
  349.             $this->eventDispatcher->dispatch(
  350.                 $definition,
  351.                 'data_definitions.import.status',
  352.                 sprintf('Skipped Object %s'$object->getFullPath()),
  353.                 $params
  354.             );
  355.         }
  356.         $this->eventDispatcher->dispatch(
  357.             $definition,
  358.             'data_definitions.import.status',
  359.             sprintf('Imported Object %s'$object->getFullPath()),
  360.             $params
  361.         );
  362.         $this->eventDispatcher->dispatch(
  363.             $definition,
  364.             'data_definitions.import.object.finished',
  365.             $object,
  366.             $params
  367.         );
  368.         if ($runner instanceof RunnerInterface) {
  369.             if ($runner instanceof LoggerAwareInterface) {
  370.                 $runner->setLogger($this->logger);
  371.             }
  372.             $runner->postRun($runnerContext);
  373.         }
  374.         return $object;
  375.     }
  376.     private function setObjectValue(
  377.         Concrete $object,
  378.         ImportMapping $map,
  379.         $value,
  380.         array $data,
  381.         ImportDataSetInterface $dataSet,
  382.         ImportDefinitionInterface $definition,
  383.         array $params,
  384.         RunnerInterface $runner null
  385.     ): void {
  386.         if ($map->getInterpreter()) {
  387.             try {
  388.                 $interpreter $this->interpreterRegistry->get($map->getInterpreter());
  389.                 if (!$interpreter instanceof InterpreterInterface) {
  390.                     return;
  391.                 }
  392.                 if ($interpreter instanceof LoggerAwareInterface) {
  393.                     $interpreter->setLogger($this->logger);
  394.                 }
  395.                 try {
  396.                     $context $this->contextFactory->createInterpreterContext(
  397.                         $definition,
  398.                         $params,
  399.                         $map->getInterpreterConfig() ?? [],
  400.                         $data,
  401.                         $dataSet,
  402.                         $object,
  403.                         $value,
  404.                         $map
  405.                     );
  406.                     $value $interpreter->interpret($context);
  407.                 } catch (UnexpectedValueException $ex) {
  408.                     $this->logger->info(
  409.                         sprintf(
  410.                             'Unexpected Value from Interpreter "%s" with message "%s"',
  411.                             $map->getInterpreter(),
  412.                             $ex->getMessage()
  413.                         )
  414.                     );
  415.                 }
  416.             } catch (DoNotSetException $ex) {
  417.                 return;
  418.             }
  419.         }
  420.         if ($map->getToColumn() === 'o_type' && $map->getSetter() !== 'object_type') {
  421.             throw new InvalidArgumentException('Type has to be used with ObjectType Setter!');
  422.         }
  423.         $shouldSetField true;
  424.         if ($runner instanceof SetterRunnerInterface) {
  425.             if ($runner instanceof LoggerAwareInterface) {
  426.                 $runner->setLogger($this->logger);
  427.             }
  428.             $shouldSetField $runner->shouldSetField($object$map$value$data$definition$params);
  429.         }
  430.         if (!$shouldSetField) {
  431.             return;
  432.         }
  433.         if ($map->getSetter()) {
  434.             $setter $this->setterRegistry->get($map->getSetter());
  435.             $setterContext $this->contextFactory->createSetterContext(
  436.                 $definition,
  437.                 $params,
  438.                 $object,
  439.                 $map,
  440.                 $data,
  441.                 $dataSet,
  442.                 $value
  443.             );
  444.             if ($setter instanceof SetterInterface) {
  445.                 if ($setter instanceof LoggerAwareInterface) {
  446.                     $setter->setLogger($this->logger);
  447.                 }
  448.                 $setter->set($setterContext);
  449.             }
  450.         } else {
  451.             $object->setValue($map->getToColumn(), $value);
  452.         }
  453.     }
  454.     private function getObject(
  455.         ImportDefinitionInterface $definition,
  456.         $data,
  457.         ImportDataSetInterface $dataSet,
  458.         $params
  459.     ): Concrete {
  460.         $class $definition->getClass();
  461.         $classObject '\Pimcore\Model\DataObject\\'.ucfirst($class);
  462.         $classDefinition ClassDefinition::getByName($class);
  463.         if (!$classDefinition instanceof ClassDefinition) {
  464.             throw new InvalidArgumentException(sprintf('Class not found %s'$class));
  465.         }
  466.         /**
  467.          * @var $loader LoaderInterface
  468.          */
  469.         if ($definition->getLoader()) {
  470.             $loader $this->loaderRegistry->get($definition->getLoader());
  471.         } else {
  472.             $loader $this->loaderRegistry->get('primary_key');
  473.         }
  474.         $loaderContext $this->contextFactory->createLoaderContext($definition$params$data$dataSet$class);
  475.         $obj $loader->load($loaderContext);
  476.         if (null === $obj) {
  477.             $classImplementation $this->modelFactory->getClassNameFor($classObject) ?? $classObject;
  478.             $obj = new $classImplementation();
  479.         }
  480.         $key Service::getValidKey($this->createKey($definition$data), 'object');
  481.         if ($definition->getRelocateExistingObjects() || !$obj->getId()) {
  482.             $obj->setParent(Service::createFolderByPath($this->createPath($definition$data)));
  483.         }
  484.         if ($definition->getRenameExistingObjects() || !$obj->getId()) {
  485.             if ($key && $definition->getKey()) {
  486.                 $obj->setKey($key);
  487.             } else {
  488.                 $obj->setKey(File::getValidFilename(uniqid(''true)));
  489.             }
  490.         }
  491.         if (!$obj->getKey()) {
  492.             throw new InvalidArgumentException('No key set, please check your import-data');
  493.         }
  494.         $obj->setKey(Service::getUniqueKey($obj));
  495.         return $obj;
  496.     }
  497.     private function createPath(ImportDefinitionInterface $definition, array $data): string
  498.     {
  499.         if (!$definition->getObjectPath()) {
  500.             return '';
  501.         }
  502.         if (str_starts_with($definition->getObjectPath(), '@')) {
  503.             return $this->expressionLanguage->evaluate(substr($definition->getObjectPath(), 1), $data);
  504.         }
  505.         return $definition->getObjectPath() ?? '';
  506.     }
  507.     private function createKey(ImportDefinitionInterface $definition, array $data): string
  508.     {
  509.         if (!$definition->getKey()) {
  510.             return '';
  511.         }
  512.         if (str_starts_with($definition->getKey(), '@')) {
  513.             return $this->expressionLanguage->evaluate(substr($definition->getKey(), 1), $data);
  514.         }
  515.         return $definition->getKey();
  516.     }
  517.     private function saveObject(Concrete $objectImportDefinitionInterface $definition, array $params): void
  518.     {
  519.         $persister null;
  520.         if ($definition->getPersister()) {
  521.             $persister $this->persisterRegistry->get($definition->getPersister());
  522.         }
  523.         if (!$persister instanceof PersisterInterface) {
  524.             $persister $this->persisterRegistry->get('persister');
  525.         }
  526.         if ($persister instanceof LoggerAwareInterface) {
  527.             $persister->setLogger($this->logger);
  528.         }
  529.         $persister->persist($object$definition$params);
  530.     }
  531. }