<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\Framework\View\Layout\Reader;
use Magento\Framework\Data\Argument\InterpreterInterface;
use Magento\Framework\View\Layout;
use Magento\Framework\View\Layout\Element;
use Magento\Framework\View\Layout\ScheduledStructure;
use Magento\Framework\View\Layout\Reader\Visibility\Condition;
/**
* Block structure reader
*/
class Block implements Layout\ReaderInterface
{
/**#@+
* Supported types
*/
const TYPE_BLOCK = 'block';
const TYPE_REFERENCE_BLOCK = 'referenceBlock';
/**#@-*/
/**#@+
* Supported subtypes for blocks
*/
const TYPE_ARGUMENTS = 'arguments';
const TYPE_ACTION = 'action';
/**#@-*/
/**#@+
* Names of block attributes in layout
*/
const ATTRIBUTE_GROUP = 'group';
const ATTRIBUTE_CLASS = 'class';
const ATTRIBUTE_TEMPLATE = 'template';
const ATTRIBUTE_TTL = 'ttl';
const ATTRIBUTE_DISPLAY = 'display';
const ATTRIBUTE_ACL = 'aclResource';
/**#@-*/
/**#@-*/
protected $attributes = [
self::ATTRIBUTE_GROUP,
self::ATTRIBUTE_CLASS,
self::ATTRIBUTE_TEMPLATE,
self::ATTRIBUTE_TTL,
self::ATTRIBUTE_DISPLAY
];
/**
* @var \Magento\Framework\View\Layout\ScheduledStructure\Helper
*/
protected $helper;
/**
* @var \Magento\Framework\View\Layout\Argument\Parser
*/
protected $argumentParser;
/**
* @var \Magento\Framework\View\Layout\ReaderPool
*/
protected $readerPool;
/**
* @var string
*/
protected $scopeType;
/**
* @var InterpreterInterface
*/
protected $argumentInterpreter;
/**
* @var Condition
*/
private $conditionReader;
/**
* @deprecated 101.0.0
* @var string
*/
private $deprecatedAttributeAcl = 'acl';
/**
* Constructor
*
* @param Layout\ScheduledStructure\Helper $helper
* @param Layout\Argument\Parser $argumentParser
* @param Layout\ReaderPool $readerPool
* @param InterpreterInterface $argumentInterpreter
* @param Condition $conditionReader
* @param string|null $scopeType
*/
public function __construct(
Layout\ScheduledStructure\Helper $helper,
Layout\Argument\Parser $argumentParser,
Layout\ReaderPool $readerPool,
InterpreterInterface $argumentInterpreter,
Condition $conditionReader,
$scopeType = null
) {
$this->helper = $helper;
$this->argumentParser = $argumentParser;
$this->readerPool = $readerPool;
$this->scopeType = $scopeType;
$this->argumentInterpreter = $argumentInterpreter;
$this->conditionReader = $conditionReader;
}
/**
* @inheritdoc
*/
public function getSupportedNodes()
{
return [self::TYPE_BLOCK, self::TYPE_REFERENCE_BLOCK];
}
/**
* @inheritdoc
*/
public function interpret(Context $readerContext, Element $currentElement)
{
$scheduledStructure = $readerContext->getScheduledStructure();
switch ($currentElement->getName()) {
case self::TYPE_BLOCK:
$this->scheduleBlock($scheduledStructure, $currentElement);
break;
case self::TYPE_REFERENCE_BLOCK:
$this->scheduleReference($scheduledStructure, $currentElement);
break;
default:
break;
}
$this->readerPool->interpret($readerContext, $currentElement);
return $this;
}
/**
* Process block element their attributes and children
*
* @param ScheduledStructure $scheduledStructure
* @param Element $currentElement
* @return void
*/
protected function scheduleBlock(
ScheduledStructure $scheduledStructure,
Element $currentElement
) {
$elementName = $this->helper->scheduleStructure(
$scheduledStructure,
$currentElement,
$currentElement->getParent()
);
$data = $scheduledStructure->getStructureElementData($elementName, []);
$data['attributes'] = $this->mergeBlockAttributes($data, $currentElement);
$this->updateScheduledData($currentElement, $data);
$this->evaluateArguments($currentElement, $data);
$data['attributes'] = array_merge(
$data['attributes'],
['visibilityConditions' => $this->conditionReader->parseConditions($currentElement)]
);
$scheduledStructure->setStructureElementData($elementName, $data);
}
/**
* Merge Block attributes
*
* @param array $elementData
* @param Element $currentElement
* @return array
*/
protected function mergeBlockAttributes(array $elementData, Element $currentElement)
{
$currentElement = $this->replaceDeprecatedAclKey($currentElement);
if (isset($elementData['attributes'])) {
$elementData['attributes'] = $this->replaceDeprecatedAclKey($elementData['attributes']);
$keys = array_keys($elementData['attributes']);
foreach ($keys as $key) {
if (isset($currentElement[$key])) {
$elementData['attributes'][$key] = (string)$currentElement[$key];
}
}
} else {
$elementData['attributes'] = [
self::ATTRIBUTE_CLASS => (string)$currentElement[self::ATTRIBUTE_CLASS],
self::ATTRIBUTE_GROUP => (string)$currentElement[self::ATTRIBUTE_GROUP],
self::ATTRIBUTE_TEMPLATE => (string)$currentElement[self::ATTRIBUTE_TEMPLATE],
self::ATTRIBUTE_TTL => (string)$currentElement[self::ATTRIBUTE_TTL],
self::ATTRIBUTE_DISPLAY => (string)$currentElement[self::ATTRIBUTE_DISPLAY],
self::ATTRIBUTE_ACL => (string)$currentElement[self::ATTRIBUTE_ACL],
];
}
return $elementData['attributes'];
}
/**
* Replaces old ACL attribute key to new.
*
* @param array|Element $data
*
* @return array|Element
*/
private function replaceDeprecatedAclKey($data)
{
if (isset($data[$this->deprecatedAttributeAcl])) {
$data[self::ATTRIBUTE_ACL] = (string)$data[$this->deprecatedAttributeAcl];
}
return $data;
}
/**
* Schedule reference data
*
* @param ScheduledStructure $scheduledStructure
* @param Element $currentElement
* @return void
*/
protected function scheduleReference(
ScheduledStructure $scheduledStructure,
Element $currentElement
) {
$elementName = $currentElement->getAttribute('name');
$elementRemove = filter_var($currentElement->getAttribute('remove'), FILTER_VALIDATE_BOOLEAN);
if ($elementRemove) {
$scheduledStructure->setElementToRemoveList($elementName);
return;
} elseif ($currentElement->getAttribute('remove')) {
$scheduledStructure->unsetElementFromListToRemove($elementName);
}
$data = $scheduledStructure->getStructureElementData($elementName, []);
$data['attributes'] = $this->mergeBlockAttributes($data, $currentElement);
$this->updateScheduledData($currentElement, $data);
$this->evaluateArguments($currentElement, $data);
$scheduledStructure->setStructureElementData($elementName, $data);
}
/**
* Update data for scheduled element
*
* @param Element $currentElement
* @param array $data
* @return array
*/
protected function updateScheduledData($currentElement, array &$data)
{
$actions = $this->getActions($currentElement);
$arguments = $this->getArguments($currentElement);
$data['actions'] = isset($data['actions'])
? array_merge($data['actions'], $actions)
: $actions;
$data['arguments'] = isset($data['arguments'])
? array_replace_recursive($data['arguments'], $arguments)
: $arguments;
return $data;
}
/**
* Get block attributes
*
* @param Element $blockElement
* @return array
*/
protected function getAttributes(Element $blockElement)
{
$attributes = [];
foreach ($this->attributes as $attributeName) {
$attributes[$attributeName] = (string)$blockElement->getAttribute($attributeName);
}
return $attributes;
}
/**
* Get actions for block element
*
* @param Element $blockElement
* @return array[]
*/
protected function getActions(Element $blockElement)
{
$actions = [];
/** @var $actionElement Element */
foreach ($this->getElementsByType($blockElement, self::TYPE_ACTION) as $actionElement) {
$configPath = $actionElement->getAttribute('ifconfig');
$methodName = $actionElement->getAttribute('method');
$actionArguments = $this->parseArguments($actionElement);
$actions[] = [$methodName, $actionArguments, $configPath, $this->scopeType];
}
return $actions;
}
/**
* Get block arguments
*
* @param Element $blockElement
* @return array
*/
protected function getArguments(Element $blockElement)
{
$arguments = $this->getElementsByType($blockElement, self::TYPE_ARGUMENTS);
// We have only one declaration of <arguments> node in block or its reference
$argumentElement = reset($arguments);
return $argumentElement ? $this->parseArguments($argumentElement) : [];
}
/**
* Get elements by type
*
* @param Element $element
* @param string $type
* @return array
*/
protected function getElementsByType(Element $element, $type)
{
$elements = [];
/** @var $childElement Element */
foreach ($element as $childElement) {
if ($childElement->getName() === $type) {
$elements[] = $childElement;
}
}
return $elements;
}
/**
* Parse argument nodes and return their array representation
*
* @param Element $node
* @return array
*/
protected function parseArguments(Element $node)
{
$nodeDom = dom_import_simplexml($node);
$result = [];
foreach ($nodeDom->childNodes as $argumentNode) {
if ($argumentNode instanceof \DOMElement && $argumentNode->nodeName == 'argument') {
$argumentName = $argumentNode->getAttribute('name');
$result[$argumentName] = $this->argumentParser->parse($argumentNode);
}
}
return $result;
}
/**
* Compute argument values
*
* @param Element $blockElement
* @param array $data
* @return void
*/
protected function evaluateArguments(Element $blockElement, array &$data)
{
$arguments = $this->getArguments($blockElement);
foreach ($arguments as $argumentName => $argumentData) {
if (isset($argumentData['updater'])) {
continue;
}
$result = $this->argumentInterpreter->evaluate($argumentData);
if (is_array($result)) {
$data['arguments'][$argumentName] = isset($data['arguments'][$argumentName])
? array_replace_recursive($data['arguments'][$argumentName], $result)
: $result;
} else {
$data['arguments'][$argumentName] = $result;
}
}
}
}
|