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

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