33
2
mirror of https://github.com/joomla-extensions/jedchecker.git synced 2024-11-16 18:15:18 +00:00
jedchecker/administrator/components/com_jedchecker/libraries/rules/xmlinfo.php

393 lines
11 KiB
PHP
Raw Permalink Normal View History

<?php
/**
2019-03-09 19:44:14 +00:00
* @package Joomla.JEDChecker
*
2019-03-10 16:09:42 +00:00
* @copyright Copyright (C) 2017 - 2019 Open Source Matters, Inc. All rights reserved.
* Copyright (C) 2008 - 2016 compojoom.com . All rights reserved.
2019-03-10 08:49:52 +00:00
* @author Daniel Dimitrov <daniel@compojoom.com>
* eaxs <support@projectfork.net>
*
2019-03-09 19:44:14 +00:00
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die('Restricted access');
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
// Include the rule base class
2013-11-05 20:17:39 +00:00
require_once JPATH_COMPONENT_ADMINISTRATOR . '/models/rule.php';
// Include the helper class
require_once JPATH_COMPONENT_ADMINISTRATOR . '/libraries/helper.php';
/**
2013-11-05 20:17:39 +00:00
* class JedcheckerRulesXMLinfo
*
2021-04-04 10:50:33 +00:00
* This class searches all xml manifests for specific tags
2013-11-05 20:17:39 +00:00
*
* @since 1.0
*/
2013-11-05 20:17:39 +00:00
class JedcheckerRulesXMLinfo extends JEDcheckerRule
{
2013-11-05 20:17:39 +00:00
/**
* The formal ID of this rule. For example: SE1.
*
* @var string
*/
protected $id = 'INFO_XML';
/**
* The title or caption of this rule.
*
* @var string
*/
protected $title = 'COM_JEDCHECKER_INFO_XML';
/**
* The description of this rule.
*
* @var string
*/
protected $description = 'COM_JEDCHECKER_INFO_XML_DESC';
/**
* The ordering value to sort rules in the menu.
*
* @var integer
*/
public static $ordering = 0;
2021-08-28 16:14:50 +00:00
/**
* List of JED extension types
*
* @var string[]
*/
protected $jedTypes = array(
'component', 'module', 'package', 'plugin', 'library'
);
/**
* Mapping of the plugin title prefix to the plugin group
*
* @var string[]
*/
protected $pluginsGroupMap = array(
'button' => 'editors-xtd',
'editor' => 'editors',
'smartsearch' => 'finder',
'twofactorauthentication' => 'twofactorauth'
);
2013-11-05 20:17:39 +00:00
/**
* Initiates the search and check
*
* @return void
*/
public function check()
{
// Find all XML files of the extension
$files = JEDCheckerHelper::findManifests($this->basedir);
2013-11-05 20:17:39 +00:00
$manifestFound = false;
if (count($files))
2013-11-05 20:17:39 +00:00
{
$topLevelDepth = substr_count($files[0], '/');
// Iterate through all the xml files
foreach ($files as $file)
{
$isTopLevel = substr_count($file, '/') === $topLevelDepth;
2013-11-05 20:17:39 +00:00
// Try to find the license
if ($this->find($file, $isTopLevel))
{
$manifestFound = true;
}
}
}
if (!$manifestFound)
2013-11-05 20:17:39 +00:00
{
$this->report->addError('', Text::_('COM_JEDCHECKER_INFO_XML_NO_MANIFEST'));
2013-11-05 20:17:39 +00:00
}
}
/**
* Reads a file and searches for the license
*
* @param string $file - The path to the file
* @param bool $isTopLevel - Is the file located in the top-level manifests directory?
2013-11-05 20:17:39 +00:00
*
* @return boolean True if the manifest file was found, otherwise False.
2013-11-05 20:17:39 +00:00
*/
protected function find($file, $isTopLevel)
2013-11-05 20:17:39 +00:00
{
2021-02-13 20:01:07 +00:00
$xml = simplexml_load_file($file);
2019-03-09 19:44:14 +00:00
2013-11-05 20:17:39 +00:00
// Failed to parse the xml file.
// Assume that this is not a extension manifest
if (!$xml)
{
return false;
2013-11-05 20:17:39 +00:00
}
2013-11-05 20:17:39 +00:00
// Check if this is an extension manifest
// 1.5 uses 'install', 1.6+ uses 'extension'
2021-04-04 13:06:01 +00:00
if ($xml->getName() === 'install')
{
$this->report->addWarning($file, Text::sprintf('COM_JEDCHECKER_INFO_XML_MANIFEST_OUTDATED'));
2021-04-04 13:06:01 +00:00
}
if ($xml->getName() !== 'extension')
{
return false;
}
2021-03-10 22:56:04 +00:00
// Get extension type
$type = (string) $xml['type'];
2021-03-10 22:56:04 +00:00
// Load the language of the extension (if any)
if (!$this->loadExtensionLanguage($xml, dirname($file))) {
$lang_file = JEDCheckerHelper::getElementName($xml) . '.sys.ini';
if ($type === 'plugin' && isset($xml['group']) && strpos($lang_file, 'plg_') !== 0)
{
$lang_file = 'plg_' . $xml['group'] . '_' . $lang_file;
}
$this->report->addNotice($file, Text::sprintf('COM_JEDCHECKER_INFO_XML_NO_LANGUAGE_FILE_FOUND', $lang_file, 'en-GB'));
}
2019-03-09 19:44:14 +00:00
// Get the real extension's name now that the language has been loaded
$lang = Factory::getLanguage();
$extensionName = $lang->_((string) $xml->name);
2019-03-09 19:44:14 +00:00
$info[] = Text::sprintf('COM_JEDCHECKER_INFO_XML_NAME_XML', $extensionName);
$info[] = Text::sprintf('COM_JEDCHECKER_INFO_XML_VERSION_XML', (string) $xml->version);
$info[] = Text::sprintf('COM_JEDCHECKER_INFO_XML_CREATIONDATE_XML', (string) $xml->creationDate);
2013-11-05 20:17:39 +00:00
$this->report->addInfo($file, implode('<br />', $info));
if ($isTopLevel)
2021-05-11 20:08:21 +00:00
{
// JED allows components, modules, plugins, and packages (as a container) only
if (!in_array($type, $this->jedTypes, true))
{
$this->report->addError($file, Text::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))
{
2021-09-09 07:32:52 +00:00
$this->report->addIssue(JEDcheckerReport::LEVEL_ERROR, 'NM3', $file,
Text::sprintf('COM_JEDCHECKER_INFO_XML_NAME_RESERVED_KEYWORDS', $extensionName, strtolower($match[0])));
}
2021-05-11 20:08:21 +00:00
// Extension name shouldn't start with extension type prefix
if (preg_match('/^\s*(?:mod|com|plg|tpl|pkg)_/i', $extensionName))
{
$this->report->addError($file, Text::sprintf('COM_JEDCHECKER_INFO_XML_NAME_PREFIXED', $extensionName));
}
// NM5 - Version in name/title
if (preg_match('/(?:\bversion\b|\d\.\d)/i', $extensionName))
{
2021-09-09 07:32:52 +00:00
$this->report->addIssue(JEDcheckerReport::LEVEL_ERROR, 'NM5', $file,
Text::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"
2021-09-09 07:32:52 +00:00
$this->report->addIssue(JEDcheckerReport::LEVEL_ERROR, 'TM2', $file,
Text::sprintf('COM_JEDCHECKER_INFO_XML_NAME_JOOMLA', $extensionName));
}
else
{
$cleanName = preg_replace('/\s+for\s+Joomla!?$/', '', $extensionName);
2021-11-16 17:20:22 +00:00
if (stripos($cleanName, 'joomla') !== false)
{
// Extensions that use "Joomla" or a derivative of Joomla in the extension name need to be licensed by OSM
2021-09-09 07:32:52 +00:00
$this->report->addIssue(JEDcheckerReport::LEVEL_WARNING, 'TM2', $file,
Text::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, Text::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, Text::sprintf('COM_JEDCHECKER_INFO_XML_NAME_TOO_LONG', $extensionName));
}
elseif ($nameLen > 40)
{
$this->report->addWarning($file, Text::sprintf('COM_JEDCHECKER_INFO_XML_NAME_TOO_LONG', $extensionName));
}
}
2021-05-11 20:32:32 +00:00
// Validate URLs
$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, Text::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, Text::sprintf('COM_JEDCHECKER_INFO_XML_NAME_PLUGIN_FORMAT', $extensionName));
}
}
2013-11-05 20:17:39 +00:00
// 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 $rootDir The basepath
* @param string $langTag The language to load
*
* @return bool True if language file found, and false otherwise
*/
protected function loadExtensionLanguage($xml, $rootDir, $langTag = 'en-GB')
{
2021-03-10 22:56:04 +00:00
// Get extension's element name (simulates work of Joomla's installer)
$extension = JEDCheckerHelper::getElementName($xml);
2019-03-09 19:44:14 +00:00
$type = (string) $xml['type'];
2019-03-09 19:44:14 +00:00
2021-03-24 12:11:59 +00:00
// Plugin's element name starts with plg_
2021-03-10 22:56:04 +00:00
if ($type === 'plugin' && isset($xml['group']) && strpos($extension, 'plg_') !== 0)
2021-02-23 21:16:31 +00:00
{
$extension = 'plg_' . $xml['group'] . '_' . $extension;
2019-03-09 19:44:14 +00:00
}
// Load the language of the extension (if any)
$lang = Factory::getLanguage();
2019-03-09 19:44:14 +00:00
2021-03-10 22:56:04 +00:00
// Populate list of directories to look for
2021-04-04 10:50:33 +00:00
$lookupLangDirs = array();
2021-02-23 21:16:31 +00:00
if (isset($xml->administration->files['folder']))
2013-11-05 20:17:39 +00:00
{
2021-04-04 10:50:33 +00:00
$lookupLangDirs[] = trim($xml->administration->files['folder'], '/') . '/language/' . $langTag;
2013-11-05 20:17:39 +00:00
}
if (isset($xml->files['folder']))
2013-11-05 20:17:39 +00:00
{
2021-04-04 10:50:33 +00:00
$lookupLangDirs[] = trim($xml->files['folder'], '/') . '/language/' . $langTag;
2013-11-05 20:17:39 +00:00
}
2021-02-23 21:16:31 +00:00
2021-04-04 10:50:33 +00:00
$lookupLangDirs[] = 'language/' . $langTag;
2021-02-23 21:16:31 +00:00
if (isset($xml->administration->languages))
{
2021-02-23 21:16:31 +00:00
$folder = trim($xml->administration->languages['folder'], '/');
foreach ($xml->administration->languages->language as $language)
{
2021-04-04 10:50:33 +00:00
if (trim($language['tag']) === $langTag)
2021-02-23 21:16:31 +00:00
{
2021-04-04 10:50:33 +00:00
$lookupLangDirs[] = trim($folder . '/' . dirname($language), '/');
2021-02-23 21:16:31 +00:00
}
}
}
2021-02-23 21:16:31 +00:00
if (isset($xml->languages))
{
2023-02-21 15:05:27 +00:00
$folder = trim((string)$xml->languages['folder'], '/');
2021-02-23 21:16:31 +00:00
foreach ($xml->languages->language as $language)
{
2021-04-04 10:50:33 +00:00
if (trim($language['tag']) === $langTag)
2021-02-23 21:16:31 +00:00
{
2021-04-04 10:50:33 +00:00
$lookupLangDirs[] = trim($folder . '/' . dirname($language), '/');
2021-02-23 21:16:31 +00:00
}
}
}
2021-02-23 21:16:31 +00:00
2021-04-04 10:50:33 +00:00
$lookupLangDirs[] = '';
2021-04-04 10:50:33 +00:00
$lookupLangDirs = array_unique($lookupLangDirs);
2021-02-23 21:16:31 +00:00
$lookupLangFiles = array(
$langTag. '.' . $extension . '.sys.ini', // classical filename
$extension . '.sys.ini', // modern filename
);
2021-03-10 22:01:14 +00:00
// Looking for language file in specified directories
2021-04-04 10:50:33 +00:00
foreach ($lookupLangDirs as $dir)
{
foreach ($lookupLangFiles as $file)
{
$langSysFile = $rootDir . '/' . ($dir === '' ? '' : $dir . '/') . $file;
if (is_file($langSysFile))
{
$loadLanguage = new ReflectionMethod($lang, 'loadLanguage');
$loadLanguage->setAccessible(true);
$loadLanguage->invoke($lang, $langSysFile, $extension);
return true;
}
}
}
return false;
2013-11-05 20:17:39 +00:00
}
/**
* Check domain name contains "Joomla"/derivative
*
* @param string $file Current file name
* @param string $url URL to validate
2021-04-04 12:08:43 +00:00
*
* @return void
*/
protected function validateDomain($file, $url)
{
$domain = (strpos($url, '//') === false) ? $url : parse_url(trim($url), PHP_URL_HOST);
2021-05-08 17:30:05 +00:00
if (stripos($domain, 'joomla') !== false)
{
// Extensions that use "Joomla" or a derivative of Joomla in the domain name need to be licensed by OSM
2021-09-09 07:32:52 +00:00
$this->report->addIssue(JEDcheckerReport::LEVEL_ERROR, 'TM1', $file,
Text::sprintf('COM_JEDCHECKER_INFO_XML_URL_JOOMLA_DERIVATIVE', $url, 'https://tm.joomla.org/approved-domains.html'));
}
}
}