271 lines
8.2 KiB
PHP
Executable File
271 lines
8.2 KiB
PHP
Executable File
<?php
|
|
/**
|
|
* Copyright © Magento, Inc. All rights reserved.
|
|
* See COPYING.txt for license details.
|
|
*/
|
|
namespace Magento\Ui\Component;
|
|
|
|
use Magento\Framework\App\Filesystem\DirectoryList;
|
|
use Magento\Framework\Component\ComponentFile;
|
|
use Magento\Framework\Component\ComponentRegistrar;
|
|
use Magento\Framework\Component\DirSearch;
|
|
use Magento\Framework\Exception\FileSystemException;
|
|
use Magento\Framework\Exception\ValidatorException;
|
|
use Magento\Framework\Filesystem;
|
|
use Magento\Framework\Filesystem\Directory\ReadInterface;
|
|
use Magento\TestFramework\Helper\Bootstrap;
|
|
use Magento\Ui\Config\Reader\DefinitionMap;
|
|
|
|
/**
|
|
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
|
|
*/
|
|
class ConfigurationTest extends \PHPUnit\Framework\TestCase
|
|
{
|
|
/**
|
|
* @var DirSearch
|
|
*/
|
|
private $dirSearch;
|
|
|
|
/**
|
|
* @var ReadInterface
|
|
*/
|
|
private $appDir;
|
|
|
|
/**
|
|
* @var ReadInterface
|
|
*/
|
|
private $rootDir;
|
|
|
|
/**
|
|
* @var \DOMDocument
|
|
*/
|
|
private $dom;
|
|
|
|
/**
|
|
* @var array
|
|
*/
|
|
private $map;
|
|
|
|
/**
|
|
* @var string
|
|
*/
|
|
private $currentComponent;
|
|
|
|
/**
|
|
* @var ComponentFile
|
|
*/
|
|
private $currentFile;
|
|
|
|
/**
|
|
* @var array
|
|
*/
|
|
private $whiteList = [
|
|
'argument[@name="data"]/item[@name="config"]/item[@name="multiple"]' => [
|
|
'//*[@formElement="select"]',
|
|
'//*[substring(@component, string-length(@component) - string-length("ui-group") +1) = "ui-group"]'
|
|
]
|
|
];
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$objectManager = Bootstrap::getObjectManager();
|
|
$mapReader = $objectManager->create(DefinitionMap::class);
|
|
$this->map = $mapReader->read();
|
|
|
|
$this->dirSearch = $objectManager->create(DirSearch::class);
|
|
|
|
/** @var Filesystem $filesystem */
|
|
$filesystem = $objectManager->create(Filesystem::class);
|
|
$this->appDir = $filesystem->getDirectoryRead(DirectoryList::APP);
|
|
$this->rootDir = $filesystem->getDirectoryRead(DirectoryList::ROOT);
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
*/
|
|
public function testConfiguration()
|
|
{
|
|
$uiConfigurationFiles = $this->dirSearch->collectFilesWithContext(
|
|
ComponentRegistrar::MODULE,
|
|
'view/*/ui_component/*.xml'
|
|
);
|
|
$this->generateXpaths();
|
|
|
|
$result = [];
|
|
/** @var ComponentFile $file */
|
|
foreach ($uiConfigurationFiles as $file) {
|
|
$this->currentFile = $file;
|
|
$fullPath = $file->getFullPath();
|
|
// by default search files in `app` directory but Magento can be installed via composer
|
|
// or some modules can be in `vendor` directory (like bundled extensions)
|
|
try {
|
|
$content = $this->appDir->readFile($this->appDir->getRelativePath($fullPath));
|
|
} catch (ValidatorException $e) {
|
|
$content = $this->rootDir->readFile($this->rootDir->getRelativePath($fullPath));
|
|
}
|
|
$this->assertConfigurationSemantic($this->getDom($content), $result);
|
|
}
|
|
if (!empty($result)) {
|
|
$this->fail(implode("\n\n", $result));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
*/
|
|
private function generateXpaths()
|
|
{
|
|
foreach ($this->map as $name => &$map) {
|
|
$this->currentComponent = $name;
|
|
$xpaths = [];
|
|
$counter = 0;
|
|
while (!empty($map)) {
|
|
$this->hasXpaths($map, $xpaths, $counter);
|
|
$counter++;
|
|
}
|
|
$this->map[$name]['xpaths'] = $xpaths;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param \DOMNode $node
|
|
* @param array $result
|
|
* @return void
|
|
*/
|
|
private function assertConfigurationSemantic(\DOMNode $node, &$result = [])
|
|
{
|
|
foreach ($node->childNodes as $child) {
|
|
if ($child->nodeType === XML_ELEMENT_NODE) {
|
|
if (isset($this->map[$child->localName])) {
|
|
$xpaths = [];
|
|
$this->currentComponent = $child->localName;
|
|
if (isset($this->map[$this->currentComponent]['xpaths'])) {
|
|
$xpaths = $this->map[$this->currentComponent]['xpaths'];
|
|
}
|
|
|
|
$domXpath = new \DOMXPath($this->getDom());
|
|
foreach ($xpaths as $xpathData) {
|
|
if ($domXpath->query($xpathData['xpath'], $child)->length !== 0
|
|
&& !$this->isAvailable($xpathData['xpath'], $child)
|
|
) {
|
|
$result[] = 'Xpath: "' . $xpathData['xpath'] . '" is a forbidden.' . "\n" .
|
|
'This node should migrate to "' . trim($xpathData['target']) . "\"\n" .
|
|
'File: ' . $this->currentFile->getFullPath() . "\n";
|
|
}
|
|
}
|
|
}
|
|
$this->assertConfigurationSemantic($child, $result);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param string $targetXpath
|
|
* @param \DOMElement $node
|
|
* @return bool
|
|
*/
|
|
private function isAvailable($targetXpath, \DOMElement $node)
|
|
{
|
|
$domXpath = new \DOMXPath($this->getDom());
|
|
if (isset($this->whiteList[$targetXpath])) {
|
|
$availableForXpath = $this->whiteList[$targetXpath];
|
|
foreach ($availableForXpath as $xpath) {
|
|
$result = $domXpath->query($xpath, $node);
|
|
if ($result->length != 0) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @param array $data
|
|
* @param array $result
|
|
* @param int $counter
|
|
* @return bool
|
|
*
|
|
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
|
|
*/
|
|
private function hasXpaths(array &$data, array &$result, $counter)
|
|
{
|
|
|
|
foreach ($data as $name => &$child) {
|
|
if (!is_array($child)) {
|
|
continue;
|
|
}
|
|
if (isset($child['value'])) {
|
|
$result[$counter]['xpath'] .= '[@name="' . $child['name'] . '"]';
|
|
$result[$counter]['target'] = $child['value'];
|
|
unset($data[$name]);
|
|
$this->deleteEmptyNodes($this->map[$this->currentComponent]);
|
|
return true;
|
|
}
|
|
|
|
if (isset($child['name']) && is_string($child['name'])) {
|
|
$result[$counter]['xpath'] .= '[@name="' . $child['name'] . '"]';
|
|
$break = false;
|
|
if (isset($child['item'])) {
|
|
$result[$counter]['xpath'] .= '/item';
|
|
$break = $this->hasXpaths($child['item'], $result, $counter);
|
|
} elseif (isset($child['argument'])) {
|
|
$result[$counter]['xpath'] .= '/argument';
|
|
$break = $this->hasXpaths($child['argument'], $result, $counter);
|
|
}
|
|
if ($break) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (!isset($result[$counter]['xpath'])) {
|
|
$result[$counter]['xpath'] = '';
|
|
}
|
|
$result[$counter]['xpath'] .= $name;
|
|
$this->hasXpaths($child, $result, $counter);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @param array $map
|
|
* @param bool $isRemoveParentNode
|
|
* @return bool
|
|
*
|
|
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
|
|
*/
|
|
private function deleteEmptyNodes(array &$map, $isRemoveParentNode = false)
|
|
{
|
|
if (empty($map)) {
|
|
return true;
|
|
}
|
|
foreach ($map as $name => &$child) {
|
|
if (is_array($child)) {
|
|
$isRemoveParentNode = $this->deleteEmptyNodes($map[$name]);
|
|
}
|
|
if (empty($map[$name]) || $isRemoveParentNode) {
|
|
if ((isset($map['item']) && empty($map['item']))
|
|
|| (isset($map['argument']) && empty($map['argument']))
|
|
) {
|
|
unset($map[$name]);
|
|
return true;
|
|
}
|
|
unset($map[$name]);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @param string|null $content
|
|
* @return \DOMDocument
|
|
*/
|
|
private function getDom($content = null)
|
|
{
|
|
if ($content) {
|
|
$this->dom = new \DOMDocument();
|
|
$this->dom->loadXML($content);
|
|
}
|
|
return $this->dom;
|
|
}
|
|
}
|