diff --git a/administrator/components/com_jedchecker/language/en-GB/en-GB.com_jedchecker.ini b/administrator/components/com_jedchecker/language/en-GB/en-GB.com_jedchecker.ini index 3ca2359..e2b1028 100644 --- a/administrator/components/com_jedchecker/language/en-GB/en-GB.com_jedchecker.ini +++ b/administrator/components/com_jedchecker/language/en-GB/en-GB.com_jedchecker.ini @@ -45,12 +45,14 @@ COM_JEDCHECKER_INFO_XML_NAME_XML="The name tag in this file is: %s" COM_JEDCHECKER_INFO_XML_VERSION_XML="Version tag has the value: %s" COM_JEDCHECKER_INFO_XML_CREATIONDATE_XML="The creationDate tag has the value: %s" COM_JEDCHECKER_INFO_XML_NO_MANIFEST="No manifest file found" -COM_JEDCHECKER_INFO_XML_NAME_MODULE_PLUGIN="Listing name ('%s') contains 'module' or 'plugin'" -COM_JEDCHECKER_INFO_XML_NAME_RESERVED_KEYWORDS="Keywords such as module, plugin or template are considered reserved words and can't be used in the extension names ('%s')" +COM_JEDCHECKER_INFO_XML_NAME_RESERVED_KEYWORDS="Keywords such as module, plugin or template are considered reserved words ('%2$s') and can't be used in the extension names ('%1$s')" +COM_JEDCHECKER_INFO_XML_NAME_PREFIXED="Listing name ('%s') starts with extension type prefix" COM_JEDCHECKER_INFO_XML_NAME_VERSION="Version in name/title ('%s')" COM_JEDCHECKER_INFO_XML_NAME_JOOMLA="An extension name ('%s') can't start with the word 'Joomla'" -COM_JEDCHECKER_INFO_XML_NAME_JOOMLA_DERIVATIVE="Extensions that use 'Joomla' or a derivative of Joomla in the extension name ('%s') need to be licensed by OSM" -COM_JEDCHECKER_INFO_XML_URL_JOOMLA_DERIVATIVE="Domain names that use 'Joomla' or a derivative of Joomla ('%1$s') need to be licensed by OSM. Please, check your domain name is listed on the Joomla! Trademark Approval Registry page." +COM_JEDCHECKER_INFO_XML_NAME_JOOMLA_DERIVATIVE="Extensions that use 'Joomla' or a derivative of Joomla! in the extension name ('%s') need to be licensed by OSM" +COM_JEDCHECKER_INFO_XML_NAME_NON_ASCII="Listing name ('%s') contains non-ASCII characters" +COM_JEDCHECKER_INFO_XML_URL_JOOMLA_DERIVATIVE="Domain names that use 'Joomla' or a derivative of Joomla! ('%1$s') need to be licensed by OSM. Please, check your domain name is listed on the Joomla! Trademark Approval Registry page." +COM_JEDCHECKER_INFO_XML_NAME_TOO_LONG="Listing name ('%s') is too long, consider to shorten it" COM_JEDCHECKER_INFO_XML_NAME_ADMIN_MENU="The admin menu name '%1$s' isn't the same as the extension name '%2$s'" COM_JEDCHECKER_INFO_XML_NAME_PLUGIN_FORMAT="The name of the plugin ('%s') must comply with the JED naming conventions in the form '{Type} - {Extension Name}'" COM_JEDCHECKER_RULE_PH1="PHP Headers missing GPL License Notice" diff --git a/administrator/components/com_jedchecker/libraries/rules/xmlinfo.php b/administrator/components/com_jedchecker/libraries/rules/xmlinfo.php index b0355df..6e5ed6e 100644 --- a/administrator/components/com_jedchecker/libraries/rules/xmlinfo.php +++ b/administrator/components/com_jedchecker/libraries/rules/xmlinfo.php @@ -16,6 +16,9 @@ defined('_JEXEC') or die('Restricted access'); // Include the rule base class require_once JPATH_COMPONENT_ADMINISTRATOR . '/models/rule.php'; +// Include the helper class +require_once JPATH_COMPONENT_ADMINISTRATOR . '/libraries/helper.php'; + /** * class JedcheckerRulesXMLinfo @@ -47,6 +50,15 @@ class JedcheckerRulesXMLinfo extends JEDcheckerRule */ protected $description = 'COM_JEDCHECKER_INFO_XML_DESC'; + /** + * List of JED extension types + * + * @var string[] + */ + protected $jedTypes = array( + 'component', 'module', 'package', 'plugin' + ); + /** * Mapping of the plugin title prefix to the plugin group * @@ -67,17 +79,24 @@ class JedcheckerRulesXMLinfo extends JEDcheckerRule public function check() { // Find all XML files of the extension - $files = JFolder::files($this->basedir, '\.xml$', true, true); + $files = JEDCheckerHelper::findManifests($this->basedir); $manifestFound = false; - // Iterate through all the xml files - foreach ($files as $file) + if (count($files)) { - // Try to find the license - if ($this->find($file)) + $topLevelDepth = substr_count($files[0], '/'); + + // Iterate through all the xml files + foreach ($files as $file) { - $manifestFound = true; + $isTopLevel = substr_count($file, '/') === $topLevelDepth; + + // Try to find the license + if ($this->find($file, $isTopLevel)) + { + $manifestFound = true; + } } } @@ -90,11 +109,12 @@ class JedcheckerRulesXMLinfo extends JEDcheckerRule /** * Reads a file and searches for the license * - * @param string $file - The path to the file + * @param string $file - The path to the file + * @param bool $isTopLevel - Is the file located in the top-level manifests directory? * * @return boolean True if the manifest file was found, otherwise False. */ - protected function find($file) + protected function find($file, $isTopLevel) { $xml = JFactory::getXml($file); @@ -120,39 +140,137 @@ class JedcheckerRulesXMLinfo extends JEDcheckerRule // Get extension type $type = (string) $xml['type']; - // Get extension's element name (simulates work of Joomla's installer) + // Load the language of the extension (if any) + $this->loadExtensionLanguage($xml, dirname($file)); - // Firstly, check for node - if (isset($xml->element)) - { - $extension = (string) $xml->element; - } - else - { - // Otherwise, use node or plugin/module attribute in the section - $extension = (string) $xml->name; + // Get the real extension's name now that the language has been loaded + $lang = JFactory::getLanguage(); + $extensionName = $lang->_((string) $xml->name); - if (isset($xml->files)) + $info[] = JText::sprintf('COM_JEDCHECKER_INFO_XML_NAME_XML', $extensionName); + $info[] = JText::sprintf('COM_JEDCHECKER_INFO_XML_VERSION_XML', (string) $xml->version); + $info[] = JText::sprintf('COM_JEDCHECKER_INFO_XML_CREATIONDATE_XML', (string) $xml->creationDate); + + $this->report->addInfo($file, implode('
', $info)); + + if ($isTopLevel) + { + // JED allows components, modules, plugins, and packages (as a container) only + if (!in_array($type, $this->jedTypes, true)) { - foreach ($xml->files->children() as $child) + $this->report->addError($file, JText::sprintf('COM_JEDCHECKER_MANIFEST_TYPE_NOT_ACCEPTED', $type)); + } + + // NM3 - Listing name contains “module” or “plugin” + // (and other reserved words) + if (preg_match('/\b(?:module|plugin|component|template|extension|free)\b/i', $extensionName, $match)) + { + $this->report->addError($file, JText::sprintf('COM_JEDCHECKER_INFO_XML_NAME_RESERVED_KEYWORDS', $extensionName, strtolower($match[0]))); + } + + // Extension name shouldn't start with extension type prefix + if (preg_match('/^\s*(?:mod|com|plg|tpl|pkg)_/i', $extensionName)) + { + $this->report->addError($file, JText::sprintf('COM_JEDCHECKER_INFO_XML_NAME_PREFIXED', $extensionName)); + } + + // NM5 - Version in name/title + if (preg_match('/(?:\bversion\b|\d\.\d)/i', $extensionName)) + { + $this->report->addError($file, JText::sprintf('COM_JEDCHECKER_INFO_XML_NAME_VERSION', $extensionName)); + } + + // Check for "Joomla" in the name + if (stripos($extensionName, 'joomla') === 0) + { + // An extension name can't start with the word "Joomla" + $this->report->addError($file, JText::sprintf('COM_JEDCHECKER_INFO_XML_NAME_JOOMLA', $extensionName)); + } + else + { + $cleanName = preg_replace('/\s+for\s+Joomla!?$/', '', $extensionName); + + if (stripos($cleanName, 'joom') !== false) { - if (isset($child[$type])) - { - $extension = (string) $child[$type]; - } + // Extensions that use "Joomla" or a derivative of Joomla in the extension name need to be licensed by OSM + $this->report->addWarning($file, + JText::sprintf('COM_JEDCHECKER_INFO_XML_NAME_JOOMLA_DERIVATIVE', $extensionName, 'https://tm.joomla.org/approved-domains.html') + ); } } + + // Check extension name consists of ASCII characters only + if (preg_match('/[^\x20-\x7E]/', $extensionName)) + { + $this->report->addError($file, JText::sprintf('COM_JEDCHECKER_INFO_XML_NAME_NON_ASCII', $extensionName)); + } + + // Extension name shouldn't be too long + $nameLen = strlen($extensionName); + + if ($nameLen > 80) + { + $this->report->addError($file, JText::sprintf('COM_JEDCHECKER_INFO_XML_NAME_TOO_LONG', $extensionName)); + } + elseif ($nameLen > 40) + { + $this->report->addWarning($file, JText::sprintf('COM_JEDCHECKER_INFO_XML_NAME_TOO_LONG', $extensionName)); + } } - // Filter extension's element name - $extension = strtolower(JFilterInput::getInstance()->clean($extension, 'cmd')); + // Validate URLs + $this->validateDomain($file, (string) $xml->authorUrl); - // Component's element name starts with com_ - if ($type === 'component' && strpos($extension, 'com_') !== 0) + if ($type === 'package' && (string) $xml->packagerurl !== (string) $xml->authorUrl) { - $extension = 'com_' . $extension; + $this->validateDomain($file, (string) $xml->packagerurl); } + if ($type === 'component' && isset($xml->administration->menu)) + { + $menuName = $lang->_((string) $xml->administration->menu); + // Do name the Component's admin menu the same as the extension name + if ($extensionName !== $menuName) + { + $this->report->addWarning($file, JText::sprintf('COM_JEDCHECKER_INFO_XML_NAME_ADMIN_MENU', $menuName, $extensionName)); + } + } + + if ($isTopLevel && $type === 'plugin') + { + // The name of your plugin must comply with the JED naming conventions - plugins in the form “{Type} - {Extension Name}”. + $parts = explode(' - ', $extensionName, 2); + $extensionNameGroup = isset($parts[1]) ? strtolower(preg_replace('/\s/', '', $parts[0])) : false; + $group = (string) $xml['group']; + + if ($extensionNameGroup !== $group && $extensionNameGroup !== str_replace('-', '', $group) + && !(isset($this->pluginsGroupMap[$extensionNameGroup]) && $this->pluginsGroupMap[$extensionNameGroup] === $group) + ) + { + $this->report->addWarning($file, JText::sprintf('COM_JEDCHECKER_INFO_XML_NAME_PLUGIN_FORMAT', $extensionName)); + } + } + + // All checks passed. Return true + return true; + } + + /** + * Locate and load extension's .sys.ini translation file + * + * @param SimpleXMLElement $xml Extension's XML manifest + * @param string $langDir The basepath + * @param string $langTag The language to load + * + * @return void + */ + protected function loadExtensionLanguage($xml, $langDir, $langTag = 'en-GB') + { + // Get extension's element name (simulates work of Joomla's installer) + $extension = JEDCheckerHelper::getElementName($xml); + + $type = (string) $xml['type']; + // Plugin's element name starts with plg_ if ($type === 'plugin' && isset($xml['group']) && strpos($extension, 'plg_') !== 0) { @@ -162,10 +280,6 @@ class JedcheckerRulesXMLinfo extends JEDcheckerRule // Load the language of the extension (if any) $lang = JFactory::getLanguage(); - // Search for .sys.ini translation file - $langDir = dirname($file); - $langTag = 'en-GB'; // $lang->getDefault(); - // Populate list of directories to look for $lookupLangDirs = array(); @@ -226,79 +340,6 @@ class JedcheckerRulesXMLinfo extends JEDcheckerRule break; } } - - // Get the real extension's name now that the language has been loaded - $extensionName = $lang->_((string) $xml->name); - - $info[] = JText::sprintf('COM_JEDCHECKER_INFO_XML_NAME_XML', $extensionName); - $info[] = JText::sprintf('COM_JEDCHECKER_INFO_XML_VERSION_XML', (string) $xml->version); - $info[] = JText::sprintf('COM_JEDCHECKER_INFO_XML_CREATIONDATE_XML', (string) $xml->creationDate); - - $this->report->addInfo($file, implode('
', $info)); - - // NM3 - Listing name contains “module” or “plugin” - if (preg_match('/module|plugin/i', $extensionName)) - { - $this->report->addError($file, JText::sprintf('COM_JEDCHECKER_INFO_XML_NAME_MODULE_PLUGIN', $extensionName)); - } - - // The "template" is reserved keyword - if (stripos($extensionName, 'template') !== false) - { - $this->report->addWarning($file, JText::sprintf('COM_JEDCHECKER_INFO_XML_NAME_RESERVED_KEYWORDS', $extensionName)); - } - - // NM5 - Version in name/title - if (preg_match('/(?:\bversion\b|\d\.\d)/i', $extensionName)) - { - $this->report->addError($file, JText::sprintf('COM_JEDCHECKER_INFO_XML_NAME_VERSION', $extensionName)); - } - - if (stripos($extensionName, 'joomla') === 0) - { - // An extension name can't start with the word "Joomla" - $this->report->addError($file, JText::sprintf('COM_JEDCHECKER_INFO_XML_NAME_JOOMLA', $extensionName)); - } - elseif (stripos($extensionName, 'joom') !== false) - { - // Extensions that use "Joomla" or a derivative of Joomla in the extension name need to be licensed by OSM - $this->report->addWarning($file, JText::sprintf('COM_JEDCHECKER_INFO_XML_NAME_JOOMLA_DERIVATIVE', $extensionName)); - } - - $this->validateDomain($file, (string) $xml->authorUrl); - - if ($type === 'package' && (string) $xml->packagerurl !== (string) $xml->authorUrl) - { - $this->validateDomain($file, (string) $xml->packagerurl); - } - - if ($type === 'component' && isset($xml->administration->menu)) - { - $menuName = $lang->_((string) $xml->administration->menu); - // Do name the Component's admin menu the same as the extension name - if ($extensionName !== $menuName) - { - $this->report->addWarning($file, JText::sprintf('COM_JEDCHECKER_INFO_XML_NAME_ADMIN_MENU', $menuName, $extensionName)); - } - } - - if ($type === 'plugin') - { - // The name of your plugin must comply with the JED naming conventions - plugins in the form “{Type} - {Extension Name}”. - $parts = explode(' - ', $extensionName, 2); - $extensionNameGroup = isset($parts[1]) ? strtolower(preg_replace('/\s/', '', $parts[0])) : false; - $group = (string) $xml['group']; - - if ($extensionNameGroup !== $group && $extensionNameGroup !== str_replace('-', '', $group) - && !(isset($this->pluginsGroupMap[$extensionNameGroup]) && $this->pluginsGroupMap[$extensionNameGroup] === $group) - ) - { - $this->report->addWarning($file, JText::sprintf('COM_JEDCHECKER_INFO_XML_NAME_PLUGIN_FORMAT', $extensionName)); - } - } - - // All checks passed. Return true - return true; } /**