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

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