Block.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\View\Layout\Reader;
  7. use Magento\Framework\Data\Argument\InterpreterInterface;
  8. use Magento\Framework\View\Layout;
  9. use Magento\Framework\View\Layout\Element;
  10. use Magento\Framework\View\Layout\ScheduledStructure;
  11. use Magento\Framework\View\Layout\Reader\Visibility\Condition;
  12. /**
  13. * Block structure reader
  14. */
  15. class Block implements Layout\ReaderInterface
  16. {
  17. /**#@+
  18. * Supported types
  19. */
  20. const TYPE_BLOCK = 'block';
  21. const TYPE_REFERENCE_BLOCK = 'referenceBlock';
  22. /**#@-*/
  23. /**#@+
  24. * Supported subtypes for blocks
  25. */
  26. const TYPE_ARGUMENTS = 'arguments';
  27. const TYPE_ACTION = 'action';
  28. /**#@-*/
  29. /**#@+
  30. * Names of block attributes in layout
  31. */
  32. const ATTRIBUTE_GROUP = 'group';
  33. const ATTRIBUTE_CLASS = 'class';
  34. const ATTRIBUTE_TEMPLATE = 'template';
  35. const ATTRIBUTE_TTL = 'ttl';
  36. const ATTRIBUTE_DISPLAY = 'display';
  37. const ATTRIBUTE_ACL = 'aclResource';
  38. /**#@-*/
  39. /**#@-*/
  40. protected $attributes = [
  41. self::ATTRIBUTE_GROUP,
  42. self::ATTRIBUTE_CLASS,
  43. self::ATTRIBUTE_TEMPLATE,
  44. self::ATTRIBUTE_TTL,
  45. self::ATTRIBUTE_DISPLAY
  46. ];
  47. /**
  48. * @var \Magento\Framework\View\Layout\ScheduledStructure\Helper
  49. */
  50. protected $helper;
  51. /**
  52. * @var \Magento\Framework\View\Layout\Argument\Parser
  53. */
  54. protected $argumentParser;
  55. /**
  56. * @var \Magento\Framework\View\Layout\ReaderPool
  57. */
  58. protected $readerPool;
  59. /**
  60. * @var string
  61. */
  62. protected $scopeType;
  63. /**
  64. * @var InterpreterInterface
  65. */
  66. protected $argumentInterpreter;
  67. /**
  68. * @var Condition
  69. */
  70. private $conditionReader;
  71. /**
  72. * @deprecated 101.0.0
  73. * @var string
  74. */
  75. private $deprecatedAttributeAcl = 'acl';
  76. /**
  77. * Constructor
  78. *
  79. * @param Layout\ScheduledStructure\Helper $helper
  80. * @param Layout\Argument\Parser $argumentParser
  81. * @param Layout\ReaderPool $readerPool
  82. * @param InterpreterInterface $argumentInterpreter
  83. * @param Condition $conditionReader
  84. * @param string|null $scopeType
  85. */
  86. public function __construct(
  87. Layout\ScheduledStructure\Helper $helper,
  88. Layout\Argument\Parser $argumentParser,
  89. Layout\ReaderPool $readerPool,
  90. InterpreterInterface $argumentInterpreter,
  91. Condition $conditionReader,
  92. $scopeType = null
  93. ) {
  94. $this->helper = $helper;
  95. $this->argumentParser = $argumentParser;
  96. $this->readerPool = $readerPool;
  97. $this->scopeType = $scopeType;
  98. $this->argumentInterpreter = $argumentInterpreter;
  99. $this->conditionReader = $conditionReader;
  100. }
  101. /**
  102. * {@inheritdoc}
  103. *
  104. * @return string[]
  105. */
  106. public function getSupportedNodes()
  107. {
  108. return [self::TYPE_BLOCK, self::TYPE_REFERENCE_BLOCK];
  109. }
  110. /**
  111. * {@inheritdoc}
  112. *
  113. * @param Context $readerContext
  114. * @param Element $currentElement
  115. * @param Element $parentElement
  116. * @return $this
  117. */
  118. public function interpret(Context $readerContext, Element $currentElement)
  119. {
  120. $scheduledStructure = $readerContext->getScheduledStructure();
  121. switch ($currentElement->getName()) {
  122. case self::TYPE_BLOCK:
  123. $this->scheduleBlock($scheduledStructure, $currentElement);
  124. break;
  125. case self::TYPE_REFERENCE_BLOCK:
  126. $this->scheduleReference($scheduledStructure, $currentElement);
  127. break;
  128. default:
  129. break;
  130. }
  131. $this->readerPool->interpret($readerContext, $currentElement);
  132. return $this;
  133. }
  134. /**
  135. * Process block element their attributes and children
  136. *
  137. * @param ScheduledStructure $scheduledStructure
  138. * @param Element $currentElement
  139. * @return void
  140. */
  141. protected function scheduleBlock(
  142. ScheduledStructure $scheduledStructure,
  143. Element $currentElement
  144. ) {
  145. $elementName = $this->helper->scheduleStructure(
  146. $scheduledStructure,
  147. $currentElement,
  148. $currentElement->getParent()
  149. );
  150. $data = $scheduledStructure->getStructureElementData($elementName, []);
  151. $data['attributes'] = $this->mergeBlockAttributes($data, $currentElement);
  152. $this->updateScheduledData($currentElement, $data);
  153. $this->evaluateArguments($currentElement, $data);
  154. $data['attributes'] = array_merge(
  155. $data['attributes'],
  156. ['visibilityConditions' => $this->conditionReader->parseConditions($currentElement)]
  157. );
  158. $scheduledStructure->setStructureElementData($elementName, $data);
  159. }
  160. /**
  161. * Merge Block attributes
  162. *
  163. * @param array $elementData
  164. * @param Element $currentElement
  165. * @return array
  166. */
  167. protected function mergeBlockAttributes(array $elementData, Element $currentElement)
  168. {
  169. $currentElement = $this->replaceDeprecatedAclKey($currentElement);
  170. if (isset($elementData['attributes'])) {
  171. $elementData['attributes'] = $this->replaceDeprecatedAclKey($elementData['attributes']);
  172. $keys = array_keys($elementData['attributes']);
  173. foreach ($keys as $key) {
  174. if (isset($currentElement[$key])) {
  175. $elementData['attributes'][$key] = (string)$currentElement[$key];
  176. }
  177. }
  178. } else {
  179. $elementData['attributes'] = [
  180. self::ATTRIBUTE_CLASS => (string)$currentElement[self::ATTRIBUTE_CLASS],
  181. self::ATTRIBUTE_GROUP => (string)$currentElement[self::ATTRIBUTE_GROUP],
  182. self::ATTRIBUTE_TEMPLATE => (string)$currentElement[self::ATTRIBUTE_TEMPLATE],
  183. self::ATTRIBUTE_TTL => (string)$currentElement[self::ATTRIBUTE_TTL],
  184. self::ATTRIBUTE_DISPLAY => (string)$currentElement[self::ATTRIBUTE_DISPLAY],
  185. self::ATTRIBUTE_ACL => (string)$currentElement[self::ATTRIBUTE_ACL],
  186. ];
  187. }
  188. return $elementData['attributes'];
  189. }
  190. /**
  191. * Replaces old ACL attribute key to new.
  192. *
  193. * @param array|Element $data
  194. *
  195. * @return array|Element
  196. */
  197. private function replaceDeprecatedAclKey($data)
  198. {
  199. if (isset($data[$this->deprecatedAttributeAcl])) {
  200. $data[self::ATTRIBUTE_ACL] = (string)$data[$this->deprecatedAttributeAcl];
  201. }
  202. return $data;
  203. }
  204. /**
  205. * Schedule reference data
  206. *
  207. * @param ScheduledStructure $scheduledStructure
  208. * @param Element $currentElement
  209. * @return void
  210. */
  211. protected function scheduleReference(
  212. ScheduledStructure $scheduledStructure,
  213. Element $currentElement
  214. ) {
  215. $elementName = $currentElement->getAttribute('name');
  216. $elementRemove = filter_var($currentElement->getAttribute('remove'), FILTER_VALIDATE_BOOLEAN);
  217. if ($elementRemove) {
  218. $scheduledStructure->setElementToRemoveList($elementName);
  219. return;
  220. } elseif ($currentElement->getAttribute('remove')) {
  221. $scheduledStructure->unsetElementFromListToRemove($elementName);
  222. }
  223. $data = $scheduledStructure->getStructureElementData($elementName, []);
  224. $data['attributes'] = $this->mergeBlockAttributes($data, $currentElement);
  225. $this->updateScheduledData($currentElement, $data);
  226. $this->evaluateArguments($currentElement, $data);
  227. $scheduledStructure->setStructureElementData($elementName, $data);
  228. }
  229. /**
  230. * Update data for scheduled element
  231. *
  232. * @param Element $currentElement
  233. * @param array &$data
  234. * @return array
  235. */
  236. protected function updateScheduledData($currentElement, array &$data)
  237. {
  238. $actions = $this->getActions($currentElement);
  239. $arguments = $this->getArguments($currentElement);
  240. $data['actions'] = isset($data['actions'])
  241. ? array_merge($data['actions'], $actions)
  242. : $actions;
  243. $data['arguments'] = isset($data['arguments'])
  244. ? array_replace_recursive($data['arguments'], $arguments)
  245. : $arguments;
  246. return $data;
  247. }
  248. /**
  249. * Get block attributes
  250. *
  251. * @param Element $blockElement
  252. * @return array
  253. */
  254. protected function getAttributes(Element $blockElement)
  255. {
  256. $attributes = [];
  257. foreach ($this->attributes as $attributeName) {
  258. $attributes[$attributeName] = (string)$blockElement->getAttribute($attributeName);
  259. }
  260. return $attributes;
  261. }
  262. /**
  263. * Get actions for block element
  264. *
  265. * @param Element $blockElement
  266. * @return array[]
  267. */
  268. protected function getActions(Element $blockElement)
  269. {
  270. $actions = [];
  271. /** @var $actionElement Element */
  272. foreach ($this->getElementsByType($blockElement, self::TYPE_ACTION) as $actionElement) {
  273. $configPath = $actionElement->getAttribute('ifconfig');
  274. $methodName = $actionElement->getAttribute('method');
  275. $actionArguments = $this->parseArguments($actionElement);
  276. $actions[] = [$methodName, $actionArguments, $configPath, $this->scopeType];
  277. }
  278. return $actions;
  279. }
  280. /**
  281. * Get block arguments
  282. *
  283. * @param Element $blockElement
  284. * @return array
  285. */
  286. protected function getArguments(Element $blockElement)
  287. {
  288. $arguments = $this->getElementsByType($blockElement, self::TYPE_ARGUMENTS);
  289. // We have only one declaration of <arguments> node in block or its reference
  290. $argumentElement = reset($arguments);
  291. return $argumentElement ? $this->parseArguments($argumentElement) : [];
  292. }
  293. /**
  294. * Get elements by type
  295. *
  296. * @param Element $element
  297. * @param string $type
  298. * @return array
  299. */
  300. protected function getElementsByType(Element $element, $type)
  301. {
  302. $elements = [];
  303. /** @var $childElement Element */
  304. foreach ($element as $childElement) {
  305. if ($childElement->getName() === $type) {
  306. $elements[] = $childElement;
  307. }
  308. }
  309. return $elements;
  310. }
  311. /**
  312. * Parse argument nodes and return their array representation
  313. *
  314. * @param Element $node
  315. * @return array
  316. */
  317. protected function parseArguments(Element $node)
  318. {
  319. $nodeDom = dom_import_simplexml($node);
  320. $result = [];
  321. foreach ($nodeDom->childNodes as $argumentNode) {
  322. if ($argumentNode instanceof \DOMElement && $argumentNode->nodeName == 'argument') {
  323. $argumentName = $argumentNode->getAttribute('name');
  324. $result[$argumentName] = $this->argumentParser->parse($argumentNode);
  325. }
  326. }
  327. return $result;
  328. }
  329. /**
  330. * Compute argument values
  331. *
  332. * @param Element $blockElement
  333. * @param array $data
  334. * @return void
  335. */
  336. protected function evaluateArguments(Element $blockElement, array &$data)
  337. {
  338. $arguments = $this->getArguments($blockElement);
  339. foreach ($arguments as $argumentName => $argumentData) {
  340. if (isset($argumentData['updater'])) {
  341. continue;
  342. }
  343. $result = $this->argumentInterpreter->evaluate($argumentData);
  344. if (is_array($result)) {
  345. $data['arguments'][$argumentName] = isset($data['arguments'][$argumentName])
  346. ? array_replace_recursive($data['arguments'][$argumentName], $result)
  347. : $result;
  348. } else {
  349. $data['arguments'][$argumentName] = $result;
  350. }
  351. }
  352. }
  353. }