123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240 |
- <?php
- /**
- * Copyright © Magento, Inc. All rights reserved.
- * See COPYING.txt for license details.
- */
- namespace Magento\Framework\View\Layout\ScheduledStructure;
- use Magento\Framework\View\Layout;
- use Magento\Framework\App\State;
- class Helper
- {
- /**#@+
- * Scheduled structure array indexes
- */
- const SCHEDULED_STRUCTURE_INDEX_TYPE = 0;
- const SCHEDULED_STRUCTURE_INDEX_ALIAS = 1;
- const SCHEDULED_STRUCTURE_INDEX_PARENT_NAME = 2;
- const SCHEDULED_STRUCTURE_INDEX_SIBLING_NAME = 3;
- const SCHEDULED_STRUCTURE_INDEX_IS_AFTER = 4;
- /**#@-*/
- /**#@-*/
- protected $counter = 0;
- /**
- * @var \Psr\Log\LoggerInterface
- */
- protected $logger;
- /**
- * @var State
- */
- protected $state;
- /**
- * @param \Psr\Log\LoggerInterface $logger
- * @param State $state
- */
- public function __construct(
- \Psr\Log\LoggerInterface $logger,
- State $state
- ) {
- $this->logger = $logger;
- $this->state = $state;
- }
- /**
- * Generate anonymous element name for structure
- *
- * @param string $class
- * @return string
- */
- protected function _generateAnonymousName($class)
- {
- $position = strpos($class, '\\Block\\');
- $key = $position !== false ? substr($class, $position + 7) : $class;
- $key = strtolower(trim($key, '_'));
- return $key . $this->counter++;
- }
- /**
- * Populate queue for generating structural elements
- *
- * @param Layout\ScheduledStructure $scheduledStructure
- * @param \Magento\Framework\View\Layout\Element $currentNode
- * @param \Magento\Framework\View\Layout\Element $parentNode
- * @return string
- * @see scheduleElement() where the scheduledStructure is used
- */
- public function scheduleStructure(
- Layout\ScheduledStructure $scheduledStructure,
- Layout\Element $currentNode,
- Layout\Element $parentNode
- ) {
- // if it hasn't a name it must be generated
- if (!(string)$currentNode->getAttribute('name')) {
- $name = $this->_generateAnonymousName($parentNode->getElementName() . '_schedule_block');
- $currentNode->setAttribute('name', $name);
- }
- $path = $name = (string)$currentNode->getAttribute('name');
- // Prepare scheduled element with default parameters [type, alias, parentName, siblingName, isAfter]
- $row = [
- self::SCHEDULED_STRUCTURE_INDEX_TYPE => $currentNode->getName(),
- self::SCHEDULED_STRUCTURE_INDEX_ALIAS => '',
- self::SCHEDULED_STRUCTURE_INDEX_PARENT_NAME => '',
- self::SCHEDULED_STRUCTURE_INDEX_SIBLING_NAME => null,
- self::SCHEDULED_STRUCTURE_INDEX_IS_AFTER => true,
- ];
- $parentName = $parentNode->getElementName();
- //if this element has a parent element, there must be reset [alias, parentName, siblingName, isAfter]
- if ($parentName) {
- $row[self::SCHEDULED_STRUCTURE_INDEX_ALIAS] = (string)$currentNode->getAttribute('as');
- $row[self::SCHEDULED_STRUCTURE_INDEX_PARENT_NAME] = $parentName;
- list($row[self::SCHEDULED_STRUCTURE_INDEX_SIBLING_NAME],
- $row[self::SCHEDULED_STRUCTURE_INDEX_IS_AFTER]) = $this->_beforeAfterToSibling($currentNode);
- // materialized path for referencing nodes in the plain array of _scheduledStructure
- if ($scheduledStructure->hasPath($parentName)) {
- $path = $scheduledStructure->getPath($parentName) . '/' . $path;
- }
- }
- $this->_overrideElementWorkaround($scheduledStructure, $name, $path);
- $scheduledStructure->setPathElement($name, $path);
- $scheduledStructure->setStructureElement($name, $row);
- return $name;
- }
- /**
- * Destroy previous element with same name and all its children, if new element overrides it
- *
- * This is a workaround to handle situation, when an element emerges with name of element that already exists.
- * In this case we destroy entire structure of the former element and replace with the new one.
- *
- * @param Layout\ScheduledStructure $scheduledStructure
- * @param string $name
- * @param string $path
- * @return void
- */
- protected function _overrideElementWorkaround(Layout\ScheduledStructure $scheduledStructure, $name, $path)
- {
- if ($scheduledStructure->hasStructureElement($name)) {
- $scheduledStructure->setStructureElementData($name, []);
- foreach ($scheduledStructure->getPaths() as $potentialChild => $childPath) {
- if (0 === strpos($childPath, "{$path}/")) {
- $scheduledStructure->unsetPathElement($potentialChild);
- $scheduledStructure->unsetStructureElement($potentialChild);
- }
- }
- }
- }
- /**
- * Analyze "before" and "after" information in the node and return sibling name and whether "after" or "before"
- *
- * @param \Magento\Framework\View\Layout\Element $node
- * @return array
- */
- protected function _beforeAfterToSibling($node)
- {
- $result = [null, true];
- if (isset($node['after'])) {
- $result[0] = (string)$node['after'];
- } elseif (isset($node['before'])) {
- $result[0] = (string)$node['before'];
- $result[1] = false;
- }
- return $result;
- }
- /**
- * Process queue of structural elements and actually add them to structure, and schedule elements for generation
- *
- * The catch is to populate parents first, if they are not in the structure yet.
- * Since layout updates could come in arbitrary order, a case is possible where an element is declared in reference,
- * while referenced element itself is not declared yet.
- *
- * @param \Magento\Framework\View\Layout\ScheduledStructure $scheduledStructure
- * @param Layout\Data\Structure $structure
- * @param string $key in _scheduledStructure represent element name
- * @return void
- * @SuppressWarnings(PHPMD.NPathComplexity)
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
- */
- public function scheduleElement(
- Layout\ScheduledStructure $scheduledStructure,
- Layout\Data\Structure $structure,
- $key
- ) {
- $row = $scheduledStructure->getStructureElement($key);
- $data = $scheduledStructure->getStructureElementData($key);
- // if we have reference container to not existed element
- if (!isset($row[self::SCHEDULED_STRUCTURE_INDEX_TYPE])) {
- $this->logger->critical("Broken reference: missing declaration of the element '{$key}'.");
- $scheduledStructure->unsetPathElement($key);
- $scheduledStructure->unsetStructureElement($key);
- return;
- }
- list($type, $alias, $parentName, $siblingName, $isAfter) = $row;
- $name = $this->_createStructuralElement($structure, $key, $type, $parentName . $alias);
- if ($parentName) {
- // recursively populate parent first
- if ($scheduledStructure->hasStructureElement($parentName)) {
- $this->scheduleElement($scheduledStructure, $structure, $parentName);
- }
- if ($structure->hasElement($parentName)) {
- try {
- $structure->setAsChild($name, $parentName, $alias);
- } catch (\Exception $e) {
- $this->logger->critical($e);
- }
- } else {
- $scheduledStructure->setElementToBrokenParentList($key);
- if ($this->state->getMode() === State::MODE_DEVELOPER) {
- $this->logger->info(
- "Broken reference: the '{$name}' element cannot be added as child to '{$parentName}', " .
- 'because the latter doesn\'t exist'
- );
- }
- }
- }
- // Move from scheduledStructure to scheduledElement
- $scheduledStructure->unsetStructureElement($key);
- $scheduledStructure->setElement($name, [$type, $data]);
- /**
- * Some elements provide info "after" or "before" which sibling they are supposed to go
- * Add element into list of sorting
- */
- if ($siblingName) {
- $scheduledStructure->setElementToSortList($parentName, $name, $siblingName, $isAfter);
- }
- }
- /**
- * Register an element in structure
- *
- * Will assign an "anonymous" name to the element, if provided with an empty name
- *
- * @param Layout\Data\Structure $structure
- * @param string $name
- * @param string $type
- * @param string $class
- * @return string
- */
- protected function _createStructuralElement(Layout\Data\Structure $structure, $name, $type, $class)
- {
- if (empty($name)) {
- $name = $this->_generateAnonymousName($class);
- }
- $structure->createElement($name, ['type' => $type]);
- return $name;
- }
- }
|