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

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