| Current Path : /home/w/u/e/wuectly/www/03cbe/ |
| Current File : /home/w/u/e/wuectly/www/03cbe/Filter.tar |
Wrapper/OutputFilterWrapper.php 0000604 00000007766 15245530366 0012731 0 ustar 00 <?php
/**
* Joomla! Content Management System
*
* @copyright (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\CMS\Filter\Wrapper;
defined('JPATH_PLATFORM') or die;
use Joomla\Filter\OutputFilter;
/**
* Wrapper class for OutputFilter
*
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Filter\OutputFilter` directly
*/
class OutputFilterWrapper
{
/**
* Helper wrapper method for objectHTMLSafe
*
* @param object &$mixed An object to be parsed.
* @param integer $quoteStyle The optional quote style for the htmlspecialchars function.
* @param mixed $excludeKeys An optional string single field name or array of field names not.
*
* @return void
*
* @see OutputFilter::objectHTMLSafe()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Filter\OutputFilter` directly
*/
public function objectHTMLSafe(&$mixed, $quoteStyle = 3, $excludeKeys = '')
{
return OutputFilter::objectHTMLSafe($mixed, $quoteStyle, $excludeKeys);
}
/**
* Helper wrapper method for linkXHTMLSafe
*
* @param string $input String to process.
*
* @return string Processed string.
*
* @see OutputFilter::linkXHTMLSafe()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Filter\OutputFilter` directly
*/
public function linkXHTMLSafe($input)
{
return OutputFilter::linkXHTMLSafe($input);
}
/**
* Helper wrapper method for stringURLSafe
*
* @param string $string String to process.
*
* @return string Processed string.
*
* @see OutputFilter::stringURLSafe()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Filter\OutputFilter` directly
*/
public function stringURLSafe($string)
{
return OutputFilter::stringURLSafe($string);
}
/**
* Helper wrapper method for stringURLUnicodeSlug
*
* @param string $string String to process.
*
* @return string Processed string.
*
* @see OutputFilter::stringURLUnicodeSlug()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Filter\OutputFilter` directly
*/
public function stringURLUnicodeSlug($string)
{
return OutputFilter::stringURLUnicodeSlug($string);
}
/**
* Helper wrapper method for ampReplace
*
* @param string $text Text to process.
*
* @return string Processed string.
*
* @see OutputFilter::ampReplace()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Filter\OutputFilter` directly
*/
public function ampReplace($text)
{
return OutputFilter::ampReplace($text);
}
/**
* Helper wrapper method for _ampReplaceCallback
*
* @param string $m String to process.
*
* @return string Replaced string.
*
* @see OutputFilter::_ampReplaceCallback()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Filter\OutputFilter` directly
*/
public function _ampReplaceCallback($m)
{
return OutputFilter::_ampReplaceCallback($m);
}
/**
* Helper wrapper method for cleanText
*
* @param string &$text Text to clean.
*
* @return string Cleaned text.
*
* @see OutputFilter::cleanText()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Filter\OutputFilter` directly
*/
public function cleanText(&$text)
{
return OutputFilter::cleanText($text);
}
/**
* Helper wrapper method for stripImages
*
* @param string $string Sting to be cleaned.
*
* @return string Cleaned string.
*
* @see OutputFilter::stripImages()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Filter\OutputFilter` directly
*/
public function stripImages($string)
{
return OutputFilter::stripImages($string);
}
/**
* Helper wrapper method for stripIframes
*
* @param string $string Sting to be cleaned.
*
* @return string Cleaned string.
*
* @see OutputFilter::stripIframes()
* @since 3.4
* @deprecated 4.0 Use `Joomla\CMS\Filter\OutputFilter` directly
*/
public function stripIframes($string)
{
return OutputFilter::stripIframes($string);
}
}
InputFilter.php 0000604 00000101646 15245530366 0007537 0 ustar 00 <?php
/**
* Joomla! Content Management System
*
* @copyright (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\CMS\Filter;
defined('JPATH_PLATFORM') or die;
use Joomla\Filter\InputFilter as BaseInputFilter;
use Joomla\String\StringHelper;
/**
* InputFilter is a class for filtering input from any data source
*
* Forked from the php input filter library by: Daniel Morris <dan@rootcube.com>
* Original Contributors: Gianpaolo Racca, Ghislain Picard, Marco Wandschneider, Chris Tobin and Andrew Eddie.
*
* @since 1.7.0
*/
class InputFilter extends BaseInputFilter
{
/**
* A flag for Unicode Supplementary Characters (4-byte Unicode character) stripping.
*
* @var integer
*
* @since 3.5
*/
public $stripUSC = 0;
/**
* Constructor for inputFilter class. Only first parameter is required.
*
* @param array $tagsArray List of user-defined tags
* @param array $attrArray List of user-defined attributes
* @param integer $tagsMethod WhiteList method = 0, BlackList method = 1
* @param integer $attrMethod WhiteList method = 0, BlackList method = 1
* @param integer $xssAuto Only auto clean essentials = 0, Allow clean blacklisted tags/attr = 1
* @param integer $stripUSC Strip 4-byte unicode characters = 1, no strip = 0, ask the database driver = -1
*
* @since 1.7.0
*/
public function __construct($tagsArray = array(), $attrArray = array(), $tagsMethod = 0, $attrMethod = 0, $xssAuto = 1, $stripUSC = -1)
{
// Make sure user defined arrays are in lowercase
$tagsArray = array_map('strtolower', (array) $tagsArray);
$attrArray = array_map('strtolower', (array) $attrArray);
// Assign member variables
$this->tagsArray = $tagsArray;
$this->attrArray = $attrArray;
$this->tagsMethod = $tagsMethod;
$this->attrMethod = $attrMethod;
$this->xssAuto = $xssAuto;
$this->stripUSC = $stripUSC;
/**
* If Unicode Supplementary Characters stripping is not set we have to check with the database driver. If the
* driver does not support USCs (i.e. there is no utf8mb4 support) we will enable USC stripping.
*/
if ($this->stripUSC === -1)
{
try
{
// Get the database driver
$db = \JFactory::getDbo();
// This trick is required to let the driver determine the utf-8 multibyte support
$db->connect();
// And now we can decide if we should strip USCs
$this->stripUSC = $db->hasUTF8mb4Support() ? 0 : 1;
}
catch (\RuntimeException $e)
{
// Could not connect to MySQL. Strip USC to be on the safe side.
$this->stripUSC = 1;
}
}
}
/**
* Returns an input filter object, only creating it if it doesn't already exist.
*
* @param array $tagsArray List of user-defined tags
* @param array $attrArray List of user-defined attributes
* @param integer $tagsMethod WhiteList method = 0, BlackList method = 1
* @param integer $attrMethod WhiteList method = 0, BlackList method = 1
* @param integer $xssAuto Only auto clean essentials = 0, Allow clean blacklisted tags/attr = 1
* @param integer $stripUSC Strip 4-byte unicode characters = 1, no strip = 0, ask the database driver = -1
*
* @return InputFilter The InputFilter object.
*
* @since 1.7.0
*/
public static function &getInstance($tagsArray = array(), $attrArray = array(), $tagsMethod = 0, $attrMethod = 0, $xssAuto = 1, $stripUSC = -1)
{
$sig = md5(serialize(array($tagsArray, $attrArray, $tagsMethod, $attrMethod, $xssAuto)));
if (empty(self::$instances[$sig]))
{
self::$instances[$sig] = new InputFilter($tagsArray, $attrArray, $tagsMethod, $attrMethod, $xssAuto, $stripUSC);
}
return self::$instances[$sig];
}
/**
* Method to be called by another php script. Processes for XSS and
* specified bad code.
*
* @param mixed $source Input string/array-of-string to be 'cleaned'
* @param string $type The return type for the variable:
* INT: An integer, or an array of integers,
* UINT: An unsigned integer, or an array of unsigned integers,
* FLOAT: A floating point number, or an array of floating point numbers,
* BOOLEAN: A boolean value,
* WORD: A string containing A-Z or underscores only (not case sensitive),
* ALNUM: A string containing A-Z or 0-9 only (not case sensitive),
* CMD: A string containing A-Z, 0-9, underscores, periods or hyphens (not case sensitive),
* BASE64: A string containing A-Z, 0-9, forward slashes, plus or equals (not case sensitive),
* STRING: A fully decoded and sanitised string (default),
* HTML: A sanitised string,
* ARRAY: An array,
* PATH: A sanitised file path, or an array of sanitised file paths,
* TRIM: A string trimmed from normal, non-breaking and multibyte spaces
* USERNAME: Do not use (use an application specific filter),
* RAW: The raw string is returned with no filtering,
* unknown: An unknown filter will act like STRING. If the input is an array it will return an
* array of fully decoded and sanitised strings.
*
* @return mixed 'Cleaned' version of input parameter
*
* @since 1.7.0
*/
public function clean($source, $type = 'string')
{
// Strip Unicode Supplementary Characters when requested to do so
if ($this->stripUSC)
{
// Alternatively: preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xE2\xAF\x91", $source) but it'd be slower.
$source = $this->stripUSC($source);
}
// Handle the type constraint cases
switch (strtoupper($type))
{
case 'INT':
case 'INTEGER':
$pattern = '/[-+]?[0-9]+/';
if (is_array($source))
{
$result = array();
// Iterate through the array
foreach ($source as $eachString)
{
preg_match($pattern, (string) $eachString, $matches);
$result[] = isset($matches[0]) ? (int) $matches[0] : 0;
}
}
else
{
preg_match($pattern, (string) $source, $matches);
$result = isset($matches[0]) ? (int) $matches[0] : 0;
}
break;
case 'UINT':
$pattern = '/[-+]?[0-9]+/';
if (is_array($source))
{
$result = array();
// Iterate through the array
foreach ($source as $eachString)
{
preg_match($pattern, (string) $eachString, $matches);
$result[] = isset($matches[0]) ? abs((int) $matches[0]) : 0;
}
}
else
{
preg_match($pattern, (string) $source, $matches);
$result = isset($matches[0]) ? abs((int) $matches[0]) : 0;
}
break;
case 'FLOAT':
case 'DOUBLE':
$pattern = '/[-+]?[0-9]+(\.[0-9]+)?([eE][-+]?[0-9]+)?/';
if (is_array($source))
{
$result = array();
// Iterate through the array
foreach ($source as $eachString)
{
preg_match($pattern, (string) $eachString, $matches);
$result[] = isset($matches[0]) ? (float) $matches[0] : 0;
}
}
else
{
preg_match($pattern, (string) $source, $matches);
$result = isset($matches[0]) ? (float) $matches[0] : 0;
}
break;
case 'BOOL':
case 'BOOLEAN':
if (is_array($source))
{
$result = array();
// Iterate through the array
foreach ($source as $eachString)
{
$result[] = (bool) $eachString;
}
}
else
{
$result = (bool) $source;
}
break;
case 'WORD':
$pattern = '/[^A-Z_]/i';
if (is_array($source))
{
$result = array();
// Iterate through the array
foreach ($source as $eachString)
{
$result[] = (string) preg_replace($pattern, '', $eachString);
}
}
else
{
$result = (string) preg_replace($pattern, '', $source);
}
break;
case 'ALNUM':
$pattern = '/[^A-Z0-9]/i';
if (is_array($source))
{
$result = array();
// Iterate through the array
foreach ($source as $eachString)
{
$result[] = (string) preg_replace($pattern, '', $eachString);
}
}
else
{
$result = (string) preg_replace($pattern, '', $source);
}
break;
case 'CMD':
$pattern = '/[^A-Z0-9_\.-]/i';
if (is_array($source))
{
$result = array();
// Iterate through the array
foreach ($source as $eachString)
{
$cleaned = (string) preg_replace($pattern, '', $eachString);
$result[] = ltrim($cleaned, '.');
}
}
else
{
$result = (string) preg_replace($pattern, '', $source);
$result = ltrim($result, '.');
}
break;
case 'BASE64':
$pattern = '/[^A-Z0-9\/+=]/i';
if (is_array($source))
{
$result = array();
// Iterate through the array
foreach ($source as $eachString)
{
$result[] = (string) preg_replace($pattern, '', $eachString);
}
}
else
{
$result = (string) preg_replace($pattern, '', $source);
}
break;
case 'STRING':
if (is_array($source))
{
$result = array();
// Iterate through the array
foreach ($source as $eachString)
{
$result[] = (string) $this->remove($this->decode((string) $eachString));
}
}
else
{
$result = (string) $this->remove($this->decode((string) $source));
}
break;
case 'HTML':
if (is_array($source))
{
$result = array();
// Iterate through the array
foreach ($source as $eachString)
{
$result[] = (string) $this->remove((string) $eachString);
}
}
else
{
$result = (string) $this->remove((string) $source);
}
break;
case 'ARRAY':
$result = (array) $source;
break;
case 'PATH':
$result = parent::clean($source, 'path');
break;
case 'TRIM':
if (is_array($source))
{
$result = array();
// Iterate through the array
foreach ($source as $eachString)
{
$cleaned = (string) trim($eachString);
$cleaned = StringHelper::trim($cleaned, chr(0xE3) . chr(0x80) . chr(0x80));
$result[] = StringHelper::trim($cleaned, chr(0xC2) . chr(0xA0));
}
}
else
{
$result = (string) trim($source);
$result = StringHelper::trim($result, chr(0xE3) . chr(0x80) . chr(0x80));
$result = StringHelper::trim($result, chr(0xC2) . chr(0xA0));
}
break;
case 'USERNAME':
$pattern = '/[\x00-\x1F\x7F<>"\'%&]/';
if (is_array($source))
{
$result = array();
// Iterate through the array
foreach ($source as $eachString)
{
$result[] = (string) preg_replace($pattern, '', $eachString);
}
}
else
{
$result = (string) preg_replace($pattern, '', $source);
}
break;
case 'RAW':
$result = $source;
break;
default:
// Are we dealing with an array?
if (is_array($source))
{
foreach ($source as $key => $value)
{
// Filter element for XSS and other 'bad' code etc.
if (is_string($value))
{
$source[$key] = $this->_remove($this->_decode($value));
}
}
$result = $source;
}
else
{
// Or a string?
if (is_string($source) && !empty($source))
{
// Filter source for XSS and other 'bad' code etc.
$result = $this->_remove($this->_decode($source));
}
else
{
// Not an array or string... return the passed parameter
$result = $source;
}
}
break;
}
return $result;
}
/**
* Function to punyencode utf8 mail when saving content
*
* @param string $text The strings to encode
*
* @return string The punyencoded mail
*
* @since 3.5
*/
public function emailToPunycode($text)
{
$pattern = '/(("mailto:)+[\w\.\-\+]+\@[^"?]+\.+[^."?]+("|\?))/';
if (preg_match_all($pattern, $text, $matches))
{
foreach ($matches[0] as $match)
{
$match = (string) str_replace(array('?', '"'), '', $match);
$text = (string) str_replace($match, \JStringPunycode::emailToPunycode($match), $text);
}
}
return $text;
}
/**
* Checks an uploaded for suspicious naming and potential PHP contents which could indicate a hacking attempt.
*
* The options you can define are:
* null_byte Prevent files with a null byte in their name (buffer overflow attack)
* forbidden_extensions Do not allow these strings anywhere in the file's extension
* php_tag_in_content Do not allow `<?php` tag in content
* phar_stub_in_content Do not allow the `__HALT_COMPILER()` phar stub in content
* shorttag_in_content Do not allow short tag `<?` in content
* shorttag_extensions Which file extensions to scan for short tags in content
* fobidden_ext_in_content Do not allow forbidden_extensions anywhere in content
* php_ext_content_extensions Which file extensions to scan for .php in content
*
* This code is an adaptation and improvement of Admin Tools' UploadShield feature,
* relicensed and contributed by its author.
*
* @param array $file An uploaded file descriptor
* @param array $options The scanner options (see the code for details)
*
* @return boolean True of the file is safe
*
* @since 3.4
*/
public static function isSafeFile($file, $options = array())
{
$defaultOptions = array(
// Null byte in file name
'null_byte' => true,
// Forbidden string in extension (e.g. php matched .php, .xxx.php, .php.xxx and so on)
'forbidden_extensions' => array(
'php', 'phps', 'pht', 'phtml', 'php3', 'php4', 'php5', 'php6', 'php7',
'php8', 'phar', 'inc', 'pl', 'cgi', 'fcgi', 'java', 'jar', 'py',
),
// <?php tag in file contents
'php_tag_in_content' => true,
// <? tag in file contents
'shorttag_in_content' => true,
// __HALT_COMPILER()
'phar_stub_in_content' => true,
// Which file extensions to scan for short tags
'shorttag_extensions' => array(
'inc', 'phps', 'class', 'php3', 'php4', 'php5', 'php6', 'php7', 'php8', 'txt', 'dat', 'tpl', 'tmpl',
),
// Forbidden extensions anywhere in the content
'fobidden_ext_in_content' => true,
// Which file extensions to scan for .php in the content
'php_ext_content_extensions' => array('zip', 'rar', 'tar', 'gz', 'tgz', 'bz2', 'tbz', 'jpa'),
);
$options = array_merge($defaultOptions, $options);
// Make sure we can scan nested file descriptors
$descriptors = $file;
if (isset($file['name']) && isset($file['tmp_name']))
{
$descriptors = self::decodeFileData(
array(
$file['name'],
$file['type'],
$file['tmp_name'],
$file['error'],
$file['size'],
)
);
}
// Handle non-nested descriptors (single files)
if (isset($descriptors['name']))
{
$descriptors = array($descriptors);
}
// Scan all descriptors detected
foreach ($descriptors as $fileDescriptor)
{
if (!isset($fileDescriptor['name']))
{
// This is a nested descriptor. We have to recurse.
if (!self::isSafeFile($fileDescriptor, $options))
{
return false;
}
continue;
}
$tempNames = $fileDescriptor['tmp_name'];
$intendedNames = $fileDescriptor['name'];
if (!is_array($tempNames))
{
$tempNames = array($tempNames);
}
if (!is_array($intendedNames))
{
$intendedNames = array($intendedNames);
}
$len = count($tempNames);
for ($i = 0; $i < $len; $i++)
{
$tempName = array_shift($tempNames);
$intendedName = array_shift($intendedNames);
// 1. Null byte check
if ($options['null_byte'])
{
if (strstr($intendedName, "\x00"))
{
return false;
}
}
// 2. PHP-in-extension check (.php, .php.xxx[.yyy[.zzz[...]]], .xxx[.yyy[.zzz[...]]].php)
if (!empty($options['forbidden_extensions']))
{
$explodedName = explode('.', $intendedName);
$explodedName = array_reverse($explodedName);
array_pop($explodedName);
$explodedName = array_map('strtolower', $explodedName);
/*
* DO NOT USE array_intersect HERE! array_intersect expects the two arrays to
* be set, i.e. they should have unique values.
*/
foreach ($options['forbidden_extensions'] as $ext)
{
if (in_array($ext, $explodedName))
{
return false;
}
}
}
// 3. File contents scanner (PHP tag in file contents)
if ($options['php_tag_in_content']
|| $options['shorttag_in_content'] || $options['phar_stub_in_content']
|| ($options['fobidden_ext_in_content'] && !empty($options['forbidden_extensions'])))
{
$fp = strlen($tempName) ? @fopen($tempName, 'r') : false;
if ($fp !== false)
{
$data = '';
while (!feof($fp))
{
$data .= @fread($fp, 131072);
if ($options['php_tag_in_content'] && stripos($data, '<?php') !== false)
{
return false;
}
if ($options['phar_stub_in_content'] && stripos($data, '__HALT_COMPILER()') !== false)
{
return false;
}
if ($options['shorttag_in_content'])
{
$suspiciousExtensions = $options['shorttag_extensions'];
if (empty($suspiciousExtensions))
{
$suspiciousExtensions = array(
'inc', 'phps', 'class', 'php3', 'php4', 'txt', 'dat', 'tpl', 'tmpl',
);
}
/*
* DO NOT USE array_intersect HERE! array_intersect expects the two arrays to
* be set, i.e. they should have unique values.
*/
$collide = false;
foreach ($suspiciousExtensions as $ext)
{
if (in_array($ext, $explodedName))
{
$collide = true;
break;
}
}
if ($collide)
{
// These are suspicious text files which may have the short tag (<?) in them
if (strstr($data, '<?'))
{
return false;
}
}
}
if ($options['fobidden_ext_in_content'] && !empty($options['forbidden_extensions']))
{
$suspiciousExtensions = $options['php_ext_content_extensions'];
if (empty($suspiciousExtensions))
{
$suspiciousExtensions = array(
'zip', 'rar', 'tar', 'gz', 'tgz', 'bz2', 'tbz', 'jpa',
);
}
/*
* DO NOT USE array_intersect HERE! array_intersect expects the two arrays to
* be set, i.e. they should have unique values.
*/
$collide = false;
foreach ($suspiciousExtensions as $ext)
{
if (in_array($ext, $explodedName))
{
$collide = true;
break;
}
}
if ($collide)
{
/*
* These are suspicious text files which may have an executable
* file extension in them
*/
foreach ($options['forbidden_extensions'] as $ext)
{
if (strstr($data, '.' . $ext))
{
return false;
}
}
}
}
/*
* This makes sure that we don't accidentally skip a <?php tag if it's across
* a read boundary, even on multibyte strings
*/
$data = substr($data, -10);
}
fclose($fp);
}
}
}
}
return true;
}
/**
* Method to decode a file data array.
*
* @param array $data The data array to decode.
*
* @return array
*
* @since 3.4
*/
protected static function decodeFileData(array $data)
{
$result = array();
if (is_array($data[0]))
{
foreach ($data[0] as $k => $v)
{
$result[$k] = self::decodeFileData(array($data[0][$k], $data[1][$k], $data[2][$k], $data[3][$k], $data[4][$k]));
}
return $result;
}
return array('name' => $data[0], 'type' => $data[1], 'tmp_name' => $data[2], 'error' => $data[3], 'size' => $data[4]);
}
/**
* Internal method to iteratively remove all unwanted tags and attributes
*
* @param string $source Input string to be 'cleaned'
*
* @return string 'Cleaned' version of input parameter
*
* @since 1.7.0
* @deprecated 4.0 Use InputFilter::remove() instead
*/
protected function _remove($source)
{
return $this->remove($source);
}
/**
* Internal method to iteratively remove all unwanted tags and attributes
*
* @param string $source Input string to be 'cleaned'
*
* @return string 'Cleaned' version of input parameter
*
* @since 3.5
*/
protected function remove($source)
{
// Check for invalid UTF-8 byte sequence
if (!preg_match('//u', $source))
{
// String contains invalid byte sequence, remove it
$source = htmlspecialchars_decode(htmlspecialchars($source, ENT_IGNORE, 'UTF-8'));
}
// Iteration provides nested tag protection
do
{
$temp = $source;
$source = $this->_cleanTags($source);
}
while ($temp !== $source);
return $source;
}
/**
* Internal method to strip a string of certain tags
*
* @param string $source Input string to be 'cleaned'
*
* @return string 'Cleaned' version of input parameter
*
* @since 1.7.0
* @deprecated 4.0 Use InputFilter::cleanTags() instead
*/
protected function _cleanTags($source)
{
return $this->cleanTags($source);
}
/**
* Internal method to strip a string of certain tags
*
* @param string $source Input string to be 'cleaned'
*
* @return string 'Cleaned' version of input parameter
*
* @since 3.5
*/
protected function cleanTags($source)
{
// First, pre-process this for illegal characters inside attribute values
$source = $this->_escapeAttributeValues($source);
// In the beginning we don't really have a tag, so result is empty
$result = '';
$offset = 0;
$length = strlen($source);
// Is there a tag? If so it will certainly start with a '<'.
$tagOpenStartOffset = strpos($source, '<');
// Is there any close tag
$tagOpenEndOffset = strpos($source, '>');
while ($offset < $length)
{
// Preserve '>' character which exists before related '<'
if ($tagOpenEndOffset !== false && ($tagOpenStartOffset === false || $tagOpenEndOffset < $tagOpenStartOffset))
{
$result .= substr($source, $offset, $tagOpenEndOffset - $offset) . '>';
$offset = $tagOpenEndOffset + 1;
// Search for a new closing indicator
$tagOpenEndOffset = strpos($source, '>', $offset);
continue;
}
// Add safe text appearing before the '<'
if ($tagOpenStartOffset > $offset)
{
$result .= substr($source, $offset, $tagOpenStartOffset - $offset);
$offset = $tagOpenStartOffset;
}
// There is no more tags
if ($tagOpenStartOffset === false && $tagOpenEndOffset === false)
{
$result .= substr($source, $offset, $length - $offset);
$offset = $length;
break;
}
// Remove every '<' character if '>' does not exists or we have '<>'
if ($tagOpenStartOffset !== false && $tagOpenEndOffset === false || $tagOpenStartOffset + 1 == $tagOpenEndOffset)
{
$offset++;
// Search for a new opening indicator
$tagOpenStartOffset = strpos($source, '<', $offset);
continue;
}
// Check for mal-formed tag where we have a second '<' before the '>'
$nextOpenStartOffset = strpos($source, '<', $tagOpenStartOffset + 1);
if ($nextOpenStartOffset !== false && $nextOpenStartOffset < $tagOpenEndOffset)
{
// At this point we have a mal-formed tag, skip previous '<'
$offset++;
// Set a new opening indicator position
$tagOpenStartOffset = $nextOpenStartOffset;
continue;
}
// Let's get some information about our tag and setup attribute pairs
// Now we have something like 'span class="" style=""', '/span', 'br/', 'br /' or 'hr disabled /'
$tagContent = substr($source, $offset + 1, $tagOpenEndOffset - 1 - $offset);
// All ASCII whitespaces replace by 0x20
$tagNormalized = preg_replace('/\s/', ' ', $tagContent);
$tagLength = strlen($tagContent);
$spaceOffset = strpos($tagNormalized, ' ');
// Are we an open tag or a close tag?
$isClosingTag = $tagContent[0] === '/' ? 1 : 0;
$isSelfClosingTag = substr($tagContent, -1) === '/' ? 1 : 0;
if ($spaceOffset !== false)
{
$tagName = substr($tagContent, $isClosingTag, $spaceOffset - $isClosingTag);
}
else
{
$tagName = substr($tagContent, $isClosingTag, $tagLength - $isClosingTag - $isSelfClosingTag);
}
/*
* Exclude all "non-regular" tagnames
* OR no tagname
* OR remove if xssauto is on and tag is blacklisted
*/
if (!$tagName
|| !preg_match("/^[a-z][a-z0-9]*$/i", $tagName)
|| ($this->xssAuto && in_array(strtolower($tagName), $this->tagBlacklist)))
{
$offset += $tagLength + 2;
$tagOpenStartOffset = strpos($source, '<', $offset);
$tagOpenEndOffset = strpos($source, '>', $offset);
// Strip tag
continue;
}
$attrSet = array();
/*
* Time to grab any attributes from the tag... need this section in
* case attributes have spaces in the values.
*/
while ($spaceOffset !== false && $spaceOffset + 1 < $tagLength)
{
$attrStartOffset = $spaceOffset + 1;
// Find position of equal and open quote
if (preg_match('#= *(")[^"]*(")#', $tagNormalized, $matches, PREG_OFFSET_CAPTURE, $attrStartOffset))
{
$equalOffset = $matches[0][1];
$quote1Offset = $matches[1][1];
$quote2Offset = $matches[2][1];
$nextSpaceOffset = strpos($tagNormalized, ' ', $quote2Offset);
}
else
{
$equalOffset = strpos($tagNormalized, '=', $attrStartOffset);
$quote1Offset = strpos($tagNormalized, '"', $attrStartOffset);
$nextSpaceOffset = strpos($tagNormalized, ' ', $attrStartOffset);
if ($quote1Offset !== false)
{
$quote2Offset = strpos($tagNormalized, '"', $quote1Offset + 1);
}
else
{
$quote2Offset = false;
}
}
// Do we have an attribute to process? [check for equal sign]
if ($tagContent[$attrStartOffset] !== '/'
&& ($equalOffset && $nextSpaceOffset && $nextSpaceOffset < $equalOffset || !$equalOffset))
{
// Search for attribute without value, ex: 'checked/' or 'checked '
if ($nextSpaceOffset)
{
$attrEndOffset = $nextSpaceOffset;
}
else
{
$attrEndOffset = strpos($tagContent, '/', $attrStartOffset);
if ($attrEndOffset === false)
{
$attrEndOffset = $tagLength;
}
}
// If there is an ending, use this, if not, do not worry.
if ($attrEndOffset > $attrStartOffset)
{
$attrSet[] = substr($tagContent, $attrStartOffset, $attrEndOffset - $attrStartOffset);
}
}
elseif ($equalOffset !== false)
{
/*
* If the attribute value is wrapped in quotes we need to grab the substring from
* the closing quote, otherwise grab until the next space.
*/
if ($quote1Offset !== false && $quote2Offset !== false)
{
// Add attribute, ex: 'class="body abc"'
$attrSet[] = substr($tagContent, $attrStartOffset, $quote2Offset + 1 - $attrStartOffset);
}
else
{
if ($nextSpaceOffset)
{
$attrEndOffset = $nextSpaceOffset;
}
else
{
$attrEndOffset = $tagLength;
}
// Add attribute, ex: 'class=body'
$attrSet[] = substr($tagContent, $attrStartOffset, $attrEndOffset - $attrStartOffset);
}
}
$spaceOffset = $nextSpaceOffset;
}
// Is our tag in the user input array?
$tagFound = in_array(strtolower($tagName), $this->tagsArray);
// If the tag is allowed let's append it to the output string.
if ((!$tagFound && $this->tagsMethod) || ($tagFound && !$this->tagsMethod))
{
// Reconstruct tag with allowed attributes
if ($isClosingTag)
{
$result .= "</$tagName>";
}
else
{
$attrSet = $this->_cleanAttributes($attrSet);
// Open or single tag
$result .= '<' . $tagName;
if ($attrSet)
{
$result .= ' ' . implode(' ', $attrSet);
}
// Reformat single tags to XHTML
if (strpos($source, "</$tagName>", $tagOpenStartOffset) !== false)
{
$result .= '>';
}
else
{
$result .= ' />';
}
}
}
$offset += $tagLength + 2;
if ($offset < $length)
{
// Find next tag's start and continue iteration
$tagOpenStartOffset = strpos($source, '<', $offset);
$tagOpenEndOffset = strpos($source, '>', $offset);
}
}
return $result;
}
/**
* Internal method to strip a tag of certain attributes
*
* @param array $attrSet Array of attribute pairs to filter
*
* @return array Filtered array of attribute pairs
*
* @since 1.7.0
* @deprecated 4.0 Use InputFilter::cleanAttributes() instead
*/
protected function _cleanAttributes($attrSet)
{
return $this->cleanAttributes($attrSet);
}
/**
* Escape < > and " inside attribute values
*
* @param string $source The source string.
*
* @return string Filtered string
*
* @since 3.5
*/
protected function escapeAttributeValues($source)
{
$alreadyFiltered = '';
$remainder = $source;
$badChars = array('<', '"', '>');
$escapedChars = array('<', '"', '>');
/*
* Process each portion based on presence of =" and "<space>, "/>, or ">
* See if there are any more attributes to process
*/
while (preg_match('#<[^>]*?=\s*?(\"|\')#s', $remainder, $matches, PREG_OFFSET_CAPTURE))
{
// Get the portion before the attribute value
$quotePosition = $matches[0][1];
$nextBefore = $quotePosition + strlen($matches[0][0]);
/*
* Figure out if we have a single or double quote and look for the matching closing quote
* Closing quote should be "/>, ">, "<space>, or " at the end of the string
*/
$quote = substr($matches[0][0], -1);
$pregMatch = ($quote == '"') ? '#(\"\s*/\s*>|\"\s*>|\"\s+|\"$)#' : "#(\'\s*/\s*>|\'\s*>|\'\s+|\'$)#";
// Get the portion after attribute value
if (preg_match($pregMatch, substr($remainder, $nextBefore), $matches, PREG_OFFSET_CAPTURE))
{
// We have a closing quote
$nextAfter = $nextBefore + $matches[0][1];
}
else
{
// No closing quote
$nextAfter = strlen($remainder);
}
// Get the actual attribute value
$attributeValue = substr($remainder, $nextBefore, $nextAfter - $nextBefore);
// Escape bad chars
$attributeValue = str_replace($badChars, $escapedChars, $attributeValue);
$attributeValue = $this->_stripCSSExpressions($attributeValue);
$alreadyFiltered .= substr($remainder, 0, $nextBefore) . $attributeValue . $quote;
$remainder = substr($remainder, $nextAfter + 1);
}
// At this point, we just have to return the $alreadyFiltered and the $remainder
return $alreadyFiltered . $remainder;
}
/**
* Try to convert to plaintext
*
* @param string $source The source string.
*
* @return string Plaintext string
*
* @since 1.7.0
* @deprecated 4.0 Use InputFilter::decode() instead
*/
protected function _decode($source)
{
return $this->decode($source);
}
/**
* Try to convert to plaintext
*
* @param string $source The source string.
*
* @return string Plaintext string
*
* @since 3.5
*/
protected function decode($source)
{
static $ttr;
if (!is_array($ttr))
{
// Entity decode
$trans_tbl = get_html_translation_table(HTML_ENTITIES, ENT_COMPAT, 'ISO-8859-1');
foreach ($trans_tbl as $k => $v)
{
$ttr[$v] = utf8_encode($k);
}
}
$source = strtr($source, $ttr);
// Convert decimal
$source = preg_replace_callback('/&#(\d+);/m', function($m)
{
return utf8_encode(chr($m[1]));
}, $source
);
// Convert hex
$source = preg_replace_callback('/&#x([a-f0-9]+);/mi', function($m)
{
return utf8_encode(chr(hexdec($m[1])));
}, $source
);
return $source;
}
/**
* Escape < > and " inside attribute values
*
* @param string $source The source string.
*
* @return string Filtered string
*
* @since 1.7.0
* @deprecated 4.0 Use InputFilter::escapeAttributeValues() instead
*/
protected function _escapeAttributeValues($source)
{
return $this->escapeAttributeValues($source);
}
/**
* Remove CSS Expressions in the form of `<property>:expression(...)`
*
* @param string $source The source string.
*
* @return string Filtered string
*
* @since 1.7.0
* @deprecated 4.0 Use InputFilter::stripCSSExpressions() instead
*/
protected function _stripCSSExpressions($source)
{
return $this->stripCSSExpressions($source);
}
/**
* Recursively strip Unicode Supplementary Characters from the source. Not: objects cannot be filtered.
*
* @param mixed $source The data to filter
*
* @return mixed The filtered result
*
* @since 3.5
*/
protected function stripUSC($source)
{
if (is_object($source))
{
return $source;
}
if (is_array($source))
{
$filteredArray = array();
foreach ($source as $k => $v)
{
$filteredArray[$k] = $this->stripUSC($v);
}
return $filteredArray;
}
return preg_replace('/[\xF0-\xF7].../s', "\xE2\xAF\x91", $source);
}
}
OutputFilter.php 0000604 00000006345 15245530366 0007740 0 ustar 00 <?php
/**
* Joomla! Content Management System
*
* @copyright (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\CMS\Filter;
defined('JPATH_PLATFORM') or die;
use Joomla\Filter\OutputFilter as BaseOutputFilter;
use Joomla\String\StringHelper;
use Joomla\CMS\Language\Language;
/**
* OutputFilter
*
* @since 1.7.0
*/
class OutputFilter extends BaseOutputFilter
{
/**
* This method processes a string and replaces all instances of & with & in links only.
*
* @param string $input String to process
*
* @return string Processed string
*
* @since 1.7.0
*/
public static function linkXHTMLSafe($input)
{
$regex = 'href="([^"]*(&(amp;){0})[^"]*)*?"';
return preg_replace_callback("#$regex#i", array('\\Joomla\\CMS\\Filter\\OutputFilter', '_ampReplaceCallback'), $input);
}
/**
* This method processes a string and escapes it for use in JavaScript
*
* @param string $string String to process
*
* @return string Processed text
*/
public static function stringJSSafe($string)
{
$chars = preg_split('//u', $string, -1, PREG_SPLIT_NO_EMPTY);
$new_str = '';
foreach ($chars as $chr)
{
$code = str_pad(dechex(StringHelper::ord($chr)), 4, '0', STR_PAD_LEFT);
if (strlen($code) < 5)
{
$new_str .= '\\u' . $code;
}
else
{
$new_str .= '\\u{' . $code . '}';
}
}
return $new_str;
}
/**
* This method processes a string and replaces all accented UTF-8 characters by unaccented
* ASCII-7 "equivalents", whitespaces are replaced by hyphens and the string is lowercase.
*
* @param string $string String to process
* @param string $language Language to transliterate to
*
* @return string Processed string
*
* @since 1.7.0
*/
public static function stringURLSafe($string, $language = '')
{
// Remove any '-' from the string since they will be used as concatenaters
$str = str_replace('-', ' ', $string);
// Transliterate on the language requested (fallback to current language if not specified)
$lang = $language == '' || $language == '*' ? \JFactory::getLanguage() : Language::getInstance($language);
$str = $lang->transliterate($str);
// Trim white spaces at beginning and end of alias and make lowercase
$str = trim(StringHelper::strtolower($str));
// Remove any duplicate whitespace, and ensure all characters are alphanumeric
$str = preg_replace('/(\s|[^A-Za-z0-9\-])+/', '-', $str);
// Trim dashes at beginning and end of alias
$str = trim($str, '-');
return $str;
}
/**
* Callback method for replacing & with & in a string
*
* @param string $m String to process
*
* @return string Replaced string
*
* @since 3.5
*/
public static function ampReplaceCallback($m)
{
$rx = '&(?!amp;)';
return preg_replace('#' . $rx . '#', '&', $m[0]);
}
/**
* Callback method for replacing & with & in a string
*
* @param string $m String to process
*
* @return string Replaced string
*
* @since 1.7.0
* @deprecated 4.0 Use OutputFilter::ampReplaceCallback() instead
*/
public static function _ampReplaceCallback($m)
{
return static::ampReplaceCallback($m);
}
}
Number.php 0000604 00000016045 15245567746 0006534 0 ustar 00 <?php
/**
* @package FOF
* @copyright Copyright (c)2010-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 2, or later
*/
namespace FOF30\Model\DataModel\Filter;
defined('_JEXEC') || die;
class Number extends AbstractFilter
{
/**
* The partial match is mapped to an exact match
*
* @param mixed $value The value to compare to
*
* @return string The SQL where clause for this search
*/
public function partial($value)
{
return $this->exact($value);
}
/**
* Perform a between limits match. When $include is true
* the condition tested is:
* $from <= VALUE <= $to
* When $include is false the condition tested is:
* $from < VALUE < $to
*
* @param mixed $from The lowest value to compare to
* @param mixed $to The higherst value to compare to
* @param boolean $include Should we include the boundaries in the search?
*
* @return string The SQL where clause for this search
*/
public function between($from, $to, $include = true)
{
$from = (float) $from;
$to = (float) $to;
if ($this->isEmpty($from) || $this->isEmpty($to))
{
return '';
}
$extra = '';
if ($include)
{
$extra = '=';
}
$from = $this->sanitiseValue($from);
$to = $this->sanitiseValue($to);
$sql = '((' . $this->getFieldName() . ' >' . $extra . ' ' . $from . ') AND ';
$sql .= '(' . $this->getFieldName() . ' <' . $extra . ' ' . $to . '))';
return $sql;
}
/**
* Perform an outside limits match. When $include is true
* the condition tested is:
* (VALUE <= $from) || (VALUE >= $to)
* When $include is false the condition tested is:
* (VALUE < $from) || (VALUE > $to)
*
* @param mixed $from The lowest value of the excluded range
* @param mixed $to The higherst value of the excluded range
* @param boolean $include Should we include the boundaries in the search?
*
* @return string The SQL where clause for this search
*/
public function outside($from, $to, $include = false)
{
$from = (float) $from;
$to = (float) $to;
if ($this->isEmpty($from) || $this->isEmpty($to))
{
return '';
}
$extra = '';
if ($include)
{
$extra = '=';
}
$from = $this->sanitiseValue($from);
$to = $this->sanitiseValue($to);
$sql = '((' . $this->getFieldName() . ' <' . $extra . ' ' . $from . ') OR ';
$sql .= '(' . $this->getFieldName() . ' >' . $extra . ' ' . $to . '))';
return $sql;
}
/**
* Perform an interval match. It's similar to a 'between' match, but the
* from and to values are calculated based on $value and $interval:
* $value - $interval < VALUE < $value + $interval
*
* @param integer|float $value The center value of the search space
* @param integer|float $interval The width of the search space
* @param boolean $include Should I include the boundaries in the search?
*
* @return string The SQL where clause
*/
public function interval($value, $interval, $include = true)
{
if ($this->isEmpty($value))
{
return '';
}
// Convert them to float, just to be sure
$value = (float) $value;
$interval = (float) $interval;
$from = $value - $interval;
$to = $value + $interval;
$extra = '';
if ($include)
{
$extra = '=';
}
$from = $this->sanitiseValue($from);
$to = $this->sanitiseValue($to);
$sql = '((' . $this->getFieldName() . ' >' . $extra . ' ' . $from . ') AND ';
$sql .= '(' . $this->getFieldName() . ' <' . $extra . ' ' . $to . '))';
return $sql;
}
/**
* Perform a range limits match. When $include is true
* the condition tested is:
* $from <= VALUE <= $to
* When $include is false the condition tested is:
* $from < VALUE < $to
*
* @param mixed $from The lowest value to compare to
* @param mixed $to The higherst value to compare to
* @param boolean $include Should we include the boundaries in the search?
*
* @return string The SQL where clause for this search
*/
public function range($from, $to, $include = true)
{
if ($this->isEmpty($from) && $this->isEmpty($to))
{
return '';
}
$extra = '';
if ($include)
{
$extra = '=';
}
$sql = [];
if ($from)
{
$sql[] = '(' . $this->getFieldName() . ' >' . $extra . ' ' . $from . ')';
}
if ($to)
{
$sql[] = '(' . $this->getFieldName() . ' <' . $extra . ' ' . $to . ')';
}
$sql = '(' . implode(' AND ', $sql) . ')';
return $sql;
}
/**
* Perform an interval match. It's similar to a 'between' match, but the
* from and to values are calculated based on $value and $interval:
* $value - $interval < VALUE < $value + $interval
*
* @param integer|float $value The starting value of the search space
* @param integer|float $interval The interval period of the search space
* @param boolean $include Should I include the boundaries in the search?
*
* @return string The SQL where clause
*/
public function modulo($value, $interval, $include = true)
{
if ($this->isEmpty($value) || $this->isEmpty($interval))
{
return '';
}
$extra = '';
if ($include)
{
$extra = '=';
}
$sql = '(' . $this->getFieldName() . ' >' . $extra . ' ' . $value . ' AND ';
$sql .= '(' . $this->getFieldName() . ' - ' . $value . ') % ' . $interval . ' = 0)';
return $sql;
}
/**
* Overrides the parent to handle floats in locales where the decimal separator is a comma instead of a dot
*
* @param mixed $value
* @param string $operator
*
* @return string
*/
public function search($value, $operator = '=')
{
$value = $this->sanitiseValue($value);
return parent::search($value, $operator);
}
/**
* Sanitises float values. Really ugly and desperate workaround. Read below.
*
* Some locales, such as el-GR, use a comma as the decimal separator. This means that $x = 1.23; echo (string) $x;
* will yield 1,23 (with a comma!) instead of 1.23 (with a dot!). This affects the way the SQL WHERE clauses are
* generated. All database servers expect a dot as the decimal separator. If they see a decimal with a comma as the
* separator they throw a SQL error.
*
* This method will try to replace commas with dots. I tried working around this with locale switching and the %F
* (capital F) format option in sprintf to no avail. I'm pretty sure I was doing something wrong, but I ran out of
* time trying to find an academically correct solution. The current implementation of sanitiseValue is a silly
* hack around the problem. If you have a proper –and better performing– solution please send in a PR and I'll put
* it to the test.
*
* @param mixed $value A string representing a number, integer, float or array of them.
*
* @return mixed The sanitised value, or null if the input wasn't numeric.
*/
public function sanitiseValue($value)
{
if (!is_numeric($value) && !is_string($value) && !is_array($value))
{
$value = null;
}
if (!is_array($value))
{
$value = str_replace(',', '.', (string) $value);
}
else
{
$value = array_map([$this, 'sanitiseValue'], $value);
}
return $value;
}
}
Date.php 0000604 00000012017 15245567746 0006154 0 ustar 00 <?php
/**
* @package FOF
* @copyright Copyright (c)2010-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 2, or later
*/
namespace FOF30\Model\DataModel\Filter;
defined('_JEXEC') || die;
class Date extends Text
{
/**
* Returns the default search method for this field.
*
* @return string
*/
public function getDefaultSearchMethod()
{
return 'exact';
}
/**
* Perform a between limits match. When $include is true
* the condition tested is:
* $from <= VALUE <= $to
* When $include is false the condition tested is:
* $from < VALUE < $to
*
* @param mixed $from The lowest value to compare to
* @param mixed $to The highest value to compare to
* @param boolean $include Should we include the boundaries in the search?
*
* @return string The SQL where clause for this search
*/
public function between($from, $to, $include = true)
{
if ($this->isEmpty($from) || $this->isEmpty($to))
{
return '';
}
$extra = '';
if ($include)
{
$extra = '=';
}
$sql = '((' . $this->getFieldName() . ' >' . $extra . ' ' . $this->db->q($from) . ') AND ';
$sql .= '(' . $this->getFieldName() . ' <' . $extra . ' ' . $this->db->q($to) . '))';
return $sql;
}
/**
* Perform an outside limits match. When $include is true
* the condition tested is:
* (VALUE <= $from) || (VALUE >= $to)
* When $include is false the condition tested is:
* (VALUE < $from) || (VALUE > $to)
*
* @param mixed $from The lowest value of the excluded range
* @param mixed $to The highest value of the excluded range
* @param boolean $include Should we include the boundaries in the search?
*
* @return string The SQL where clause for this search
*/
public function outside($from, $to, $include = false)
{
if ($this->isEmpty($from) || $this->isEmpty($to))
{
return '';
}
$extra = '';
if ($include)
{
$extra = '=';
}
$sql = '((' . $this->getFieldName() . ' <' . $extra . ' ' . $this->db->q($from) . ') AND ';
$sql .= '(' . $this->getFieldName() . ' >' . $extra . ' ' . $this->db->q($to) . '))';
return $sql;
}
/**
* Interval date search
*
* @param string $value The value to search
* @param string|array|object $interval The interval. Can be (+1 MONTH or array('value' => 1, 'unit' =>
* 'MONTH', 'sign' => '+'))
* @param boolean $include If the borders should be included
*
* @return string the sql string
*/
public function interval($value, $interval, $include = true)
{
if ($this->isEmpty($value) || $this->isEmpty($interval))
{
return '';
}
$interval = $this->getInterval($interval);
// Sanity check on $interval array
if (!isset($interval['sign']) || !isset($interval['value']) || !isset($interval['unit']))
{
return '';
}
if ($interval['sign'] == '+')
{
$function = 'DATE_ADD';
}
else
{
$function = 'DATE_SUB';
}
$extra = '';
if ($include)
{
$extra = '=';
}
$sql = '(' . $this->getFieldName() . ' >' . $extra . ' ' . $function;
$sql .= '(' . $this->getFieldName() . ', INTERVAL ' . $interval['value'] . ' ' . $interval['unit'] . '))';
return $sql;
}
/**
* Perform a between limits match. When $include is true
* the condition tested is:
* $from <= VALUE <= $to
* When $include is false the condition tested is:
* $from < VALUE < $to
*
* @param mixed $from The lowest value to compare to
* @param mixed $to The highest value to compare to
* @param boolean $include Should we include the boundaries in the search?
*
* @return string The SQL where clause for this search
*/
public function range($from, $to, $include = true)
{
if ($this->isEmpty($from) && $this->isEmpty($to))
{
return '';
}
$extra = '';
if ($include)
{
$extra = '=';
}
$sql = [];
if ($from)
{
$sql[] = '(' . $this->getFieldName() . ' >' . $extra . ' ' . $this->db->q($from) . ')';
}
if ($to)
{
$sql[] = '(' . $this->getFieldName() . ' <' . $extra . ' ' . $this->db->q($to) . ')';
}
$sql = '(' . implode(' AND ', $sql) . ')';
return $sql;
}
/**
* Parses an interval –which may be given as a string, array or object– into
* a standardised hash array that can then be used bu the interval() method.
*
* @param string|array|object $interval The interval expression to parse
*
* @return array The parsed, hash array form of the interval
*/
protected function getInterval($interval)
{
if (is_string($interval))
{
if (strlen($interval) > 2)
{
$interval = explode(" ", $interval);
$sign = ($interval[0] == '-') ? '-' : '+';
$value = (int) substr($interval[0], 1);
$interval = [
'unit' => $interval[1],
'value' => $value,
'sign' => $sign,
];
}
else
{
$interval = [
'unit' => 'MONTH',
'value' => 1,
'sign' => '+',
];
}
}
else
{
$interval = (array) $interval;
}
return $interval;
}
}
AbstractFilter.php 0000604 00000023301 15245567746 0010206 0 ustar 00 <?php
/**
* @package FOF
* @copyright Copyright (c)2010-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 2, or later
*/
namespace FOF30\Model\DataModel\Filter;
defined('_JEXEC') || die;
use FOF30\Model\DataModel\Filter\Exception\InvalidFieldObject;
use FOF30\Model\DataModel\Filter\Exception\NoDatabaseObject;
use InvalidArgumentException;
use JDatabaseDriver;
use ReflectionClass;
use ReflectionMethod;
abstract class AbstractFilter
{
/**
* The null value for this type
*
* @var mixed
*/
public $null_value = null;
protected $db = null;
/**
* The column name of the table field
*
* @var string
*/
protected $name = '';
/**
* The column type of the table field
*
* @var string
*/
protected $type = '';
/**
* Should I allow filtering against the number 0?
*
* @var bool
*/
protected $filterZero = true;
/**
* Prefix each table name with this table alias. For example, field bar normally creates a WHERE clause:
* `bar` = '1'
* If tableAlias is set to "foo" then the WHERE clause it generates becomes
* `foo`.`bar` = '1'
*
* @var null
*/
protected $tableAlias = null;
/**
* Constructor
*
* @param JDatabaseDriver $db The database object
* @param object $field The field information as taken from the db
*/
public function __construct($db, $field)
{
$this->db = $db;
if (!is_object($field) || !isset($field->name) || !isset($field->type))
{
throw new InvalidFieldObject;
}
$this->name = $field->name;
$this->type = $field->type;
if (isset ($field->filterZero))
{
$this->filterZero = $field->filterZero;
}
if (isset ($field->tableAlias))
{
$this->tableAlias = $field->tableAlias;
}
}
/**
* Creates a field Object based on the field column type
*
* @param object $field The field information
* @param array $config The field configuration (like the db object to use)
*
* @return AbstractFilter The Filter object
*
* @throws InvalidArgumentException
*/
public static function getField($field, $config = [])
{
if (!is_object($field) || !isset($field->name) || !isset($field->type))
{
throw new InvalidFieldObject;
}
$type = $field->type;
$classType = self::getFieldType($type);
$className = '\\FOF30\\Model\\DataModel\\Filter\\' . ucfirst($classType);
if (($classType !== false) && class_exists($className, true))
{
if (!isset($config['dbo']))
{
throw new NoDatabaseObject($className);
}
$db = $config['dbo'];
$field = new $className($db, $field);
return $field;
}
return null;
}
/**
* Get the class name based on the field Type
*
* @param string $type The type of the field
*
* @return string the class name suffix
*/
public static function getFieldType($type)
{
// Remove parentheses, indicating field options / size (they don't matter in type detection)
if (!empty($type))
{
[$type, ] = explode('(', $type);
}
$detectedType = null;
switch (trim($type))
{
case 'varchar':
case 'text':
case 'smalltext':
case 'longtext':
case 'char':
case 'mediumtext':
case 'character varying':
case 'nvarchar':
case 'nchar':
$detectedType = 'Text';
break;
case 'date':
case 'datetime':
case 'time':
case 'year':
case 'timestamp':
case 'timestamp without time zone':
case 'timestamp with time zone':
$detectedType = 'Date';
break;
case 'tinyint':
case 'smallint':
$detectedType = 'Boolean';
break;
}
// Sometimes we have character types followed by a space and some cruft. Let's handle them.
if (is_null($detectedType) && !empty($type))
{
[$type, ] = explode(' ', $type);
switch (trim($type))
{
case 'varchar':
case 'text':
case 'smalltext':
case 'longtext':
case 'char':
case 'mediumtext':
case 'nvarchar':
case 'nchar':
$detectedType = 'Text';
break;
case 'date':
case 'datetime':
case 'time':
case 'year':
case 'timestamp':
$detectedType = 'Date';
break;
case 'tinyint':
case 'smallint':
$detectedType = 'Boolean';
break;
default:
$detectedType = 'Number';
break;
}
}
// If all else fails assume it's a Number and hope for the best
if (empty($detectedType))
{
$detectedType = 'Number';
}
return $detectedType;
}
/**
* Is it a null or otherwise empty value?
*
* @param mixed $value The value to test for emptiness
*
* @return boolean
*/
public function isEmpty($value)
{
return (($value === $this->null_value) || empty($value))
&& !($this->filterZero && ($value === "0"));
}
/**
* Returns the default search method for a field. This always returns 'exact'
* and you are supposed to override it in specialised classes. The possible
* values are exact, partial, between and outside, unless something
* different is returned by getSearchMethods().
*
* @return string
* @see self::getSearchMethods()
*
*/
public function getDefaultSearchMethod()
{
return 'exact';
}
/**
* Return the search methods available for this field class,
*
* @return array
*/
public function getSearchMethods()
{
$ignore = [
'isEmpty', 'getField', 'getFieldType', '__construct', 'getDefaultSearchMethod', 'getSearchMethods',
'getFieldName',
];
$class = new ReflectionClass(__CLASS__);
$methods = $class->getMethods(ReflectionMethod::IS_PUBLIC);
$tmp = [];
foreach ($methods as $method)
{
$tmp[] = $method->name;
}
$methods = $tmp;
if ($methods = array_diff($methods, $ignore))
{
return $methods;
}
return [];
}
/**
* Perform an exact match (equality matching)
*
* @param mixed $value The value to compare to
*
* @return string The SQL where clause for this search
*/
public function exact($value)
{
if ($this->isEmpty($value))
{
return '';
}
if (is_array($value))
{
$db = $this->db;
$value = array_map([$db, 'quote'], $value);
return '(' . $this->getFieldName() . ' IN (' . implode(',', $value) . '))';
}
else
{
return $this->search($value, '=');
}
}
/**
* Perform a partial match (usually: search in string)
*
* @param mixed $value The value to compare to
*
* @return string The SQL where clause for this search
*/
abstract public function partial($value);
/**
* Perform a between limits match (usually: search for a value between
* two numbers or a date between two preset dates). When $include is true
* the condition tested is:
* $from <= VALUE <= $to
* When $include is false the condition tested is:
* $from < VALUE < $to
*
* @param mixed $from The lowest value to compare to
* @param mixed $to The highest value to compare to
* @param boolean $include Should we include the boundaries in the search?
*
* @return string The SQL where clause for this search
*/
abstract public function between($from, $to, $include = true);
/**
* Perform an outside limits match (usually: search for a value outside an
* area or a date outside a preset period). When $include is true
* the condition tested is:
* (VALUE <= $from) || (VALUE >= $to)
* When $include is false the condition tested is:
* (VALUE < $from) || (VALUE > $to)
*
* @param mixed $from The lowest value of the excluded range
* @param mixed $to The highest value of the excluded range
* @param boolean $include Should we include the boundaries in the search?
*
* @return string The SQL where clause for this search
*/
abstract public function outside($from, $to, $include = false);
/**
* Perform an interval search (usually: a date interval check)
*
* @param string $from The value to search
* @param string|array|object $interval The interval
*
* @return string The SQL where clause for this search
*/
abstract public function interval($from, $interval);
/**
* Perform a between limits match (usually: search for a value between
* two numbers or a date between two preset dates). When $include is true
* the condition tested is:
* $from <= VALUE <= $to
* When $include is false the condition tested is:
* $from < VALUE < $to
*
* @param mixed $from The lowest value to compare to
* @param mixed $to The highest value to compare to
* @param boolean $include Should we include the boundaries in the search?
*
* @return string The SQL where clause for this search
*/
abstract public function range($from, $to, $include = true);
/**
* Perform an modulo search
*
* @param integer|float $from The starting value of the search space
* @param integer|float $interval The interval period of the search space
* @param boolean $include Should I include the boundaries in the search?
*
* @return string The SQL where clause
*/
abstract public function modulo($from, $interval, $include = true);
/**
* Return the SQL where clause for a search
*
* @param mixed $value The value to search for
* @param string $operator The operator to use
*
* @return string The SQL where clause for this search
*/
public function search($value, $operator = '=')
{
if ($this->isEmpty($value))
{
return '';
}
$prefix = '';
if (substr($operator, 0, 1) == '!')
{
$prefix = 'NOT ';
$operator = substr($operator, 1);
}
return $prefix . '(' . $this->getFieldName() . ' ' . $operator . ' ' . $this->db->quote($value) . ')';
}
/**
* Get the field name
*
* @return string The field name
*/
public function getFieldName()
{
$name = $this->db->qn($this->name);
if (!empty($this->tableAlias))
{
$name = $this->db->qn($this->tableAlias) . '.' . $name;
}
return $name;
}
}
Exception/InvalidFieldObject.php 0000604 00000001161 15245567746 0012714 0 ustar 00 <?php
/**
* @package FOF
* @copyright Copyright (c)2010-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 2, or later
*/
namespace FOF30\Model\DataModel\Filter\Exception;
defined('_JEXEC') || die;
use Exception;
use InvalidArgumentException;
use Joomla\CMS\Language\Text;
class InvalidFieldObject extends InvalidArgumentException
{
public function __construct($message = "", $code = 500, Exception $previous = null)
{
if (empty($message))
{
$message = Text::_('LIB_FOF_MODEL_ERR_FILTER_INVALIDFIELD');
}
parent::__construct($message, $code, $previous);
}
}
Exception/NoDatabaseObject.php 0000604 00000001134 15245567746 0012363 0 ustar 00 <?php
/**
* @package FOF
* @copyright Copyright (c)2010-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 2, or later
*/
namespace FOF30\Model\DataModel\Filter\Exception;
defined('_JEXEC') || die;
use Exception;
use InvalidArgumentException;
use Joomla\CMS\Language\Text;
class NoDatabaseObject extends InvalidArgumentException
{
public function __construct($fieldType, $code = 500, Exception $previous = null)
{
$message = Text::sprintf('LIB_FOF_MODEL_ERR_FILTER_NODBOBJECT', $fieldType);
parent::__construct($message, $code, $previous);
}
}
Text.php 0000604 00000006234 15245567746 0006227 0 ustar 00 <?php
/**
* @package FOF
* @copyright Copyright (c)2010-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 2, or later
*/
namespace FOF30\Model\DataModel\Filter;
defined('_JEXEC') || die;
use JDatabaseDriver;
class Text extends AbstractFilter
{
/**
* Constructor
*
* @param JDatabaseDriver $db The database object
* @param object $field The field information as taken from the db
*/
public function __construct($db, $field)
{
parent::__construct($db, $field);
$this->null_value = '';
}
/**
* Returns the default search method for this field.
*
* @return string
*/
public function getDefaultSearchMethod()
{
return 'partial';
}
/**
* Perform a partial match (search in string)
*
* @param mixed $value The value to compare to
*
* @return string The SQL where clause for this search
*/
public function partial($value)
{
if ($this->isEmpty($value))
{
return '';
}
return '(' . $this->getFieldName() . ' LIKE ' . $this->db->quote('%' . $value . '%') . ')';
}
/**
* Perform an exact match (match string)
*
* @param mixed $value The value to compare to
*
* @return string The SQL where clause for this search
*/
public function exact($value)
{
if ($this->isEmpty($value))
{
return '';
}
if (is_array($value) || is_object($value))
{
$value = (array) $value;
$db = $this->db;
$value = array_map([$db, 'quote'], $value);
return '(' . $this->getFieldName() . ' IN (' . implode(',', $value) . '))';
}
return '(' . $this->getFieldName() . ' LIKE ' . $this->db->quote($value) . ')';
}
/**
* Dummy method; this search makes no sense for text fields
*
* @param mixed $from Ignored
* @param mixed $to Ignored
* @param boolean $include Ignored
*
* @return string Empty string
*/
public function between($from, $to, $include = true)
{
return '';
}
/**
* Dummy method; this search makes no sense for text fields
*
* @param mixed $from Ignored
* @param mixed $to Ignored
* @param boolean $include Ignored
*
* @return string Empty string
*/
public function outside($from, $to, $include = false)
{
return '';
}
/**
* Dummy method; this search makes no sense for text fields
*
* @param mixed $value Ignored
* @param mixed $interval Ignored
* @param boolean $include Ignored
*
* @return string Empty string
*/
public function interval($value, $interval, $include = true)
{
return '';
}
/**
* Dummy method; this search makes no sense for text fields
*
* @param mixed $from Ignored
* @param mixed $to Ignored
* @param boolean $include Ignored
*
* @return string Empty string
*/
public function range($from, $to, $include = false)
{
return '';
}
/**
* Dummy method; this search makes no sense for text fields
*
* @param mixed $from Ignored
* @param mixed $interval Ignored
* @param boolean $include Ignored
*
* @return string Empty string
*/
public function modulo($from, $interval, $include = false)
{
return '';
}
}
Boolean.php 0000604 00000000761 15245567746 0006661 0 ustar 00 <?php
/**
* @package FOF
* @copyright Copyright (c)2010-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 2, or later
*/
namespace FOF30\Model\DataModel\Filter;
defined('_JEXEC') || die;
class Boolean extends Number
{
/**
* Is it a null or otherwise empty value?
*
* @param mixed $value The value to test for emptiness
*
* @return boolean
*/
public function isEmpty($value)
{
return is_null($value) || ($value === '');
}
}
Relation.php 0000604 00000001410 15245567746 0007047 0 ustar 00 <?php
/**
* @package FOF
* @copyright Copyright (c)2010-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 2, or later
*/
namespace FOF30\Model\DataModel\Filter;
use JDatabaseQuery;
defined('_JEXEC') || die;
class Relation extends Number
{
/** @var JDatabaseQuery The COUNT sub-query to filter by */
protected $subQuery = null;
public function __construct($db, $relationName, $subQuery)
{
$field = (object) [
'name' => $relationName,
'type' => 'relation',
];
parent::__construct($db, $field);
$this->subQuery = $subQuery;
}
public function callback($value)
{
return call_user_func($value, $this->subQuery);
}
public function getFieldName()
{
return '(' . (string) $this->subQuery . ')';
}
}