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

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