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');
|
|
|
|
|
|
|
|
// Include the rule base class
|
|
|
|
require_once JPATH_COMPONENT_ADMINISTRATOR . '/models/rule.php';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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';
|
|
|
|
|
|
|
|
protected $tests = false;
|
|
|
|
|
2014-02-24 11:04:25 +00:00
|
|
|
protected $leftover_folders;
|
|
|
|
|
2014-02-23 03:35:18 +00:00
|
|
|
/**
|
|
|
|
* Initiates the file search and check
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function check()
|
|
|
|
{
|
2021-02-24 11:48:47 +00:00
|
|
|
// Warn about code versioning files included
|
|
|
|
$leftover_folders = $this->params->get('leftover_folders');
|
|
|
|
$regex_leftover_folders = '(?:' . str_replace(',', '|', preg_quote($leftover_folders, '/')) . ')$';
|
|
|
|
|
2021-04-04 11:03:34 +00:00
|
|
|
// Get matched files and folder (w/o default exclusion list)
|
2021-02-24 11:48:47 +00:00
|
|
|
$folders = JFolder::folders($this->basedir, $regex_leftover_folders, true, true, array(), array());
|
|
|
|
$files = JFolder::files($this->basedir, $regex_leftover_folders, true, true, array(), array());
|
|
|
|
|
2021-03-09 20:36:58 +00:00
|
|
|
if ($folders !== false)
|
2021-02-24 11:48:47 +00:00
|
|
|
{
|
2021-03-09 20:36:58 +00:00
|
|
|
// Warn on leftover folders found
|
|
|
|
foreach ($folders as $folder)
|
|
|
|
{
|
|
|
|
$this->report->addWarning($folder, JText::_("COM_JEDCHECKER_ERROR_FRAMEWORK_LEFTOVER_FOLDER"));
|
|
|
|
}
|
2021-02-24 11:48:47 +00:00
|
|
|
}
|
|
|
|
|
2021-03-09 20:36:58 +00:00
|
|
|
if ($files !== false)
|
2021-02-24 11:48:47 +00:00
|
|
|
{
|
2021-03-09 20:36:58 +00:00
|
|
|
// Warn on leftover filess found
|
|
|
|
foreach ($files as $file)
|
|
|
|
{
|
|
|
|
$this->report->addWarning($file, JText::_("COM_JEDCHECKER_ERROR_FRAMEWORK_LEFTOVER_FILE"));
|
|
|
|
}
|
2021-02-24 11:48:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
$this->leftover_folders = explode(',', $leftover_folders);
|
|
|
|
|
2021-02-13 20:12:08 +00:00
|
|
|
$files = JFolder::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
|
|
|
/**
|
2021-02-24 11:48:47 +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)
|
|
|
|
{
|
|
|
|
foreach ($this->leftover_folders as $leftover_folder)
|
|
|
|
{
|
2021-02-24 11:48:47 +00:00
|
|
|
if (strpos($file, '/' . $leftover_folder . '/') !== false)
|
2014-02-24 11:04:25 +00:00
|
|
|
{
|
2021-02-24 11:48:47 +00:00
|
|
|
return true;
|
2014-02-24 11:04:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-24 11:48:47 +00:00
|
|
|
return false;
|
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)
|
|
|
|
{
|
2021-02-23 20:23:45 +00:00
|
|
|
$origContent = (array) file($file);
|
|
|
|
$cleanContent = preg_split("/(?:\r\n|\n|\r)(?!$)/", $this->cleanNonCode($file));
|
|
|
|
|
2014-02-23 03:35:18 +00:00
|
|
|
$result = false;
|
|
|
|
|
|
|
|
foreach ($this->getTests() as $testObject)
|
|
|
|
{
|
2021-02-23 20:23:45 +00:00
|
|
|
if ($this->runTest($file, $origContent, $cleanContent, $testObject))
|
2014-02-23 03:35:18 +00:00
|
|
|
{
|
|
|
|
$result = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
2021-02-23 20:23:45 +00:00
|
|
|
/**
|
|
|
|
* @param string $file
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
protected function cleanNonCode($file)
|
|
|
|
{
|
|
|
|
$content = file_get_contents($file);
|
|
|
|
|
|
|
|
if (!preg_match('/<\?php\s/i', $content, $match, PREG_OFFSET_CAPTURE))
|
|
|
|
{
|
|
|
|
// No PHP code found
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
$pos = $match[0][1];
|
|
|
|
$cleanContent = $this->removeContent(substr($content, 0, $pos));
|
|
|
|
|
|
|
|
while (preg_match('/(?:[\'"]|\/\*|\/\/|\?>)/', $content, $match, PREG_OFFSET_CAPTURE, $pos))
|
|
|
|
{
|
|
|
|
$foundPos = $match[0][1];
|
|
|
|
$cleanContent .= substr($content, $pos, $foundPos - $pos);
|
|
|
|
$pos = $foundPos;
|
|
|
|
|
|
|
|
switch ($match[0][0])
|
|
|
|
{
|
|
|
|
case '"':
|
|
|
|
case "'":
|
|
|
|
$q = $match[0][0];
|
|
|
|
|
|
|
|
if (!preg_match("/$q(?>[^$q\\\\]+|\\\\.)*$q/As", $content, $match, 0, $pos))
|
|
|
|
{
|
|
|
|
return $cleanContent . $q;
|
|
|
|
}
|
|
|
|
|
|
|
|
$cleanContent .= $q . $this->removeContent($match[0]) . $q;
|
|
|
|
$pos += strlen($match[0]);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case '/*':
|
|
|
|
$cleanContent .= '/*';
|
|
|
|
$pos += 2;
|
|
|
|
|
|
|
|
$endPos = strpos($content, '*/', $pos);
|
|
|
|
|
|
|
|
if ($endPos === false)
|
|
|
|
{
|
|
|
|
return $cleanContent;
|
|
|
|
}
|
|
|
|
|
|
|
|
$cleanContent .= $this->removeContent(substr($content, $pos, $endPos - $pos)) . '*/';
|
|
|
|
$pos = $endPos + 2;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case '//':
|
|
|
|
$pos += strcspn($content, "\r\n", $pos);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case '?>':
|
|
|
|
$cleanContent .= '?>';
|
|
|
|
$pos += 2;
|
|
|
|
|
|
|
|
if (!preg_match('/<\?php\s/i', $content, $match, PREG_OFFSET_CAPTURE, $pos))
|
|
|
|
{
|
|
|
|
// No PHP code found (up to the end of the file)
|
|
|
|
return $cleanContent;
|
|
|
|
}
|
|
|
|
|
|
|
|
$foundPos = $match[0][1];
|
|
|
|
$cleanContent .= $this->removeContent(substr($content, $pos, $foundPos - $pos)) . $match[0][0];
|
|
|
|
$pos = $foundPos + strlen($match[0][0]);
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $cleanContent;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-04-04 11:03:34 +00:00
|
|
|
* Remove all text content by keeping newline characters only (to preserve line numbers)
|
2021-02-23 20:23:45 +00:00
|
|
|
*
|
|
|
|
* @param string $content
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
protected function removeContent($content)
|
|
|
|
{
|
|
|
|
return str_repeat("\n", substr_count($content, "\n"));
|
|
|
|
}
|
|
|
|
|
2014-02-23 03:35:18 +00:00
|
|
|
/**
|
|
|
|
* 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
|
2021-02-23 20:23:45 +00:00
|
|
|
* @param array $origContent The file content
|
|
|
|
* @param array $cleanContent The file content w/o non-code elements
|
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
|
|
|
|
*/
|
2021-02-23 20:23:45 +00:00
|
|
|
private function runTest($file, $origContent, $cleanContent, $testObject)
|
2014-02-23 03:35:18 +00:00
|
|
|
{
|
2021-02-23 20:23:45 +00:00
|
|
|
// @todo remove as unused?
|
2014-02-23 03:35:18 +00:00
|
|
|
$error_count = 0;
|
|
|
|
|
2021-02-23 20:23:45 +00:00
|
|
|
foreach ($cleanContent as $line_number => $line)
|
2014-02-23 03:35:18 +00:00
|
|
|
{
|
2021-02-23 20:23:45 +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
|
|
|
{
|
2021-02-23 20:23:45 +00:00
|
|
|
$regex = preg_quote($singleTest, '/');
|
|
|
|
|
2021-03-10 10:13:30 +00:00
|
|
|
// Add word boundary check for rules staring/ending with a letter (to avoid false-positives because of partial match)
|
|
|
|
|
2021-02-23 20:23:45 +00:00
|
|
|
if (ctype_alpha($singleTest[0]))
|
2014-02-23 03:35:18 +00:00
|
|
|
{
|
2021-03-10 10:13:30 +00:00
|
|
|
$regex = '\b' . $regex;
|
2021-02-23 20:23:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (ctype_alpha($singleTest[strlen($singleTest) - 1]))
|
|
|
|
{
|
2021-03-10 10:13:30 +00:00
|
|
|
$regex .= '\b';
|
2021-02-23 20:23:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (preg_match('/' . $regex . '/i', $line))
|
|
|
|
{
|
|
|
|
$origLine = str_ireplace($singleTest, '<b>' . $singleTest . '</b>', htmlspecialchars($origLine));
|
|
|
|
$error_message = JText::_('COM_JEDCHECKER_ERROR_FRAMEWORK_' . strtoupper($testObject->group)) . ':<pre>' . $origLine . '</pre>';
|
2014-02-23 03:35:18 +00:00
|
|
|
|
|
|
|
switch ($testObject->kind)
|
|
|
|
{
|
2021-02-23 20:23:45 +00:00
|
|
|
case 'error':
|
|
|
|
$this->report->addError($file, $error_message, $line_number);
|
2014-02-23 03:35:18 +00:00
|
|
|
break;
|
2021-02-23 20:23:45 +00:00
|
|
|
case 'warning':
|
|
|
|
$this->report->addWarning($file, $error_message, $line_number);
|
2014-02-23 03:35:18 +00:00
|
|
|
break;
|
2021-02-23 20:23:45 +00:00
|
|
|
case 'compatibility':
|
|
|
|
$this->report->addCompat($file, $error_message, $line_number);
|
2014-02-23 03:35:18 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
// Case 'notice':
|
|
|
|
$this->report->addInfo($file, $error_message, $line_number);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2021-02-23 20:23:45 +00:00
|
|
|
|
2014-02-23 03:35:18 +00:00
|
|
|
// If you scored 10 errors on a single file, that's enough for now.
|
|
|
|
if ($error_count > 10)
|
|
|
|
{
|
|
|
|
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 = explode(",", $this->params->get($group));
|
|
|
|
$this->tests[] = $newTest;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->tests;
|
|
|
|
}
|
|
|
|
}
|