vendor/pimcore/pimcore/models/Document/Editable/Areablock.php line 31

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\Model\Document\Editable;
  15. use Pimcore\Document\Editable\Block\BlockName;
  16. use Pimcore\Document\Editable\EditableHandler;
  17. use Pimcore\Extension\Document\Areabrick\AreabrickManagerInterface;
  18. use Pimcore\Extension\Document\Areabrick\EditableDialogBoxInterface;
  19. use Pimcore\Model;
  20. use Pimcore\Model\Document;
  21. use Pimcore\Templating\Renderer\EditableRenderer;
  22. use Pimcore\Tool;
  23. use Pimcore\Tool\HtmlUtils;
  24. /**
  25.  * @method \Pimcore\Model\Document\Editable\Dao getDao()
  26.  */
  27. class Areablock extends Model\Document\Editable implements BlockInterface
  28. {
  29.     /**
  30.      * Contains an array of indices, which represent the order of the elements in the block
  31.      *
  32.      * @internal
  33.      *
  34.      * @var array
  35.      */
  36.     protected $indices = [];
  37.     /**
  38.      * Current step of the block while iteration
  39.      *
  40.      * @internal
  41.      *
  42.      * @var int
  43.      */
  44.     protected $current 0;
  45.     /**
  46.      * @internal
  47.      *
  48.      * @var array|null
  49.      */
  50.     protected $currentIndex;
  51.     /**
  52.      * @internal
  53.      *
  54.      * @var bool|null
  55.      */
  56.     protected $blockStarted;
  57.     /**
  58.      * @internal
  59.      *
  60.      * @var array
  61.      */
  62.     protected $brickTypeUsageCounter = [];
  63.     /**
  64.      * {@inheritdoc}
  65.      */
  66.     public function getType()
  67.     {
  68.         return 'areablock';
  69.     }
  70.     /**
  71.      * {@inheritdoc}
  72.      */
  73.     public function getData()
  74.     {
  75.         return $this->indices;
  76.     }
  77.     /**
  78.      * {@inheritdoc}
  79.      */
  80.     public function admin()
  81.     {
  82.         $this->frontend();
  83.     }
  84.     /**
  85.      * {@inheritdoc}
  86.      */
  87.     public function frontend()
  88.     {
  89.         if (!is_array($this->indices)) {
  90.             $this->indices = [];
  91.         }
  92.         reset($this->indices);
  93.         while ($this->loop());
  94.     }
  95.     /**
  96.      * @internal
  97.      *
  98.      * @param int $index
  99.      * @param bool $return
  100.      */
  101.     public function renderIndex($index$return false)
  102.     {
  103.         $this->start($return);
  104.         $this->currentIndex $this->indices[$index];
  105.         $this->current $index;
  106.         $this->blockConstruct();
  107.         $templateParams $this->blockStart();
  108.         $content $this->content(null$templateParams$return);
  109.         if (!$return) {
  110.             echo $content;
  111.         }
  112.         $this->blockDestruct();
  113.         $this->blockEnd();
  114.         $this->end($return);
  115.         if ($return) {
  116.             return $content;
  117.         }
  118.     }
  119.     /**
  120.      * {@inheritdoc}
  121.      */
  122.     public function getIterator()
  123.     {
  124.         while ($this->loop()) {
  125.             yield $this->getCurrentIndex();
  126.         }
  127.     }
  128.     /**
  129.      * @internal
  130.      *
  131.      * @return bool
  132.      */
  133.     public function loop()
  134.     {
  135.         $disabled false;
  136.         $config $this->getConfig();
  137.         $manual = (($config['manual'] ?? false) == true);
  138.         if ($this->current 0) {
  139.             if (!$manual && $this->blockStarted) {
  140.                 $this->blockDestruct();
  141.                 $this->blockEnd();
  142.             }
  143.         } else {
  144.             if (!$manual) {
  145.                 $this->start();
  146.             }
  147.         }
  148.         if ($this->current count($this->indices) && $this->current $config['limit']) {
  149.             $index current($this->indices);
  150.             next($this->indices);
  151.             $this->currentIndex $index;
  152.             if (!empty($config['allowed']) && !in_array($index['type'], $config['allowed'])) {
  153.                 $disabled true;
  154.             }
  155.             $brickTypeLimit $config['limits'][$this->currentIndex['type']] ?? 100000;
  156.             $brickTypeUsageCounter $this->brickTypeUsageCounter[$this->currentIndex['type']] ?? 0;
  157.             if ($brickTypeUsageCounter >= $brickTypeLimit) {
  158.                 $disabled true;
  159.             }
  160.             if (!$this->getEditableHandler()->isBrickEnabled($this$index['type']) && ($config['dontCheckEnabled'] ?? false) !== true) {
  161.                 $disabled true;
  162.             }
  163.             $this->blockStarted false;
  164.             $info $this->buildInfoObject();
  165.             if (!$manual && !$disabled) {
  166.                 $this->blockConstruct();
  167.                 $templateParams $this->blockStart($info);
  168.                 $this->content($info$templateParams);
  169.             } elseif (!$manual) {
  170.                 $this->current++;
  171.             }
  172.             return true;
  173.         } else {
  174.             if (!$manual) {
  175.                 $this->end();
  176.             }
  177.             return false;
  178.         }
  179.     }
  180.     /**
  181.      * @internal
  182.      *
  183.      * @return Area\Info
  184.      */
  185.     public function buildInfoObject(): Area\Info
  186.     {
  187.         $config $this->getConfig();
  188.         // create info object and assign it to the view
  189.         $info = new Area\Info();
  190.         $info->setId($this->currentIndex $this->currentIndex['type'] : null);
  191.         $info->setEditable($this);
  192.         $info->setIndex($this->current);
  193.         $params = [];
  194.         if (is_array($config['params'][$this->currentIndex['type']] ?? null)) {
  195.             $params $config['params'][$this->currentIndex['type']];
  196.         }
  197.         if (is_array($config['globalParams'] ?? null)) {
  198.             $params array_merge($config['globalParams'], $params);
  199.         }
  200.         $info->setParams($params);
  201.         return $info;
  202.     }
  203.     /**
  204.      * @param null|Document\Editable\Area\Info $info
  205.      * @param array $templateParams
  206.      * @param bool $return
  207.      *
  208.      * @return string|void
  209.      */
  210.     public function content($info null$templateParams = [], $return false)
  211.     {
  212.         if (!$info) {
  213.             $info $this->buildInfoObject();
  214.         }
  215.         $content '';
  216.         if ($this->editmode || !isset($this->currentIndex['hidden']) || !$this->currentIndex['hidden']) {
  217.             $templateParams['isAreaBlock'] = true;
  218.             $content $this->getEditableHandler()->renderAreaFrontend($info$templateParams);
  219.             if (!$return) {
  220.                 echo $content;
  221.             }
  222.             $this->brickTypeUsageCounter += [$this->currentIndex['type'] => 0];
  223.             $this->brickTypeUsageCounter[$this->currentIndex['type']]++;
  224.         }
  225.         $this->current++;
  226.         if ($return) {
  227.             return $content;
  228.         }
  229.     }
  230.     /**
  231.      * @internal
  232.      *
  233.      * @return EditableHandler
  234.      */
  235.     protected function getEditableHandler()
  236.     {
  237.         // TODO inject area handler via DI when editables are built through container
  238.         return \Pimcore::getContainer()->get(EditableHandler::class);
  239.     }
  240.     /**
  241.      * {@inheritdoc}
  242.      */
  243.     public function setDataFromResource($data)
  244.     {
  245.         $this->indices Tool\Serialize::unserialize($data);
  246.         if (!is_array($this->indices)) {
  247.             $this->indices = [];
  248.         }
  249.         return $this;
  250.     }
  251.     /**
  252.      * {@inheritdoc}
  253.      */
  254.     public function setDataFromEditmode($data)
  255.     {
  256.         $this->indices $data;
  257.         return $this;
  258.     }
  259.     /**
  260.      * {@inheritdoc}
  261.      */
  262.     public function blockConstruct()
  263.     {
  264.         // set the current block suffix for the child elements (0, 1, 3, ...)
  265.         // this will be removed in blockDestruct
  266.         $this->getBlockState()->pushIndex($this->indices[$this->current]['key']);
  267.     }
  268.     /**
  269.      * {@inheritdoc}
  270.      */
  271.     public function blockDestruct()
  272.     {
  273.         $this->getBlockState()->popIndex();
  274.     }
  275.     /**
  276.      * @return array
  277.      */
  278.     private function getToolBarDefaultConfig()
  279.     {
  280.         return [
  281.             'areablock_toolbar' => [
  282.                 'width' => 172,
  283.                 'buttonWidth' => 168,
  284.                 'buttonMaxCharacters' => 20,
  285.             ],
  286.         ];
  287.     }
  288.     /**
  289.      * {@inheritdoc}
  290.      */
  291.     public function getEditmodeDefinition(): array
  292.     {
  293.         $config array_merge($this->getToolBarDefaultConfig(), $this->getConfig());
  294.         $options parent::getEditmodeDefinition();
  295.         $options array_merge($options, [
  296.             'config' => $config,
  297.         ]);
  298.         return $options;
  299.     }
  300.     /**
  301.      * {@inheritdoc}
  302.      */
  303.     protected function getEditmodeElementAttributes(): array
  304.     {
  305.         $attributes parent::getEditmodeElementAttributes();
  306.         $attributes array_merge($attributes, [
  307.             'name' => $this->getName(),
  308.             'type' => $this->getType(),
  309.         ]);
  310.         return $attributes;
  311.     }
  312.     /**
  313.      * @param bool $return
  314.      */
  315.     public function start($return false)
  316.     {
  317.         if (($this->config['manual'] ?? false) === true) {
  318.             // in manual mode $this->render() is not called for the areablock, so we need to add
  319.             // the editable to the collector manually here
  320.             if ($editableDefCollector $this->getEditableDefinitionCollector()) {
  321.                 $editableDefCollector->add($this);
  322.             }
  323.         }
  324.         reset($this->indices);
  325.         // set name suffix for the whole block element, this will be added to all child elements of the block
  326.         $this->getBlockState()->pushBlock(BlockName::createFromEditable($this));
  327.         $attributes $this->getEditmodeElementAttributes();
  328.         $attributeString HtmlUtils::assembleAttributeString($attributes);
  329.         $html '<div ' $attributeString '>';
  330.         if ($return) {
  331.             return $html;
  332.         } else {
  333.             $this->outputEditmode($html);
  334.         }
  335.         return $this;
  336.     }
  337.     /**
  338.      * @param bool $return
  339.      */
  340.     public function end($return false)
  341.     {
  342.         $this->current 0;
  343.         // remove the current block which was set by $this->start()
  344.         $this->getBlockState()->popBlock();
  345.         $html '</div>';
  346.         if ($return) {
  347.             return $html;
  348.         } else {
  349.             $this->outputEditmode($html);
  350.         }
  351.     }
  352.     /**
  353.      * @param Document\Editable\Area\Info $info
  354.      */
  355.     public function blockStart($info null)
  356.     {
  357.         $this->blockStarted true;
  358.         $attributes = [
  359.             'data-name' => $this->getName(),
  360.             'data-real-name' => $this->getRealName(),
  361.         ];
  362.         $hidden 'false';
  363.         if (isset($this->indices[$this->current]['hidden']) && $this->indices[$this->current]['hidden']) {
  364.             $hidden 'true';
  365.         }
  366.         $outerAttributes = [
  367.             'key' => $this->indices[$this->current]['key'],
  368.             'type' => $this->indices[$this->current]['type'],
  369.             'data-hidden' => $hidden,
  370.         ];
  371.         $areabrickManager \Pimcore::getContainer()->get(AreabrickManagerInterface::class);
  372.         $dialogConfig null;
  373.         $brick $areabrickManager->getBrick($this->indices[$this->current]['type']);
  374.         if ($this->getEditmode() && $brick instanceof EditableDialogBoxInterface) {
  375.             $dialogConfig $brick->getEditableDialogBoxConfiguration($this$info);
  376.             if ($dialogConfig->getItems()) {
  377.                 $dialogConfig->setId('dialogBox-' $this->getName() . '-' $this->indices[$this->current]['key']);
  378.             } else {
  379.                 $dialogConfig null;
  380.             }
  381.         }
  382.         $attr HtmlUtils::assembleAttributeString($attributes);
  383.         $oAttr HtmlUtils::assembleAttributeString($outerAttributes);
  384.         $dialogAttributes '';
  385.         if ($dialogConfig) {
  386.             $dialogAttributes HtmlUtils::assembleAttributeString([
  387.                 'data-dialog-id' => $dialogConfig->getId(),
  388.             ]);
  389.         }
  390.         $dialogHtml '';
  391.         if ($dialogConfig) {
  392.             $editableRenderer \Pimcore::getContainer()->get(EditableRenderer::class);
  393.             $this->renderDialogBoxEditables($dialogConfig->getItems(), $editableRenderer$dialogConfig->getId(), $dialogHtml);
  394.         }
  395.         return [
  396.             'editmodeOuterAttributes' => $oAttr,
  397.             'editmodeGenericAttributes' => $attr,
  398.             'editableDialog' => $dialogConfig,
  399.             'editableDialogAttributes' => $dialogAttributes,
  400.             'dialogHtml' => $dialogHtml,
  401.         ];
  402.     }
  403.     /**
  404.      * This method needs to be `protected` as it is used in other bundles such as pimcore/headless-documents
  405.      *
  406.      * @param array $config
  407.      * @param EditableRenderer $editableRenderer
  408.      * @param string $dialogId
  409.      * @param string $html
  410.      *
  411.      * @throws \Exception
  412.      *
  413.      * @internal
  414.      */
  415.     protected function renderDialogBoxEditables(array $configEditableRenderer $editableRendererstring $dialogIdstring &$html)
  416.     {
  417.         if (isset($config['items']) && is_array($config['items'])) {
  418.             // layout component
  419.             foreach ($config['items'] as $child) {
  420.                 $this->renderDialogBoxEditables($child$editableRenderer$dialogId$html);
  421.             }
  422.         } elseif (isset($config['name']) && isset($config['type'])) {
  423.             $editable $editableRenderer->getEditable($this->getDocument(), $config['type'], $config['name'], $config['config'] ?? []);
  424.             if (!$editable instanceof Document\Editable) {
  425.                 throw new \Exception(sprintf('Invalid editable type "%s" configured for Dialog Box'$config['type']));
  426.             }
  427.             $editable->setInDialogBox($dialogId);
  428.             $editable->addConfig('dialogBoxConfig'$config);
  429.             $html .= $editable->render();
  430.         } elseif (is_array($config) && isset($config[0])) {
  431.             foreach ($config as $item) {
  432.                 $this->renderDialogBoxEditables($item$editableRenderer$dialogId$html);
  433.             }
  434.         }
  435.     }
  436.     /**
  437.      * {@inheritdoc}
  438.      */
  439.     public function blockEnd()
  440.     {
  441.         $this->blockStarted false;
  442.     }
  443.     /**
  444.      * {@inheritdoc}
  445.      */
  446.     public function setConfig($config)
  447.     {
  448.         // we need to set this here otherwise custom areaDir's won't work
  449.         $this->config $config;
  450.         if (!isset($config['allowed']) || !is_array($config['allowed'])) {
  451.             $config['allowed'] = [];
  452.         }
  453.         $availableAreas $this->getEditableHandler()->getAvailableAreablockAreas($this$config);
  454.         $availableAreas $this->sortAvailableAreas($availableAreas$config);
  455.         $config['types'] = $availableAreas;
  456.         if (isset($config['group']) && is_array($config['group'])) {
  457.             $groupingareas = [];
  458.             foreach ($availableAreas as $area) {
  459.                 $groupingareas[$area['type']] = $area['type'];
  460.             }
  461.             $groups = [];
  462.             foreach ($config['group'] as $name => $areas) {
  463.                 $n $name;
  464.                 $groups[$n] = $areas;
  465.                 foreach ($areas as $area) {
  466.                     unset($groupingareas[$area]);
  467.                 }
  468.             }
  469.             if (count($groupingareas) > 0) {
  470.                 $uncatAreas = [];
  471.                 foreach ($groupingareas as $area) {
  472.                     $uncatAreas[] = $area;
  473.                 }
  474.                 $n 'Uncategorized';
  475.                 $groups[$n] = $uncatAreas;
  476.             }
  477.             $config['group'] = $groups;
  478.         }
  479.         if (empty($config['limit'])) {
  480.             $config['limit'] = 1000000;
  481.         }
  482.         $config['blockStateStack'] = json_encode($this->getBlockStateStack());
  483.         $this->config $config;
  484.         if (($this->config['manual'] ?? false) === true) {
  485.             $this->config['reload'] = true;
  486.         }
  487.         return $this;
  488.     }
  489.     /**
  490.      * Sorts areas by index (sorting option) first, then by name
  491.      *
  492.      * @param array $areas
  493.      * @param array $config
  494.      *
  495.      * @return array
  496.      */
  497.     private function sortAvailableAreas(array $areas, array $config)
  498.     {
  499.         if (isset($config['sorting']) && is_array($config['sorting']) && count($config['sorting'])) {
  500.             $sorting $config['sorting'];
  501.         } else {
  502.             if (isset($config['allowed']) && is_array($config['allowed']) && count($config['allowed'])) {
  503.                 $sorting $config['allowed'];
  504.             } else {
  505.                 $sorting = [];
  506.             }
  507.         }
  508.         $result = [
  509.             'name' => [],
  510.             'index' => [],
  511.         ];
  512.         foreach ($areas as $area) {
  513.             $sortIndex false;
  514.             if (!empty($sorting)) {
  515.                 $sortIndex array_search($area['type'], $sorting);
  516.             }
  517.             $sortKey 'name'// allowed and sorting is not set || areaName is not in allowed
  518.             if (false !== $sortIndex) {
  519.                 $sortKey 'index';
  520.                 $area['sortIndex'] = $sortIndex;
  521.             }
  522.             $result[$sortKey][] = $area;
  523.         }
  524.         // sort with translated names
  525.         if (count($result['name'])) {
  526.             usort($result['name'], function ($a$b) {
  527.                 if ($a['name'] == $b['name']) {
  528.                     return 0;
  529.                 }
  530.                 return ($a['name'] < $b['name']) ? -1;
  531.             });
  532.         }
  533.         // sort by allowed brick config order
  534.         if (count($result['index'])) {
  535.             usort($result['index'], function ($a$b) {
  536.                 return $a['sortIndex'] - $b['sortIndex'];
  537.             });
  538.         }
  539.         $result array_merge($result['index'], $result['name']);
  540.         return $result;
  541.     }
  542.     /**
  543.      * {@inheritdoc}
  544.      */
  545.     public function getCount()
  546.     {
  547.         return count($this->indices);
  548.     }
  549.     /**
  550.      * {@inheritdoc}
  551.      */
  552.     public function getCurrent()
  553.     {
  554.         return $this->current 1;
  555.     }
  556.     /**
  557.      * {@inheritdoc}
  558.      */
  559.     public function getCurrentIndex()
  560.     {
  561.         return $this->indices[$this->getCurrent()]['key'] ?? null;
  562.     }
  563.     /**
  564.      * @return array
  565.      */
  566.     public function getIndices()
  567.     {
  568.         return $this->indices;
  569.     }
  570.     /**
  571.      * If object was serialized, set the counter back to 0
  572.      */
  573.     public function __wakeup()
  574.     {
  575.         $this->current 0;
  576.         reset($this->indices);
  577.     }
  578.     /**
  579.      * {@inheritdoc}
  580.      */
  581.     public function isEmpty()
  582.     {
  583.         return !(bool) count($this->indices);
  584.     }
  585.     /**
  586.      * @param string $name
  587.      *
  588.      * @return Areablock\Item[]
  589.      */
  590.     public function getElement(string $name)
  591.     {
  592.         $document $this->getDocument();
  593.         $parentBlockNames $this->getParentBlockNames();
  594.         $parentBlockNames[] = $this->getName();
  595.         $list = [];
  596.         foreach ($this->getData() as $index => $item) {
  597.             if ($item['type'] === $name) {
  598.                 $list[$index] = new Areablock\Item($document$parentBlockNames, (int)$item['key']);
  599.             }
  600.         }
  601.         return $list;
  602.     }
  603. }