vendor/pimcore/pimcore/models/Document/Editable/Areablock.php line 32
<?php/*** Pimcore** This source file is available under two different licenses:* - GNU General Public License version 3 (GPLv3)* - Pimcore Commercial License (PCL)* Full copyright and license information is available in* LICENSE.md which is distributed with this source code.** @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)* @license http://www.pimcore.org/license GPLv3 and PCL*/namespace Pimcore\Model\Document\Editable;use Pimcore\Document\Editable\Block\BlockName;use Pimcore\Document\Editable\EditableHandler;use Pimcore\Extension\Document\Areabrick\AreabrickManagerInterface;use Pimcore\Extension\Document\Areabrick\EditableDialogBoxInterface;use Pimcore\Model;use Pimcore\Model\Document;use Pimcore\Templating\Renderer\EditableRenderer;use Pimcore\Tool;use Pimcore\Tool\HtmlUtils;/*** @method \Pimcore\Model\Document\Editable\Dao getDao()*/class Areablock extends Model\Document\Editable implements BlockInterface{/*** Contains an array of indices, which represent the order of the elements in the block** @internal**/protected array $indices = [];/*** Current step of the block while iteration** @internal**/protected int $current = 0;/*** @internal**/protected ?array $currentIndex = null;/*** @internal**/protected ?bool $blockStarted = false;/*** @internal**/protected array $brickTypeUsageCounter = [];public function getType(): string{return 'areablock';}public function getData(): mixed{return $this->indices;}public function admin(): void{$this->frontend();}public function frontend(): void{reset($this->indices);while ($this->loop());}/*** @internal** @return ($return is true ? string : void)*/public function renderIndex(int $index, bool $return = false){$this->start($return);$this->currentIndex = $this->indices[$index];$this->current = $index;$this->blockConstruct();$templateParams = $this->blockStart();$content = $this->content(null, $templateParams, $return);if (!$return) {echo $content;}$this->blockDestruct();$this->blockEnd();$this->end($return);if ($return) {return $content;}}public function getIterator(): \Generator{while ($this->loop()) {yield $this->getCurrentIndex();}}/*** @internal**/public function loop(): bool{$disabled = false;$config = $this->getConfig();$manual = (($config['manual'] ?? false) == true);if ($this->current > 0) {if (!$manual && $this->blockStarted) {$this->blockDestruct();$this->blockEnd();}} else {if (!$manual) {$this->start();}}if ($this->current < count($this->indices) && $this->current < $config['limit']) {$index = current($this->indices);next($this->indices);$this->currentIndex = $index;if (!empty($config['allowed']) && !in_array($index['type'], $config['allowed'])) {$disabled = true;}$brickTypeLimit = $config['limits'][$this->currentIndex['type']] ?? 100000;$brickTypeUsageCounter = $this->brickTypeUsageCounter[$this->currentIndex['type']] ?? 0;if ($brickTypeUsageCounter >= $brickTypeLimit) {$disabled = true;}$this->blockStarted = false;$info = $this->buildInfoObject();if (!$manual && !$disabled) {$this->blockConstruct();$templateParams = $this->blockStart($info);$this->content($info, $templateParams);} elseif (!$manual) {$this->current++;}return true;} else {if (!$manual) {$this->end();}return false;}}/*** @internal**/public function buildInfoObject(): Area\Info{$config = $this->getConfig();// create info object and assign it to the view$info = new Area\Info();$info->setId($this->currentIndex ? $this->currentIndex['type'] : null);$info->setEditable($this);$info->setIndex($this->current);$params = [];if (is_array($config['params'][$this->currentIndex['type']] ?? null)) {$params = $config['params'][$this->currentIndex['type']];}if (is_array($config['globalParams'] ?? null)) {$params = array_merge($config['globalParams'], $params);}$info->setParams($params);return $info;}/*** @param null|Document\Editable\Area\Info $info** @return string|void*/public function content(Area\Info $info = null, array $templateParams = [], bool $return = false){if (!$info) {$info = $this->buildInfoObject();}$content = '';if ($this->editmode || !isset($this->currentIndex['hidden']) || !$this->currentIndex['hidden']) {$templateParams['isAreaBlock'] = true;$content = $this->getEditableHandler()->renderAreaFrontend($info, $templateParams);if (!$return) {echo $content;}$this->brickTypeUsageCounter += [$this->currentIndex['type'] => 0];$this->brickTypeUsageCounter[$this->currentIndex['type']]++;}$this->current++;if ($return) {return $content;}}/*** @internal**/protected function getEditableHandler(): EditableHandler{// TODO inject area handler via DI when editables are built through containerreturn \Pimcore::getContainer()->get(EditableHandler::class);}public function setDataFromResource(mixed $data): static{$unserializedData = Tool\Serialize::unserialize($data);if (is_array($unserializedData)) {$this->indices = $unserializedData;} else {$this->indices = [];}return $this;}public function setDataFromEditmode(mixed $data): static{$this->indices = $data;return $this;}public function blockConstruct(): void{// set the current block suffix for the child elements (0, 1, 3, ...)// this will be removed in blockDestruct$this->getBlockState()->pushIndex($this->indices[$this->current]['key']);}public function blockDestruct(): void{$this->getBlockState()->popIndex();}private function getToolBarDefaultConfig(): array{return ['areablock_toolbar' => ['width' => 172,'buttonWidth' => 168,'buttonMaxCharacters' => 20,],];}public function getEditmodeDefinition(): array{$config = array_merge($this->getToolBarDefaultConfig(), $this->getConfig());$options = parent::getEditmodeDefinition();$options = array_merge($options, ['config' => $config,]);return $options;}protected function getEditmodeElementAttributes(): array{$attributes = parent::getEditmodeElementAttributes();$attributes = array_merge($attributes, ['name' => $this->getName(),'type' => $this->getType(),]);return $attributes;}public function start(bool $return = false){if (($this->config['manual'] ?? false) === true) {// in manual mode $this->render() is not called for the areablock, so we need to add// the editable to the collector manually hereif ($editableDefCollector = $this->getEditableDefinitionCollector()) {$editableDefCollector->add($this);}}reset($this->indices);// set name suffix for the whole block element, this will be added to all child elements of the block$this->getBlockState()->pushBlock(BlockName::createFromEditable($this));$attributes = $this->getEditmodeElementAttributes();$attributeString = HtmlUtils::assembleAttributeString($attributes);$html = '<div ' . $attributeString . '>';if ($return) {return $html;}$this->outputEditmode($html);return $this;}public function end(bool $return = false){$this->current = 0;// remove the current block which was set by $this->start()$this->getBlockState()->popBlock();$html = '</div>';if ($return) {return $html;}$this->outputEditmode($html);}public function blockStart(Area\Info $info = null): array{$this->blockStarted = true;$attributes = ['data-name' => $this->getName(),'data-real-name' => $this->getRealName(),];$hidden = 'false';if (isset($this->indices[$this->current]['hidden']) && $this->indices[$this->current]['hidden']) {$hidden = 'true';}$outerAttributes = ['key' => $this->indices[$this->current]['key'],'type' => $this->indices[$this->current]['type'],'data-hidden' => $hidden,];$areabrickManager = \Pimcore::getContainer()->get(AreabrickManagerInterface::class);$dialogConfig = null;$brick = $areabrickManager->getBrick($this->indices[$this->current]['type']);if ($this->getEditmode() && $brick instanceof EditableDialogBoxInterface) {$dialogConfig = $brick->getEditableDialogBoxConfiguration($this, $info);if ($dialogConfig->getItems()) {$dialogConfig->setId('dialogBox-' . $this->getName() . '-' . $this->indices[$this->current]['key']);} else {$dialogConfig = null;}}$attr = HtmlUtils::assembleAttributeString($attributes);$oAttr = HtmlUtils::assembleAttributeString($outerAttributes);$dialogAttributes = '';if ($dialogConfig) {$dialogAttributes = HtmlUtils::assembleAttributeString(['data-dialog-id' => $dialogConfig->getId(),]);}$dialogHtml = '';if ($dialogConfig) {$editableRenderer = \Pimcore::getContainer()->get(EditableRenderer::class);$items = $this->renderDialogBoxEditables($dialogConfig->getItems(), $editableRenderer, $dialogConfig->getId(), $dialogHtml);$dialogConfig->setItems($items);}return ['editmodeOuterAttributes' => $oAttr,'editmodeGenericAttributes' => $attr,'editableDialog' => $dialogConfig,'editableDialogAttributes' => $dialogAttributes,'dialogHtml' => $dialogHtml,];}/*** This method needs to be `protected` as it is used in other bundles such as pimcore/headless-documents*** @throws \Exception** @internal*/protected function renderDialogBoxEditables(array|Document\Editable $config, EditableRenderer $editableRenderer, string $dialogId, string &$html): array{if ($config instanceof BlockInterface || $config instanceof Area) {// Unsupported element was passed (e.g., Block, Areablock, ...)// or an Areas was passed, which is not supported to avoid too long editable namesthrow new \Exception(sprintf('Using editables of type "%s" for the editable dialog "%s" is not supported.', get_debug_type($config), $dialogId));} elseif ($config instanceof Document\Editable) {// Map editable to array config$config = ['type' => $config->getType(),'name' => $config->getName(),'label' => $config->getLabel(),'config' => $config->getConfig(),'description' => $config->getDialogDescription(),];}if (isset($config['items']) && is_array($config['items'])) {// layout componentforeach ($config['items'] as $index => $child) {$config['items'][$index] = $this->renderDialogBoxEditables($child, $editableRenderer, $dialogId, $html);}} elseif (isset($config['name']) && isset($config['type'])) {$editable = $editableRenderer->getEditable($this->getDocument(), $config['type'], $config['name'], $config['config'] ?? []);if (!$editable instanceof Document\Editable) {throw new \Exception(sprintf('Invalid editable type "%s" configured for Dialog Box', $config['type']));}$editable->setInDialogBox($dialogId);$editable->addConfig('dialogBoxConfig', $config);$html .= $editable->render();} else {foreach ($config as $index => $item) {$config['items'][$index] = $this->renderDialogBoxEditables($item, $editableRenderer, $dialogId, $html);}}return $config;}public function blockEnd(): void{$this->blockStarted = false;}public function setConfig(array $config): static{// we need to set this here otherwise custom areaDir's won't work$this->config = $config;if (!isset($config['allowed']) || !is_array($config['allowed'])) {$config['allowed'] = [];}$availableAreas = $this->getEditableHandler()->getAvailableAreablockAreas($this, $config);$availableAreas = $this->sortAvailableAreas($availableAreas, $config);$config['types'] = $availableAreas;if (isset($config['group']) && is_array($config['group'])) {$groupingareas = [];foreach ($availableAreas as $area) {$groupingareas[$area['type']] = $area['type'];}$groups = [];foreach ($config['group'] as $name => $areas) {$n = $name;$groups[$n] = $areas;foreach ($areas as $area) {unset($groupingareas[$area]);}}if (count($groupingareas) > 0) {$uncatAreas = [];foreach ($groupingareas as $area) {$uncatAreas[] = $area;}$n = 'Uncategorized';$groups[$n] = $uncatAreas;}$config['group'] = $groups;}if (empty($config['limit'])) {$config['limit'] = 1000000;}$config['blockStateStack'] = json_encode($this->getBlockStateStack());$this->config = $config;if (($this->config['manual'] ?? false) === true) {$this->config['reload'] = true;}return $this;}/*** Sorts areas by index (sorting option) first, then by name*/private function sortAvailableAreas(array $areas, array $config): array{if (isset($config['sorting']) && is_array($config['sorting']) && count($config['sorting'])) {$sorting = $config['sorting'];} else {if (isset($config['allowed']) && is_array($config['allowed']) && count($config['allowed'])) {$sorting = $config['allowed'];} else {$sorting = [];}}$result = ['name' => [],'index' => [],];foreach ($areas as $area) {$sortIndex = false;if (!empty($sorting)) {$sortIndex = array_search($area['type'], $sorting);}$sortKey = 'name'; // allowed and sorting is not set || areaName is not in allowedif (false !== $sortIndex) {$sortKey = 'index';$area['sortIndex'] = $sortIndex;}$result[$sortKey][] = $area;}// sort with translated namesif (count($result['name'])) {usort($result['name'], function ($a, $b) {if ($a['name'] == $b['name']) {return 0;}return ($a['name'] < $b['name']) ? -1 : 1;});}// sort by allowed brick config orderif (count($result['index'])) {usort($result['index'], function ($a, $b) {return $a['sortIndex'] - $b['sortIndex'];});}$result = array_merge($result['index'], $result['name']);return $result;}public function getCount(): int{return count($this->indices);}public function getCurrent(): int{return $this->current - 1;}public function getCurrentIndex(): int{return $this->indices[$this->getCurrent()]['key'] ?? 0;}public function getIndices(): array{return $this->indices;}/*** If object was serialized, set the counter back to 0*/public function __wakeup(): void{$this->current = 0;reset($this->indices);}public function isEmpty(): bool{return !(bool) count($this->indices);}/**** @return Areablock\Item[]*/public function getElement(string $name): array{$document = $this->getDocument();$parentBlockNames = $this->getParentBlockNames();$parentBlockNames[] = $this->getName();$list = [];foreach ($this->getData() as $index => $item) {if ($item['type'] === $name) {$list[$index] = new Areablock\Item($document, $parentBlockNames, (int)$item['key']);}}return $list;}}