Helper.php 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\View\Layout\ScheduledStructure;
  7. use Magento\Framework\View\Layout;
  8. use Magento\Framework\App\State;
  9. class Helper
  10. {
  11. /**#@+
  12. * Scheduled structure array indexes
  13. */
  14. const SCHEDULED_STRUCTURE_INDEX_TYPE = 0;
  15. const SCHEDULED_STRUCTURE_INDEX_ALIAS = 1;
  16. const SCHEDULED_STRUCTURE_INDEX_PARENT_NAME = 2;
  17. const SCHEDULED_STRUCTURE_INDEX_SIBLING_NAME = 3;
  18. const SCHEDULED_STRUCTURE_INDEX_IS_AFTER = 4;
  19. /**#@-*/
  20. /**#@-*/
  21. protected $counter = 0;
  22. /**
  23. * @var \Psr\Log\LoggerInterface
  24. */
  25. protected $logger;
  26. /**
  27. * @var State
  28. */
  29. protected $state;
  30. /**
  31. * @param \Psr\Log\LoggerInterface $logger
  32. * @param State $state
  33. */
  34. public function __construct(
  35. \Psr\Log\LoggerInterface $logger,
  36. State $state
  37. ) {
  38. $this->logger = $logger;
  39. $this->state = $state;
  40. }
  41. /**
  42. * Generate anonymous element name for structure
  43. *
  44. * @param string $class
  45. * @return string
  46. */
  47. protected function _generateAnonymousName($class)
  48. {
  49. $position = strpos($class, '\\Block\\');
  50. $key = $position !== false ? substr($class, $position + 7) : $class;
  51. $key = strtolower(trim($key, '_'));
  52. return $key . $this->counter++;
  53. }
  54. /**
  55. * Populate queue for generating structural elements
  56. *
  57. * @param Layout\ScheduledStructure $scheduledStructure
  58. * @param \Magento\Framework\View\Layout\Element $currentNode
  59. * @param \Magento\Framework\View\Layout\Element $parentNode
  60. * @return string
  61. * @see scheduleElement() where the scheduledStructure is used
  62. */
  63. public function scheduleStructure(
  64. Layout\ScheduledStructure $scheduledStructure,
  65. Layout\Element $currentNode,
  66. Layout\Element $parentNode
  67. ) {
  68. // if it hasn't a name it must be generated
  69. if (!(string)$currentNode->getAttribute('name')) {
  70. $name = $this->_generateAnonymousName($parentNode->getElementName() . '_schedule_block');
  71. $currentNode->setAttribute('name', $name);
  72. }
  73. $path = $name = (string)$currentNode->getAttribute('name');
  74. // Prepare scheduled element with default parameters [type, alias, parentName, siblingName, isAfter]
  75. $row = [
  76. self::SCHEDULED_STRUCTURE_INDEX_TYPE => $currentNode->getName(),
  77. self::SCHEDULED_STRUCTURE_INDEX_ALIAS => '',
  78. self::SCHEDULED_STRUCTURE_INDEX_PARENT_NAME => '',
  79. self::SCHEDULED_STRUCTURE_INDEX_SIBLING_NAME => null,
  80. self::SCHEDULED_STRUCTURE_INDEX_IS_AFTER => true,
  81. ];
  82. $parentName = $parentNode->getElementName();
  83. //if this element has a parent element, there must be reset [alias, parentName, siblingName, isAfter]
  84. if ($parentName) {
  85. $row[self::SCHEDULED_STRUCTURE_INDEX_ALIAS] = (string)$currentNode->getAttribute('as');
  86. $row[self::SCHEDULED_STRUCTURE_INDEX_PARENT_NAME] = $parentName;
  87. list($row[self::SCHEDULED_STRUCTURE_INDEX_SIBLING_NAME],
  88. $row[self::SCHEDULED_STRUCTURE_INDEX_IS_AFTER]) = $this->_beforeAfterToSibling($currentNode);
  89. // materialized path for referencing nodes in the plain array of _scheduledStructure
  90. if ($scheduledStructure->hasPath($parentName)) {
  91. $path = $scheduledStructure->getPath($parentName) . '/' . $path;
  92. }
  93. }
  94. $this->_overrideElementWorkaround($scheduledStructure, $name, $path);
  95. $scheduledStructure->setPathElement($name, $path);
  96. $scheduledStructure->setStructureElement($name, $row);
  97. return $name;
  98. }
  99. /**
  100. * Destroy previous element with same name and all its children, if new element overrides it
  101. *
  102. * This is a workaround to handle situation, when an element emerges with name of element that already exists.
  103. * In this case we destroy entire structure of the former element and replace with the new one.
  104. *
  105. * @param Layout\ScheduledStructure $scheduledStructure
  106. * @param string $name
  107. * @param string $path
  108. * @return void
  109. */
  110. protected function _overrideElementWorkaround(Layout\ScheduledStructure $scheduledStructure, $name, $path)
  111. {
  112. if ($scheduledStructure->hasStructureElement($name)) {
  113. $scheduledStructure->setStructureElementData($name, []);
  114. foreach ($scheduledStructure->getPaths() as $potentialChild => $childPath) {
  115. if (0 === strpos($childPath, "{$path}/")) {
  116. $scheduledStructure->unsetPathElement($potentialChild);
  117. $scheduledStructure->unsetStructureElement($potentialChild);
  118. }
  119. }
  120. }
  121. }
  122. /**
  123. * Analyze "before" and "after" information in the node and return sibling name and whether "after" or "before"
  124. *
  125. * @param \Magento\Framework\View\Layout\Element $node
  126. * @return array
  127. */
  128. protected function _beforeAfterToSibling($node)
  129. {
  130. $result = [null, true];
  131. if (isset($node['after'])) {
  132. $result[0] = (string)$node['after'];
  133. } elseif (isset($node['before'])) {
  134. $result[0] = (string)$node['before'];
  135. $result[1] = false;
  136. }
  137. return $result;
  138. }
  139. /**
  140. * Process queue of structural elements and actually add them to structure, and schedule elements for generation
  141. *
  142. * The catch is to populate parents first, if they are not in the structure yet.
  143. * Since layout updates could come in arbitrary order, a case is possible where an element is declared in reference,
  144. * while referenced element itself is not declared yet.
  145. *
  146. * @param \Magento\Framework\View\Layout\ScheduledStructure $scheduledStructure
  147. * @param Layout\Data\Structure $structure
  148. * @param string $key in _scheduledStructure represent element name
  149. * @return void
  150. * @SuppressWarnings(PHPMD.NPathComplexity)
  151. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  152. */
  153. public function scheduleElement(
  154. Layout\ScheduledStructure $scheduledStructure,
  155. Layout\Data\Structure $structure,
  156. $key
  157. ) {
  158. $row = $scheduledStructure->getStructureElement($key);
  159. $data = $scheduledStructure->getStructureElementData($key);
  160. // if we have reference container to not existed element
  161. if (!isset($row[self::SCHEDULED_STRUCTURE_INDEX_TYPE])) {
  162. $this->logger->critical("Broken reference: missing declaration of the element '{$key}'.");
  163. $scheduledStructure->unsetPathElement($key);
  164. $scheduledStructure->unsetStructureElement($key);
  165. return;
  166. }
  167. list($type, $alias, $parentName, $siblingName, $isAfter) = $row;
  168. $name = $this->_createStructuralElement($structure, $key, $type, $parentName . $alias);
  169. if ($parentName) {
  170. // recursively populate parent first
  171. if ($scheduledStructure->hasStructureElement($parentName)) {
  172. $this->scheduleElement($scheduledStructure, $structure, $parentName);
  173. }
  174. if ($structure->hasElement($parentName)) {
  175. try {
  176. $structure->setAsChild($name, $parentName, $alias);
  177. } catch (\Exception $e) {
  178. $this->logger->critical($e);
  179. }
  180. } else {
  181. $scheduledStructure->setElementToBrokenParentList($key);
  182. if ($this->state->getMode() === State::MODE_DEVELOPER) {
  183. $this->logger->info(
  184. "Broken reference: the '{$name}' element cannot be added as child to '{$parentName}', " .
  185. 'because the latter doesn\'t exist'
  186. );
  187. }
  188. }
  189. }
  190. // Move from scheduledStructure to scheduledElement
  191. $scheduledStructure->unsetStructureElement($key);
  192. $scheduledStructure->setElement($name, [$type, $data]);
  193. /**
  194. * Some elements provide info "after" or "before" which sibling they are supposed to go
  195. * Add element into list of sorting
  196. */
  197. if ($siblingName) {
  198. $scheduledStructure->setElementToSortList($parentName, $name, $siblingName, $isAfter);
  199. }
  200. }
  201. /**
  202. * Register an element in structure
  203. *
  204. * Will assign an "anonymous" name to the element, if provided with an empty name
  205. *
  206. * @param Layout\Data\Structure $structure
  207. * @param string $name
  208. * @param string $type
  209. * @param string $class
  210. * @return string
  211. */
  212. protected function _createStructuralElement(Layout\Data\Structure $structure, $name, $type, $class)
  213. {
  214. if (empty($name)) {
  215. $name = $this->_generateAnonymousName($class);
  216. }
  217. $structure->createElement($name, ['type' => $type]);
  218. return $name;
  219. }
  220. }