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/framework.php

372 lines
9.8 KiB
PHP
Raw Permalink Normal View History

2014-02-23 03:35:18 +00:00
<?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 fasterjoomla.com. All rights reserved.
2019-03-10 08:49:52 +00:00
* @author Riccardo Zorn <support@fasterjoomla.com>
*
2019-03-09 19:44:14 +00:00
* @license GNU General Public License version 2 or later; see LICENSE.txt
2014-02-23 03:35:18 +00:00
*/
defined('_JEXEC') or die('Restricted access');
use Joomla\CMS\Filesystem\Folder;
use Joomla\CMS\Language\Text;
2014-02-23 03:35:18 +00:00
// 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';
2014-02-23 03:35:18 +00:00
/**
* JedcheckerRulesFramework
*
* @since 2014-02-23
* Attempts to identify deprecated code, unsafe code, leftover stuff
*/
class JedcheckerRulesFramework extends JEDcheckerRule
{
/**
* The formal ID of this rule. For example: SE1.
*
* @var string
*/
protected $id = 'Framework';
/**
* The title or caption of this rule.
*
* @var string
*/
protected $title = 'COM_JEDCHECKER_RULE_FRAMEWORK';
/**
* The description of this rule.
*
* @var string
*/
protected $description = 'COM_JEDCHECKER_RULE_FRAMEWORK_DESC';
/**
* The ordering value to sort rules in the menu.
*
* @var integer
*/
public static $ordering = 700;
2014-02-23 03:35:18 +00:00
protected $tests = false;
2021-05-11 15:45:51 +00:00
protected $regexLeftoverFolders;
2014-02-24 11:04:25 +00:00
2014-02-23 03:35:18 +00:00
/**
* Initiates the file search and check
*
* @return void
*/
public function check()
{
// Warn about code versioning files included
2021-05-11 15:45:51 +00:00
$leftoverFolders = $this->params->get('leftover_folders');
$leftoverFoldersWhitelist = $this->params->get('leftover_folders_whitelist');
2021-05-11 15:45:51 +00:00
$this->regexLeftoverFolders = '';
if (!empty($leftoverFoldersWhitelist))
{
2021-05-11 11:44:10 +00:00
$this->regexLeftoverFolders .=
'(?!(?:'
. str_replace(array(',', '\*'), array('|', '.*'), preg_quote($leftoverFoldersWhitelist, '/'))
. '))';
}
2021-05-11 15:45:51 +00:00
$this->regexLeftoverFolders .= '(?:' . str_replace(array(',', '\*'), array('|', '.*'), preg_quote($leftoverFolders, '/')) . ')';
$regexLeftoverFolders = '^' . $this->regexLeftoverFolders . '$';
2021-04-04 11:03:34 +00:00
// Get matched files and folder (w/o default exclusion list)
$folders = Folder::folders($this->basedir, $regexLeftoverFolders, true, true, array(), array());
$files = Folder::files($this->basedir, $regexLeftoverFolders, true, true, array(), array());
if ($folders !== false)
{
// Warn on leftover folders found
foreach ($folders as $folder)
{
$this->report->addWarning($folder, Text::_("COM_JEDCHECKER_ERROR_FRAMEWORK_LEFTOVER_FOLDER"));
}
}
if ($files !== false)
{
// Warn on leftover files found
foreach ($files as $file)
{
$this->report->addWarning($file, Text::_("COM_JEDCHECKER_ERROR_FRAMEWORK_LEFTOVER_FILE"));
}
}
$files = Folder::files($this->basedir, '\.php$', true, true);
2014-02-23 03:35:18 +00:00
foreach ($files as $file)
{
2014-02-24 11:04:25 +00:00
if (!$this->excludeResource($file))
2014-02-23 03:35:18 +00:00
{
2014-02-24 11:04:25 +00:00
// Process the file
if ($this->find($file))
{
// Error messages are set by find() based on the errors found.
}
2014-02-23 03:35:18 +00:00
}
}
}
2014-02-24 11:04:25 +00:00
/**
* Check if the given resource is inside of a leftover folder
2014-02-24 11:04:25 +00:00
*
2016-05-25 16:10:08 +00:00
* @param string $file The file name to test
2014-02-24 11:04:25 +00:00
*
* @return boolean
*/
private function excludeResource($file)
{
2021-05-11 15:45:51 +00:00
return (bool) preg_match('/\/' . $this->regexLeftoverFolders . '\//', $file);
2014-02-24 11:04:25 +00:00
}
2014-02-23 03:35:18 +00:00
/**
* reads a file and searches for any function defined in the params
*
2016-05-25 16:10:08 +00:00
* @param string $file The file name
2014-02-23 03:35:18 +00:00
*
* @return boolean True if the statement was found, otherwise False.
*/
protected function find($file)
{
$origContent = (array) file($file);
if (count($origContent) === 0)
{
return false;
}
2014-02-23 03:35:18 +00:00
$result = false;
$content = file_get_contents($file);
2021-05-17 17:05:35 +00:00
// Check BOM
if (strncmp($content, "\xEF\xBB\xBF", 3) === 0)
{
$this->report->addError($file, Text::_('COM_JEDCHECKER_ERROR_FRAMEWORK_BOM_FOUND'));
2021-05-17 17:05:35 +00:00
$result = true;
}
// Report spaces/tabs/EOLs at the beginning of file
if (strpos(" \t\n\r\v\f", $content[0]) !== false)
{
$this->report->addNotice($file, Text::_('COM_JEDCHECKER_ERROR_FRAMEWORK_LEADING_SPACES'));
$result = true;
}
2021-05-17 17:05:45 +00:00
// Clean non-code
$cleanContent = JEDCheckerHelper::cleanPhpCode(
$content,
JEDCheckerHelper::CLEAN_HTML | JEDCheckerHelper::CLEAN_COMMENTS | JEDCheckerHelper::CLEAN_STRINGS
);
$cleanContent = JEDCheckerHelper::resolveAliases($cleanContent);
2021-05-17 17:05:45 +00:00
// Check short PHP tag
if (preg_match('/<\?\s/', $cleanContent, $match, PREG_OFFSET_CAPTURE))
2021-05-13 19:03:51 +00:00
{
$lineno = substr_count($cleanContent, "\n", 0, $match[0][1]);
$this->report->addError($file, Text::_('COM_JEDCHECKER_ERROR_FRAMEWORK_SHORT_PHP_TAG'), $lineno + 1, $origContent[$lineno]);
2021-05-13 19:03:51 +00:00
$result = true;
}
$cleanContentKeepStrings = JEDCheckerHelper::cleanPhpCode(
$content,
JEDCheckerHelper::CLEAN_HTML | JEDCheckerHelper::CLEAN_COMMENTS
);
$cleanContent = JEDCheckerHelper::splitLines($cleanContent);
$cleanContentKeepStrings = JEDCheckerHelper::splitLines($cleanContentKeepStrings);
2021-05-17 17:05:45 +00:00
// Run other tests
2014-02-23 03:35:18 +00:00
foreach ($this->getTests() as $testObject)
{
if ($this->runTest($file, $origContent, $cleanContent, $cleanContentKeepStrings, $testObject))
2014-02-23 03:35:18 +00:00
{
$result = true;
}
}
return $result;
}
/**
* runs tests and reports to the appropriate function if strings match.
*
2021-04-04 10:36:35 +00:00
* @param string $file The file name
* @param array $origContent The file content
* @param array $cleanContent The file content w/o non-code elements
* @param array $cleanContentKeepStrings The file content w/o comments and HTML
2021-04-04 10:36:35 +00:00
* @param object $testObject The test object generated by getTests()
2014-02-23 03:35:18 +00:00
*
* @return boolean
*/
private function runTest($file, $origContent, $cleanContent, $cleanContentKeepStrings, $testObject)
2014-02-23 03:35:18 +00:00
{
// @todo remove as unused?
2014-02-23 03:35:18 +00:00
$error_count = 0;
foreach ($cleanContent as $line_number => $line)
2014-02-23 03:35:18 +00:00
{
$origLine = $origContent[$line_number];
2021-04-04 10:36:35 +00:00
foreach ($testObject->tests as $singleTest)
2014-02-23 03:35:18 +00:00
{
$lineContent = $singleTest->keepStrings ? $cleanContentKeepStrings[$line_number] : $line;
if (preg_match($singleTest->regex, $lineContent))
{
$origLine = str_ireplace($singleTest->test, '<b>' . $singleTest->test . '</b>', htmlspecialchars($origLine));
$error_message = Text::_('COM_JEDCHECKER_ERROR_FRAMEWORK_' . strtoupper($testObject->group)) . ':<pre>' . $origLine . '</pre>';
2014-02-23 03:35:18 +00:00
if ($singleTest->replacement !== false)
{
$error_message .= Text::_('COM_JEDCHECKER_ERROR_FRAMEWORK_INSTEAD_USE') . ': ' . $singleTest->replacement;
}
2014-02-23 03:35:18 +00:00
switch ($testObject->kind)
{
case 'error':
$this->report->addError($file, $error_message, $line_number);
2014-02-23 03:35:18 +00:00
break;
case 'warning':
$this->report->addWarning($file, $error_message, $line_number);
2014-02-23 03:35:18 +00:00
break;
case 'compatibility':
$this->report->addCompat($file, $error_message, $line_number);
2014-02-23 03:35:18 +00:00
break;
default:
// Case 'notice':
2021-09-09 07:32:52 +00:00
$this->report->addNotice($file, $error_message, $line_number);
2014-02-23 03:35:18 +00:00
break;
}
}
// If you scored 100 errors on a single file, that's enough for now.
if ($error_count > 100)
2014-02-23 03:35:18 +00:00
{
return true;
}
}
}
return $error_count > 0;
}
/**
* Lazyloads the tests from the framework.ini params.
* The whole structure depends on the file. The vars
* error_groups, warning_groups, notice_groups, compatibility_groups
* serve as lists of other rules, which are grouped and show a different error message per rule.
* Please note: if you want to add more rules, simply do so in the .ini file
* BUT MAKE SURE that you add the relevant key to the translation files:
* COM_JEDCHECKER_ERROR_NOFRAMEWOR_SOMEKEY
*
2021-04-04 10:36:35 +00:00
* @return array
2014-02-23 03:35:18 +00:00
*/
private function getTests()
{
if (!$this->tests)
{
// Build the test array. Please read the comments in the framework.ini file
$this->tests = array();
$testNames = array('error','warning','notice','compatibility');
foreach ($testNames as $test)
{
2021-04-04 10:36:35 +00:00
foreach (explode(",", $this->params->get($test . '_groups')) as $group)
2014-02-23 03:35:18 +00:00
{
$newTest = new stdClass;
$newTest->group = $group;
$newTest->kind = $test;
$newTest->tests = array();
2023-09-05 17:15:56 +00:00
foreach (explode(",", $this->params->get($group)) as $match)
{
2023-09-05 17:15:56 +00:00
if (strpos($match, '=>') !== false)
{
2023-09-05 17:15:56 +00:00
list($match, $replacement) = explode('=>', $match, 2);
}
else
{
$replacement = false;
}
$testObj = new stdClass;
2023-09-05 17:15:56 +00:00
$testObj->test = $match;
$testObj->regex = $this->generateRegex($match);
$testObj->replacement = $replacement;
2023-09-05 17:15:56 +00:00
$testObj->keepStrings = strpos($match, "'") !== false;
$newTest->tests[] = $testObj;
}
2014-02-23 03:35:18 +00:00
$this->tests[] = $newTest;
}
}
2023-09-05 08:12:54 +00:00
$newTest = new stdClass;
$newTest->group = 'j5legacy';
$newTest->kind = 'compatibility';
2023-09-05 08:12:54 +00:00
$newTest->tests = array();
// Too many tests for j5legacy group, so that they are extracted to separate file
$j5legacy = parse_ini_file(__DIR__ . '/framework_j5legacy.ini');
foreach ($j5legacy as $oldClass => $newClass)
{
$testObj = new stdClass;
$testObj->test = $oldClass;
$testObj->regex = $this->generateRegex($oldClass, true);
$testObj->replacement = $newClass;
$testObj->keepStrings = false;
$newTest->tests[] = $testObj;
}
$this->tests[] = $newTest;
2014-02-23 03:35:18 +00:00
}
return $this->tests;
}
/**
* Generates regular expression for a given test
*
* @param string $test The string to match
* @param boolean $matchCase True for case-sensitive matching
*
* @return string
*/
private function generateRegex($test, $matchCase = false)
{
$regex = preg_quote($test, '/');
// Add word boundary check for rules staring/ending with a letter (to avoid false-positives because of partial match)
if (ctype_alpha($test[0]))
{
$regex = '\b' . $regex;
}
if (ctype_alpha($test[strlen($test) - 1]))
{
$regex .= '\b';
}
return '/' . $regex . '/' . ($matchCase ? '' : 'i');
}
2014-02-23 03:35:18 +00:00
}