vendor/pimcore/pimcore/bundles/AdminBundle/Controller/Admin/WorkflowController.php line 223

Open in your IDE?
  1. <?php
  2. /**
  3.  * Pimcore
  4.  *
  5.  * This source file is available under two different licenses:
  6.  * - GNU General Public License version 3 (GPLv3)
  7.  * - Pimcore Commercial License (PCL)
  8.  * Full copyright and license information is available in
  9.  * LICENSE.md which is distributed with this source code.
  10.  *
  11.  *  @copyright  Copyright (c) Pimcore GmbH (http://www.pimcore.org)
  12.  *  @license    http://www.pimcore.org/license     GPLv3 and PCL
  13.  */
  14. namespace Pimcore\Bundle\AdminBundle\Controller\Admin;
  15. use Pimcore\Bundle\AdminBundle\Controller\AdminAbstractController;
  16. use Pimcore\Controller\KernelControllerEventInterface;
  17. use Pimcore\Model\Asset;
  18. use Pimcore\Model\DataObject;
  19. use Pimcore\Model\DataObject\Concrete as ConcreteObject;
  20. use Pimcore\Model\Document;
  21. use Pimcore\Model\Element\ValidationException;
  22. use Pimcore\Tool\Console;
  23. use Pimcore\Workflow\ActionsButtonService;
  24. use Pimcore\Workflow\Manager;
  25. use Pimcore\Workflow\Notes\CustomHtmlServiceInterface;
  26. use Pimcore\Workflow\Place\StatusInfo;
  27. use Pimcore\Workflow\Transition;
  28. use Symfony\Component\HttpFoundation\JsonResponse;
  29. use Symfony\Component\HttpFoundation\Request;
  30. use Symfony\Component\HttpFoundation\Response;
  31. use Symfony\Component\HttpKernel\Event\ControllerEvent;
  32. use Symfony\Component\Process\Process;
  33. use Symfony\Component\Routing\Annotation\Route;
  34. use Symfony\Component\Routing\RouterInterface;
  35. use Symfony\Component\Workflow\Registry;
  36. use Symfony\Component\Workflow\Workflow;
  37. use Symfony\Contracts\Translation\TranslatorInterface;
  38. /**
  39.  * @Route("/workflow")
  40.  *
  41.  * @internal
  42.  */
  43. class WorkflowController extends AdminAbstractController implements KernelControllerEventInterface
  44. {
  45.     public function __construct(protected TranslatorInterface $translator)
  46.     {
  47.     }
  48.     /**
  49.      * @var Document|Asset|ConcreteObject|null $element
  50.      */
  51.     private $element;
  52.     /**
  53.      * Returns a JSON of the available workflow actions to the admin panel
  54.      *
  55.      * @Route("/get-workflow-form", name="pimcore_admin_workflow_getworkflowform")
  56.      *
  57.      * @param Request $request
  58.      *
  59.      * @return JsonResponse
  60.      */
  61.     public function getWorkflowFormAction(Request $requestManager $workflowManager)
  62.     {
  63.         try {
  64.             $workflow $workflowManager->getWorkflowIfExists($this->element, (string) $request->get('workflowName'));
  65.             if (empty($workflow)) {
  66.                 $wfConfig = [
  67.                     'message' => 'workflow not found',
  68.                 ];
  69.             } else {
  70.                 //this is the default returned workflow data
  71.                 $wfConfig = [
  72.                     'message' => '',
  73.                     'notes_enabled' => false,
  74.                     'notes_required' => false,
  75.                     'additional_fields' => [],
  76.                 ];
  77.                 $enabledTransitions $workflow->getEnabledTransitions($this->element);
  78.                 $transition null;
  79.                 foreach ($enabledTransitions as $_transition) {
  80.                     if ($_transition->getName() === $request->get('transitionName')) {
  81.                         $transition $_transition;
  82.                     }
  83.                 }
  84.                 if (!$transition instanceof Transition) {
  85.                     $wfConfig['message'] = sprintf('transition %s currently not allowed', (string) $request->get('transitionName'));
  86.                 } else {
  87.                     $wfConfig['notes_required'] = $transition->getNotesCommentRequired();
  88.                     $wfConfig['additional_fields'] = [];
  89.                 }
  90.             }
  91.         } catch (\Exception $e) {
  92.             $wfConfig['message'] = $e->getMessage();
  93.         }
  94.         return $this->adminJson($wfConfig);
  95.     }
  96.     /**
  97.      * @Route("/submit-workflow-transition", name="pimcore_admin_workflow_submitworkflowtransition", methods={"POST"})
  98.      *
  99.      * @param Request $request
  100.      *
  101.      * @return JsonResponse
  102.      */
  103.     public function submitWorkflowTransitionAction(Request $requestRegistry $workflowRegistryManager $workflowManager)
  104.     {
  105.         $workflowOptions $request->get('workflow', []);
  106.         $workflow $workflowRegistry->get($this->element$request->get('workflowName'));
  107.         if ($workflow->can($this->element$request->get('transition'))) {
  108.             try {
  109.                 $workflowManager->applyWithAdditionalData($workflow$this->element$request->get('transition'), $workflowOptionstrue);
  110.                 $data = [
  111.                     'success' => true,
  112.                     'callback' => 'reloadObject',
  113.                 ];
  114.             } catch (ValidationException $e) {
  115.                 $reason '';
  116.                 if (count((array)$e->getSubItems()) > 0) {
  117.                     $reason '<ul>' implode(''array_map(function ($item) {
  118.                         return '<li>' $item '</li>';
  119.                     }, $e->getSubItems())) . '</ul>';
  120.                 }
  121.                 $data = [
  122.                     'success' => false,
  123.                     'message' => $e->getMessage(),
  124.                     'reasons' => [$reason],
  125.                 ];
  126.             } catch (\Exception $e) {
  127.                 $data = [
  128.                     'success' => false,
  129.                     'message' => 'error performing action on this element',
  130.                     'reasons' => [$e->getMessage()],
  131.                 ];
  132.             }
  133.         } else {
  134.             $blockTransitionList $workflow->buildTransitionBlockerList($this->element$request->get('transition'));
  135.             $reasons array_map(function ($blockTransitionItem) {
  136.                 return $blockTransitionItem->getMessage();
  137.             }, iterator_to_array($blockTransitionList->getIterator(), true));
  138.             $data = [
  139.                 'success' => false,
  140.                 'message' => 'transition failed',
  141.                 'reasons' => $reasons,
  142.             ];
  143.         }
  144.         return $this->adminJson($data);
  145.     }
  146.     /**
  147.      * @Route("/submit-global-action", name="pimcore_admin_workflow_submitglobal", methods={"POST"})
  148.      *
  149.      * @param Request $request
  150.      *
  151.      * @return JsonResponse
  152.      */
  153.     public function submitGlobalAction(Request $requestRegistry $workflowRegistryManager $workflowManager)
  154.     {
  155.         $workflowOptions $request->get('workflow', []);
  156.         $workflow $workflowRegistry->get($this->element$request->get('workflowName'));
  157.         try {
  158.             $workflowManager->applyGlobalAction($workflow$this->element$request->get('transition'), $workflowOptionstrue);
  159.             $data = [
  160.                 'success' => true,
  161.                 'callback' => 'reloadObject',
  162.             ];
  163.         } catch (ValidationException $e) {
  164.             $reason '';
  165.             if (count((array)$e->getSubItems()) > 0) {
  166.                 $reason '<ul>' implode(''array_map(function ($item) {
  167.                     return '<li>' $item '</li>';
  168.                 }, $e->getSubItems())) . '</ul>';
  169.             }
  170.             $data = [
  171.                 'success' => false,
  172.                 'message' => $e->getMessage(),
  173.                 'reasons' => [$reason],
  174.             ];
  175.         } catch (\Exception $e) {
  176.             $data = [
  177.                 'success' => false,
  178.                 'message' => 'error performing action on this element',
  179.                 'reasons' => [$e->getMessage()],
  180.             ];
  181.         }
  182.         return $this->adminJson($data);
  183.     }
  184.     /**
  185.      * Returns the JSON needed by the workflow elements detail tab store
  186.      *
  187.      * @Route("/get-workflow-details", name="pimcore_admin_workflow_getworkflowdetailsstore")
  188.      *
  189.      * @param Request $request
  190.      * @param Manager $workflowManager
  191.      * @param StatusInfo $placeStatusInfo
  192.      * @param RouterInterface $router
  193.      *
  194.      * @return JsonResponse
  195.      *
  196.      * @throws \Exception
  197.      */
  198.     public function getWorkflowDetailsStore(Request $requestManager $workflowManagerStatusInfo $placeStatusInfoRouterInterface $routerActionsButtonService $actionsButtonService)
  199.     {
  200.         $data = [];
  201.         foreach ($workflowManager->getAllWorkflowsForSubject($this->element) as $workflow) {
  202.             $workflowConfig $workflowManager->getWorkflowConfig($workflow->getName());
  203.             $svg null;
  204.             $msg '';
  205.             try {
  206.                 $svg $this->getWorkflowSvg($workflow);
  207.             } catch (\InvalidArgumentException $e) {
  208.                 $msg $e->getMessage();
  209.             }
  210.             $url $router->generate(
  211.                 'pimcore_admin_workflow_show_graph',
  212.                 [
  213.                     'cid' => $request->get('cid'),
  214.                     'ctype' => $request->get('ctype'),
  215.                     'workflow' => $workflow->getName(),
  216.                 ]
  217.             );
  218.             $allowedTransitions $actionsButtonService->getAllowedTransitions($workflow$this->element);
  219.             $globalActions $actionsButtonService->getGlobalActions($workflow$this->element);
  220.             $data[] = [
  221.                 'workflowName' =>  $this->translator->trans($workflowConfig->getLabel(), [], 'admin'),
  222.                 'placeInfo' => $placeStatusInfo->getAllPalacesHtml($this->element$workflow->getName()),
  223.                 'graph' => $msg ?: '<a href="' $url .'" target="_blank"><div class="workflow-graph-preview">'.$svg.'</div></a>',
  224.                 'allowedTransitions' => $allowedTransitions,
  225.                 'globalActions' => $globalActions,
  226.             ];
  227.         }
  228.         return $this->adminJson([
  229.             'data' => $data,
  230.             'success' => true,
  231.             'total' => count($data),
  232.         ]);
  233.     }
  234.     /**
  235.      * Returns the JSON needed by the workflow elements detail tab store
  236.      *
  237.      * @Route("/show-graph", name="pimcore_admin_workflow_show_graph")
  238.      *
  239.      * @param Request $request
  240.      * @param Manager $workflowManager
  241.      *
  242.      * @return Response
  243.      *
  244.      * @throws \Exception
  245.      */
  246.     public function showGraph(Request $requestManager $workflowManager)
  247.     {
  248.         $workflow $workflowManager->getWorkflowByName($request->get('workflow'));
  249.         $response = new Response($this->getWorkflowSvg($workflow));
  250.         $response->headers->set('Content-Type''image/svg+xml');
  251.         return $response;
  252.     }
  253.     /**
  254.      * Get custom HTML for the workflow transition submit modal, depending whether it is configured or not.
  255.      *
  256.      * @Route("/modal-custom-html", name="pimcore_admin_workflow_modal_custom_html", methods={"POST"})
  257.      *
  258.      * @param Request $request
  259.      * @param Registry $workflowRegistry
  260.      * @param Manager $manager
  261.      *
  262.      * @return Response
  263.      *
  264.      * @throws \Exception
  265.      */
  266.     public function getModalCustomHtml(Request $requestRegistry $workflowRegistryManager $manager)
  267.     {
  268.         $workflow $workflowRegistry->get($this->element$request->get('workflowName'));
  269.         if ($request->get('isGlobalAction') == 'true') {
  270.             $globalAction $manager->getGlobalAction($workflow->getName(), $request->get('transition'));
  271.             if ($globalAction) {
  272.                 return $this->customHtmlResponse($globalAction->getCustomHtmlService());
  273.             }
  274.         } elseif ($workflow->can($this->element$request->get('transition'))) {
  275.             $enabledTransitions $workflow->getEnabledTransitions($this->element);
  276.             $transition null;
  277.             foreach ($enabledTransitions as $_transition) {
  278.                 if ($_transition->getName() === $request->get('transition')) {
  279.                     $transition $_transition;
  280.                 }
  281.             }
  282.             if ($transition instanceof Transition) {
  283.                 return $this->customHtmlResponse($transition->getCustomHtmlService());
  284.             }
  285.         }
  286.         $data = [
  287.             'success' => false,
  288.             'message' => 'error validating the action on this element, element cannot peform this action',
  289.         ];
  290.         return new JsonResponse($data);
  291.     }
  292.     private function customHtmlResponse(CustomHtmlServiceInterface $customHtmlService null): JsonResponse
  293.     {
  294.         $data = [
  295.             'success' => true,
  296.             'customHtml' => [],
  297.         ];
  298.         if ($customHtmlService) {
  299.             foreach (['top''center''bottom'] as $position) {
  300.                 $data['customHtml'][$position] = $customHtmlService->renderHtmlForRequestedPosition($this->element$position);
  301.             }
  302.         }
  303.         return new JsonResponse($data);
  304.     }
  305.     /**
  306.      * @param Workflow $workflow
  307.      *
  308.      * @return string
  309.      *
  310.      * @throws \Exception
  311.      */
  312.     private function getWorkflowSvg(Workflow $workflow)
  313.     {
  314.         $marking $workflow->getMarking($this->element);
  315.         $php Console::getExecutable('php');
  316.         $dot Console::getExecutable('dot');
  317.         if (!$php) {
  318.             throw new \InvalidArgumentException($this->translator->trans('workflow_cmd_not_found', ['php'], 'admin'));
  319.         }
  320.         if (!$dot) {
  321.             throw new \InvalidArgumentException($this->translator->trans('workflow_cmd_not_found', ['dot'], 'admin'));
  322.         }
  323.         $cmd $php ' ' PIMCORE_PROJECT_ROOT '/bin/console pimcore:workflow:dump ${WNAME} ${WPLACES} | ${DOT} -Tsvg';
  324.         $params = [
  325.             'WNAME' => $workflow->getName(),
  326.             'WPLACES' => implode(' 'array_keys($marking->getPlaces())),
  327.             'DOT' => $dot,
  328.         ];
  329.         Console::addLowProcessPriority($cmd);
  330.         $process Process::fromShellCommandline($cmd);
  331.         $process->run(null$params);
  332.         return $process->getOutput();
  333.     }
  334.     /**
  335.      * @template T of Document|Asset|DataObject
  336.      *
  337.      * @param T $element
  338.      *
  339.      * @return T
  340.      */
  341.     protected function getLatestVersion($element)
  342.     {
  343.         if (
  344.             $element instanceof Document\Folder
  345.             || $element instanceof Asset\Folder
  346.             || $element instanceof DataObject\Folder
  347.             || $element instanceof Document\Hardlink
  348.             || $element instanceof Document\Link
  349.         ) {
  350.             return $element;
  351.         }
  352.         //TODO move this maybe to a service method, since this is also used in DataObjectController and DocumentControllers
  353.         if ($element instanceof Document\PageSnippet) {
  354.             $latestVersion $element->getLatestVersion();
  355.             if ($latestVersion) {
  356.                 $latestDoc $latestVersion->loadData();
  357.                 if ($latestDoc instanceof Document\PageSnippet) {
  358.                     $element $latestDoc;
  359.                 }
  360.             }
  361.         }
  362.         if ($element instanceof DataObject\Concrete) {
  363.             $latestVersion $element->getLatestVersion();
  364.             if ($latestVersion) {
  365.                 $latestObj $latestVersion->loadData();
  366.                 if ($latestObj instanceof ConcreteObject) {
  367.                     $element $latestObj;
  368.                 }
  369.             }
  370.         }
  371.         return $element;
  372.     }
  373.     /**
  374.      * @param ControllerEvent $event
  375.      *
  376.      * @throws \Exception
  377.      */
  378.     public function onKernelControllerEvent(ControllerEvent $event)
  379.     {
  380.         if (!$event->isMainRequest()) {
  381.             return;
  382.         }
  383.         $request $event->getRequest();
  384.         if ($request->get('ctype') === 'document') {
  385.             $this->element Document::getById((int) $request->get('cid'0));
  386.         } elseif ($request->get('ctype') === 'asset') {
  387.             $this->element Asset::getById((int) $request->get('cid'0));
  388.         } elseif ($request->get('ctype') === 'object') {
  389.             $this->element ConcreteObject::getById((int) $request->get('cid'0));
  390.         }
  391.         if (!$this->element) {
  392.             throw new \Exception('Cannot load element' $request->get('cid') . ' of type \'' $request->get('ctype') . '\'');
  393.         }
  394.         //get the latest available version of the element -
  395.         $this->element $this->getLatestVersion($this->element);
  396.         $this->element->setUserModification($this->getAdminUser()->getId());
  397.     }
  398. }