diff --git a/Magento2/Sniffs/Legacy/ClassReferencesInConfigurationFilesSniff.php b/Magento2/Sniffs/Legacy/ClassReferencesInConfigurationFilesSniff.php new file mode 100644 index 00000000..f2b5b400 --- /dev/null +++ b/Magento2/Sniffs/Legacy/ClassReferencesInConfigurationFilesSniff.php @@ -0,0 +1,221 @@ + 0) { + return; + } + + // We need to format the incoming XML to avoid tags split into several lines. In that case, PHP's DOMElement + // returns the position of the closing /> as the position of the tag, and we need the position of < + // instead, as it is the one we compare with $stackPtr later on. + $xml = simplexml_load_string($this->getFormattedXML($phpcsFile)); + if ($xml === false) { + $phpcsFile->addError( + sprintf( + "Couldn't parse contents of '%s', check that they are in valid XML format", + $phpcsFile->getFilename(), + ), + $stackPtr, + self::ERROR_CODE_CONFIG + ); + } + + $classes = $this->collectClassesInConfig($xml); + $this->assertNonFactoryName($phpcsFile, $classes); + + $modules = $this->getValuesFromXmlTagAttribute($xml, '//@module', 'module'); + $this->assertNonFactoryNameModule($phpcsFile, $modules); + } + + /** + * Check whether specified class names are right according PSR-1 Standard. + * + * @param File $phpcsFile + * @param array $elements + */ + private function assertNonFactoryName(File $phpcsFile, array $elements) + { + foreach ($elements as $element) { + if (stripos($element['value'], 'Magento') === false) { + continue; + } + if (preg_match('/^([A-Z][a-z\d\\\\]+)+$/', $element['value']) !== 1) { + $phpcsFile->addError( + self::ERROR_MESSAGE_CONFIG, + $element['lineNumber'], + self::ERROR_CODE_CONFIG, + ); + } + } + } + + /** + * Check whether specified class names in modules are right according PSR-1 Standard. + * + * @param File $phpcsFile + * @param array $classes + */ + private function assertNonFactoryNameModule(File $phpcsFile, array $classes) + { + foreach ($classes as $element) { + if (preg_match('/^([A-Z][A-Za-z\d_]+)+$/', $element['value']) !== 1) { + $phpcsFile->addError( + self::ERROR_MESSAGE_MODULE, + $element['lineNumber'], + self::ERROR_CODE_MODULE, + ); + } + } + } + + /** + * Format the incoming XML to avoid tags split into several lines. + * + * @param File $phpcsFile + * @return false|string + */ + private function getFormattedXML(File $phpcsFile) + { + $doc = new DomDocument('1.0'); + $doc->formatOutput = true; + $doc->loadXML($phpcsFile->getTokensAsString(0, count($phpcsFile->getTokens()))); + return $doc->saveXML(); + } + + /** + * Parse an XML for references to PHP class names in selected tags or attributes + * + * @param SimpleXMLElement $xml + * @return array + */ + private function collectClassesInConfig(SimpleXMLElement $xml): array + { + $classes = $this->getValuesFromXmlTagContent( + $xml, + ' + /config//resource_adapter | /config/*[not(name()="sections")]//class[not(ancestor::observers)] + | //model[not(parent::connection)] | //backend_model | //source_model | //price_model + | //model_token | //writer_model | //clone_model | //frontend_model | //working_model + | //admin_renderer | //renderer', + ); + + $classes = array_merge( + $classes, + $this->getValuesFromXmlTagAttribute( + $xml, + '//@backend_model', + 'backend_model' + ), + $this->getValuesFromXmlTagAttribute( + $xml, + '/config//preference', + 'type' + ), + $this->getValuesFromXmlTagName( + $xml, + '/logging/*/expected_models/* | /logging/*/actions/*/expected_models/*', + ) + ); + + $classes = array_map( + function (array $extendedNode) { + $extendedNode['value'] = explode('::', trim($extendedNode['value']))[0]; + return $extendedNode; + }, + $classes + ); + + return $classes; + } + + /** + * Extract value from tag contents which exist in the XML path + * + * @param SimpleXMLElement $xml + * @param string $xPath + * @return array + */ + private function getValuesFromXmlTagContent(SimpleXMLElement $xml, string $xPath): array + { + $nodes = $xml->xpath($xPath) ?: []; + return array_map(function ($item) { + return [ + 'value' => (string)$item, + 'lineNumber' => dom_import_simplexml($item)->getLineNo()-1, + ]; + }, $nodes); + } + + /** + * Extract value from tag names which exist in the XML path + * + * @param SimpleXMLElement $xml + * @param string $xPath + * @return array + */ + private function getValuesFromXmlTagName(SimpleXMLElement $xml, string $xPath): array + { + $nodes = $xml->xpath($xPath) ?: []; + return array_map(function ($item) { + return [ + 'value' => $item->getName(), + 'lineNumber' => dom_import_simplexml($item)->getLineNo()-1, + ]; + }, $nodes); + } + + /** + * Extract value from tag attributes which exist in the XML path + * + * @param SimpleXMLElement $xml + * @param string $xPath + * @param string $attr + * @return array + */ + private function getValuesFromXmlTagAttribute(SimpleXMLElement $xml, string $xPath, string $attr): array + { + $nodes = $xml->xpath($xPath) ?: []; + return array_map(function ($item) use ($attr) { + $nodeArray = (array)$item; + if (isset($nodeArray['@attributes'][$attr])) { + return [ + 'value' => $nodeArray['@attributes'][$attr], + 'lineNumber' => dom_import_simplexml($item)->getLineNo()-1, + ]; + } + }, $nodes); + } +} diff --git a/Magento2/Tests/Legacy/ClassReferencesInConfigurationFilesUnitTest.1.xml b/Magento2/Tests/Legacy/ClassReferencesInConfigurationFilesUnitTest.1.xml new file mode 100644 index 00000000..864a154e --- /dev/null +++ b/Magento2/Tests/Legacy/ClassReferencesInConfigurationFilesUnitTest.1.xml @@ -0,0 +1,45 @@ + + + + +
+ + + + + Magento\Config\Model\Config\Source\Yesno + + + + + + + Magento\CONFIG\Model\Config\Source\Yesno + + + + + + + Config\Model\Config\Source\Yesno + + + + + + + Sales\MODEL\Config\Source\Order\Status\NewStatus + + + + MAGENTO\Payment\Model\Config\Source\Allspecificcountries + + +
+
+
diff --git a/Magento2/Tests/Legacy/ClassReferencesInConfigurationFilesUnitTest.2.xml b/Magento2/Tests/Legacy/ClassReferencesInConfigurationFilesUnitTest.2.xml new file mode 100644 index 00000000..2753291f --- /dev/null +++ b/Magento2/Tests/Legacy/ClassReferencesInConfigurationFilesUnitTest.2.xml @@ -0,0 +1,52 @@ + + + + +
+ + + + + Magento\Config\Model\Config\Source\Yesno + + + + + + + + Magento\CONFIG\Model\Config\Source\Yesno + + + + + + + + Config\Model\Config\Source\Yesno + + + + + + + Sales\MODEL\Config\Source\Order\Status\NewStatus + + + + + + + MAGENTO\Payment\Model\Config\Source\Allspecificcountries + + + + +
+
+
diff --git a/Magento2/Tests/Legacy/ClassReferencesInConfigurationFilesUnitTest.3.xml b/Magento2/Tests/Legacy/ClassReferencesInConfigurationFilesUnitTest.3.xml new file mode 100644 index 00000000..4884a838 --- /dev/null +++ b/Magento2/Tests/Legacy/ClassReferencesInConfigurationFilesUnitTest.3.xml @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/Magento2/Tests/Legacy/ClassReferencesInConfigurationFilesUnitTest.4.xml b/Magento2/Tests/Legacy/ClassReferencesInConfigurationFilesUnitTest.4.xml new file mode 100644 index 00000000..a99f0589 --- /dev/null +++ b/Magento2/Tests/Legacy/ClassReferencesInConfigurationFilesUnitTest.4.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + system/currency/installed + + + + + Magento\Framework\Communication\Config\Reader\XmlReader\Converter + Magento\Framework\Communication\Config\Reader\XmlReader\SchemaLocator + communication.xml + + name + name + + + + + + Magento + + + diff --git a/Magento2/Tests/Legacy/ClassReferencesInConfigurationFilesUnitTest.php b/Magento2/Tests/Legacy/ClassReferencesInConfigurationFilesUnitTest.php new file mode 100644 index 00000000..83bb6f1d --- /dev/null +++ b/Magento2/Tests/Legacy/ClassReferencesInConfigurationFilesUnitTest.php @@ -0,0 +1,49 @@ + 1, + 40 => 1, + ]; + } + if ($testFile === 'ClassReferencesInConfigurationFilesUnitTest.2.xml') { + return [ + 22 => 1, + 42 => 1, + ]; + } + if ($testFile === 'ClassReferencesInConfigurationFilesUnitTest.3.xml') { + return [ + 10 => 1, + ]; + } + if ($testFile === 'ClassReferencesInConfigurationFilesUnitTest.4.xml') { + return [ + 10 => 1, + ]; + } + return []; + } + + /** + * @inheritdoc + */ + public function getWarningList($testFile = '') + { + return []; + } +} diff --git a/Magento2/ruleset.xml b/Magento2/ruleset.xml index 1b1c7f1b..751f2cda 100644 --- a/Magento2/ruleset.xml +++ b/Magento2/ruleset.xml @@ -148,6 +148,14 @@ *\.xml$ *\.js$ + + *\/etc/*.xml$ + *\/etc/wsdl.xml$ + *\/etc/wsdl2.xml$ + *\/etc/wsi.xml$ + 10 + error + 9