MDL-82427 core_filters: Coding style tidyup

This commit is contained in:
Andrew Nicols 2024-07-10 13:41:20 +08:00
parent 01f7456a96
commit 8d284e3ce0
No known key found for this signature in database
GPG Key ID: 6D1E3157C8CFBF14
29 changed files with 637 additions and 596 deletions

View File

@ -39,7 +39,7 @@ use stdClass;
* You will then need to edit your moodle/config.php to invoke mathml_filter.php
* -------------------------------------------------------------------------
*
* @package filter
* @package filter_algebra
* @subpackage algebra
* @copyright 2004 Zbigniew Fiedorowicz fiedorow@math.ohio-state.edu
* Originally based on code provided by Bruno Vernier bruno@vsbeducation.ca
@ -50,39 +50,22 @@ class text_filter extends \core_filters\text_filter {
public function filter($text, array $options = []) {
global $CFG, $DB;
/// Do a quick check using stripos to avoid unnecessary wor
if (!preg_match('/<algebra/i',$text) && !strstr($text,'@@')) {
// Do a quick check using stripos to avoid unnecessary work.
if (!preg_match('/<algebra/i', $text) && !strstr($text, '@@')) {
return $text;
}
//restrict filtering to forum 130 (Maths Tools on moodle.org)
# $scriptname = $_SERVER['SCRIPT_NAME'];
# if (!strstr($scriptname,'/forum/')) {
# return $text;
# }
# if (strstr($scriptname,'post.php')) {
# $parent = forum_get_post_full($_GET['reply']);
# $discussion = $DB->get_record("forum_discussions",array("id"=>$parent->discussion));
# } else if (strstr($scriptname,'discuss.php')) {
# $discussion = $DB->get_record("forum_discussions",array("id"=>$_GET['d']));
# } else {
# return $text;
# }
# if ($discussion->forum != 130) {
# return $text;
# }
preg_match_all('/@(@@+)([^@])/',$text,$matches);
for ($i=0;$i<count($matches[0]);$i++) {
$replacement = str_replace('@','&#x00040;',$matches[1][$i]).$matches[2][$i];
$text = str_replace($matches[0][$i],$replacement,$text);
preg_match_all('/@(@@+)([^@])/', $text, $matches);
for ($i = 0; $i < count($matches[0]); $i++) {
$replacement = str_replace('@', '&#x00040;', $matches[1][$i]) . $matches[2][$i];
$text = str_replace($matches[0][$i], $replacement, $text);
}
// The following regular expression matches TeX expressions delimited by:
// <algebra> some algebraic input expression </algebra>
// or @@ some algebraic input expression @@
// or @@ some algebraic input expression @@.
preg_match_all('/<algebra>(.+?)<\/algebra>|@@(.+?)@@/is', $text, $matches);
for ($i=0; $i<count($matches[0]); $i++) {
for ($i = 0; $i < count($matches[0]); $i++) {
$algebra = $matches[1][$i] . $matches[2][$i];
// Look for some common false positives, and skip processing them.
@ -95,109 +78,113 @@ class text_filter extends \core_filters\text_filter {
continue;
}
$algebra = str_replace('<nolink>','',$algebra);
$algebra = str_replace('</nolink>','',$algebra);
$algebra = str_replace('<span class="nolink">','',$algebra);
$algebra = str_replace('</span>','',$algebra);
$algebra = str_replace('<nolink>', '', $algebra);
$algebra = str_replace('</nolink>', '', $algebra);
$algebra = str_replace('<span class="nolink">', '', $algebra);
$algebra = str_replace('</span>', '', $algebra);
$align = "middle";
if (preg_match('/^align=bottom /',$algebra)) {
$align = "text-bottom";
$algebra = preg_replace('/^align=bottom /','',$algebra);
} else if (preg_match('/^align=top /',$algebra)) {
$align = "text-top";
$algebra = preg_replace('/^align=top /','',$algebra);
if (preg_match('/^align=bottom /', $algebra)) {
$align = "text-bottom";
$algebra = preg_replace('/^align=bottom /', '', $algebra);
} else if (preg_match('/^align=top /', $algebra)) {
$align = "text-top";
$algebra = preg_replace('/^align=top /', '', $algebra);
}
$md5 = md5($algebra);
$filename = $md5 . ".gif";
if (! $texcache = $DB->get_record("cache_filters",array("filter"=>"algebra", "md5key"=>$md5))) {
$algebra = str_replace('&lt;','<',$algebra);
$algebra = str_replace('&gt;','>',$algebra);
$algebra = str_replace('<>','#',$algebra);
$algebra = str_replace('<=','%',$algebra);
$algebra = str_replace('>=','!',$algebra);
$algebra = preg_replace('/([=><%!#] *)-/',"\$1 zeroplace -",$algebra);
$algebra = str_replace('delta','zdelta',$algebra);
$algebra = str_replace('beta','bita',$algebra);
$algebra = str_replace('theta','thita',$algebra);
$algebra = str_replace('zeta','zita',$algebra);
$algebra = str_replace('eta','xeta',$algebra);
$algebra = str_replace('epsilon','zepslon',$algebra);
$algebra = str_replace('upsilon','zupslon',$algebra);
$algebra = preg_replace('!\r\n?!',' ',$algebra);
$algebra = escapeshellarg($algebra);
if ( (PHP_OS == "WINNT") || (PHP_OS == "WIN32") || (PHP_OS == "Windows")) {
$cmd = "cd $CFG->dirroot\\filter\\algebra & algebra2tex.pl $algebra";
} else {
$cmd = "cd $CFG->dirroot/filter/algebra; ./algebra2tex.pl $algebra";
}
$texexp = `$cmd`;
if (preg_match('/parsehilight/',$texexp)) {
$text = str_replace( $matches[0][$i],"<b>Syntax error:</b> " . $texexp,$text);
} else if ($texexp) {
$texexp = str_replace('zeroplace','',$texexp);
$texexp = str_replace('#','\not= ',$texexp);
$texexp = str_replace('%','\leq ',$texexp);
$texexp = str_replace('!','\geq ',$texexp);
$texexp = str_replace('\left{','{',$texexp);
$texexp = str_replace('\right}','}',$texexp);
$texexp = str_replace('\fun',' ',$texexp);
$texexp = str_replace('infty','\infty',$texexp);
$texexp = str_replace('alpha','\alpha',$texexp);
$texexp = str_replace('gamma','\gamma',$texexp);
$texexp = str_replace('iota','\iota',$texexp);
$texexp = str_replace('kappa','\kappa',$texexp);
$texexp = str_replace('lambda','\lambda',$texexp);
$texexp = str_replace('mu','\mu',$texexp);
$texexp = str_replace('nu','\nu',$texexp);
$texexp = str_replace('xi','\xi',$texexp);
$texexp = str_replace('rho','\rho',$texexp);
$texexp = str_replace('sigma','\sigma',$texexp);
$texexp = str_replace('tau','\tau',$texexp);
$texexp = str_replace('phi','\phi',$texexp);
$texexp = str_replace('chi','\chi',$texexp);
$texexp = str_replace('psi','\psi',$texexp);
$texexp = str_replace('omega','\omega',$texexp);
$texexp = str_replace('zdelta','\delta',$texexp);
$texexp = str_replace('bita','\beta',$texexp);
$texexp = str_replace('thita','\theta',$texexp);
$texexp = str_replace('zita','\zeta',$texexp);
$texexp = str_replace('xeta','\eta',$texexp);
$texexp = str_replace('zepslon','\epsilon',$texexp);
$texexp = str_replace('zupslon','\upsilon',$texexp);
$texexp = str_replace('\mbox{logten}','\mbox{log}_{10}',$texexp);
$texexp = str_replace('\mbox{acos}','\mbox{cos}^{-1}',$texexp);
$texexp = str_replace('\mbox{asin}','\mbox{sin}^{-1}',$texexp);
$texexp = str_replace('\mbox{atan}','\mbox{tan}^{-1}',$texexp);
$texexp = str_replace('\mbox{asec}','\mbox{sec}^{-1}',$texexp);
$texexp = str_replace('\mbox{acsc}','\mbox{csc}^{-1}',$texexp);
$texexp = str_replace('\mbox{acot}','\mbox{cot}^{-1}',$texexp);
$texexp = str_replace('\mbox{acosh}','\mbox{cosh}^{-1}',$texexp);
$texexp = str_replace('\mbox{asinh}','\mbox{sinh}^{-1}',$texexp);
$texexp = str_replace('\mbox{atanh}','\mbox{tanh}^{-1}',$texexp);
$texexp = str_replace('\mbox{asech}','\mbox{sech}^{-1}',$texexp);
$texexp = str_replace('\mbox{acsch}','\mbox{csch}^{-1}',$texexp);
$texexp = str_replace('\mbox{acoth}','\mbox{coth}^{-1}',$texexp);
//$texexp = preg_replace('/\\\frac{(.+?)}{\\\left\((.+?)\\\right\)}/s','\frac{'."\$1}{\$2}",$texexp);
$texexp = preg_replace('/\\\sqrt{(.+?),(.+?)}/s','\sqrt['. "\$2]{\$1}",$texexp);
$texexp = preg_replace('/\\\mbox{abs}\\\left\((.+?)\\\right\)/s',"|\$1|",$texexp);
$texexp = preg_replace('/\\\log\\\left\((.+?),(.+?)\\\right\)/s','\log_{'. "\$2}\\left(\$1\\right)",$texexp);
$texexp = preg_replace('/(\\\cos|\\\sin|\\\tan|\\\sec|\\\csc|\\\cot)([h]*)\\\left\((.+?),(.+?)\\\right\)/s',"\$1\$2^{". "\$4}\\left(\$3\\right)",$texexp);
$texexp = preg_replace('/\\\int\\\left\((.+?),(.+?),(.+?)\\\right\)/s','\int_'. "{\$2}^{\$3}\$1 ",$texexp);
$texexp = preg_replace('/\\\int\\\left\((.+?d[a-z])\\\right\)/s','\int '. "\$1 ",$texexp);
$texexp = preg_replace('/\\\lim\\\left\((.+?),(.+?),(.+?)\\\right\)/s','\lim_'. "{\$2\\to \$3}\$1 ",$texexp);
// Remove a forbidden keyword.
$texexp = str_replace('\mbox', '', $texexp);
$texcache = new stdClass();
$texcache->filter = 'algebra';
$texcache->version = 1;
$texcache->md5key = $md5;
$texcache->rawtext = $texexp;
$texcache->timemodified = time();
$DB->insert_record("cache_filters", $texcache, false);
$text = str_replace( $matches[0][$i], self::get_image_markup($filename, $texexp, '', '', $align), $text);
} else {
$text = str_replace( $matches[0][$i],"<b>Undetermined error:</b> " . $matches[0][$i], $text);
}
$md5 = md5($algebra);
$filename = $md5 . ".gif";
if (! $texcache = $DB->get_record("cache_filters", ["filter" => "algebra", "md5key" => $md5])) {
$algebra = str_replace('&lt;', '<', $algebra);
$algebra = str_replace('&gt;', '>', $algebra);
$algebra = str_replace('<>', '#', $algebra);
$algebra = str_replace('<=', '%', $algebra);
$algebra = str_replace('>=', '!', $algebra);
$algebra = preg_replace('/([=><%!#] *)-/', "\$1 zeroplace -", $algebra);
$algebra = str_replace('delta', 'zdelta', $algebra);
$algebra = str_replace('beta', 'bita', $algebra);
$algebra = str_replace('theta', 'thita', $algebra);
$algebra = str_replace('zeta', 'zita', $algebra);
$algebra = str_replace('eta', 'xeta', $algebra);
$algebra = str_replace('epsilon', 'zepslon', $algebra);
$algebra = str_replace('upsilon', 'zupslon', $algebra);
$algebra = preg_replace('!\r\n?!', ' ', $algebra);
$algebra = escapeshellarg($algebra);
if ((PHP_OS == "WINNT") || (PHP_OS == "WIN32") || (PHP_OS == "Windows")) {
$cmd = "cd $CFG->dirroot\\filter\\algebra & algebra2tex.pl $algebra";
} else {
$cmd = "cd $CFG->dirroot/filter/algebra; ./algebra2tex.pl $algebra";
}
$texexp = `$cmd`;
if (preg_match('/parsehilight/', $texexp)) {
$text = str_replace($matches[0][$i], "<b>Syntax error:</b> " . $texexp, $text);
} else if ($texexp) {
$texexp = str_replace('zeroplace', '', $texexp);
$texexp = str_replace('#', '\not= ', $texexp);
$texexp = str_replace('%', '\leq ', $texexp);
$texexp = str_replace('!', '\geq ', $texexp);
$texexp = str_replace('\left{', '{', $texexp);
$texexp = str_replace('\right}', '}', $texexp);
$texexp = str_replace('\fun', ' ', $texexp);
$texexp = str_replace('infty', '\infty', $texexp);
$texexp = str_replace('alpha', '\alpha', $texexp);
$texexp = str_replace('gamma', '\gamma', $texexp);
$texexp = str_replace('iota', '\iota', $texexp);
$texexp = str_replace('kappa', '\kappa', $texexp);
$texexp = str_replace('lambda', '\lambda', $texexp);
$texexp = str_replace('mu', '\mu', $texexp);
$texexp = str_replace('nu', '\nu', $texexp);
$texexp = str_replace('xi', '\xi', $texexp);
$texexp = str_replace('rho', '\rho', $texexp);
$texexp = str_replace('sigma', '\sigma', $texexp);
$texexp = str_replace('tau', '\tau', $texexp);
$texexp = str_replace('phi', '\phi', $texexp);
$texexp = str_replace('chi', '\chi', $texexp);
$texexp = str_replace('psi', '\psi', $texexp);
$texexp = str_replace('omega', '\omega', $texexp);
$texexp = str_replace('zdelta', '\delta', $texexp);
$texexp = str_replace('bita', '\beta', $texexp);
$texexp = str_replace('thita', '\theta', $texexp);
$texexp = str_replace('zita', '\zeta', $texexp);
$texexp = str_replace('xeta', '\eta', $texexp);
$texexp = str_replace('zepslon', '\epsilon', $texexp);
$texexp = str_replace('zupslon', '\upsilon', $texexp);
$texexp = str_replace('\mbox{logten}', '\mbox{log}_{10}', $texexp);
$texexp = str_replace('\mbox{acos}', '\mbox{cos}^{-1}', $texexp);
$texexp = str_replace('\mbox{asin}', '\mbox{sin}^{-1}', $texexp);
$texexp = str_replace('\mbox{atan}', '\mbox{tan}^{-1}', $texexp);
$texexp = str_replace('\mbox{asec}', '\mbox{sec}^{-1}', $texexp);
$texexp = str_replace('\mbox{acsc}', '\mbox{csc}^{-1}', $texexp);
$texexp = str_replace('\mbox{acot}', '\mbox{cot}^{-1}', $texexp);
$texexp = str_replace('\mbox{acosh}', '\mbox{cosh}^{-1}', $texexp);
$texexp = str_replace('\mbox{asinh}', '\mbox{sinh}^{-1}', $texexp);
$texexp = str_replace('\mbox{atanh}', '\mbox{tanh}^{-1}', $texexp);
$texexp = str_replace('\mbox{asech}', '\mbox{sech}^{-1}', $texexp);
$texexp = str_replace('\mbox{acsch}', '\mbox{csch}^{-1}', $texexp);
$texexp = str_replace('\mbox{acoth}', '\mbox{coth}^{-1}', $texexp);
$texexp = preg_replace('/\\\sqrt{(.+?),(.+?)}/s', '\sqrt[' . "\$2]{\$1}", $texexp);
$texexp = preg_replace('/\\\mbox{abs}\\\left\((.+?)\\\right\)/s', "|\$1|", $texexp);
$texexp = preg_replace('/\\\log\\\left\((.+?),(.+?)\\\right\)/s', '\log_{' . "\$2}\\left(\$1\\right)", $texexp);
$texexp = preg_replace(
'/(\\\cos|\\\sin|\\\tan|\\\sec|\\\csc|\\\cot)([h]*)\\\left\((.+?),(.+?)\\\right\)/s',
"\$1\$2^{" . "\$4}\\left(\$3\\right)",
$texexp,
);
$texexp = preg_replace('/\\\int\\\left\((.+?),(.+?),(.+?)\\\right\)/s', '\int_' . "{\$2}^{\$3}\$1 ", $texexp);
$texexp = preg_replace('/\\\int\\\left\((.+?d[a-z])\\\right\)/s', '\int ' . "\$1 ", $texexp);
$texexp = preg_replace('/\\\lim\\\left\((.+?),(.+?),(.+?)\\\right\)/s', '\lim_' . "{\$2\\to \$3}\$1 ", $texexp);
// Remove a forbidden keyword.
$texexp = str_replace('\mbox', '', $texexp);
$texcache = new stdClass();
$texcache->filter = 'algebra';
$texcache->version = 1;
$texcache->md5key = $md5;
$texcache->rawtext = $texexp;
$texcache->timemodified = time();
$DB->insert_record("cache_filters", $texcache, false);
$text = str_replace($matches[0][$i], filter_algebra_image($filename, $texexp, '', '', $align), $text);
} else {
$text = str_replace($matches[0][$i], "<b>Undetermined error:</b> " . $matches[0][$i], $text);
}
} else {
$text = str_replace($matches[0][$i], self::get_image_markup($filename, $texcache->rawtext), $text);
}
@ -251,7 +238,8 @@ class text_filter extends \core_filters\text_filter {
}
$anchorcontents .= "\" $style />";
if (!file_exists("$CFG->dataroot/filter/algebra/$imagefile") && has_capability('moodle/site:config', context_system::instance())) {
$imagefound = file_exists("$CFG->dataroot/filter/algebra/$imagefile");
if (!$imagefound && has_capability('moodle/site:config', context_system::instance())) {
$link = '/filter/algebra/algebradebug.php';
$action = null;
} else {

View File

@ -33,7 +33,6 @@ use context;
* @since Moodle 4.4
*/
class get_all_states extends external_api {
/**
* Webservice parameters.
*
@ -85,24 +84,22 @@ class get_all_states extends external_api {
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure(
[
'filters' => new external_multiple_structure(
new external_single_structure(
[
'contextlevel' => new external_value(PARAM_ALPHA, 'The context level where the filters are:
(coursecat, course, module).'),
'instanceid' => new external_value(PARAM_INT, 'The instance id of item associated with the context.'),
'contextid' => new external_value(PARAM_INT, 'The context id.'),
'filter' => new external_value(PARAM_PLUGIN, 'Filter plugin name.'),
'state' => new external_value(PARAM_INT, 'Filter state: 1 for on, -1 for off, -9999 if disabled.'),
'sortorder' => new external_value(PARAM_INT, 'Execution order.'),
]
return new external_single_structure([
'filters' => new external_multiple_structure(
new external_single_structure([
'contextlevel' => new external_value(
PARAM_ALPHA,
'The context level where the filters are: (coursecat, course, module).',
),
'All filters states'
),
'warnings' => new external_warnings(),
]
);
'instanceid' => new external_value(PARAM_INT, 'The instance id of item associated with the context.'),
'contextid' => new external_value(PARAM_INT, 'The context id.'),
'filter' => new external_value(PARAM_PLUGIN, 'Filter plugin name.'),
'state' => new external_value(PARAM_INT, 'Filter state: 1 for on, -1 for off, -9999 if disabled.'),
'sortorder' => new external_value(PARAM_INT, 'Execution order.'),
]),
'All filters states'
),
'warnings' => new external_warnings(),
]);
}
}

View File

@ -23,9 +23,6 @@
*/
namespace core_filters\external;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/filterlib.php');
use core_external\external_api;
use core_external\external_function_parameters;
@ -49,19 +46,18 @@ class get_available_in_context extends external_api {
* @since Moodle 3.4
*/
public static function execute_parameters() {
return new external_function_parameters (
array(
'contexts' => new external_multiple_structure(
new external_single_structure(
array(
'contextlevel' => new external_value(PARAM_ALPHA, 'The context level where the filters are:
(coursecat, course, module)'),
'instanceid' => new external_value(PARAM_INT, 'The instance id of item associated with the context.')
)
), 'The list of contexts to check.'
),
)
);
return new external_function_parameters([
'contexts' => new external_multiple_structure(
new external_single_structure([
'contextlevel' => new external_value(
PARAM_ALPHA,
'The context level where the filters are: (coursecat, course, module)',
),
'instanceid' => new external_value(PARAM_INT, 'The instance id of item associated with the context.'),
]),
'The list of contexts to check.'
),
]);
}
/**
@ -72,8 +68,12 @@ class get_available_in_context extends external_api {
* @since Moodle 3.4
*/
public static function execute($contexts) {
$params = self::validate_parameters(self::get_available_in_context_parameters(), array('contexts' => $contexts));
$filters = $warnings = array();
global $CFG;
require_once($CFG->libdir . '/filterlib.php');
$params = self::validate_parameters(self::execute_parameters(), ['contexts' => $contexts]);
$filters = $warnings = [];
foreach ($params['contexts'] as $contextinfo) {
try {
@ -81,12 +81,12 @@ class get_available_in_context extends external_api {
self::validate_context($context);
$contextinfo['contextid'] = $context->id;
} catch (Exception $e) {
$warnings[] = array(
$warnings[] = [
'item' => 'context',
'itemid' => $contextinfo['instanceid'],
'warningcode' => $e->getCode(),
'message' => $e->getMessage(),
);
];
continue;
}
$contextfilters = filter_get_available_in_context($context);
@ -96,10 +96,10 @@ class get_available_in_context extends external_api {
}
}
return array(
return [
'filters' => $filters,
'warnings' => $warnings,
);
];
}
/**
@ -109,24 +109,22 @@ class get_available_in_context extends external_api {
* @since Moodle 3.4
*/
public static function execute_returns() {
return new external_single_structure(
array(
'filters' => new external_multiple_structure(
new external_single_structure(
array(
'contextlevel' => new external_value(PARAM_ALPHA, 'The context level where the filters are:
(coursecat, course, module).'),
'instanceid' => new external_value(PARAM_INT, 'The instance id of item associated with the context.'),
'contextid' => new external_value(PARAM_INT, 'The context id.'),
'filter' => new external_value(PARAM_PLUGIN, 'Filter plugin name.'),
'localstate' => new external_value(PARAM_INT, 'Filter state: 1 for on, -1 for off, 0 if inherit.'),
'inheritedstate' => new external_value(PARAM_INT, '1 or 0 to use when localstate is set to inherit.'),
)
return new external_single_structure([
'filters' => new external_multiple_structure(
new external_single_structure([
'contextlevel' => new external_value(
PARAM_ALPHA,
'The context level where the filters are: (coursecat, course, module).',
),
'Available filters'
),
'warnings' => new external_warnings(),
)
);
'instanceid' => new external_value(PARAM_INT, 'The instance id of item associated with the context.'),
'contextid' => new external_value(PARAM_INT, 'The context id.'),
'filter' => new external_value(PARAM_PLUGIN, 'Filter plugin name.'),
'localstate' => new external_value(PARAM_INT, 'Filter state: 1 for on, -1 for off, 0 if inherit.'),
'inheritedstate' => new external_value(PARAM_INT, '1 or 0 to use when localstate is set to inherit.'),
]),
'Available filters'
),
'warnings' => new external_warnings(),
]);
}
}

View File

@ -36,22 +36,22 @@ class filter_manager {
* @var text_filter[][] This list of active filters, by context, for filtering content.
* An array contextid => ordered array of filter name => filter objects.
*/
protected $textfilters = array();
protected $textfilters = [];
/**
* @var text_filter[][] This list of active filters, by context, for filtering strings.
* An array contextid => ordered array of filter name => filter objects.
*/
protected $stringfilters = array();
protected $stringfilters = [];
/** @var array Exploded version of $CFG->stringfilters. */
protected $stringfilternames = array();
protected $stringfilternames = [];
/** @var filter_manager Holds the singleton instance. */
protected static $singletoninstance;
/**
* Constructor. Protected. Use {@link instance()} instead.
* Constructor. Protected. Use {@see instance()} instead.
*/
protected function __construct() {
$this->stringfilternames = filter_get_string_filters();
@ -65,7 +65,7 @@ class filter_manager {
public static function instance() {
global $CFG;
if (is_null(self::$singletoninstance)) {
if (!empty($CFG->perfdebug) and $CFG->perfdebug > 7) {
if (!empty($CFG->perfdebug) && $CFG->perfdebug > 7) {
self::$singletoninstance = new performance_measuring_filter_manager();
} else {
self::$singletoninstance = new self();
@ -88,9 +88,9 @@ class filter_manager {
* Unloads all filters and other cached information
*/
protected function unload_all_filters() {
$this->textfilters = array();
$this->stringfilters = array();
$this->stringfilternames = array();
$this->textfilters = [];
$this->stringfilters = [];
$this->stringfilternames = [];
}
/**
@ -100,8 +100,8 @@ class filter_manager {
*/
protected function load_filters($context) {
$filters = filter_get_active_in_context($context);
$this->textfilters[$context->id] = array();
$this->stringfilters[$context->id] = array();
$this->textfilters[$context->id] = [];
$this->stringfilters[$context->id] = [];
foreach ($filters as $filtername => $localconfig) {
$filter = $this->make_filter_object($filtername, $context, $localconfig);
if (is_null($filter)) {
@ -131,7 +131,7 @@ class filter_manager {
return new $filterclass($context, $localconfig);
}
$path = $CFG->dirroot .'/filter/'. $filtername .'/filter.php';
$path = $CFG->dirroot . '/filter/' . $filtername . '/filter.php';
if (!is_readable($path)) {
return null;
}
@ -148,13 +148,17 @@ class filter_manager {
/**
* Apply a list of filters to some content.
* @param string $text
* @param moodle_text_filter[] $filterchain array filter name => filter object.
* @param text_filter[] $filterchain array filter name => filter object.
* @param array $options options passed to the filters.
* @param array $skipfilters of filter names. Any filters that should not be applied to this text.
* @param null|array $skipfilters of filter names. Any filters that should not be applied to this text.
* @return string $text
*/
protected function apply_filter_chain($text, $filterchain, array $options = array(),
array $skipfilters = null) {
protected function apply_filter_chain(
$text,
$filterchain,
array $options = [],
?array $skipfilters = null
) {
if (!isset($options['stage'])) {
$filtermethod = 'filter';
} else if (in_array($options['stage'], ['pre_format', 'pre_clean', 'post_clean', 'string'], true)) {
@ -208,15 +212,19 @@ class filter_manager {
* @param string $text The text to filter
* @param context $context the context.
* @param array $options options passed to the filters
* @param array $skipfilters of filter names. Any filters that should not be applied to this text.
* @param null|array $skipfilters of filter names. Any filters that should not be applied to this text.
* @return string resulting text
*/
public function filter_text($text, $context, array $options = array(),
array $skipfilters = null) {
public function filter_text(
$text,
$context,
array $options = [],
?array $skipfilters = null
) {
$text = $this->apply_filter_chain($text, $this->get_text_filters($context), $options, $skipfilters);
if (!isset($options['stage']) || $options['stage'] === 'post_clean') {
// Remove <nolink> tags for XHTML compatibility after the last filtering stage.
$text = str_replace(array('<nolink>', '</nolink>'), '', $text);
$text = str_replace(['<nolink>', '</nolink>'], '', $text);
}
return $text;
}

View File

@ -77,15 +77,18 @@ class filter_object {
* list($linkobject->hreftagbegin, $linkobject->hreftagend, $linkobject->replacementphrase) =
* call_user_func_array($linkobject->replacementcallback, $linkobject->replacementcallbackdata);
* so the return should be an array [$hreftagbegin, $hreftagend, $replacementphrase], the last of which may be null.
* @param array $replacementcallbackdata data to be passed to $replacementcallback (optional).
* @param null|array $replacementcallbackdata data to be passed to $replacementcallback (optional).
*/
public function __construct($phrase, $hreftagbegin = '<span class="highlight">',
$hreftagend = '</span>',
$casesensitive = false,
$fullmatch = false,
$replacementphrase = null,
$replacementcallback = null,
array $replacementcallbackdata = null) {
public function __construct(
$phrase,
$hreftagbegin = '<span class="highlight">',
$hreftagend = '</span>',
$casesensitive = false,
$fullmatch = false,
$replacementphrase = null,
$replacementcallback = null,
?array $replacementcallbackdata = null
) {
$this->phrase = $phrase;
$this->hreftagbegin = $hreftagbegin;

View File

@ -1,5 +1,4 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
@ -17,6 +16,7 @@
namespace core_filters;
use core\context;
use moodleform;
defined('MOODLE_INTERNAL') || die();
@ -27,19 +27,24 @@ require_once($CFG->libdir . '/formslib.php');
* A Moodle form base class for editing local filter settings.
*
* @copyright Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package core_filters
*/
abstract class local_settings_form extends moodleform {
/** @var string The filter to manage */
protected $filter;
/** @var \core\context The context */
protected $context;
public function __construct($submiturl, $filter, $context) {
$this->filter = $filter;
$this->context = $context;
/**
* Create an instance of the form.
*
* @param string $submiturl
* @param string $filter
* @param context $context
*/
public function __construct(
string $submiturl,
/** @var string The filter to manage */
protected string $filter,
/** @var \core\context The context */
protected context $context,
) {
parent::__construct($submiturl);
}
@ -63,7 +68,7 @@ abstract class local_settings_form extends moodleform {
/**
* Override this method to add your form controls.
*
* @param $mform the form we are building. $this->_form, but passed in for convenience.
* @param \MoodleQuickForm $mform the form we are building. $this->_form, but passed in for convenience.
*/
abstract protected function definition_inner($mform);

View File

@ -28,21 +28,25 @@ use core\context;
*/
class null_filter_manager {
/**
* As for the equivalent {@link filter_manager} method.
* As for the equivalent {@see filter_manager} method.
*
* @param string $text The text to filter
* @param context $context not used.
* @param array $options not used
* @param array $skipfilters not used
* @param null|array $skipfilters not used
* @return string resulting text.
*/
public function filter_text($text, $context, array $options = array(),
array $skipfilters = null) {
public function filter_text(
$text,
$context,
array $options = [],
?array $skipfilters = null
) {
return $text;
}
/**
* As for the equivalent {@link filter_manager} method.
* As for the equivalent {@see filter_manager} method.
*
* @param string $string The text to filter
* @param context $context not used.

View File

@ -25,14 +25,15 @@ namespace core_filters;
*/
class performance_measuring_filter_manager extends filter_manager {
/** @var int number of filter objects created. */
protected $filterscreated = 0;
protected int $filterscreated = 0;
/** @var int number of calls to filter_text. */
protected $textsfiltered = 0;
protected int $textsfiltered = 0;
/** @var int number of calls to filter_string. */
protected $stringsfiltered = 0;
protected int $stringsfiltered = 0;
#[\Override]
protected function unload_all_filters() {
parent::unload_all_filters();
$this->filterscreated = 0;
@ -40,40 +41,51 @@ class performance_measuring_filter_manager extends filter_manager {
$this->stringsfiltered = 0;
}
#[\Override]
protected function make_filter_object($filtername, $context, $localconfig) {
$this->filterscreated++;
return parent::make_filter_object($filtername, $context, $localconfig);
}
public function filter_text($text, $context, array $options = array(),
array $skipfilters = null) {
#[\Override]
public function filter_text(
$text,
$context,
array $options = [],
?array $skipfilters = null
) {
if (!isset($options['stage']) || $options['stage'] === 'post_clean') {
$this->textsfiltered++;
}
return parent::filter_text($text, $context, $options, $skipfilters);
}
#[\Override]
public function filter_string($string, $context) {
$this->stringsfiltered++;
return parent::filter_string($string, $context);
}
/**
* Return performance information, in the form required by {@link get_performance_info()}.
* Return performance information, in the form required by {@see get_performance_info()}.
*
* @return array the performance info.
*/
public function get_performance_summary() {
return array(array(
'contextswithfilters' => count($this->textfilters),
'filterscreated' => $this->filterscreated,
'textsfiltered' => $this->textsfiltered,
'stringsfiltered' => $this->stringsfiltered,
), array(
'contextswithfilters' => 'Contexts for which filters were loaded',
'filterscreated' => 'Filters created',
'textsfiltered' => 'Pieces of content filtered',
'stringsfiltered' => 'Strings filtered',
));
public function get_performance_summary(): array {
return [
[
'contextswithfilters' => count($this->textfilters),
'filterscreated' => $this->filterscreated,
'textsfiltered' => $this->textsfiltered,
'stringsfiltered' => $this->stringsfiltered,
],
[
'contextswithfilters' => 'Contexts for which filters were loaded',
'filterscreated' => 'Filters created',
'textsfiltered' => 'Pieces of content filtered',
'stringsfiltered' => 'Strings filtered',
],
];
}
}

View File

@ -14,26 +14,16 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_filters\privacy;
/**
* Privacy Subsystem implementation for core_filters.
* Privacy Subsystem for core_filters implementing null_provider.
*
* @package core_filters
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_filters\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for core_filters implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.

View File

@ -74,7 +74,7 @@ abstract class text_filter {
* @param array $options options passed to the filters
* @return string the HTML content after the filtering has been applied.
*/
abstract public function filter($text, array $options = array());
abstract public function filter($text, array $options = []);
/**
* Filter text before changing format to HTML.

View File

@ -26,36 +26,32 @@ use core_filters\filter_object;
*/
class text_filter extends \core_filters\text_filter {
#[\Override]
public function filter($text, array $options = array()) {
public function filter($text, array $options = []) {
global $CFG, $DB, $USER;
// Trivial-cache - keyed on $cachedcourseid + $cacheduserid.
static $cachedcourseid = null;
static $cacheduserid = null;
static $coursecontentlist = array();
static $sitecontentlist = array();
static $coursecontentlist = [];
static $sitecontentlist = [];
static $nothingtodo;
// Try to get current course.
$coursectx = $this->context->get_course_context(false);
if (!$coursectx) {
// We could be in a course category so no entries for courseid == 0 will be found.
$courseid = 0;
} else {
$courseid = $coursectx->instanceid;
}
// We could be in a course category so no entries for courseid == 0 will be found.
$courseid = $coursectx?->instanceid ?: 0;
if ($cacheduserid !== $USER->id) {
// Invalidate all caches if the user changed.
$coursecontentlist = array();
$sitecontentlist = array();
$coursecontentlist = [];
$sitecontentlist = [];
$cacheduserid = $USER->id;
$cachedcourseid = $courseid;
$nothingtodo = false;
} else if ($courseid != get_site()->id && $courseid != 0 && $cachedcourseid != $courseid) {
// Invalidate course-level caches if the course id changed.
$coursecontentlist = array();
$coursecontentlist = [];
$cachedcourseid = $courseid;
$nothingtodo = false;
}
@ -65,6 +61,7 @@ class text_filter extends \core_filters\text_filter {
}
// If courseid == 0 only site entries will be returned.
$site = get_site();
if ($courseid == get_site()->id || $courseid == 0) {
$contentlist = & $sitecontentlist;
} else {
@ -73,12 +70,12 @@ class text_filter extends \core_filters\text_filter {
// Create a list of all the resources to search for. It may be cached already.
if (empty($contentlist)) {
$coursestosearch = $courseid ? array($courseid) : array(); // Add courseid if found
if (get_site()->id != $courseid) { // Add siteid if was not courseid
$coursestosearch = $courseid ? [$courseid] : []; // Add courseid if found.
if (get_site()->id != $courseid) { // Add siteid if was not courseid.
$coursestosearch[] = get_site()->id;
}
// We look for text field contents only if have autolink enabled (param1)
list ($coursesql, $params) = $DB->get_in_or_equal($coursestosearch);
// We look for text field contents only if have autolink enabled (param1).
[$coursesql, $params] = $DB->get_in_or_equal($coursestosearch);
$sql = 'SELECT dc.id AS contentid, dr.id AS recordid, dc.content AS content, d.id AS dataid
FROM {data} d
JOIN {data_fields} df ON df.dataid = d.id
@ -94,7 +91,7 @@ class text_filter extends \core_filters\text_filter {
}
foreach ($contents as $key => $content) {
// Trim empty or unlinkable concepts
// Trim empty or unlinkable concepts.
$currentcontent = trim(strip_tags($content->content));
if (empty($currentcontent)) {
unset($contents[$key]);
@ -103,7 +100,7 @@ class text_filter extends \core_filters\text_filter {
$contents[$key]->content = $currentcontent;
}
// Rule out any small integers. See bug 1446
// Rule out any small integers. See bug 1446.
$currentint = intval($currentcontent);
if ($currentint && (strval($currentint) == $currentcontent) && $currentint < 1000) {
unset($contents[$key]);
@ -118,17 +115,24 @@ class text_filter extends \core_filters\text_filter {
usort($contents, [self::class, 'sort_entries_by_length']);
foreach ($contents as $content) {
$href_tag_begin = '<a class="data autolink dataid'.$content->dataid.'" title="'.s($content->content).'" '.
'href="'.$CFG->wwwroot.'/mod/data/view.php?d='.$content->dataid.
'&amp;rid='.$content->recordid.'">';
$contentlist[] = new filter_object($content->content, $href_tag_begin, '</a>', false, true);
$hrefopen = '<a class="data autolink dataid' . $content->dataid . '" title="' . s($content->content) . '" ' .
'href="' . $CFG->wwwroot . '/mod/data/view.php?d=' . $content->dataid .
'&amp;rid=' . $content->recordid . '">';
$contentlist[] = new filter_object($content->content, $hrefopen, '</a>', false, true);
}
$contentlist = filter_remove_duplicates($contentlist); // Clean dupes
$contentlist = filter_remove_duplicates($contentlist); // Clean dupes.
}
return filter_phrases($text, $contentlist); // Look for all these links in the text
return filter_phrases($text, $contentlist); // Look for all these links in the text.
}
/**
* Helper to sort array values by content length.
*
* @param mixed $content0
* @param mixed $content1
* @return int
*/
private static function sort_entries_by_length($content0, $content1) {
$len0 = strlen($content0->content);
$len1 = strlen($content1->content);

View File

@ -31,22 +31,14 @@ use core_h5p\local\library\autoloader;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class text_filter extends \core_filters\text_filter {
/**
* @var boolean $loadresizerjs This is whether to request the resize.js script.
*/
/** @var bool $loadresizerjs This is whether to request the resize.js script */
private static $loadresizerjs = true;
/**
* Function filter replaces any h5p-sources.
*
* @param string $text HTML content to process
* @param array $options options passed to the filters
* @return string
*/
public function filter($text, array $options = array()) {
#[\Override]
public function filter($text, array $options = []) {
global $CFG, $USER;
if (!is_string($text) or empty($text)) {
if (!is_string($text) || empty($text)) {
// Non string data can not be filtered anyway.
return $text;
}
@ -60,18 +52,18 @@ class text_filter extends \core_filters\text_filter {
$allowedsources = get_config('filter_displayh5p', 'allowedsources');
$allowedsources = array_filter(array_map('trim', explode("\n", $allowedsources)));
$localsource = '('.preg_quote($CFG->wwwroot, '~').'/[^ &\#"\'<]*\.h5p([?][^ "\'<]*)?[^ \#"\'<]*)';
$localsource = '(' . preg_quote($CFG->wwwroot, '~') . '/[^ &\#"\'<]*\.h5p([?][^ "\'<]*)?[^ \#"\'<]*)';
$allowedsources[] = $localsource;
$params = array(
$params = [
'tagbegin' => '<iframe src="',
'tagend' => '</iframe>'
);
'tagend' => '</iframe>',
];
$specialchars = ['?', '&'];
$escapedspecialchars = ['\?', '&amp;'];
$h5pcontents = array();
$h5plinks = array();
$h5pcontents = [];
$h5plinks = [];
// Check all allowed sources.
foreach ($allowedsources as $source) {
@ -82,7 +74,7 @@ class text_filter extends \core_filters\text_filter {
// only if the user has the proper capabilities.
$params['canbeedited'] = (!empty($USER->editing)) && ($source == $localsource);
if ($source == $localsource) {
$params['tagbegin'] = '<iframe src="'.$CFG->wwwroot.'/h5p/embed.php?url=';
$params['tagbegin'] = '<iframe src="' . $CFG->wwwroot . '/h5p/embed.php?url=';
$escapechars = $source;
$ultimatepattern = $source;
} else {
@ -100,17 +92,33 @@ class text_filter extends \core_filters\text_filter {
continue;
}
$h5pcontenturl = new filter_object($source, null, null, false,
false, null, [$this, 'filterobject_prepare_replacement_callback'], $params + ['ish5plink' => false]);
$h5pcontenturl = new filter_object(
$source,
null,
null,
false,
false,
null,
[$this, 'filterobject_prepare_replacement_callback'],
$params + ['ish5plink' => false]
);
$h5pcontenturl->workregexp = '#'.$ultimatepattern.'#';
$h5pcontenturl->workregexp = '#' . $ultimatepattern . '#';
$h5pcontents[] = $h5pcontenturl;
// Regex to find h5p extensions in an <a> tag.
$linkregexp = '~<a [^>]*href=["\']('.$escapechars.'[^"\']*)["\'][^>]*>([^<]*)</a>~is';
$linkregexp = '~<a [^>]*href=["\'](' . $escapechars . '[^"\']*)["\'][^>]*>([^<]*)</a>~is';
$h5plinkurl = new filter_object($linkregexp, null, null, false,
false, null, [$this, 'filterobject_prepare_replacement_callback'], $params + ['ish5plink' => true]);
$h5plinkurl = new filter_object(
$linkregexp,
null,
null,
false,
false,
null,
[$this, 'filterobject_prepare_replacement_callback'],
$params + ['ish5plink' => true]
);
$h5plinkurl->workregexp = $linkregexp;
$h5plinks[] = $h5plinkurl;
}
@ -123,7 +131,8 @@ class text_filter extends \core_filters\text_filter {
// Apply filter inside <a> tag href attribute.
// We can not use filter_phrase function because it removes all tags and can not be applied in tag attributes.
foreach ($h5plinks as $h5plink) {
$text = preg_replace_callback($h5plink->workregexp,
$text = preg_replace_callback(
$h5plink->workregexp,
function ($matches) use ($h5plink) {
if ($matches[1] == $matches[2]) {
filter_prepare_phrase_for_replacement($h5plink);
@ -132,7 +141,9 @@ class text_filter extends \core_filters\text_filter {
} else {
return $matches[0];
}
}, $text);
},
$text
);
}
// The "Edit" button below each H5P content will be displayed only for users with permissions to edit the content (to
@ -140,7 +151,8 @@ class text_filter extends \core_filters\text_filter {
// As the H5P URL is required in order to get this information, this action can be done only here(the
// prepare_replacement_callback method has only the placeholders).
foreach ($h5pcontents as $h5pcontent) {
$text = preg_replace_callback($h5pcontent->workregexp,
$text = preg_replace_callback(
$h5pcontent->workregexp,
function ($matches) use ($h5pcontent) {
global $USER, $CFG;
@ -153,7 +165,7 @@ class text_filter extends \core_filters\text_filter {
}
$contenturl = $matches[0];
list($file, $h5p) = \core_h5p\api::get_original_content_from_pluginfile_url($contenturl, true, true);
[$file, $h5p] = \core_h5p\api::get_original_content_from_pluginfile_url($contenturl, true, true);
if ($file) {
filter_prepare_phrase_for_replacement($h5pcontent);
@ -178,7 +190,9 @@ class text_filter extends \core_filters\text_filter {
}
return $matches[0];
}, $text);
},
$text
);
}
$result = filter_phrases($text, $h5pcontents, null, null, false, true);
@ -187,18 +201,21 @@ class text_filter extends \core_filters\text_filter {
// embed.php page is requesting a PARAM_LOCALURL url parameter, so for files/directories use non-alphanumeric
// characters, we need to encode the parameter. Fetch url parameter added to embed.php and encode the whole url.
$localurl = '#\?url=([^" <]*[\/]+[^" <]*\.h5p)([?][^"]*)?#';
$result = preg_replace_callback($localurl,
$result = preg_replace_callback(
$localurl,
function ($matches) {
$baseurl = rawurlencode($matches[1]);
// Deal with possible parameters in the url link.
if (!empty($matches[2])) {
$match = explode('?', $matches[2]);
if (!empty($match[1])) {
$baseurl = $baseurl."&".$match[1];
$baseurl = $baseurl . "&" . $match[1];
}
}
return "?url=".$baseurl;
}, $result);
return "?url=" . $baseurl;
},
$result
);
return $result;
}
@ -214,7 +231,6 @@ class text_filter extends \core_filters\text_filter {
* @return array [$hreftagbegin, $hreftagend, $replacementphrase] for filterobject.
*/
public function filterobject_prepare_replacement_callback($tagbegin, $tagend, $urlmodifier, $canbeedited, $ish5plink) {
$sourceurl = "$1";
if ($urlmodifier !== "") {
$sourceurl .= $urlmodifier;

View File

@ -51,7 +51,6 @@ final class text_filter_test extends \advanced_testcase {
'filter_displayh5p'
);
$filterplugin = new text_filter(null, []);
$filteredtext = $filterplugin->filter($text);
@ -59,7 +58,7 @@ final class text_filter_test extends \advanced_testcase {
}
/**
* Provides texts to filter for the {@link self::test_filter_urls} method.
* Provides texts to filter for the {@see self::test_filter_urls} method.
*
* @return array
*/
@ -103,6 +102,7 @@ final class text_filter_test extends \advanced_testcase {
],
[
"https://generic.wordpress.soton.ac.uk/altc/wp-admin/admin-ajax.php?action=h5p_embed&amp;id=13",
// phpcs:ignore moodle.Files.LineLength.TooLong
"#<iframe src=\"https://generic.wordpress.soton.ac.uk/altc/wp-admin/admin-ajax.php\?action=h5p_embed\&amp\;id=13\"[^>]+?>#",
],
[

View File

@ -21,32 +21,29 @@ namespace filter_emailprotect;
*
* This class looks for email addresses in Moodle text and hides them using the Moodle obfuscate_text function.
*
* @package filter
* @package filter_emailprotect
* @subpackage emailprotect
* @copyright 2004 Mike Churchward
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class text_filter extends \core_filters\text_filter {
#[\Override]
public function filter($text, array $options = array()) {
/// Do a quick check using stripos to avoid unnecessary work
public function filter($text, array $options = []) {
// Do a quick check using stripos to avoid unnecessary work.
if (strpos($text, '@') === false) {
return $text;
}
/// There might be an email in here somewhere so continue ...
$matches = array();
/// regular expression to define a standard email string.
// Regular expression to define a standard email string.
$emailregex = '((?:[\w\.\-])+\@(?:(?:[a-zA-Z\d\-])+\.)+(?:[a-zA-Z\d]{2,4}))';
/// pattern to find a mailto link with the linked text.
$pattern = '|(<a\s+href\s*=\s*[\'"]?mailto:)'.$emailregex.'([\'"]?\s*>)'.'(.*)'.'(</a>)|iU';
// Pattern to find a mailto link with the linked text.
$pattern = '|(<a\s+href\s*=\s*[\'"]?mailto:)' . $emailregex . '([\'"]?\s*>)' . '(.*)' . '(</a>)|iU';
$text = preg_replace_callback($pattern, 'filter_emailprotect_alter_mailto', $text);
$text = preg_replace_callback($pattern, [self::class, 'alter_mailto'], $text);
/// pattern to find any other email address in the text.
$pattern = '/(^|\s+|>)'.$emailregex.'($|\s+|\.\s+|\.$|<)/i';
// Pattern to find any other email address in the text.
$pattern = '/(^|\s+|>)' . $emailregex . '($|\s+|\.\s+|\.$|<)/i';
$text = preg_replace_callback($pattern, 'filter_emailprotect_alter_email', $text);
$text = preg_replace_callback($pattern, [self::class, 'alter_email'], $text);

View File

@ -1,5 +1,4 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
@ -34,7 +33,7 @@ class text_filter extends \core_filters\text_filter {
* - dimension 2: theme.
* @var array
*/
protected static $emoticontexts = array();
protected static $emoticontexts = [];
/**
* Internal cache used for replacing. Multidimensional array;
@ -42,22 +41,14 @@ class text_filter extends \core_filters\text_filter {
* - dimension 2: theme.
* @var array
*/
protected static $emoticonimgs = array();
/**
* Apply the filter to the text
*
* @see \core_filters\filter_manager::apply_filter_chain()
* @param string $text to be processed by the text
* @param array $options filter options
* @return string text after processing
*/
public function filter($text, array $options = array()) {
protected static $emoticonimgs = [];
#[\Override]
public function filter($text, array $options = []) {
if (!isset($options['originalformat'])) {
// if the format is not specified, we are probably called by {@see format_string()}
// If the format is not specified, we are probably called by {@see format_string()}
// in that case, it would be dangerous to replace text with the image because it could
// be stripped. therefore, we do nothing
// be stripped. therefore, we do nothing.
return $text;
}
if (in_array($options['originalformat'], explode(',', get_config('filter_emoticon', 'formats')))) {
@ -66,10 +57,6 @@ class text_filter extends \core_filters\text_filter {
return $text;
}
////////////////////////////////////////////////////////////////////////////
// internal implementation starts here
////////////////////////////////////////////////////////////////////////////
/**
* Replace emoticons found in the text with their images
*
@ -82,12 +69,12 @@ class text_filter extends \core_filters\text_filter {
$lang = current_language();
$theme = $PAGE->theme->name;
if (!isset(self::$emoticontexts[$lang][$theme]) or !isset(self::$emoticonimgs[$lang][$theme])) {
// prepare internal caches
if (!isset(self::$emoticontexts[$lang][$theme]) || !isset(self::$emoticonimgs[$lang][$theme])) {
// Prepare internal caches.
$manager = get_emoticon_manager();
$emoticons = $manager->get_emoticons();
self::$emoticontexts[$lang][$theme] = array();
self::$emoticonimgs[$lang][$theme] = array();
self::$emoticontexts[$lang][$theme] = [];
self::$emoticonimgs[$lang][$theme] = [];
foreach ($emoticons as $emoticon) {
self::$emoticontexts[$lang][$theme][] = $emoticon->text;
self::$emoticonimgs[$lang][$theme][] = $OUTPUT->render($manager->prepare_renderable_emoticon($emoticon));
@ -107,7 +94,7 @@ class text_filter extends \core_filters\text_filter {
$exclude = 0;
// Define the patterns that mark the start of the forbidden zones.
$excludepattern = array('/^<script/is', '/^<span[^>]+class="nolink[^"]*"/is', '/^<pre/is');
$excludepattern = ['/^<script/is', '/^<span[^>]+class="nolink[^"]*"/is', '/^<pre/is'];
// Loop through the fragments.
foreach ($processing as $fragment) {
@ -122,20 +109,26 @@ class text_filter extends \core_filters\text_filter {
}
if ($exclude > 0) {
// If we are ignoring the fragment, then we must check if we may have reached the end of the zone.
if (strpos($fragment, '</span') !== false || strpos($fragment, '</script') !== false
|| strpos($fragment, '</pre') !== false) {
if (
strpos($fragment, '</span') !== false || strpos($fragment, '</script') !== false
|| strpos($fragment, '</pre') !== false
) {
$exclude -= 1;
// This is needed because of a double increment at the first element.
if ($exclude == 1) {
$exclude -= 1;
}
} else if (strpos($fragment, '<span') !== false || strpos($fragment, '<script') !== false
|| strpos($fragment, '<pre') !== false) {
} else if (
strpos($fragment, '<span') !== false || strpos($fragment, '<script') !== false
|| strpos($fragment, '<pre') !== false
) {
// If we find a nested tag we increase the exclusion level.
$exclude = $exclude + 1;
}
} else if (strpos($fragment, '<span') === false ||
strpos($fragment, '</span') === false) {
} else if (
strpos($fragment, '<span') === false ||
strpos($fragment, '</span') === false
) {
// This is the meat of the code - this is run every time.
// This code only runs for fragments that are not ignored (including the tags themselves).
$fragment = str_replace(self::$emoticontexts[$lang][$theme], self::$emoticonimgs[$lang][$theme], $fragment);

View File

@ -88,7 +88,7 @@ final class text_filter_test extends \advanced_testcase {
*
* @return array
*/
public static function filter_emoticon_provider() {
public static function filter_emoticon_provider(): array {
$grr = '(grr)';
return [
'FORMAT_MOODLE is not filtered' => [
@ -191,11 +191,12 @@ final class text_filter_test extends \advanced_testcase {
/**
* Get a copy of the filter configured for testing.
*
* @param array $args
* @param array ...$args
* @return \filter_emoticon\text_filter
*/
protected function get_testable_filter_emoticon(...$args): text_filter {
return new class extends text_filter {
// phpcs:ignore moodle.Commenting.MissingDocblock.MissingTestcaseMethodDescription
public function __construct(...$args) {
// Reset static emoticon caches.
parent::$emoticontexts = [];

View File

@ -23,6 +23,9 @@ use core\url;
use core_filters\filter_object;
use stdClass;
// phpcs:disable moodle.NamingConventions.ValidVariableName.VariableNameLowerCase -- GLOSSARY_EXCLUDEENTRY
// phpcs:disable moodle.NamingConventions.ValidVariableName.VariableNameUnderscore -- GLOSSARY_EXCLUDEENTRY
/**
* This filter provides automatic linking to glossary entries, aliases and categories when found inside every Moodle text.
*
@ -36,6 +39,7 @@ class text_filter extends \core_filters\text_filter {
/** @var null|cache_store cache used to store the terms for this course. */
protected $cache = null;
#[\Override]
public function setup($page, $context) {
if ($page->requires->should_create_one_time_item_now('filter_glossary_autolinker')) {
$page->requires->js_call_amd('filter_glossary/autolinker', 'init', []);
@ -72,7 +76,7 @@ class text_filter extends \core_filters\text_filter {
return $cached->cacheconceptlist;
}
list($glossaries, $allconcepts) = \mod_glossary\local\concept_cache::get_concepts($courseid);
[$glossaries, $allconcepts] = \mod_glossary\local\concept_cache::get_concepts($courseid);
if (!$allconcepts) {
$tocache = new stdClass();
@ -83,13 +87,20 @@ class text_filter extends \core_filters\text_filter {
return [];
}
$conceptlist = array();
$conceptlist = [];
foreach ($allconcepts as $concepts) {
foreach ($concepts as $concept) {
$conceptlist[] = new filter_object($concept->concept, null, null,
$concept->casesensitive, $concept->fullmatch, null,
[$this, 'filterobject_prepare_replacement_callback'], [$concept, $glossaries]);
$conceptlist[] = new filter_object(
$concept->concept,
null,
null,
$concept->casesensitive,
$concept->fullmatch,
null,
[$this, 'filterobject_prepare_replacement_callback'],
[$concept, $glossaries]
);
}
}
@ -120,30 +131,39 @@ class text_filter extends \core_filters\text_filter {
global $CFG;
if ($concept->category) { // Link to a category.
$title = get_string('glossarycategory', 'filter_glossary',
['glossary' => $glossaries[$concept->glossaryid], 'category' => $concept->concept]);
$link = new url('/mod/glossary/view.php',
['g' => $concept->glossaryid, 'mode' => 'cat', 'hook' => $concept->id]);
$attributes = array(
$title = get_string(
'glossarycategory',
'filter_glossary',
['glossary' => $glossaries[$concept->glossaryid], 'category' => $concept->concept]
);
$link = new url(
'/mod/glossary/view.php',
['g' => $concept->glossaryid, 'mode' => 'cat', 'hook' => $concept->id]
);
$attributes = [
'href' => $link,
'title' => $title,
'class' => 'glossary autolink category glossaryid' . $concept->glossaryid);
'class' => 'glossary autolink category glossaryid' . $concept->glossaryid, ];
} else { // Link to entry or alias.
$title = get_string('glossaryconcept', 'filter_glossary',
['glossary' => $glossaries[$concept->glossaryid], 'concept' => $concept->concept]);
$title = get_string(
'glossaryconcept',
'filter_glossary',
['glossary' => $glossaries[$concept->glossaryid], 'concept' => $concept->concept]
);
// Hardcoding dictionary format in the URL rather than defaulting
// to the current glossary format which may not work in a popup.
// for example "entry list" means the popup would only contain
// a link that opens another popup.
$link = new url('/mod/glossary/showentry.php',
['eid' => $concept->id, 'displayformat' => 'dictionary']);
$attributes = array(
$link = new url(
'/mod/glossary/showentry.php',
['eid' => $concept->id, 'displayformat' => 'dictionary']
);
$attributes = [
'href' => $link,
'title' => str_replace('&amp;', '&', $title), // Undo the s() mangling.
'class' => 'glossary autolink concept glossaryid' . $concept->glossaryid,
'data-entryid' => $concept->id,
);
];
}
// This flag is optionally set by resource_pluginfile()
@ -156,7 +176,7 @@ class text_filter extends \core_filters\text_filter {
}
#[\Override]
public function filter($text, array $options = array()) {
public function filter($text, array $options = []) {
global $GLOSSARY_EXCLUDEENTRY;
$conceptlist = $this->get_all_concepts();

View File

@ -26,7 +26,7 @@ use core\url;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class text_filter extends \core_filters\text_filter {
/*
/**
* Perform a mapping of the moodle language code to the equivalent for MathJax.
*
* @param string $moodlelangcode - The moodle language code - e.g. en_pirate
@ -38,7 +38,7 @@ class text_filter extends \core_filters\text_filter {
$mathjaxlangcodes = [
'ar', 'ast', 'bcc', 'bg', 'br', 'ca', 'cdo', 'ce', 'cs', 'cy', 'da', 'de', 'diq', 'en', 'eo', 'es', 'fa',
'fi', 'fr', 'gl', 'he', 'ia', 'it', 'ja', 'kn', 'ko', 'lb', 'lki', 'lt', 'mk', 'nl', 'oc', 'pl', 'pt',
'pt-br', 'qqq', 'ru', 'scn', 'sco', 'sk', 'sl', 'sv', 'th', 'tr', 'uk', 'vi', 'zh-hans', 'zh-hant'
'pt-br', 'qqq', 'ru', 'scn', 'sco', 'sk', 'sl', 'sv', 'th', 'tr', 'uk', 'vi', 'zh-hans', 'zh-hant',
];
// List of explicit mappings and known exceptions (moodle => mathjax).
@ -69,39 +69,29 @@ class text_filter extends \core_filters\text_filter {
return 'en';
}
/*
* Add the javascript to enable mathjax processing on this page.
*
* @param moodle_page $page The current page.
* @param context $context The current context.
*/
#[\Override]
public function setup($page, $context) {
if ($page->requires->should_create_one_time_item_now('filter_mathjaxloader-scripts')) {
$url = get_config('filter_mathjaxloader', 'httpsurl');
$lang = $this->map_language_code(current_language());
$url = new url($url, array('delayStartupUntil' => 'configured'));
$page->requires->js($url);
$config = get_config('filter_mathjaxloader', 'mathjaxconfig');
$wwwroot = new url('/');
$config = str_replace('{wwwroot}', $wwwroot->out(true), $config);
$params = array('mathjaxconfig' => $config, 'lang' => $lang);
$page->requires->js_call_amd('filter_mathjaxloader/loader', 'configure', [$params]);
if (!$page->requires->should_create_one_time_item_now('filter_mathjaxloader-scripts')) {
return;
}
$url = get_config('filter_mathjaxloader', 'httpsurl');
$lang = $this->map_language_code(current_language());
$url = new url($url, ['delayStartupUntil' => 'configured']);
$page->requires->js($url);
$config = get_config('filter_mathjaxloader', 'mathjaxconfig');
$wwwroot = new url('/');
$config = str_replace('{wwwroot}', $wwwroot->out(true), $config);
$params = ['mathjaxconfig' => $config, 'lang' => $lang];
$page->requires->js_call_amd('filter_mathjaxloader/loader', 'configure', [$params]);
}
/*
* This function wraps the filtered text in a span, that mathjaxloader is configured to process.
*
* @param string $text The text to filter.
* @param array $options The filter options.
*/
public function filter($text, array $options = array()) {
#[\Override]
public function filter($text, array $options = []) {
global $PAGE;
$legacy = get_config('filter_mathjaxloader', 'texfiltercompatibility');
@ -148,7 +138,7 @@ class text_filter extends \core_filters\text_filter {
// inside display math, only the outer display math is wrapped in
// a span. The span HTML inside a LaTex math environment would break
// MathJax. See MDL-61981.
list($text, $hasdisplayorinline) = $this->wrap_math_in_nolink($text);
[$text, $hasdisplayorinline] = $this->wrap_math_in_nolink($text);
}
if ($hasdisplayorinline || $hasextra) {
@ -205,8 +195,10 @@ class text_filter extends \core_filters\text_filter {
}
} else {
// Display math open.
if (($text[$i - 1] === '\\' && $text[$i] === ']' && $displaybracket) ||
($text[$i - 1] === '$' && $text[$i] === '$' && $displaydollar)) {
if (
($text[$i - 1] === '\\' && $text[$i] === ']' && $displaybracket) ||
($text[$i - 1] === '$' && $text[$i] === '$' && $displaydollar)
) {
// Display math ends, wrap the span around it.
$text = $this->insert_span($text, $displaystart, $i);
@ -221,7 +213,7 @@ class text_filter extends \core_filters\text_filter {
++$i;
}
return array($text, $changesdone);
return [$text, $changesdone];
}
/**
@ -237,10 +229,12 @@ class text_filter extends \core_filters\text_filter {
* the defined substring.
*/
protected function insert_span($text, $start, $end) {
return substr_replace($text,
'<span class="nolink">'. substr($text, $start, $end - $start + 1) .'</span>',
$start,
$end - $start + 1);
return substr_replace(
$text,
'<span class="nolink">' . substr($text, $start, $end - $start + 1) . '</span>',
$start,
$end - $start + 1
);
}
/**
@ -252,7 +246,7 @@ class text_filter extends \core_filters\text_filter {
* @return string Returns the input string with HTML tags escaped.
*/
private function escape_html_tag_wrapper(string $text): string {
return preg_replace_callback('/\{([^}]+)\}/', function(array $matches): string {
return preg_replace_callback('/\{([^}]+)\}/', function (array $matches): string {
$search = ['<', '>'];
$replace = ['&lt;', '&gt;'];
return str_replace($search, $replace, $matches[0]);

View File

@ -25,14 +25,14 @@ use core\context\system as context_system;
* @category test
* @copyright 2018 Markku Riekkinen
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \filter_mathjaxloader\text_filter
*/
final class filtermath_test extends \advanced_testcase {
/**
* Test the functionality of {@link text_filter::filter()}.
* Test the functionality of {@see text_filter::filter()}.
*
* @param string $inputtext The text given by the user.
* @param string $expected The expected output after filtering.
*
* @dataProvider math_filtering_inputs
*/
public function test_math_filtering($inputtext, $expected): void {
@ -45,53 +45,70 @@ final class filtermath_test extends \advanced_testcase {
*
* @return array of [inputtext, expectedoutput] tuples.
*/
public static function math_filtering_inputs() {
public static function math_filtering_inputs(): array {
// phpcs:disable moodle.Files.LineLength.TooLong
return [
// One inline formula.
['Some inline math \\( y = x^2 \\).',
'<span class="filter_mathjaxloader_equation">Some inline math <span class="nolink">\\( y = x^2 \\)</span>.</span>', ],
[
'Some inline math \\( y = x^2 \\).',
'<span class="filter_mathjaxloader_equation">Some inline math <span class="nolink">\\( y = x^2 \\)</span>.</span>',
],
// One inline and one display.
['Some inline math \\( y = x^2 \\) and display formula \\[ S = \\sum_{n=1}^{\\infty} 2^n \\]',
'<span class="filter_mathjaxloader_equation">Some inline math <span class="nolink">\\( y = x^2 \\)</span> and '
. 'display formula <span class="nolink">\\[ S = \\sum_{n=1}^{\\infty} 2^n \\]</span></span>', ],
[
'Some inline math \\( y = x^2 \\) and display formula \\[ S = \\sum_{n=1}^{\\infty} 2^n \\]',
'<span class="filter_mathjaxloader_equation">Some inline math <span class="nolink">\\( y = x^2 \\)</span> and '
. 'display formula <span class="nolink">\\[ S = \\sum_{n=1}^{\\infty} 2^n \\]</span></span>',
],
// One display and one inline.
['Display formula \\[ S = \\sum_{n=1}^{\\infty} 2^n \\] and some inline math \\( y = x^2 \\).',
'<span class="filter_mathjaxloader_equation">Display formula <span class="nolink">\\[ S = \\sum_{n=1}^{\\infty} 2^n \\]</span> and '
. 'some inline math <span class="nolink">\\( y = x^2 \\)</span>.</span>', ],
[
'Display formula \\[ S = \\sum_{n=1}^{\\infty} 2^n \\] and some inline math \\( y = x^2 \\).',
'<span class="filter_mathjaxloader_equation">Display formula <span class="nolink">\\[ S = \\sum_{n=1}^{\\infty} 2^n \\]</span> and '
. 'some inline math <span class="nolink">\\( y = x^2 \\)</span>.</span>',
],
// One inline and one display (with dollars).
['Some inline math \\( y = x^2 \\) and display formula $$ S = \\sum_{n=1}^{\\infty} 2^n $$',
'<span class="filter_mathjaxloader_equation">Some inline math <span class="nolink">\\( y = x^2 \\)</span> and '
. 'display formula <span class="nolink">$$ S = \\sum_{n=1}^{\\infty} 2^n $$</span></span>', ],
[
'Some inline math \\( y = x^2 \\) and display formula $$ S = \\sum_{n=1}^{\\infty} 2^n $$',
'<span class="filter_mathjaxloader_equation">Some inline math <span class="nolink">\\( y = x^2 \\)</span> and '
. 'display formula <span class="nolink">$$ S = \\sum_{n=1}^{\\infty} 2^n $$</span></span>',
],
// One display (with dollars) and one inline.
['Display formula $$ S = \\sum_{n=1}^{\\infty} 2^n $$ and some inline math \\( y = x^2 \\).',
'<span class="filter_mathjaxloader_equation">Display formula <span class="nolink">$$ S = \\sum_{n=1}^{\\infty} 2^n $$</span> and '
. 'some inline math <span class="nolink">\\( y = x^2 \\)</span>.</span>', ],
[
'Display formula $$ S = \\sum_{n=1}^{\\infty} 2^n $$ and some inline math \\( y = x^2 \\).',
'<span class="filter_mathjaxloader_equation">Display formula <span class="nolink">$$ S = \\sum_{n=1}^{\\infty} 2^n $$</span> and '
. 'some inline math <span class="nolink">\\( y = x^2 \\)</span>.</span>',
],
// Inline math environment nested inside display environment (using a custom LaTex macro).
['\\[ \\newcommand{\\False}{\\mathsf{F}} \\newcommand{\\NullF}{\\fbox{\\(\\False\\)}} \\] '
. 'Text with inline formula using the custom LaTex macro \\( a = \\NullF \\).',
'<span class="filter_mathjaxloader_equation"><span class="nolink">'
. '\\[ \\newcommand{\\False}{\\mathsf{F}} \\newcommand{\\NullF}{\\fbox{\\(\\False\\)}} \\]</span> '
. 'Text with inline formula using the custom LaTex macro <span class="nolink">\\( a = \\NullF \\)</span>.</span>', ],
[
'\\[ \\newcommand{\\False}{\\mathsf{F}} \\newcommand{\\NullF}{\\fbox{\\(\\False\\)}} \\] '
. 'Text with inline formula using the custom LaTex macro \\( a = \\NullF \\).',
'<span class="filter_mathjaxloader_equation"><span class="nolink">'
. '\\[ \\newcommand{\\False}{\\mathsf{F}} \\newcommand{\\NullF}{\\fbox{\\(\\False\\)}} \\]</span> '
. 'Text with inline formula using the custom LaTex macro <span class="nolink">\\( a = \\NullF \\)</span>.</span>',
],
// Nested environments and some more content.
['\\[ \\newcommand{\\False}{\\mathsf{F}} \\newcommand{\\NullF}{\\fbox{\\(\\False\\)}} \\] '
. 'Text with inline formula using the custom LaTex macro \\( a = \\NullF \\). Finally, a display formula '
. '$$ b = \\NullF $$',
'<span class="filter_mathjaxloader_equation"><span class="nolink">'
. '\\[ \\newcommand{\\False}{\\mathsf{F}} \\newcommand{\\NullF}{\\fbox{\\(\\False\\)}} \\]</span> '
. 'Text with inline formula using the custom LaTex macro <span class="nolink">\\( a = \\NullF \\)</span>. '
. 'Finally, a display formula <span class="nolink">$$ b = \\NullF $$</span></span>', ],
[
'\\[ \\newcommand{\\False}{\\mathsf{F}} \\newcommand{\\NullF}{\\fbox{\\(\\False\\)}} \\] '
. 'Text with inline formula using the custom LaTex macro \\( a = \\NullF \\). Finally, a display formula '
. '$$ b = \\NullF $$',
'<span class="filter_mathjaxloader_equation"><span class="nolink">'
. '\\[ \\newcommand{\\False}{\\mathsf{F}} \\newcommand{\\NullF}{\\fbox{\\(\\False\\)}} \\]</span> '
. 'Text with inline formula using the custom LaTex macro <span class="nolink">\\( a = \\NullF \\)</span>. '
. 'Finally, a display formula <span class="nolink">$$ b = \\NullF $$</span></span>',
],
// Broken math: the delimiters ($$) are not closed.
['Writing text and starting display math. $$ k = i^3 \\newcommand{\\False}{\\mathsf{F}} \\newcommand{\\NullF}{\\fbox{\\(\\False\\)}} '
. 'More text and inline math \\( x = \\NullF \\).',
'Writing text and starting display math. $$ k = i^3 \\newcommand{\\False}{\\mathsf{F}} \\newcommand{\\NullF}{\\fbox{\\(\\False\\)}} '
. 'More text and inline math \\( x = \\NullF \\).', ],
[
'Writing text and starting display math. $$ k = i^3 \\newcommand{\\False}{\\mathsf{F}} \\newcommand{\\NullF}{\\fbox{\\(\\False\\)}} '
. 'More text and inline math \\( x = \\NullF \\).',
'Writing text and starting display math. $$ k = i^3 \\newcommand{\\False}{\\mathsf{F}} \\newcommand{\\NullF}{\\fbox{\\(\\False\\)}} '
. 'More text and inline math \\( x = \\NullF \\).',
],
];
}
}

View File

@ -27,11 +27,10 @@ namespace filter_mathjaxloader;
*/
final class text_filter_test extends \advanced_testcase {
/**
* Test the functionality of {@link text_filter::map_language_code()}.
* Test the functionality of {@see text_filter::map_language_code()}.
*
* @param string $moodlelangcode the user's current language
* @param string $mathjaxlangcode the mathjax language to be used for the moodle language
*
* @dataProvider map_language_code_expected_mappings
*/
public function test_map_language_code($moodlelangcode, $mathjaxlangcode): void {
@ -44,7 +43,7 @@ final class text_filter_test extends \advanced_testcase {
*
* @return array of [moodlelangcode, mathjaxcode] tuples
*/
public static function map_language_code_expected_mappings() {
public static function map_language_code_expected_mappings(): array {
return [
['cz', 'cs'], // Explicit mapping.
['cs', 'cs'], // Implicit mapping (exact match).

View File

@ -28,7 +28,7 @@ use moodle_page;
* It is highly recommended to configure servers to be compatible with our slasharguments,
* otherwise the "?d=600x400" may not work.
*
* @package filter
* @package filter_mediaplugin
* @subpackage mediaplugin
* @copyright 2004 onwards Martin Dougiamas {@link http://moodle.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@ -56,11 +56,11 @@ class text_filter extends \core_filters\text_filter {
}
#[\Override]
public function filter($text, array $options = array()) {
public function filter($text, array $options = []) {
global $CFG, $PAGE;
if (!is_string($text) or empty($text)) {
// non string data can not be filtered anyway
if (!is_string($text) || empty($text)) {
// Non string data can not be filtered anyway.
return $text;
}
@ -70,7 +70,7 @@ class text_filter extends \core_filters\text_filter {
}
// Check permissions.
$this->trusted = !empty($options['noclean']) or !empty($CFG->allowobjectembed);
$this->trusted = !empty($options['noclean']) || !empty($CFG->allowobjectembed);
// Looking for tags.
$matches = preg_split('/(<[^>]*>)/i', $text, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
@ -92,14 +92,14 @@ class text_filter extends \core_filters\text_filter {
// and build them so that the callback function can check it for
// embedded content. Then we rebuild the string.
foreach ($matches as $idx => $tag) {
if (preg_match('|</'.$tagname.'>|', $tag) && !empty($validtag)) {
if (preg_match('|</' . $tagname . '>|', $tag) && !empty($validtag)) {
$validtag .= $tag;
// Given we now have a valid <a> tag to process it's time for
// ReDoS protection. Stop processing if a word is too large.
if (strlen($validtag) < 4096) {
if ($tagname === 'a') {
$processed = preg_replace_callback($re, array($this, 'callback'), $validtag);
$processed = preg_replace_callback($re, [$this, 'callback'], $validtag);
} else {
// For audio and video tags we just process them without precheck for embeddable markers.
$processed = $this->process_media_tag($validtag);
@ -110,8 +110,10 @@ class text_filter extends \core_filters\text_filter {
// Wipe it so we can catch any more instances to filter.
$validtag = '';
$processed = '';
} else if (preg_match('/<(a|video|audio)\s[^>]*/', $tag, $tagmatches) && $sizeofmatches > 1 &&
(empty($validtag) || $tagname === strtolower($tagmatches[1]))) {
} else if (
preg_match('/<(a|video|audio)\s[^>]*/', $tag, $tagmatches) && $sizeofmatches > 1 &&
(empty($validtag) || $tagname === strtolower($tagmatches[1]))
) {
// Looking for a starting tag. Ignore tags embedded into each other.
$validtag = $tag;
$tagname = strtolower($tagmatches[1]);
@ -147,7 +149,7 @@ class text_filter extends \core_filters\text_filter {
// Get name.
$name = trim($matches[2]);
if (empty($name) or strpos($name, 'http') === 0) {
if (empty($name) || strpos($name, 'http') === 0) {
$name = ''; // Use default name.
}

View File

@ -44,7 +44,7 @@ $enabledmediaplugins = \core\plugininfo\media::get_enabled_plugins();
\core\plugininfo\media::set_enabled_plugins('vimeo,youtube,videojs,html5audio,html5video');
// Create plugin.
$filterplugin = new \filter_mediaplugin\text_filter(null, array());
$filterplugin = new \filter_mediaplugin\text_filter(null, []);
// Note: As this is a developer test page, language strings are not used: all
// text is English-only.

View File

@ -40,30 +40,27 @@ namespace filter_multilang;
*/
class text_filter extends \core_filters\text_filter {
#[\Override]
public function filter($text, array $options = array()) {
public function filter($text, array $options = []) {
global $CFG;
// [pj] I don't know about you but I find this new implementation funny :P
// [skodak] I was laughing while rewriting it ;-)
// [nicolasconnault] Should support inverted attributes: <span class="multilang" lang="en"> (Doesn't work curently)
// [skodak] it supports it now, though it is slower - any better idea?
if (empty($text) or is_numeric($text)) {
if (empty($text) || is_numeric($text)) {
return $text;
}
if (empty($CFG->filter_multilang_force_old) and !empty($CFG->filter_multilang_converted)) {
// new syntax
if (empty($CFG->filter_multilang_force_old) && !empty($CFG->filter_multilang_converted)) {
// New syntax.
// phpcs:ignore moodle.Files.LineLength.TooLong
$search = '/(<span(\s+lang="[a-zA-Z0-9_-]+"|\s+class="multilang"){2}\s*>.*?<\/span>)(\s*<span(\s+lang="[a-zA-Z0-9_-]+"|\s+class="multilang"){2}\s*>.*?<\/span>)+/is';
} else {
// old syntax
// Old syntax.
// phpcs:ignore moodle.Files.LineLength.TooLong
$search = '/(<(?:lang|span) lang="[a-zA-Z0-9_-]*".*?>.*?<\/(?:lang|span)>)(\s*<(?:lang|span) lang="[a-zA-Z0-9_-]*".*?>.*?<\/(?:lang|span)>)+/is';
}
$result = preg_replace_callback($search, [$this, 'process_match'], $text);
if (is_null($result)) {
return $text; //error during regex processing (too many nested spans?)
return $text; // Error during regex processing (too many nested spans?).
} else {
return $result;
}
@ -83,7 +80,7 @@ class text_filter extends \core_filters\text_filter {
return $langblock[0];
}
$langlist = array();
$langlist = [];
foreach ($rawlanglist[1] as $index => $lang) {
$lang = str_replace('-', '_', strtolower($lang)); // Normalize languages.
$langlist[$lang] = $rawlanglist[2][$index];

View File

@ -56,7 +56,7 @@ final class get_available_in_context_test extends externallib_advanced_testcase
$this->setAdminUser();
$this->expectException('moodle_exception');
get_available_in_context::execute(array(array('contextlevel' => 'system', 'instanceid' => 0)));
get_available_in_context::execute([['contextlevel' => 'system', 'instanceid' => 0]]);
}
/**
@ -76,7 +76,7 @@ final class get_available_in_context_test extends externallib_advanced_testcase
filter_set_global_state($filter, TEXTFILTER_DISABLED);
}
$result = get_available_in_context::execute(array(array('contextlevel' => 'coursecat', 'instanceid' => $category->id)));
$result = get_available_in_context::execute([['contextlevel' => 'coursecat', 'instanceid' => $category->id]]);
$result = external_api::clean_returnvalue(get_available_in_context::execute_returns(), $result);
$this->assertEmpty($result['filters']); // No filters, all disabled.
$this->assertEmpty($result['warnings']);
@ -86,7 +86,7 @@ final class get_available_in_context_test extends externallib_advanced_testcase
$firstfilter = key($allfilters);
filter_set_global_state($firstfilter, TEXTFILTER_ON);
$result = get_available_in_context::execute(array(array('contextlevel' => 'coursecat', 'instanceid' => $category->id)));
$result = get_available_in_context::execute([['contextlevel' => 'coursecat', 'instanceid' => $category->id]]);
$result = external_api::clean_returnvalue(get_available_in_context::execute_returns(), $result);
$this->assertEmpty($result['warnings']);
$this->assertEquals($firstfilter, $result['filters'][0]['filter']); // OK, the filter is enabled.
@ -95,7 +95,7 @@ final class get_available_in_context_test extends externallib_advanced_testcase
// Set off the same filter at local context level.
filter_set_local_state($firstfilter, \context_coursecat::instance($category->id)->id, TEXTFILTER_OFF);
$result = get_available_in_context::execute(array(array('contextlevel' => 'coursecat', 'instanceid' => $category->id)));
$result = get_available_in_context::execute([['contextlevel' => 'coursecat', 'instanceid' => $category->id]]);
$result = external_api::clean_returnvalue(get_available_in_context::execute_returns(), $result);
$this->assertEmpty($result['warnings']);
$this->assertEquals($firstfilter, $result['filters'][0]['filter']); // OK, the filter is enabled globally.
@ -120,7 +120,7 @@ final class get_available_in_context_test extends externallib_advanced_testcase
filter_set_global_state($filter, TEXTFILTER_DISABLED);
}
$result = get_available_in_context::execute(array(array('contextlevel' => 'course', 'instanceid' => $course->id)));
$result = get_available_in_context::execute([['contextlevel' => 'course', 'instanceid' => $course->id]]);
$result = external_api::clean_returnvalue(get_available_in_context::execute_returns(), $result);
$this->assertEmpty($result['filters']); // No filters, all disabled at global level.
$this->assertEmpty($result['warnings']);
@ -130,7 +130,7 @@ final class get_available_in_context_test extends externallib_advanced_testcase
$firstfilter = key($allfilters);
filter_set_global_state($firstfilter, TEXTFILTER_ON);
$result = get_available_in_context::execute(array(array('contextlevel' => 'course', 'instanceid' => $course->id)));
$result = get_available_in_context::execute([['contextlevel' => 'course', 'instanceid' => $course->id]]);
$result = external_api::clean_returnvalue(get_available_in_context::execute_returns(), $result);
$this->assertEmpty($result['warnings']);
$this->assertEquals($firstfilter, $result['filters'][0]['filter']); // OK, the filter is enabled.
@ -139,7 +139,7 @@ final class get_available_in_context_test extends externallib_advanced_testcase
// Set off the same filter at local context level.
filter_set_local_state($firstfilter, \context_course::instance($course->id)->id, TEXTFILTER_OFF);
$result = get_available_in_context::execute(array(array('contextlevel' => 'course', 'instanceid' => $course->id)));
$result = get_available_in_context::execute([['contextlevel' => 'course', 'instanceid' => $course->id]]);
$result = external_api::clean_returnvalue(get_available_in_context::execute_returns(), $result);
$this->assertEmpty($result['warnings']);
$this->assertEquals($firstfilter, $result['filters'][0]['filter']); // OK, the filter is enabled globally.
@ -158,7 +158,7 @@ final class get_available_in_context_test extends externallib_advanced_testcase
// Create one activity.
$course = self::getDataGenerator()->create_course();
$forum = self::getDataGenerator()->create_module('forum', (object) array('course' => $course->id));
$forum = self::getDataGenerator()->create_module('forum', (object) ['course' => $course->id]);
// Get all filters and disable them all globally.
$allfilters = filter_get_all_installed();
@ -166,7 +166,7 @@ final class get_available_in_context_test extends externallib_advanced_testcase
filter_set_global_state($filter, TEXTFILTER_DISABLED);
}
$result = get_available_in_context::execute(array(array('contextlevel' => 'module', 'instanceid' => $forum->cmid)));
$result = get_available_in_context::execute([['contextlevel' => 'module', 'instanceid' => $forum->cmid]]);
$result = external_api::clean_returnvalue(get_available_in_context::execute_returns(), $result);
$this->assertEmpty($result['filters']); // No filters, all disabled at global level.
$this->assertEmpty($result['warnings']);
@ -176,7 +176,7 @@ final class get_available_in_context_test extends externallib_advanced_testcase
$firstfilter = key($allfilters);
filter_set_global_state($firstfilter, TEXTFILTER_ON);
$result = get_available_in_context::execute(array(array('contextlevel' => 'module', 'instanceid' => $forum->cmid)));
$result = get_available_in_context::execute([['contextlevel' => 'module', 'instanceid' => $forum->cmid]]);
$result = external_api::clean_returnvalue(get_available_in_context::execute_returns(), $result);
$this->assertEmpty($result['warnings']);
$this->assertEquals($firstfilter, $result['filters'][0]['filter']); // OK, the filter is enabled.
@ -185,7 +185,7 @@ final class get_available_in_context_test extends externallib_advanced_testcase
// Set off the same filter at local context level.
filter_set_local_state($firstfilter, \context_module::instance($forum->cmid)->id, TEXTFILTER_OFF);
$result = get_available_in_context::execute(array(array('contextlevel' => 'module', 'instanceid' => $forum->cmid)));
$result = get_available_in_context::execute([['contextlevel' => 'module', 'instanceid' => $forum->cmid]]);
$result = external_api::clean_returnvalue(get_available_in_context::execute_returns(), $result);
$this->assertEmpty($result['warnings']);
$this->assertEquals($firstfilter, $result['filters'][0]['filter']); // OK, the filter is enabled globally.
@ -195,7 +195,7 @@ final class get_available_in_context_test extends externallib_advanced_testcase
// Try user without permission, warning expected.
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$result = get_available_in_context::execute(array(array('contextlevel' => 'module', 'instanceid' => $forum->cmid)));
$result = get_available_in_context::execute([['contextlevel' => 'module', 'instanceid' => $forum->cmid]]);
$result = external_api::clean_returnvalue(get_available_in_context::execute_returns(), $result);
$this->assertNotEmpty($result['warnings']);
$this->assertEquals('context', $result['warnings'][0]['item']);

View File

@ -34,7 +34,7 @@ use stdClass;
* Note that there may be patent restrictions on the production of gif images
* in Canada and some parts of Western Europe and Japan until July 2004.
*
* @package filter
* @package filter_tex
* @subpackage tex
* @copyright 2004 Zbigniew Fiedorowicz fiedorow@math.ohio-state.edu
* Originally based on code provided by Bruno Vernier bruno@vsbeducation.ca
@ -45,73 +45,60 @@ class text_filter extends \core_filters\text_filter {
public function filter($text, array $options = []) {
global $CFG, $DB;
/// Do a quick check using stripos to avoid unnecessary work
if ((!preg_match('/<tex/i', $text)) &&
(strpos($text,'$$') === false) &&
(strpos($text,'\\[') === false) &&
// Do a quick check using stripos to avoid unnecessary work.
if (
(!preg_match('/<tex/i', $text)) &&
(strpos($text, '$$') === false) &&
(strpos($text, '\\[') === false) &&
(strpos($text, '\\(') === false) &&
(!preg_match('/\[tex/i',$text))) {
(!preg_match('/\[tex/i', $text))
) {
return $text;
}
# //restrict filtering to forum 130 (Maths Tools on moodle.org)
# $scriptname = $_SERVER['SCRIPT_NAME'];
# if (!strstr($scriptname,'/forum/')) {
# return $text;
# }
# if (strstr($scriptname,'post.php')) {
# $parent = forum_get_post_full($_GET['reply']);
# $discussion = $DB->get_record("forum_discussions", array("id"=>$parent->discussion));
# } else if (strstr($scriptname,'discuss.php')) {
# $discussion = $DB->get_record("forum_discussions", array("id"=>$_GET['d']));
# } else {
# return $text;
# }
# if ($discussion->forum != 130) {
# return $text;
# }
$text .= ' ';
preg_match_all('/\$(\$\$+?)([^\$])/s',$text,$matches);
for ($i=0; $i<count($matches[0]); $i++) {
$replacement = str_replace('$','&#x00024;', $matches[1][$i]).$matches[2][$i];
preg_match_all('/\$(\$\$+?)([^\$])/s', $text, $matches);
for ($i = 0; $i < count($matches[0]); $i++) {
$replacement = str_replace('$', '&#x00024;', $matches[1][$i]) . $matches[2][$i];
$text = str_replace($matches[0][$i], $replacement, $text);
}
// The following regular expression matches TeX expressions delimited by:
// <tex> TeX expression </tex>
// or <tex alt="My alternative text to be used instead of the TeX form"> TeX expression </tex>
// or $$ TeX expression $$
// or \[ TeX expression \] // original tag of MathType and TeXaide (dlnsk)
// or [tex] TeX expression [/tex] // somtime it's more comfortable than <tex> (dlnsk)
$rules = array(
// or \[ TeX expression \] // original tag of MathType and TeXaide
// or [tex] TeX expression [/tex] // somtime it's more comfortable than <tex>.
$rules = [
'<tex(?:\s+alt=["\'](.*?)["\'])?>(.+?)<\/tex>',
'\$\$(.+?)\$\$',
'\\\\\[(.+?)\\\\\]',
'\\\\\((.+?)\\\\\)',
'\\[tex\\](.+?)\\[\/tex\\]'
);
'\\[tex\\](.+?)\\[\/tex\\]',
];
$megarule = '/' . implode('|', $rules) . '/is';
preg_match_all($megarule, $text, $matches);
for ($i=0; $i<count($matches[0]); $i++) {
for ($i = 0; $i < count($matches[0]); $i++) {
$texexp = '';
for ($j = 0; $j < count($rules); $j++) {
$texexp .= $matches[$j + 2][$i];
}
$alt = $matches[1][$i];
$texexp = str_replace('<nolink>','',$texexp);
$texexp = str_replace('</nolink>','',$texexp);
$texexp = str_replace('<span class="nolink">','',$texexp);
$texexp = str_replace('</span>','',$texexp);
$texexp = preg_replace("/<br[[:space:]]*\/?>/i", '', $texexp); //dlnsk
$texexp = str_replace('<nolink>', '', $texexp);
$texexp = str_replace('</nolink>', '', $texexp);
$texexp = str_replace('<span class="nolink">', '', $texexp);
$texexp = str_replace('</span>', '', $texexp);
$texexp = preg_replace("/<br[[:space:]]*\/?>/i", '', $texexp);
$align = "middle";
if (preg_match('/^align=bottom /',$texexp)) {
$align = "text-bottom";
$texexp = preg_replace('/^align=bottom /','',$texexp);
} else if (preg_match('/^align=top /',$texexp)) {
$align = "text-top";
$texexp = preg_replace('/^align=top /','',$texexp);
if (preg_match('/^align=bottom /', $texexp)) {
$align = "text-bottom";
$texexp = preg_replace('/^align=bottom /', '', $texexp);
} else if (preg_match('/^align=top /', $texexp)) {
$align = "text-top";
$texexp = preg_replace('/^align=top /', '', $texexp);
}
// decode entities encoded by editor, luckily there is very little chance of double decoding
// Decode entities encoded by editor, luckily there is very little chance of double decoding.
$texexp = html_entity_decode($texexp, ENT_QUOTES, 'UTF-8');
if ($texexp === '') {
@ -122,7 +109,7 @@ class text_filter extends \core_filters\text_filter {
$texexp = clean_param($texexp, PARAM_TEXT);
$md5 = md5($texexp);
if (!$DB->record_exists("cache_filters", array("filter"=>"tex", "md5key"=>$md5))) {
if (!$DB->record_exists("cache_filters", ["filter" => "tex", "md5key" => $md5])) {
$texcache = new stdClass();
$texcache->filter = 'tex';
$texcache->version = 1;
@ -135,8 +122,8 @@ class text_filter extends \core_filters\text_filter {
if ($convertformat == 'svg' && !core_useragent::supports_svg()) {
$convertformat = 'png';
}
$filename = $md5.".{$convertformat}";
$text = str_replace( $matches[0][$i], self::get_image_markup($filename, $texexp, 0, 0, $align, $alt), $text);
$filename = $md5 . ".{$convertformat}";
$text = str_replace($matches[0][$i], $this->get_image_markup($filename, $texexp, 0, 0, $align, $alt), $text);
}
return $text;
}
@ -163,11 +150,11 @@ class text_filter extends \core_filters\text_filter {
global $CFG, $OUTPUT;
if (!$imagefile) {
throw new coding_exception('image file argument empty in get_image_markup()');
throw new coding_exception('Image file argument empty in get_image_markup()');
}
// Work out any necessary inline style.
$rules = array();
$rules = [];
if ($align !== 'middle') {
$rules[] = 'vertical-align:' . $align . ';';
}
@ -189,7 +176,7 @@ class text_filter extends \core_filters\text_filter {
// users (to provide a text equivalent to the equation) while the title
// is there as a convenience for sighted users who want to see the TeX
// code.
$title = 'title="'.s($tex).'"';
$title = 'title="' . s($tex) . '"';
if ($alt === '') {
$alt = s($tex);
@ -199,21 +186,24 @@ class text_filter extends \core_filters\text_filter {
// Build the output.
$anchorcontents = "<img class=\"texrender\" $title alt=\"$alt\" src=\"";
if ($CFG->slasharguments) { // Use this method if possible for better caching
if ($CFG->slasharguments) {
// Use this method if possible for better client-side caching.
$anchorcontents .= "$CFG->wwwroot/filter/tex/pix.php/$imagefile";
} else {
$anchorcontents .= "$CFG->wwwroot/filter/tex/pix.php?file=$imagefile";
}
$anchorcontents .= "\" $style/>";
if (!file_exists("$CFG->dataroot/filter/tex/$imagefile") && has_capability('moodle/site:config', context_system::instance())) {
$imagefound = file_exists("$CFG->dataroot/filter/tex/$imagefile");
if (!$imagefound && has_capability('moodle/site:config', context_system::instance())) {
$link = '/filter/tex/texdebug.php';
$action = null;
} else {
$link = new url('/filter/tex/displaytex.php', array('texexp'=>$tex));
$action = new popup_action('click', $link, 'popup', array('width'=>320,'height'=>240));
$link = new url('/filter/tex/displaytex.php', ['texexp' => $tex]);
$action = new popup_action('click', $link, 'popup', ['width' => 320, 'height' => 240]);
}
$output = $OUTPUT->action_link($link, $anchorcontents, $action, array('title'=>'TeX')); //TODO: the popups do not work when text caching is enabled!!
// TODO: the popups do not work when text caching is enabled.
$output = $OUTPUT->action_link($link, $anchorcontents, $action, ['title' => 'TeX']);
$output = "<span class=\"MathJax_Preview\">$output</span><script type=\"math/tex\">$tex</script>";
return $output;

View File

@ -81,7 +81,7 @@ final class text_filter_test extends \advanced_testcase {
['$', '$', false],
['(', ')', false],
['[', ']', false],
['$$', '\\]', false]
['$$', '\\]', false],
];
}
}

View File

@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace filter_tidy;
/**
* HTML tidy text filter.
*
@ -36,8 +38,8 @@
class text_filter extends \core_filters\text_filter {
#[\Override]
public function filter($text, array $options = []) {
// Configuration for tidy. Feel free to tune for your needs, e.g. to allow
// proprietary markup.
// Configuration for tidy.
// See https://api.html-tidy.org/tidy/quickref_5.0.0.html for details.
$tidyoptions = [
'output-xhtml' => true,
'show-body-only' => true,
@ -53,12 +55,11 @@ class text_filter extends \core_filters\text_filter {
return $text;
}
// If enabled: run tidy over the entire string.
if (function_exists('tidy_repair_string')) {
if (extension_loaded('tidy')) {
$currentlocale = \core\locale::get_locale();
try {
$text = tidy_repair_string($text, $tidyoptions, 'utf8');
$text = (new \tidy())->repairString($text, $tidyoptions, 'utf8');
} finally {
\core\locale::set_locale(LC_ALL, $currentlocale);
}

View File

@ -1,5 +1,4 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
@ -26,19 +25,19 @@ namespace filter_urltolink;
*/
class text_filter extends \core_filters\text_filter {
/**
* @var array global configuration for this filter
*
* This might be eventually moved into parent class if we found it
* useful for other filters, too.
*
* @var array global configuration for this filter
*/
protected static $globalconfig;
#[\Override]
public function filter($text, array $options = array()) {
public function filter($text, array $options = []) {
if (!isset($options['originalformat'])) {
// if the format is not specified, we are probably called by {@see format_string()}
// If the format is not specified, we are probably called by {@see format_string()}
// in that case, it would be dangerous to replace URL with the link because it could
// be stripped. therefore, we do nothing
// be stripped. therefore, we do nothing.
return $text;
}
if (in_array($options['originalformat'], explode(',', get_config('filter_urltolink', 'formats')))) {
@ -47,26 +46,22 @@ class text_filter extends \core_filters\text_filter {
return $text;
}
////////////////////////////////////////////////////////////////////////////
// internal implementation starts here
////////////////////////////////////////////////////////////////////////////
/**
* Given some text this function converts any URLs it finds into HTML links
*
* @param string $text Passed in by reference. The string to be searched for urls.
*/
protected function convert_urls_into_links(&$text) {
//I've added img tags to this list of tags to ignore.
//See MDL-21168 for more info. A better way to ignore tags whether or not
//they are escaped partially or completely would be desirable. For example:
//<a href="blah">
//&lt;a href="blah"&gt;
//&lt;a href="blah">
$filterignoretagsopen = array('<a\s[^>]+?>', '<span[^>]+?class="nolink"[^>]*?>');
$filterignoretagsclose = array('</a>', '</span>');
// I've added img tags to this list of tags to ignore.
// See MDL-21168 for more info. A better way to ignore tags whether or not
// they are escaped partially or completely would be desirable. For example:
// <a href="blah">
// &lt;a href="blah"&gt;
// &lt;a href="blah">.
$filterignoretagsopen = ['<a\s[^>]+?>', '<span[^>]+?class="nolink"[^>]*?>'];
$filterignoretagsclose = ['</a>', '</span>'];
$ignoretags = [];
filter_save_ignore_tags($text,$filterignoretagsopen,$filterignoretagsclose,$ignoretags);
filter_save_ignore_tags($text, $filterignoretagsopen, $filterignoretagsclose, $ignoretags);
// Check if we support unicode modifiers in regular expressions. Cache it.
// TODO: this check should be a environment requirement in Moodle 2.0, as far as unicode
@ -76,10 +71,10 @@ class text_filter extends \core_filters\text_filter {
// Unicode check, negative assertion and other bits from Moodle.
static $unicoderegexp;
if (!isset($unicoderegexp)) {
$unicoderegexp = @preg_match('/\pL/u', 'a'); // This will fail silently, returning false,
$unicoderegexp = @preg_match('/\pL/u', 'a'); // This will fail silently, returning false.
}
// TODO MDL-21296 - use of unicode modifiers may cause a timeout
// TODO MDL-21296 - use of unicode modifiers may cause a timeout.
$urlstart = '(?:http(s)?://|(?<!://)(www\.))';
$domainsegment = '(?:[\pLl0-9][\pLl0-9-]*[\pLl0-9]|[\pLl0-9])';
$numericip = '(?:(?:[0-9]{1,3}\.){3}[0-9]{1,3})';
@ -98,7 +93,7 @@ class text_filter extends \core_filters\text_filter {
if ($unicoderegexp) {
$regex = '#' . $regex . '#ui';
} else {
$regex = '#' . preg_replace(array('\pLl', '\PL'), 'a-z', $regex) . '#i';
$regex = '#' . preg_replace(['\pLl', '\PL'], 'a-z', $regex) . '#i';
}
// Locate any HTML tags.
@ -135,14 +130,14 @@ class text_filter extends \core_filters\text_filter {
$text = implode('', $matches);
if (!empty($ignoretags)) {
$ignoretags = array_reverse($ignoretags); /// Reversed so "progressive" str_replace() will solve some nesting problems.
$text = str_replace(array_keys($ignoretags),$ignoretags,$text);
$ignoretags = array_reverse($ignoretags); // Reversed so "progressive" str_replace() will solve some nesting problems.
$text = str_replace(array_keys($ignoretags), $ignoretags, $text);
}
if (get_config('filter_urltolink', 'embedimages')) {
// now try to inject the images, this code was originally in the mediapluing filter
// Now try to inject the images, this code was originally in the mediapluing filter
// this may be useful only if somebody relies on the fact the links in FORMAT_MOODLE get converted
// to URLs which in turn change to real images
// to URLs which in turn change to real images.
$search = '/<a href="([^"]+\.(jpg|png|gif))" class="_blanktarget">([^>]*)<\/a>/is';
$text = preg_replace_callback($search, [self::class, 'get_image_markup'], $text);
}
@ -153,14 +148,14 @@ class text_filter extends \core_filters\text_filter {
*
* This plugin is intended for automatic conversion of image URLs when FORMAT_MOODLE used.
*
* @param $link
* @param array $link
* @return string
*/
private function get_image_markup($link) {
if ($link[1] !== $link[3]) {
// this is not a link created by this filter, because the url does not match the text
// This is not a link created by this filter, because the url does not match the text.
return $link[0];
}
return '<img class="filter_urltolink_image" alt="" src="'.$link[1].'" />';
return '<img class="filter_urltolink_image" alt="" src="' . $link[1] . '" />';
}
}

View File

@ -26,11 +26,17 @@ namespace filter_urltolink;
* @covers \filter_urltolink\text_filter
*/
final class text_filter_test extends \basic_testcase {
/**
* Data provider for test_convert_urls_into_links.
*
* @return array
*/
public static function get_convert_urls_into_links_test_cases(): array {
// Create a 4095 and 4096 long URLs.
$superlong4095 = str_pad('http://www.superlong4095.com?this=something', 4095, 'a');
$superlong4096 = str_pad('http://www.superlong4096.com?this=something', 4096, 'a');
// phpcs:disable moodle.Files.LineLength.MaxExceeded, moodle.Files.LineLength.TooLong
$texts = [
// Just a url.
'http://moodle.org - URL' => '<a href="http://moodle.org" class="_blanktarget">http://moodle.org</a> - URL',
@ -70,8 +76,6 @@ final class text_filter_test extends \basic_testcase {
'URL: www.cc.org/url_(withpar)_go/?i=2' => 'URL: <a href="http://www.cc.org/url_(withpar)_go/?i=2" class="_blanktarget">www.cc.org/url_(withpar)_go/?i=2</a>',
'URL: http://cc.org/url_(with)_(par)_go/?i=2' => 'URL: <a href="http://cc.org/url_(with)_(par)_go/?i=2" class="_blanktarget">http://cc.org/url_(with)_(par)_go/?i=2</a>',
'URL: www.cc.org/url_(with)_(par)_go/?i=2' => 'URL: <a href="http://www.cc.org/url_(with)_(par)_go/?i=2" class="_blanktarget">www.cc.org/url_(with)_(par)_go/?i=2</a>',
// URL legitimately ending in a bracket. Commented out as part of MDL-22390. See next tests for work-arounds.
// 'http://en.wikipedia.org/wiki/Slash_(punctuation)'=>'<a href="http://en.wikipedia.org/wiki/Slash_(punctuation)" class="_blanktarget">http://en.wikipedia.org/wiki/Slash_(punctuation)</a>',
'http://en.wikipedia.org/wiki/%28#Parentheses_.28_.29 - URL' => '<a href="http://en.wikipedia.org/wiki/%28#Parentheses_.28_.29" class="_blanktarget">http://en.wikipedia.org/wiki/%28#Parentheses_.28_.29</a> - URL',
'http://en.wikipedia.org/wiki/(#Parentheses_.28_.29 - URL' => '<a href="http://en.wikipedia.org/wiki/(#Parentheses_.28_.29" class="_blanktarget">http://en.wikipedia.org/wiki/(#Parentheses_.28_.29</a> - URL',
// Escaped brackets in url.
@ -134,16 +138,14 @@ final class text_filter_test extends \basic_testcase {
'<table style="background-image: url(http://moodle.org/pic.jpg);">' => '<table style="background-image: url(http://moodle.org/pic.jpg);">',
'<table style="background-image: url("http://moodle.org/pic.jpg");">' => '<table style="background-image: url("http://moodle.org/pic.jpg");">',
'<table style="background-image: url( http://moodle.org/pic.jpg );">' => '<table style="background-image: url( http://moodle.org/pic.jpg );">',
// Partially escaped img tag
// Partially escaped img tag.
'partially escaped img tag &lt;img src="http://moodle.org/logo/logo-240x60.gif" />' => 'partially escaped img tag &lt;img src="http://moodle.org/logo/logo-240x60.gif" />',
// Fully escaped img tag. Commented out as part of MDL-21183
// Htmlspecialchars('fully escaped img tag <img src="http://moodle.org/logo/logo-240x60.gif" />') => 'fully escaped img tag &lt;img src="http://moodle.org/logo/logo-240x60.gif" /&gt;',
// Double http with www
// Double http with www.
'One more link like http://www.moodle.org to test' => 'One more link like <a href="http://www.moodle.org" class="_blanktarget">http://www.moodle.org</a> to test',
// Encoded URLs in the path
// Encoded URLs in the path.
'URL: http://127.0.0.1/one%28parenthesis%29/path?param=value' => 'URL: <a href="http://127.0.0.1/one%28parenthesis%29/path?param=value" class="_blanktarget">http://127.0.0.1/one%28parenthesis%29/path?param=value</a>',
'URL: www.localhost.com/one%28parenthesis%29/path?param=value' => 'URL: <a href="http://www.localhost.com/one%28parenthesis%29/path?param=value" class="_blanktarget">www.localhost.com/one%28parenthesis%29/path?param=value</a>',
// Encoded URLs in the query
// Encoded URLs in the query.
'URL: http://127.0.0.1/path/to?param=value_with%28parenthesis%29&param2=1' => 'URL: <a href="http://127.0.0.1/path/to?param=value_with%28parenthesis%29&param2=1" class="_blanktarget">http://127.0.0.1/path/to?param=value_with%28parenthesis%29&param2=1</a>',
'URL: www.localhost.com/path/to?param=value_with%28parenthesis%29&param2=1' => 'URL: <a href="http://www.localhost.com/path/to?param=value_with%28parenthesis%29&param2=1" class="_blanktarget">www.localhost.com/path/to?param=value_with%28parenthesis%29&param2=1</a>',
// Test URL less than 4096 characters in size is converted to link.
@ -173,6 +175,8 @@ final class text_filter_test extends \basic_testcase {
'<span class="nolink">URL: http://moodle.org</span>' => '<span class="nolink">URL: http://moodle.org</span>',
];
// phpcs:enable
$data = [];
foreach ($texts as $text => $correctresult) {
$data[] = [$text, $correctresult];
@ -181,7 +185,11 @@ final class text_filter_test extends \basic_testcase {
}
/**
* Test the convert_urls_into_links method.
*
* @dataProvider get_convert_urls_into_links_test_cases
* @param string $text
* @param string $correctresult
*/
public function test_convert_urls_into_links($text, $correctresult): void {
$testablefilter = $this->get_testable_text_filter();
@ -193,13 +201,15 @@ final class text_filter_test extends \basic_testcase {
/**
* Get a copy of the filter configured for testing.
*
* @param array $args
* @param array ...$args
* @return \filter_urltolink\text_filter
*/
protected function get_testable_text_filter(...$args): text_filter {
return new class extends text_filter {
// phpcs:ignore moodle.Commenting.MissingDocblock.MissingTestcaseMethodDescription
public function __construct() {
}
// phpcs:ignore moodle.Commenting.MissingDocblock.MissingTestcaseMethodDescription, Generic.CodeAnalysis.UselessOverridingMethod.Found
public function convert_urls_into_links(&$text) {
parent::convert_urls_into_links($text);
}