Merge branch 'MDL-82427-main' of https://github.com/andrewnicols/moodle
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
issueNumber: MDL-82427
|
||||
notes:
|
||||
core_filters:
|
||||
- message: >-
|
||||
Added support for autoloading of filters from `\filter_[filtername]\filter`. Existing classes should be renamed to use the new namespace.
|
||||
type: improved
|
||||
@@ -0,0 +1,7 @@
|
||||
issueNumber: MDL-82427
|
||||
notes:
|
||||
core_filters:
|
||||
- message: >-
|
||||
The `filter_manager::text_filtering_hash` method has been finally
|
||||
deprecated and removed.
|
||||
type: deprecated
|
||||
@@ -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
|
||||
@@ -15,24 +14,26 @@
|
||||
// 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_activitynames;
|
||||
|
||||
use cache;
|
||||
use cache_store;
|
||||
use core\output\html_writer;
|
||||
use core_collator;
|
||||
use filterobject;
|
||||
|
||||
/**
|
||||
* This filter provides automatic linking to
|
||||
* activities when its name (title) is found inside every Moodle text
|
||||
*
|
||||
* @package filter
|
||||
* @package filter_activitynames
|
||||
* @subpackage activitynames
|
||||
* @copyright 2004 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Activity name filtering
|
||||
*/
|
||||
class filter_activitynames extends moodle_text_filter {
|
||||
|
||||
function filter($text, array $options = array()) {
|
||||
class text_filter extends \core_filters\text_filter {
|
||||
#[\Override]
|
||||
public function filter($text, array $options = []) {
|
||||
$coursectx = $this->context->get_course_context(false);
|
||||
if (!$coursectx) {
|
||||
return $text;
|
||||
@@ -41,12 +42,12 @@ class filter_activitynames extends moodle_text_filter {
|
||||
|
||||
$activitylist = $this->get_cached_activity_list($courseid);
|
||||
|
||||
$filterslist = array();
|
||||
$filterslist = [];
|
||||
if (!empty($activitylist)) {
|
||||
$cmid = $this->context->instanceid;
|
||||
if ($this->context->contextlevel == CONTEXT_MODULE && isset($activitylist[$cmid])) {
|
||||
// remove filterobjects for the current module
|
||||
$filterslist = array_values(array_diff_key($activitylist, array($cmid => 1, $cmid.'-e' => 1)));
|
||||
// Remove filterobjects for the current module.
|
||||
$filterslist = array_values(array_diff_key($activitylist, [$cmid => 1, $cmid . '-e' => 1]));
|
||||
} else {
|
||||
$filterslist = array_values($activitylist);
|
||||
}
|
||||
@@ -89,23 +90,23 @@ class filter_activitynames extends moodle_text_filter {
|
||||
* @return filterobject[] the activities
|
||||
*/
|
||||
protected function get_activity_list($courseid) {
|
||||
$activitylist = array();
|
||||
$activitylist = [];
|
||||
|
||||
$modinfo = get_fast_modinfo($courseid);
|
||||
if (!empty($modinfo->cms)) {
|
||||
$activitylist = array(); // We will store all the created filters here.
|
||||
$activitylist = []; // We will store all the created filters here.
|
||||
|
||||
// Create array of visible activities sorted by the name length (we are only interested in properties name and url).
|
||||
$sortedactivities = array();
|
||||
$sortedactivities = [];
|
||||
foreach ($modinfo->cms as $cm) {
|
||||
// Use normal access control and visibility, but exclude labels and hidden activities.
|
||||
if ($cm->visible and $cm->has_view() and $cm->uservisible) {
|
||||
$sortedactivities[] = (object)array(
|
||||
if ($cm->visible && $cm->has_view() && $cm->uservisible) {
|
||||
$sortedactivities[] = (object)[
|
||||
'name' => $cm->name,
|
||||
'url' => $cm->url,
|
||||
'id' => $cm->id,
|
||||
'namelen' => -strlen($cm->name), // Negative value for reverse sorting.
|
||||
);
|
||||
];
|
||||
}
|
||||
}
|
||||
// Sort activities by the length of the activity name in reverse order.
|
||||
@@ -117,13 +118,15 @@ class filter_activitynames extends moodle_text_filter {
|
||||
$entitisedname = s($currentname);
|
||||
// Avoid empty or unlinkable activity names.
|
||||
if (!empty($title)) {
|
||||
$hreftagbegin = html_writer::start_tag('a',
|
||||
array('class' => 'autolink', 'title' => $title,
|
||||
'href' => $cm->url));
|
||||
$hreftagbegin = html_writer::start_tag(
|
||||
'a',
|
||||
['class' => 'autolink', 'title' => $title,
|
||||
'href' => $cm->url, ]
|
||||
);
|
||||
$activitylist[$cm->id] = new filterobject($currentname, $hreftagbegin, '</a>', false, true);
|
||||
if ($currentname != $entitisedname) {
|
||||
// If name has some entity (& " < >) add that filter too. MDL-17545.
|
||||
$activitylist[$cm->id.'-e'] = new filterobject($entitisedname, $hreftagbegin, '</a>', false, true);
|
||||
$activitylist[$cm->id . '-e'] = new filterobject($entitisedname, $hreftagbegin, '</a>', false, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
+50
-35
@@ -14,25 +14,17 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Unit tests.
|
||||
*
|
||||
* @package filter_activitynames
|
||||
* @category test
|
||||
* @copyright 2018 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace filter_activitynames;
|
||||
|
||||
/**
|
||||
* Test case for the activity names auto-linking filter.
|
||||
*
|
||||
* @package filter_activitynames
|
||||
* @copyright 2018 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \filter_activitynames\text_filter
|
||||
*/
|
||||
class filter_test extends \advanced_testcase {
|
||||
|
||||
final class text_filter_test extends \advanced_testcase {
|
||||
public function test_links(): void {
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
@@ -41,19 +33,26 @@ class filter_test extends \advanced_testcase {
|
||||
$context = \context_course::instance($course->id);
|
||||
|
||||
// Create two pages that will be linked to.
|
||||
$page1 = $this->getDataGenerator()->create_module('page',
|
||||
['course' => $course->id, 'name' => 'Test 1']);
|
||||
$page2 = $this->getDataGenerator()->create_module('page',
|
||||
['course' => $course->id, 'name' => 'Test (2)']);
|
||||
$page1 = $this->getDataGenerator()->create_module(
|
||||
'page',
|
||||
['course' => $course->id, 'name' => 'Test 1']
|
||||
);
|
||||
$page2 = $this->getDataGenerator()->create_module(
|
||||
'page',
|
||||
['course' => $course->id, 'name' => 'Test (2)']
|
||||
);
|
||||
|
||||
// Format text with all three entries in HTML.
|
||||
$html = '<p>Please read the two pages Test 1 and <i>Test (2)</i>.</p>';
|
||||
$filtered = format_text($html, FORMAT_HTML, array('context' => $context));
|
||||
$filtered = format_text($html, FORMAT_HTML, ['context' => $context]);
|
||||
|
||||
// Find all the glossary links in the result.
|
||||
$matches = [];
|
||||
preg_match_all('~<a class="autolink" title="([^"]*)" href="[^"]*/mod/page/view.php\?id=([0-9]+)">([^<]*)</a>~',
|
||||
$filtered, $matches);
|
||||
preg_match_all(
|
||||
'~<a class="autolink" title="([^"]*)" href="[^"]*/mod/page/view.php\?id=([0-9]+)">([^<]*)</a>~',
|
||||
$filtered,
|
||||
$matches
|
||||
);
|
||||
|
||||
// There should be 2 links links.
|
||||
$this->assertCount(2, $matches[1]);
|
||||
@@ -86,11 +85,14 @@ class filter_test extends \advanced_testcase {
|
||||
$page = $this->getDataGenerator()->create_module('page', ['course' => $course->id, 'name' => '-']);
|
||||
|
||||
$html = '<p>Please read the - page.</p>';
|
||||
$filtered = format_text($html, FORMAT_HTML, array('context' => $context));
|
||||
$filtered = format_text($html, FORMAT_HTML, ['context' => $context]);
|
||||
|
||||
// Find the page link in the filtered html.
|
||||
preg_match_all('~<a class="autolink" title="([^"]*)" href="[^"]*/mod/page/view.php\?id=([0-9]+)">([^<]*)</a>~',
|
||||
$filtered, $matches);
|
||||
preg_match_all(
|
||||
'~<a class="autolink" title="([^"]*)" href="[^"]*/mod/page/view.php\?id=([0-9]+)">([^<]*)</a>~',
|
||||
$filtered,
|
||||
$matches
|
||||
);
|
||||
|
||||
// We should have exactly one match.
|
||||
$this->assertCount(1, $matches[1]);
|
||||
@@ -110,42 +112,55 @@ class filter_test extends \advanced_testcase {
|
||||
$context2 = \context_course::instance($course2->id);
|
||||
|
||||
// Create page 1.
|
||||
$page1 = $this->getDataGenerator()->create_module('page',
|
||||
['course' => $course1->id, 'name' => 'Test 1']);
|
||||
$page1 = $this->getDataGenerator()->create_module(
|
||||
'page',
|
||||
['course' => $course1->id, 'name' => 'Test 1']
|
||||
);
|
||||
// Format text with page 1 in HTML.
|
||||
$html = '<p>Please read the two pages Test 1 and Test 2.</p>';
|
||||
$filtered1 = format_text($html, FORMAT_HTML, array('context' => $context1));
|
||||
$filtered1 = format_text($html, FORMAT_HTML, ['context' => $context1]);
|
||||
// Find all the activity links in the result.
|
||||
$matches = [];
|
||||
preg_match_all('~<a class="autolink" title="([^"]*)" href="[^"]*/mod/page/view.php\?id=([0-9]+)">([^<]*)</a>~',
|
||||
$filtered1, $matches);
|
||||
preg_match_all(
|
||||
'~<a class="autolink" title="([^"]*)" href="[^"]*/mod/page/view.php\?id=([0-9]+)">([^<]*)</a>~',
|
||||
$filtered1,
|
||||
$matches
|
||||
);
|
||||
// There should be 1 link.
|
||||
$this->assertCount(1, $matches[1]);
|
||||
$this->assertEquals($page1->name, $matches[1][0]);
|
||||
|
||||
// Create page 2.
|
||||
$page2 = $this->getDataGenerator()->create_module('page',
|
||||
['course' => $course1->id, 'name' => 'Test 2']);
|
||||
$page2 = $this->getDataGenerator()->create_module(
|
||||
'page',
|
||||
['course' => $course1->id, 'name' => 'Test 2']
|
||||
);
|
||||
// Filter the text again.
|
||||
$filtered2 = format_text($html, FORMAT_HTML, array('context' => $context1));
|
||||
$filtered2 = format_text($html, FORMAT_HTML, ['context' => $context1]);
|
||||
// The filter result does not change due to caching.
|
||||
$this->assertEquals($filtered1, $filtered2);
|
||||
|
||||
// Change context, so that cache for course 1 is cleared.
|
||||
$filtered3 = format_text($html, FORMAT_HTML, array('context' => $context2));
|
||||
$filtered3 = format_text($html, FORMAT_HTML, ['context' => $context2]);
|
||||
$this->assertNotEquals($filtered1, $filtered3);
|
||||
$matches = [];
|
||||
preg_match_all('~<a class="autolink" title="([^"]*)" href="[^"]*/mod/page/view.php\?id=([0-9]+)">([^<]*)</a>~',
|
||||
$filtered3, $matches);
|
||||
preg_match_all(
|
||||
'~<a class="autolink" title="([^"]*)" href="[^"]*/mod/page/view.php\?id=([0-9]+)">([^<]*)</a>~',
|
||||
$filtered3,
|
||||
$matches
|
||||
);
|
||||
// There should be no links.
|
||||
$this->assertCount(0, $matches[1]);
|
||||
|
||||
// Filter the text for course 1.
|
||||
$filtered4 = format_text($html, FORMAT_HTML, array('context' => $context1));
|
||||
$filtered4 = format_text($html, FORMAT_HTML, ['context' => $context1]);
|
||||
// Find all the activity links in the result.
|
||||
$matches = [];
|
||||
preg_match_all('~<a class="autolink" title="([^"]*)" href="[^"]*/mod/page/view.php\?id=([0-9]+)">([^<]*)</a>~',
|
||||
$filtered4, $matches);
|
||||
preg_match_all(
|
||||
'~<a class="autolink" title="([^"]*)" href="[^"]*/mod/page/view.php\?id=([0-9]+)">([^<]*)</a>~',
|
||||
$filtered4,
|
||||
$matches
|
||||
);
|
||||
// There should be 2 links.
|
||||
$this->assertCount(2, $matches[1]);
|
||||
$this->assertEquals($page1->name, $matches[1][0]);
|
||||
@@ -0,0 +1,256 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// 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_algebra;
|
||||
|
||||
use core\context\system as context_system;
|
||||
use core\output\actions\popup_action;
|
||||
use core\url;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Moodle - Filter for converting simple calculator-type algebraic expressions to cached gif images
|
||||
*
|
||||
* NOTE: This Moodle text filter converts algebraic expressions delimited
|
||||
* by either @@...@@ or by <algebra...>...</algebra> tags
|
||||
* first converts it to TeX using WeBWorK algebra parser Perl library
|
||||
* AlgParser.pm, part of the WeBWorK distribution obtained from
|
||||
* http://webhost.math.rochester.edu/downloadwebwork/
|
||||
* then converts the TeX to gif images using
|
||||
* mimetex.cgi obtained from http://www.forkosh.com/mimetex.html authored by
|
||||
* John Forkosh [email protected]. The mimetex.cgi ELF binary compiled for Linux i386
|
||||
* as well as AlgParser.pm are included with this distribution.
|
||||
* 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.
|
||||
* -------------------------------------------------------------------------
|
||||
* You will then need to edit your moodle/config.php to invoke mathml_filter.php
|
||||
* -------------------------------------------------------------------------
|
||||
*
|
||||
* @package filter_algebra
|
||||
* @subpackage algebra
|
||||
* @copyright 2004 Zbigniew Fiedorowicz [email protected]
|
||||
* Originally based on code provided by Bruno Vernier [email protected]
|
||||
* @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 = []) {
|
||||
global $CFG, $DB;
|
||||
|
||||
// Do a quick check using stripos to avoid unnecessary work.
|
||||
if (!preg_match('/<algebra/i', $text) && !strstr($text, '@@')) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
preg_match_all('/@(@@+)([^@])/', $text, $matches);
|
||||
for ($i = 0; $i < count($matches[0]); $i++) {
|
||||
$replacement = str_replace('@', '@', $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 @@.
|
||||
preg_match_all('/<algebra>(.+?)<\/algebra>|@@(.+?)@@/is', $text, $matches);
|
||||
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.
|
||||
if ($algebra == 'PLUGINFILE' || $algebra == 'DRAFTFILE') {
|
||||
// Raw pluginfile URL.
|
||||
continue;
|
||||
}
|
||||
if (preg_match('/^ -\d+(,\d+)? \+\d+(,\d+)? $/', $algebra)) {
|
||||
// Part of a unified diff.
|
||||
continue;
|
||||
}
|
||||
|
||||
$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);
|
||||
}
|
||||
|
||||
$md5 = md5($algebra);
|
||||
$filename = $md5 . ".gif";
|
||||
if (! $texcache = $DB->get_record("cache_filters", ["filter" => "algebra", "md5key" => $md5])) {
|
||||
$algebra = str_replace('<', '<', $algebra);
|
||||
$algebra = str_replace('>', '>', $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);
|
||||
}
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create image link.
|
||||
*
|
||||
* @param string $imagefile name of file
|
||||
* @param string $tex TeX notation (html entities already decoded)
|
||||
* @param int $height O means automatic
|
||||
* @param int $width O means automatic
|
||||
* @param string $align
|
||||
* @return string HTML markup
|
||||
*/
|
||||
protected function get_image_markup(
|
||||
string $imagefile,
|
||||
string $tex = "",
|
||||
int $height = 0,
|
||||
int $width = 0,
|
||||
string $align = 'middle',
|
||||
): string {
|
||||
// Given the path to a picture file in a course, or a URL,
|
||||
// this function includes the picture in the page.
|
||||
global $CFG, $OUTPUT;
|
||||
|
||||
$output = "";
|
||||
$style = 'style="border:0px; vertical-align:' . $align . ';';
|
||||
$title = '';
|
||||
if ($tex) {
|
||||
$tex = html_entity_decode($tex, ENT_QUOTES, 'UTF-8');
|
||||
$title = 'title="' . s($tex) . '"';
|
||||
}
|
||||
if ($height) {
|
||||
$style .= " height:{$height}px;";
|
||||
}
|
||||
if ($width) {
|
||||
$style .= " width:{$width}px;";
|
||||
}
|
||||
$style .= '"';
|
||||
$anchorcontents = '';
|
||||
if ($imagefile) {
|
||||
$anchorcontents .= "<img $title alt=\"" . s($tex) . "\" src=\"";
|
||||
if ($CFG->slasharguments) {
|
||||
// Use this method if possible for better caching.
|
||||
$anchorcontents .= "$CFG->wwwroot/filter/algebra/pix.php/$imagefile";
|
||||
} else {
|
||||
$anchorcontents .= "$CFG->wwwroot/filter/algebra/pix.php?file=$imagefile";
|
||||
}
|
||||
$anchorcontents .= "\" $style />";
|
||||
|
||||
$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 {
|
||||
$link = new url('/filter/tex/displaytex.php', ['texexp' => $tex]);
|
||||
// TODO: the popups do not work when text caching is enabled.
|
||||
$action = new popup_action('click', $link, 'popup', ['width' => 320, 'height' => 240]);
|
||||
}
|
||||
$output .= $OUTPUT->action_link($link, $anchorcontents, $action, ['title' => 'TeX']);
|
||||
} else {
|
||||
$output .= "Error: must pass URL or course";
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
@@ -1,251 +0,0 @@
|
||||
<?php
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Moodle - Filter for converting simple calculator-type algebraic
|
||||
* expressions to cached gif images
|
||||
*
|
||||
* @package filter
|
||||
* @subpackage algebra
|
||||
* @copyright 2004 Zbigniew Fiedorowicz [email protected]
|
||||
* Originally based on code provided by Bruno Vernier [email protected]
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// NOTE: This Moodle text filter converts algebraic expressions delimited
|
||||
// by either @@...@@ or by <algebra...>...</algebra> tags
|
||||
// first converts it to TeX using WeBWorK algebra parser Perl library
|
||||
// AlgParser.pm, part of the WeBWorK distribution obtained from
|
||||
// http://webhost.math.rochester.edu/downloadwebwork/
|
||||
// then converts the TeX to gif images using
|
||||
// mimetex.cgi obtained from http://www.forkosh.com/mimetex.html authored by
|
||||
// John Forkosh [email protected]. The mimetex.cgi ELF binary compiled for Linux i386
|
||||
// as well as AlgParser.pm are included with this distribution.
|
||||
// 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.
|
||||
//-------------------------------------------------------------------------
|
||||
// You will then need to edit your moodle/config.php to invoke mathml_filter.php
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
function filter_algebra_image($imagefile, $tex= "", $height="", $width="", $align="middle") {
|
||||
// Given the path to a picture file in a course, or a URL,
|
||||
// this function includes the picture in the page.
|
||||
global $CFG, $OUTPUT;
|
||||
|
||||
$output = "";
|
||||
$style = 'style="border:0px; vertical-align:'.$align.';';
|
||||
$title = '';
|
||||
if ($tex) {
|
||||
$tex = html_entity_decode($tex, ENT_QUOTES, 'UTF-8');
|
||||
$title = 'title="'.s($tex).'"';
|
||||
}
|
||||
if ($height) {
|
||||
$style .= " height:{$height}px;";
|
||||
}
|
||||
if ($width) {
|
||||
$style .= " width:{$width}px;";
|
||||
}
|
||||
$style .= '"';
|
||||
$anchorcontents = '';
|
||||
if ($imagefile) {
|
||||
$anchorcontents .= "<img $title alt=\"".s($tex)."\" src=\"";
|
||||
if ($CFG->slasharguments) { // Use this method if possible for better caching
|
||||
$anchorcontents .= "$CFG->wwwroot/filter/algebra/pix.php/$imagefile";
|
||||
} else {
|
||||
$anchorcontents .= "$CFG->wwwroot/filter/algebra/pix.php?file=$imagefile";
|
||||
}
|
||||
$anchorcontents .= "\" $style />";
|
||||
|
||||
if (!file_exists("$CFG->dataroot/filter/algebra/$imagefile") && has_capability('moodle/site:config', context_system::instance())) {
|
||||
$link = '/filter/algebra/algebradebug.php';
|
||||
$action = null;
|
||||
} else {
|
||||
$link = new moodle_url('/filter/tex/displaytex.php', array('texexp'=>$tex));
|
||||
$action = new popup_action('click', $link, 'popup', array('width'=>320,'height'=>240)); //TODO: the popups do not work when text caching is enabled!!
|
||||
}
|
||||
$output .= $OUTPUT->action_link($link, $anchorcontents, $action, array('title'=>'TeX'));
|
||||
|
||||
} else {
|
||||
$output .= "Error: must pass URL or course";
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
class filter_algebra extends moodle_text_filter {
|
||||
public function filter($text, array $options = array()){
|
||||
global $CFG, $DB;
|
||||
|
||||
/// Do a quick check using stripos to avoid unnecessary wor
|
||||
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('@','@',$matches[1][$i]).$matches[2][$i];
|
||||
$text = str_replace($matches[0][$i],$replacement,$text);
|
||||
}
|
||||
|
||||
// <algebra> some algebraic input expression </algebra>
|
||||
// or @@ some algebraic input expression @@
|
||||
|
||||
preg_match_all('/<algebra>(.+?)<\/algebra>|@@(.+?)@@/is', $text, $matches);
|
||||
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.
|
||||
if ($algebra == 'PLUGINFILE' || $algebra == 'DRAFTFILE') {
|
||||
// Raw pluginfile URL.
|
||||
continue;
|
||||
}
|
||||
if (preg_match('/^ -\d+(,\d+)? \+\d+(,\d+)? $/', $algebra)) {
|
||||
// Part of a unified diff.
|
||||
continue;
|
||||
}
|
||||
|
||||
$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);
|
||||
}
|
||||
$md5 = md5($algebra);
|
||||
$filename = $md5 . ".gif";
|
||||
if (! $texcache = $DB->get_record("cache_filters",array("filter"=>"algebra", "md5key"=>$md5))) {
|
||||
$algebra = str_replace('<','<',$algebra);
|
||||
$algebra = str_replace('>','>',$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], 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], filter_algebra_image($filename, $texcache->rawtext), $text);
|
||||
}
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,20 +18,13 @@
|
||||
* Unit test for the filter_algebra
|
||||
*
|
||||
* @package filter_algebra
|
||||
* @category phpunit
|
||||
* @copyright 2012 Tim Hunt
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace filter_algebra;
|
||||
|
||||
use filter_algebra;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/filter/algebra/filter.php');
|
||||
|
||||
use core\context\system as context_system;
|
||||
|
||||
/**
|
||||
* Unit tests for filter_algebra.
|
||||
@@ -41,35 +34,43 @@ require_once($CFG->dirroot . '/filter/algebra/filter.php');
|
||||
* test server, and if it does not work here, it probably does not also work
|
||||
* for other people. A failing test will be irritating noise.
|
||||
*
|
||||
* @package filter_algebra
|
||||
* @copyright 2012 Tim Hunt
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \filter_algebra\text_filter
|
||||
*/
|
||||
class filter_test extends \basic_testcase {
|
||||
|
||||
protected $filter;
|
||||
final class text_filter_test extends \basic_testcase {
|
||||
/** @var text_filter The filter to test */
|
||||
protected text_filter $filter;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->filter = new filter_algebra(\context_system::instance(), array());
|
||||
$this->filter = new text_filter(context_system::instance(), []);
|
||||
}
|
||||
|
||||
function test_algebra_filter_no_algebra(): void {
|
||||
$this->assertEquals('<p>Look no algebra!</p>',
|
||||
$this->filter->filter('<p>Look no algebra!</p>'));
|
||||
public function test_algebra_filter_no_algebra(): void {
|
||||
$this->assertEquals(
|
||||
'<p>Look no algebra!</p>',
|
||||
$this->filter->filter('<p>Look no algebra!</p>')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function test_algebra_filter_pluginfile(): void {
|
||||
$this->assertEquals('<img src="@@PLUGINFILE@@/photo.jpg">',
|
||||
$this->filter->filter('<img src="@@PLUGINFILE@@/photo.jpg">'));
|
||||
public function test_algebra_filter_pluginfile(): void {
|
||||
$this->assertEquals(
|
||||
'<img src="@@PLUGINFILE@@/photo.jpg">',
|
||||
$this->filter->filter('<img src="@@PLUGINFILE@@/photo.jpg">')
|
||||
);
|
||||
}
|
||||
|
||||
function test_algebra_filter_draftfile(): void {
|
||||
$this->assertEquals('<img src="@@DRAFTFILE@@/photo.jpg">',
|
||||
$this->filter->filter('<img src="@@DRAFTFILE@@/photo.jpg">'));
|
||||
public function test_algebra_filter_draftfile(): void {
|
||||
$this->assertEquals(
|
||||
'<img src="@@DRAFTFILE@@/photo.jpg">',
|
||||
$this->filter->filter('<img src="@@DRAFTFILE@@/photo.jpg">')
|
||||
);
|
||||
}
|
||||
|
||||
function test_algebra_filter_unified_diff(): void {
|
||||
public function test_algebra_filter_unified_diff(): void {
|
||||
$diff = '
|
||||
diff -u -r1.1 Worksheet.php
|
||||
--- Worksheet.php 26 Sep 2003 04:18:02 -0000 1.1
|
||||
@@ -88,7 +89,9 @@ diff -u -r1.1 Worksheet.php
|
||||
}
|
||||
else {
|
||||
';
|
||||
$this->assertEquals('<pre>' . $diff . '</pre>',
|
||||
$this->filter->filter('<pre>' . $diff . '</pre>'));
|
||||
$this->assertEquals(
|
||||
'<pre>' . $diff . '</pre>',
|
||||
$this->filter->filter('<pre>' . $diff . '</pre>')
|
||||
);
|
||||
}
|
||||
}
|
||||
+16
-19
@@ -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(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
+45
-48
@@ -22,10 +22,7 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core_filters;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir . '/filterlib.php');
|
||||
namespace core_filters\external;
|
||||
|
||||
use core_external\external_api;
|
||||
use core_external\external_function_parameters;
|
||||
@@ -41,28 +38,26 @@ use Exception;
|
||||
* @copyright 2017 Juan Leyva
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class external extends external_api {
|
||||
|
||||
class get_available_in_context extends external_api {
|
||||
/**
|
||||
* Returns description of get_available_in_context() parameters.
|
||||
* Returns description of get_available_in_context parameters.
|
||||
*
|
||||
* @return external_function_parameters
|
||||
* @since Moodle 3.4
|
||||
*/
|
||||
public static function get_available_in_context_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.'
|
||||
),
|
||||
)
|
||||
);
|
||||
public static function execute_parameters() {
|
||||
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,9 +67,13 @@ class external extends external_api {
|
||||
* @return array with the filters information and warnings
|
||||
* @since Moodle 3.4
|
||||
*/
|
||||
public static function get_available_in_context($contexts) {
|
||||
$params = self::validate_parameters(self::get_available_in_context_parameters(), array('contexts' => $contexts));
|
||||
$filters = $warnings = array();
|
||||
public static function execute($contexts) {
|
||||
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 {
|
||||
@@ -82,12 +81,12 @@ class external 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);
|
||||
@@ -97,37 +96,35 @@ class external extends external_api {
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
return [
|
||||
'filters' => $filters,
|
||||
'warnings' => $warnings,
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns description of get_available_in_context() result value.
|
||||
* Returns description of get_available_in_context result value.
|
||||
*
|
||||
* @return external_single_structure
|
||||
* @since Moodle 3.4
|
||||
*/
|
||||
public static function get_available_in_context_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.'),
|
||||
)
|
||||
public static function execute_returns() {
|
||||
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(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// 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;
|
||||
|
||||
use core\context;
|
||||
use core\context\system as context_system;
|
||||
use moodle_page;
|
||||
|
||||
/**
|
||||
* Class to manage the filtering of strings. It is intended that this class is
|
||||
* only used by weblib.php. Client code should probably be using the
|
||||
* format_text and format_string functions.
|
||||
*
|
||||
* This class is a singleton.
|
||||
*
|
||||
* @package core_filters
|
||||
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
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 = [];
|
||||
|
||||
/**
|
||||
* @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 = [];
|
||||
|
||||
/** @var array Exploded version of $CFG->stringfilters. */
|
||||
protected $stringfilternames = [];
|
||||
|
||||
/** @var filter_manager Holds the singleton instance. */
|
||||
protected static $singletoninstance;
|
||||
|
||||
/**
|
||||
* Constructor. Protected. Use {@see instance()} instead.
|
||||
*/
|
||||
protected function __construct() {
|
||||
$this->stringfilternames = filter_get_string_filters();
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method. Use this to get the filter manager.
|
||||
*
|
||||
* @return filter_manager the singleton instance.
|
||||
*/
|
||||
public static function instance() {
|
||||
global $CFG;
|
||||
if (is_null(self::$singletoninstance)) {
|
||||
if (!empty($CFG->perfdebug) && $CFG->perfdebug > 7) {
|
||||
self::$singletoninstance = new performance_measuring_filter_manager();
|
||||
} else {
|
||||
self::$singletoninstance = new self();
|
||||
}
|
||||
}
|
||||
return self::$singletoninstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the caches, usually to be called between unit tests
|
||||
*/
|
||||
public static function reset_caches() {
|
||||
if (self::$singletoninstance) {
|
||||
self::$singletoninstance->unload_all_filters();
|
||||
}
|
||||
self::$singletoninstance = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unloads all filters and other cached information
|
||||
*/
|
||||
protected function unload_all_filters() {
|
||||
$this->textfilters = [];
|
||||
$this->stringfilters = [];
|
||||
$this->stringfilternames = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all the filters required by this context.
|
||||
*
|
||||
* @param context $context the context.
|
||||
*/
|
||||
protected function load_filters($context) {
|
||||
$filters = filter_get_active_in_context($context);
|
||||
$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)) {
|
||||
continue;
|
||||
}
|
||||
$this->textfilters[$context->id][$filtername] = $filter;
|
||||
if (in_array($filtername, $this->stringfilternames)) {
|
||||
$this->stringfilters[$context->id][$filtername] = $filter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method for creating a filter.
|
||||
*
|
||||
* @param string $filtername The filter name, for example 'tex'.
|
||||
* @param context $context context object.
|
||||
* @param array $localconfig array of local configuration variables for this filter.
|
||||
* @return ?text_filter The filter, or null, if this type of filter is
|
||||
* not recognised or could not be created.
|
||||
*/
|
||||
protected function make_filter_object($filtername, $context, $localconfig) {
|
||||
global $CFG;
|
||||
|
||||
$filterclass = "\\filter_{$filtername}\\text_filter";
|
||||
if (class_exists($filterclass)) {
|
||||
return new $filterclass($context, $localconfig);
|
||||
}
|
||||
|
||||
$path = $CFG->dirroot . '/filter/' . $filtername . '/filter.php';
|
||||
if (!is_readable($path)) {
|
||||
return null;
|
||||
}
|
||||
include_once($path);
|
||||
|
||||
$filterclassname = 'filter_' . $filtername;
|
||||
if (class_exists($filterclassname)) {
|
||||
debugging(
|
||||
"Inclusion of filters from 'filter/{$filtername}/filter.php' " .
|
||||
"using the '{$filterclassname}' class naming has been deprecated. " .
|
||||
"Please rename your class to {$filterclass} and move it to 'filter/{$filtername}/classes/text_filter.php'. " .
|
||||
"See MDL-82427 for more information.",
|
||||
DEBUG_DEVELOPER,
|
||||
);
|
||||
return new $filterclassname($context, $localconfig);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a list of filters to some content.
|
||||
* @param string $text
|
||||
* @param text_filter[] $filterchain array filter name => filter object.
|
||||
* @param array $options options passed to the filters.
|
||||
* @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 $skipfilters = null
|
||||
) {
|
||||
if (!isset($options['stage'])) {
|
||||
$filtermethod = 'filter';
|
||||
} else if (in_array($options['stage'], ['pre_format', 'pre_clean', 'post_clean', 'string'], true)) {
|
||||
$filtermethod = 'filter_stage_' . $options['stage'];
|
||||
} else {
|
||||
$filtermethod = 'filter';
|
||||
debugging('Invalid filter stage specified in options: ' . $options['stage'], DEBUG_DEVELOPER);
|
||||
}
|
||||
if ($text === null || $text === '') {
|
||||
// Nothing to filter.
|
||||
return '';
|
||||
}
|
||||
foreach ($filterchain as $filtername => $filter) {
|
||||
if ($skipfilters !== null && in_array($filtername, $skipfilters)) {
|
||||
continue;
|
||||
}
|
||||
$text = $filter->$filtermethod($text, $options);
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the filters that apply to a given context for calls to format_text.
|
||||
*
|
||||
* @param context $context
|
||||
* @return moodle_text_filter[] A text filter
|
||||
*/
|
||||
protected function get_text_filters($context) {
|
||||
if (!isset($this->textfilters[$context->id])) {
|
||||
$this->load_filters($context);
|
||||
}
|
||||
return $this->textfilters[$context->id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the filters that apply to a given context for calls to format_string.
|
||||
*
|
||||
* @param context $context the context.
|
||||
* @return moodle_text_filter[] A text filter
|
||||
*/
|
||||
protected function get_string_filters($context) {
|
||||
if (!isset($this->stringfilters[$context->id])) {
|
||||
$this->load_filters($context);
|
||||
}
|
||||
return $this->stringfilters[$context->id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter some text
|
||||
*
|
||||
* @param string $text The text to filter
|
||||
* @param context $context the context.
|
||||
* @param array $options options passed to the filters
|
||||
* @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 $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(['<nolink>', '</nolink>'], '', $text);
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter a piece of string
|
||||
*
|
||||
* @param string $string The text to filter
|
||||
* @param context $context the context.
|
||||
* @return string resulting string
|
||||
*/
|
||||
public function filter_string($string, $context) {
|
||||
return $this->apply_filter_chain($string, $this->get_string_filters($context), ['stage' => 'string']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup page with filters requirements and other prepare stuff.
|
||||
*
|
||||
* This method is used by {@see format_text()} and {@see format_string()}
|
||||
* in order to allow filters to setup any page requirement (js, css...)
|
||||
* or perform any action needed to get them prepared before filtering itself
|
||||
* happens by calling to each every active setup() method.
|
||||
*
|
||||
* Note it's executed for each piece of text filtered, so filter implementations
|
||||
* are responsible of controlling the cardinality of the executions that may
|
||||
* be different depending of the stuff to prepare.
|
||||
*
|
||||
* @param moodle_page $page the page we are going to add requirements to.
|
||||
* @param context $context the context which contents are going to be filtered.
|
||||
* @since Moodle 2.3
|
||||
*/
|
||||
public function setup_page_for_filters($page, $context) {
|
||||
$filters = $this->get_text_filters($context);
|
||||
foreach ($filters as $filter) {
|
||||
$filter->setup($page, $context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the page for globally available filters.
|
||||
*
|
||||
* This helps setting up the page for filters which may be applied to
|
||||
* the page, even if they do not belong to the current context, or are
|
||||
* not yet visible because the content is lazily added (ajax). This method
|
||||
* always uses to the system context which determines the globally
|
||||
* available filters.
|
||||
*
|
||||
* This should only ever be called once per request.
|
||||
*
|
||||
* @param moodle_page $page The page.
|
||||
* @since Moodle 3.2
|
||||
*/
|
||||
public function setup_page_for_globally_available_filters($page) {
|
||||
$context = context_system::instance();
|
||||
$filterdata = filter_get_globally_enabled_filters_with_config();
|
||||
foreach ($filterdata as $name => $config) {
|
||||
if (isset($this->textfilters[$context->id][$name])) {
|
||||
$filter = $this->textfilters[$context->id][$name];
|
||||
} else {
|
||||
$filter = $this->make_filter_object($name, $context, $config);
|
||||
if (is_null($filter)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$filter->setup($page, $context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Alias this class to the old name.
|
||||
// This file will be autoloaded by the legacyclasses autoload system.
|
||||
// In future all uses of this class will be corrected and the legacy references will be removed.
|
||||
class_alias(filter_manager::class, \filter_manager::class);
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// 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;
|
||||
|
||||
/**
|
||||
* This is just a little object to define a phrase and some instructions
|
||||
* for how to process it. Filters can create an array of these to pass
|
||||
* to the @{link filter_phrases()} function below.
|
||||
*
|
||||
* Note that although the fields here are public, you almost certainly should
|
||||
* never use that. All that is supported is contructing new instances of this
|
||||
* class, and then passing an array of them to filter_phrases.
|
||||
*
|
||||
* @package core_filters
|
||||
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class filter_object {
|
||||
/** @var string this is the phrase that should be matched. */
|
||||
public $phrase;
|
||||
|
||||
/** @var bool whether to match complete words. If true, 'T' won't be matched in 'Tim'. */
|
||||
public $fullmatch;
|
||||
|
||||
/** @var bool whether the match needs to be case sensitive. */
|
||||
public $casesensitive;
|
||||
|
||||
/** @var string HTML to insert before any match. */
|
||||
public $hreftagbegin;
|
||||
/** @var string HTML to insert after any match. */
|
||||
public $hreftagend;
|
||||
|
||||
/** @var null|string replacement text to go inside begin and end. If not set,
|
||||
* the body of the replacement will be the original phrase.
|
||||
*/
|
||||
public $replacementphrase;
|
||||
|
||||
/** @var null|string once initialised, holds the regexp for matching this phrase. */
|
||||
public $workregexp = null;
|
||||
|
||||
/** @var null|string once initialised, holds the mangled HTML to replace the regexp with. */
|
||||
public $workreplacementphrase = null;
|
||||
|
||||
/** @var null|callable hold a replacement function to be called. */
|
||||
public $replacementcallback;
|
||||
|
||||
/** @var null|array data to be passed to $replacementcallback. */
|
||||
public $replacementcallbackdata;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $phrase this is the phrase that should be matched.
|
||||
* @param string $hreftagbegin HTML to insert before any match. Default '<span class="highlight">'.
|
||||
* @param string $hreftagend HTML to insert after any match. Default '</span>'.
|
||||
* @param bool $casesensitive whether the match needs to be case sensitive
|
||||
* @param bool $fullmatch whether to match complete words. If true, 'T' won't be matched in 'Tim'.
|
||||
* @param mixed $replacementphrase replacement text to go inside begin and end. If not set,
|
||||
* the body of the replacement will be the original phrase.
|
||||
* @param callback $replacementcallback if set, then this will be called just before
|
||||
* $hreftagbegin, $hreftagend and $replacementphrase are needed, so they can be computed only if required.
|
||||
* The call made is
|
||||
* 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 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
|
||||
) {
|
||||
|
||||
$this->phrase = $phrase;
|
||||
$this->hreftagbegin = $hreftagbegin;
|
||||
$this->hreftagend = $hreftagend;
|
||||
$this->casesensitive = !empty($casesensitive);
|
||||
$this->fullmatch = !empty($fullmatch);
|
||||
$this->replacementphrase = $replacementphrase;
|
||||
$this->replacementcallback = $replacementcallback;
|
||||
$this->replacementcallbackdata = $replacementcallbackdata;
|
||||
}
|
||||
}
|
||||
|
||||
// Alias this class to the old name.
|
||||
// This file will be autoloaded by the legacyclasses autoload system.
|
||||
// In future all uses of this class will be corrected and the legacy references will be removed.
|
||||
class_alias(filter_object::class, \filterobject::class);
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// 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;
|
||||
|
||||
use core\context;
|
||||
use moodleform;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
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 GPL v3 or later
|
||||
* @package core_filters
|
||||
*/
|
||||
abstract class local_settings_form extends moodleform {
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function definition() {
|
||||
$mform = $this->_form;
|
||||
|
||||
$this->definition_inner($mform);
|
||||
|
||||
$mform->addElement('hidden', 'contextid');
|
||||
$mform->setType('contextid', PARAM_INT);
|
||||
$mform->setDefault('contextid', $this->context->id);
|
||||
|
||||
$mform->addElement('hidden', 'filter');
|
||||
$mform->setType('filter', PARAM_SAFEPATH);
|
||||
$mform->setDefault('filter', $this->filter);
|
||||
|
||||
$this->add_action_buttons();
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method to add your form controls.
|
||||
*
|
||||
* @param \MoodleQuickForm $mform the form we are building. $this->_form, but passed in for convenience.
|
||||
*/
|
||||
abstract protected function definition_inner($mform);
|
||||
|
||||
/**
|
||||
* Override this method to save the settings to the database.
|
||||
*
|
||||
* The default implementation will probably be sufficient for most simple cases.
|
||||
*
|
||||
* @param object $data the form data that was submitted.
|
||||
*/
|
||||
public function save_changes($data) {
|
||||
$data = (array) $data;
|
||||
unset($data['filter']);
|
||||
unset($data['contextid']);
|
||||
foreach ($data as $name => $value) {
|
||||
if ($value !== '') {
|
||||
filter_set_local_config($this->filter, $this->context->id, $name, $value);
|
||||
} else {
|
||||
filter_unset_local_config($this->filter, $this->context->id, $name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class_alias(local_settings_form::class, \filter_local_settings_form::class);
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// 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;
|
||||
|
||||
use core\context;
|
||||
|
||||
/**
|
||||
* Filter manager subclass that does nothing. Having this simplifies the logic
|
||||
* of format_text, etc.
|
||||
*
|
||||
* @package core_filters
|
||||
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class null_filter_manager {
|
||||
/**
|
||||
* 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 null|array $skipfilters not used
|
||||
* @return string resulting text.
|
||||
*/
|
||||
public function filter_text(
|
||||
$text,
|
||||
$context,
|
||||
array $options = [],
|
||||
?array $skipfilters = null
|
||||
) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* As for the equivalent {@see filter_manager} method.
|
||||
*
|
||||
* @param string $string The text to filter
|
||||
* @param context $context not used.
|
||||
* @return string resulting string
|
||||
*/
|
||||
public function filter_string($string, $context) {
|
||||
return $string;
|
||||
}
|
||||
}
|
||||
|
||||
// Alias this class to the old name.
|
||||
// This file will be autoloaded by the legacyclasses autoload system.
|
||||
// In future all uses of this class will be corrected and the legacy references will be removed.
|
||||
class_alias(null_filter_manager::class, \null_filter_manager::class);
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// 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;
|
||||
|
||||
/**
|
||||
* Filter manager subclass that tracks how much work it does.
|
||||
*
|
||||
* @package core_filters
|
||||
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class performance_measuring_filter_manager extends filter_manager {
|
||||
/** @var int number of filter objects created. */
|
||||
protected int $filterscreated = 0;
|
||||
|
||||
/** @var int number of calls to filter_text. */
|
||||
protected int $textsfiltered = 0;
|
||||
|
||||
/** @var int number of calls to filter_string. */
|
||||
protected int $stringsfiltered = 0;
|
||||
|
||||
#[\Override]
|
||||
protected function unload_all_filters() {
|
||||
parent::unload_all_filters();
|
||||
$this->filterscreated = 0;
|
||||
$this->textsfiltered = 0;
|
||||
$this->stringsfiltered = 0;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
protected function make_filter_object($filtername, $context, $localconfig) {
|
||||
$this->filterscreated++;
|
||||
return parent::make_filter_object($filtername, $context, $localconfig);
|
||||
}
|
||||
|
||||
#[\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 {@see get_performance_info()}.
|
||||
*
|
||||
* @return array the performance info.
|
||||
*/
|
||||
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',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Alias this class to the old name.
|
||||
// This file will be autoloaded by the legacyclasses autoload system.
|
||||
// In future all uses of this class will be corrected and the legacy references will be removed.
|
||||
class_alias(performance_measuring_filter_manager::class, \performance_measuring_filter_manager::class);
|
||||
@@ -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 <[email protected]>
|
||||
* @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 <[email protected]>
|
||||
* @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.
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// 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;
|
||||
|
||||
use core\context;
|
||||
use core\exception\coding_exception;
|
||||
use moodle_page;
|
||||
|
||||
/**
|
||||
* Base class for text filters. You just need to override this class and
|
||||
* implement the filter method.
|
||||
*
|
||||
* @package core_filters
|
||||
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
abstract class text_filter {
|
||||
/** @var context The context we are in. */
|
||||
protected $context;
|
||||
|
||||
/** @var array Any local configuration for this filter in this context. */
|
||||
protected $localconfig;
|
||||
|
||||
/**
|
||||
* Set any context-specific configuration for this filter.
|
||||
*
|
||||
* @param context $context The current context.
|
||||
* @param array $localconfig Any context-specific configuration for this filter.
|
||||
*/
|
||||
public function __construct($context, array $localconfig) {
|
||||
$this->context = $context;
|
||||
$this->localconfig = $localconfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup page with filter requirements and other prepare stuff.
|
||||
*
|
||||
* Override this method if the filter needs to setup page
|
||||
* requirements or needs other stuff to be executed.
|
||||
*
|
||||
* Note this method is invoked from {@see setup_page_for_filters()}
|
||||
* for each piece of text being filtered, so it is responsible
|
||||
* for controlling its own execution cardinality.
|
||||
*
|
||||
* @param moodle_page $page the page we are going to add requirements to.
|
||||
* @param context $context the context which contents are going to be filtered.
|
||||
* @since Moodle 2.3
|
||||
*/
|
||||
public function setup($page, $context) {
|
||||
// Override me, if needed.
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this function to actually implement the filtering.
|
||||
*
|
||||
* Filter developers must make sure that filtering done after text cleaning
|
||||
* does not introduce security vulnerabilities.
|
||||
*
|
||||
* @param string $text some HTML content to process.
|
||||
* @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 = []);
|
||||
|
||||
/**
|
||||
* Filter text before changing format to HTML.
|
||||
*
|
||||
* @param string $text
|
||||
* @param array $options
|
||||
* @return string
|
||||
*/
|
||||
public function filter_stage_pre_format(string $text, array $options): string {
|
||||
// NOTE: override if necessary.
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter HTML text before sanitising text.
|
||||
*
|
||||
* NOTE: this is called even if $options['noclean'] is true and text is not cleaned.
|
||||
*
|
||||
* @param string $text
|
||||
* @param array $options
|
||||
* @return string
|
||||
*/
|
||||
public function filter_stage_pre_clean(string $text, array $options): string {
|
||||
// NOTE: override if necessary.
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter HTML text at the very end after text is sanitised.
|
||||
*
|
||||
* NOTE: this is called even if $options['noclean'] is true and text is not cleaned.
|
||||
*
|
||||
* @param string $text
|
||||
* @param array $options
|
||||
* @return string
|
||||
*/
|
||||
public function filter_stage_post_clean(string $text, array $options): string {
|
||||
// NOTE: override if necessary.
|
||||
return $this->filter($text, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter simple text coming from format_string().
|
||||
*
|
||||
* Note that unless $CFG->formatstringstriptags is disabled
|
||||
* HTML tags are not expected in returned value.
|
||||
*
|
||||
* @param string $text
|
||||
* @param array $options
|
||||
* @return string
|
||||
*/
|
||||
public function filter_stage_string(string $text, array $options): string {
|
||||
// NOTE: override if necessary.
|
||||
return $this->filter($text, $options);
|
||||
}
|
||||
}
|
||||
|
||||
// Alias this class to the old name.
|
||||
// This file will be autoloaded by the legacyclasses autoload system.
|
||||
// In future all uses of this class will be corrected and the legacy references will be removed.
|
||||
class_alias(text_filter::class, \moodle_text_filter::class);
|
||||
@@ -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_codehighlighter;
|
||||
|
||||
/**
|
||||
* Code highlighter filter.
|
||||
*
|
||||
@@ -23,14 +25,8 @@
|
||||
* @copyright 2023 Meirza <meirza.arson@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class filter_codehighlighter extends moodle_text_filter {
|
||||
/**
|
||||
* Apply the filter to the text
|
||||
*
|
||||
* @param string $text to be processed by the text
|
||||
* @param array $options filter options
|
||||
* @return string text after processing
|
||||
*/
|
||||
class text_filter extends \core_filters\text_filter {
|
||||
#[\Override]
|
||||
public function filter($text, array $options = []): string {
|
||||
global $PAGE;
|
||||
|
||||
@@ -14,53 +14,44 @@
|
||||
// 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_data;
|
||||
use core_filters\filter_object;
|
||||
|
||||
/**
|
||||
* This filter provides automatic linking to database activity entries
|
||||
* when found inside every Moodle text.
|
||||
* Filter providing automatic linking to database activity entries when found inside every Moodle text.
|
||||
*
|
||||
* @package filter
|
||||
* @subpackage data
|
||||
* @package filter_data
|
||||
* @copyright 2006 Vy-Shane Sin Fat
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Database activity filtering
|
||||
*/
|
||||
class filter_data extends moodle_text_filter {
|
||||
|
||||
public function filter($text, array $options = array()) {
|
||||
class text_filter extends \core_filters\text_filter {
|
||||
#[\Override]
|
||||
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;
|
||||
}
|
||||
@@ -70,6 +61,7 @@ class filter_data extends moodle_text_filter {
|
||||
}
|
||||
|
||||
// If courseid == 0 only site entries will be returned.
|
||||
$site = get_site();
|
||||
if ($courseid == get_site()->id || $courseid == 0) {
|
||||
$contentlist = & $sitecontentlist;
|
||||
} else {
|
||||
@@ -78,12 +70,12 @@ class filter_data extends moodle_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
|
||||
@@ -99,7 +91,7 @@ class filter_data extends moodle_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]);
|
||||
@@ -108,7 +100,7 @@ class filter_data extends moodle_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]);
|
||||
@@ -120,20 +112,27 @@ class filter_data extends moodle_text_filter {
|
||||
return $text;
|
||||
}
|
||||
|
||||
usort($contents, 'filter_data::sort_entries_by_length');
|
||||
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.
|
||||
'&rid='.$content->recordid.'">';
|
||||
$contentlist[] = new filterobject($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 .
|
||||
'&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);
|
||||
@@ -31,16 +31,13 @@ namespace filter_data;
|
||||
* @package filter_data
|
||||
* @copyright 2015 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \filter_data\text_filter
|
||||
*/
|
||||
class filter_test extends \advanced_testcase {
|
||||
|
||||
final class text_filter_test extends \advanced_testcase {
|
||||
/**
|
||||
* Tests that the filter applies the required changes.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_filter(): void {
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
$this->setAdminUser();
|
||||
\filter_manager::reset_caches();
|
||||
@@ -13,16 +13,12 @@
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
/**
|
||||
* Display H5P filter
|
||||
*
|
||||
* @package filter_displayh5p
|
||||
* @copyright 2019 Victor Deniz <victor@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die;
|
||||
namespace filter_displayh5p;
|
||||
|
||||
use core\output\html_writer;
|
||||
use core\url;
|
||||
use core_filters\filter_object;
|
||||
use core_h5p\local\library\autoloader;
|
||||
|
||||
/**
|
||||
@@ -34,24 +30,15 @@ use core_h5p\local\library\autoloader;
|
||||
* @copyright 2019 Victor Deniz <victor@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class filter_displayh5p extends moodle_text_filter {
|
||||
|
||||
/**
|
||||
* @var boolean $loadresizerjs This is whether to request the resize.js script.
|
||||
*/
|
||||
class text_filter extends \core_filters\text_filter {
|
||||
/** @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;
|
||||
}
|
||||
@@ -65,18 +52,18 @@ class filter_displayh5p extends moodle_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 = ['\?', '&'];
|
||||
$h5pcontents = array();
|
||||
$h5plinks = array();
|
||||
$h5pcontents = [];
|
||||
$h5plinks = [];
|
||||
|
||||
// Check all allowed sources.
|
||||
foreach ($allowedsources as $source) {
|
||||
@@ -87,7 +74,7 @@ class filter_displayh5p extends moodle_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 {
|
||||
@@ -105,17 +92,33 @@ class filter_displayh5p extends moodle_text_filter {
|
||||
continue;
|
||||
}
|
||||
|
||||
$h5pcontenturl = new filterobject($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 filterobject($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;
|
||||
}
|
||||
@@ -128,7 +131,8 @@ class filter_displayh5p extends moodle_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);
|
||||
@@ -137,7 +141,9 @@ class filter_displayh5p extends moodle_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
|
||||
@@ -145,20 +151,21 @@ class filter_displayh5p extends moodle_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;
|
||||
|
||||
// The Edit button placeholder has been added only if the file can be edited.
|
||||
if ($h5pcontent->replacementcallbackdata['canbeedited']) {
|
||||
// If the content was originally a link, ignore it (it won't have the placeholder).
|
||||
$matchurl = new \moodle_url($matches[0]);
|
||||
$matchurl = new url($matches[0]);
|
||||
if (strpos($matchurl->get_path(), 'h5p/embed.php') !== false) {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
$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);
|
||||
|
||||
@@ -183,7 +190,9 @@ class filter_displayh5p extends moodle_text_filter {
|
||||
}
|
||||
|
||||
return $matches[0];
|
||||
}, $text);
|
||||
},
|
||||
$text
|
||||
);
|
||||
}
|
||||
|
||||
$result = filter_phrases($text, $h5pcontents, null, null, false, true);
|
||||
@@ -192,18 +201,21 @@ class filter_displayh5p extends moodle_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;
|
||||
}
|
||||
@@ -219,7 +231,6 @@ class filter_displayh5p extends moodle_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;
|
||||
@@ -1,103 +0,0 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Unit tests for the filter_displayh5p
|
||||
*
|
||||
* @package filter_displayh5p
|
||||
* @category test
|
||||
* @copyright 2019 Victor Deniz <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace filter_displayh5p;
|
||||
|
||||
use filter_displayh5p;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot.'/filter/displayh5p/filter.php');
|
||||
|
||||
/**
|
||||
* Unit tests for the display H5P filter.
|
||||
*
|
||||
* @copyright 2019 Victor Deniz <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class filter_test extends \advanced_testcase {
|
||||
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
set_config('allowedsources',
|
||||
"https://moodle.h5p.com/content/[id]/embed\nhttps://moodle.h5p.com/content/[id]
|
||||
\nhttps://generic.wordpress.soton.ac.uk/altc/wp-admin/admin-ajax.php?action=h5p_embed&id=[id]",
|
||||
'filter_displayh5p');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that h5p tags with urls from allowed domains are filtered.
|
||||
*
|
||||
* @param string $text Original text
|
||||
* @param string $filteredtextpattern Text pattern after display H5P filter
|
||||
*
|
||||
* @dataProvider texts_provider
|
||||
*/
|
||||
public function test_filter_urls($text, $filteredtextpattern): void {
|
||||
|
||||
$filterplugin = new filter_displayh5p(null, array());
|
||||
|
||||
$filteredtext = $filterplugin->filter($text);
|
||||
$this->assertMatchesRegularExpression($filteredtextpattern, $filteredtext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides texts to filter for the {@link self::test_filter_urls} method.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function texts_provider() {
|
||||
global $CFG;
|
||||
|
||||
return [
|
||||
["http:://example.com", "#http:://example.com#"],
|
||||
["http://google.es/h5p/embed/3425234", "#http://google.es/h5p/embed/3425234#"],
|
||||
["https://moodle.h5p.com/content/1290729733828858779/embed", "#<iframe src=\"https://moodle.h5p.com/content/1290729733828858779/embed\"[^>]+?>#"],
|
||||
["https://moodle.h5p.com/content/1290729733828858779", "#<iframe src=\"https://moodle.h5p.com/content/1290729733828858779/embed\"[^>]+?>#"],
|
||||
["<a href=\"https://moodle.h5p.com/content/1290848995208939539/embed\">https://moodle.h5p.com/content/1290848995208939539/embed</a>",
|
||||
"#<iframe src=\"https://moodle.h5p.com/content/1290848995208939539/embed\"[^>]+?>#"],
|
||||
["<a href=\"https://moodle.org\">https://moodle.h5p.com/content/1290848995208939539/embed</a>",
|
||||
"#^((?!iframe).)*$#"],
|
||||
["<a href=\"https://moodle.h5p.com/content/1290848995208939539/embed\">link</a>", "#^((?!iframe).)*$#"],
|
||||
["this is a text with an h5p url https://moodle.h5p.com/content/1290848995208939539/embed inside",
|
||||
"#this is a text with an h5p url <iframe src=\"https://moodle.h5p.com/content/1290848995208939539/embed\"(.|\n)*> inside#"],
|
||||
["https://generic.wordpress.soton.ac.uk/altc/wp-admin/admin-ajax.php?action=h5p_embed&id=13",
|
||||
"#<iframe src=\"https://generic.wordpress.soton.ac.uk/altc/wp-admin/admin-ajax.php\?action=h5p_embed\&\;id=13\"[^>]+?>#"],
|
||||
["https://moodle.h5p.com/content/1290848995208939539/embed another content in the same page https://moodle.h5p.com/content/1290729733828858779/embed",
|
||||
"#<iframe src=\"https://moodle.h5p.com/content/1290848995208939539/embed\"[^>]+?>((?!<iframe).)*".
|
||||
"<iframe src=\"https://moodle.h5p.com/content/1290729733828858779/embed\"[^>]+?>#"],
|
||||
[$CFG->wwwroot."/pluginfile.php/5/user/private/interactive-video.h5p?export=1&embed=1",
|
||||
"#<iframe src=\"{$CFG->wwwroot}/h5p/embed.php\?url=".rawurlencode("{$CFG->wwwroot}/pluginfile.php/5/user/private/interactive-video.h5p").
|
||||
"&export=1&embed=1\"[^>]*?></iframe>#"],
|
||||
[$CFG->wwwroot."/pluginfile.php/5/user/private/accordion-6-7138%20%281%29.h5p.h5p",
|
||||
"#<iframe src=\"{$CFG->wwwroot}/h5p/embed.php\?url=".rawurlencode("{$CFG->wwwroot}/pluginfile.php/5/user/private/accordion-6-7138%20%281%29.h5p.h5p").
|
||||
"\"[^>]*?></iframe>#"]
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Unit tests for the filter_displayh5p
|
||||
*
|
||||
* @package filter_displayh5p
|
||||
* @category test
|
||||
* @copyright 2019 Victor Deniz <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace filter_displayh5p;
|
||||
|
||||
/**
|
||||
* Unit tests for the display H5P filter.
|
||||
*
|
||||
* @package filter_displayh5p
|
||||
* @copyright 2019 Victor Deniz <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \filter_displayh5p\text_filter
|
||||
*/
|
||||
final class text_filter_test extends \advanced_testcase {
|
||||
/**
|
||||
* Check that h5p tags with urls from allowed domains are filtered.
|
||||
*
|
||||
* @param string $text Original text
|
||||
* @param string $filteredtextpattern Text pattern after display H5P filter
|
||||
* @dataProvider texts_provider
|
||||
*/
|
||||
public function test_filter_urls($text, $filteredtextpattern): void {
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
set_config(
|
||||
'allowedsources',
|
||||
"https://moodle.h5p.com/content/[id]/embed\nhttps://moodle.h5p.com/content/[id]
|
||||
\nhttps://generic.wordpress.soton.ac.uk/altc/wp-admin/admin-ajax.php?action=h5p_embed&id=[id]",
|
||||
'filter_displayh5p'
|
||||
);
|
||||
|
||||
$filterplugin = new text_filter(null, []);
|
||||
|
||||
$filteredtext = $filterplugin->filter($text);
|
||||
$this->assertMatchesRegularExpression($filteredtextpattern, $filteredtext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides texts to filter for the {@see self::test_filter_urls} method.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function texts_provider(): array {
|
||||
global $CFG;
|
||||
|
||||
$contenturla = "https://moodle.h5p.com/content/1290848995208939539/embed";
|
||||
$contenturlb = "https://moodle.h5p.com/content/1290729733828858779/embed";
|
||||
|
||||
return [
|
||||
[
|
||||
"http:://example.com",
|
||||
"#http:://example.com#",
|
||||
],
|
||||
[
|
||||
"http://google.es/h5p/embed/3425234",
|
||||
"#http://google.es/h5p/embed/3425234#",
|
||||
],
|
||||
[
|
||||
$contenturlb,
|
||||
"#<iframe src=\"{$contenturlb}\"[^>]+?>#",
|
||||
],
|
||||
[
|
||||
"https://moodle.h5p.com/content/1290729733828858779",
|
||||
"#<iframe src=\"{$contenturlb}\"[^>]+?>#",
|
||||
],
|
||||
[
|
||||
"<a href=\"{$contenturla}\">{$contenturla}</a>",
|
||||
"#<iframe src=\"{$contenturla}\"[^>]+?>#", ],
|
||||
[
|
||||
"<a href=\"https://moodle.org\">{$contenturla}</a>",
|
||||
"#^((?!iframe).)*$#",
|
||||
],
|
||||
[
|
||||
"<a href=\"{$contenturla}\">link</a>",
|
||||
"#^((?!iframe).)*$#",
|
||||
],
|
||||
[
|
||||
"this is a text with an h5p url {$contenturla} inside",
|
||||
"#this is a text with an h5p url <iframe src=\"{$contenturla}\"(.|\n)*> inside#",
|
||||
],
|
||||
[
|
||||
"https://generic.wordpress.soton.ac.uk/altc/wp-admin/admin-ajax.php?action=h5p_embed&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\&\;id=13\"[^>]+?>#",
|
||||
],
|
||||
[
|
||||
"{$contenturla} another content in the same page {$contenturlb}",
|
||||
"#<iframe src=\"{$contenturla}\"[^>]+?>((?!<iframe).)*" .
|
||||
"<iframe src=\"{$contenturlb}\"[^>]+?>#",
|
||||
],
|
||||
[
|
||||
$CFG->wwwroot . "/pluginfile.php/5/user/private/interactive-video.h5p?export=1&embed=1",
|
||||
"#<iframe src=\"{$CFG->wwwroot}/h5p/embed.php\?url=" .
|
||||
rawurlencode("{$CFG->wwwroot}/pluginfile.php/5/user/private/interactive-video.h5p") .
|
||||
"&export=1&embed=1\"[^>]*?></iframe>#",
|
||||
],
|
||||
[
|
||||
$CFG->wwwroot . "/pluginfile.php/5/user/private/accordion-6-7138%20%281%29.h5p.h5p",
|
||||
"#<iframe src=\"{$CFG->wwwroot}/h5p/embed.php\?url=" .
|
||||
rawurlencode("{$CFG->wwwroot}/pluginfile.php/5/user/private/accordion-6-7138%20%281%29.h5p.h5p") .
|
||||
"\"[^>]*?></iframe>#",
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// 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_emailprotect;
|
||||
|
||||
/**
|
||||
* Basic email protection filter.
|
||||
*
|
||||
* This class looks for email addresses in Moodle text and hides them using the Moodle obfuscate_text function.
|
||||
*
|
||||
* @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 = []) {
|
||||
// Do a quick check using stripos to avoid unnecessary work.
|
||||
if (strpos($text, '@') === false) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
// 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';
|
||||
$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';
|
||||
$text = preg_replace_callback($pattern, 'filter_emailprotect_alter_email', $text);
|
||||
$text = preg_replace_callback($pattern, [self::class, 'alter_email'], $text);
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obfuscate the email address.
|
||||
*
|
||||
* @param mixed $matches
|
||||
* @return string
|
||||
*/
|
||||
private function alter_email($matches) {
|
||||
return $matches[1].obfuscate_text($matches[2]).$matches[3];
|
||||
}
|
||||
|
||||
/**
|
||||
* Obfuscate the mailto link.
|
||||
*
|
||||
* @param mixed $matches
|
||||
* @return string
|
||||
*/
|
||||
private function alter_mailto($matches) {
|
||||
return obfuscate_mailto($matches[2], $matches[4]);
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
<?php
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Basic email protection filter.
|
||||
*
|
||||
* @package filter
|
||||
* @subpackage emailprotect
|
||||
* @copyright 2004 Mike Churchward
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* This class looks for email addresses in Moodle text and
|
||||
* hides them using the Moodle obfuscate_text function.
|
||||
*/
|
||||
class filter_emailprotect extends moodle_text_filter {
|
||||
function filter($text, array $options = array()) {
|
||||
/// 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.
|
||||
$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';
|
||||
$text = preg_replace_callback($pattern, 'filter_emailprotect_alter_mailto', $text);
|
||||
|
||||
/// 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);
|
||||
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function filter_emailprotect_alter_email($matches) {
|
||||
return $matches[1].obfuscate_text($matches[2]).$matches[3];
|
||||
}
|
||||
|
||||
function filter_emailprotect_alter_mailto($matches) {
|
||||
return obfuscate_mailto($matches[2], $matches[4]);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// 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_emailprotect;
|
||||
|
||||
/**
|
||||
* Tests for the filter_emailprotect text filter.
|
||||
*
|
||||
* @package filter_emailprotect
|
||||
* @category test
|
||||
* @copyright 2024 Andrew Lyons <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \filter_emailprotect\text_filter
|
||||
*/
|
||||
final class text_filter_test extends \advanced_testcase {
|
||||
/**
|
||||
* Test the filter method.
|
||||
*
|
||||
* @dataProvider filter_provider
|
||||
* @param string $expression The regexp to check.
|
||||
* @param string $text The text to filter.
|
||||
*/
|
||||
public function test_filter(
|
||||
string $expression,
|
||||
string $text,
|
||||
): void {
|
||||
$filter = new text_filter(\core\context\system::instance(), []);
|
||||
$this->assertMatchesRegularExpression($expression, $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for the filter test.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function filter_provider(): array {
|
||||
$email = '[email protected]';
|
||||
return [
|
||||
// No email address found.
|
||||
['/Hello, world!/', 'Hello, world!'],
|
||||
// Email addresses present.
|
||||
// Note: The obfuscation randomly choose which chars to obfuscate.
|
||||
['/.*@.*/', $email],
|
||||
["~<a href='mailto:.*@.*'>.*@.*</a>~", "<a href='mailto:$email'>$email</a>"],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -15,22 +14,26 @@
|
||||
// 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_emoticon;
|
||||
|
||||
/**
|
||||
* Filter converting emoticon texts into images
|
||||
*
|
||||
* This filter uses the emoticon settings in Site admin > Appearance > HTML settings
|
||||
* and replaces emoticon texts with images.
|
||||
*
|
||||
* @package filter
|
||||
* @subpackage emoticon
|
||||
* @see emoticon_manager
|
||||
* @package filter_emoticon
|
||||
* @copyright 2010 David Mudrak <david@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
class filter_emoticon extends moodle_text_filter {
|
||||
class text_filter extends \core_filters\text_filter {
|
||||
/**
|
||||
* Internal cache used for replacing. Multidimensional array;
|
||||
* - dimension 1: language,
|
||||
* - dimension 2: theme.
|
||||
* @var array
|
||||
*/
|
||||
protected static $emoticontexts = [];
|
||||
|
||||
/**
|
||||
* Internal cache used for replacing. Multidimensional array;
|
||||
@@ -38,30 +41,14 @@ class filter_emoticon extends moodle_text_filter {
|
||||
* - dimension 2: theme.
|
||||
* @var array
|
||||
*/
|
||||
protected static $emoticontexts = array();
|
||||
|
||||
/**
|
||||
* Internal cache used for replacing. Multidimensional array;
|
||||
* - dimension 1: language,
|
||||
* - dimension 2: theme.
|
||||
* @var array
|
||||
*/
|
||||
protected static $emoticonimgs = array();
|
||||
|
||||
/**
|
||||
* Apply the filter to the text
|
||||
*
|
||||
* @see 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')))) {
|
||||
@@ -70,10 +57,6 @@ class filter_emoticon extends moodle_text_filter {
|
||||
return $text;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// internal implementation starts here
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Replace emoticons found in the text with their images
|
||||
*
|
||||
@@ -86,12 +69,12 @@ class filter_emoticon extends moodle_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));
|
||||
@@ -111,7 +94,7 @@ class filter_emoticon extends moodle_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) {
|
||||
@@ -126,20 +109,26 @@ class filter_emoticon extends moodle_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);
|
||||
+31
-36
@@ -14,6 +14,10 @@
|
||||
// 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_emoticon;
|
||||
|
||||
use core\context\system as context_system;
|
||||
|
||||
/**
|
||||
* Skype icons filter phpunit tests
|
||||
*
|
||||
@@ -21,22 +25,9 @@
|
||||
* @category test
|
||||
* @copyright 2013 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \filter_emoticon\text_filter
|
||||
*/
|
||||
|
||||
namespace filter_emoticon;
|
||||
|
||||
use filter_emoticon;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/filter/emoticon/filter.php'); // Include the code to test.
|
||||
|
||||
/**
|
||||
* Skype icons filter testcase.
|
||||
*/
|
||||
class filter_test extends \advanced_testcase {
|
||||
|
||||
final class text_filter_test extends \advanced_testcase {
|
||||
/**
|
||||
* Test that filter ignores nolink/pre element, and processes remaining text
|
||||
*
|
||||
@@ -48,7 +39,7 @@ class filter_test extends \advanced_testcase {
|
||||
public function test_filter_emoticon_filtered(string $input, string $expectedprefix): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$filteredtext = (new testable_filter_emoticon())->filter($input, [
|
||||
$filteredtext = $this->get_testable_filter_emoticon()->filter($input, [
|
||||
'originalformat' => FORMAT_HTML,
|
||||
]);
|
||||
|
||||
@@ -61,7 +52,7 @@ class filter_test extends \advanced_testcase {
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function filter_emoticon_filtered_provider(): array {
|
||||
public static function filter_emoticon_filtered_provider(): array {
|
||||
return [
|
||||
'FORMAT_HTML is filtered' => [
|
||||
'input' => 'Hello(n)',
|
||||
@@ -86,7 +77,7 @@ class filter_test extends \advanced_testcase {
|
||||
public function test_filter_emoticon($input, $format, $expected): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$filter = new testable_filter_emoticon();
|
||||
$filter = $this->get_testable_filter_emoticon();
|
||||
$this->assertEquals($expected, $filter->filter($input, [
|
||||
'originalformat' => $format,
|
||||
]));
|
||||
@@ -97,7 +88,7 @@ class filter_test extends \advanced_testcase {
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function filter_emoticon_provider() {
|
||||
public static function filter_emoticon_provider(): array {
|
||||
$grr = '(grr)';
|
||||
return [
|
||||
'FORMAT_MOODLE is not filtered' => [
|
||||
@@ -164,13 +155,12 @@ class filter_test extends \advanced_testcase {
|
||||
|
||||
/**
|
||||
* Tests the filter doesn't break anything if activated but invalid format passed.
|
||||
*
|
||||
*/
|
||||
public function test_filter_invalidformat(): void {
|
||||
global $PAGE;
|
||||
$this->resetAfterTest();
|
||||
|
||||
$filter = new testable_filter_emoticon();
|
||||
$filter = $this->get_testable_filter_emoticon();
|
||||
$input = '(grr)';
|
||||
$expected = '(grr)';
|
||||
|
||||
@@ -181,7 +171,6 @@ class filter_test extends \advanced_testcase {
|
||||
|
||||
/**
|
||||
* Tests the filter doesn't break anything if activated but no emoticons available.
|
||||
*
|
||||
*/
|
||||
public function test_filter_emptyemoticons(): void {
|
||||
global $CFG;
|
||||
@@ -189,7 +178,7 @@ class filter_test extends \advanced_testcase {
|
||||
// Empty the emoticons array.
|
||||
$CFG->emoticons = null;
|
||||
|
||||
$filter = new filter_emoticon(\context_system::instance(), array('originalformat' => FORMAT_HTML));
|
||||
$filter = new text_filter(context_system::instance(), ['originalformat' => FORMAT_HTML]);
|
||||
|
||||
$input = '(grr)';
|
||||
$expected = '(grr)';
|
||||
@@ -198,19 +187,25 @@ class filter_test extends \advanced_testcase {
|
||||
'originalformat' => FORMAT_HTML,
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclass for easier testing.
|
||||
*/
|
||||
class testable_filter_emoticon extends filter_emoticon {
|
||||
public function __construct() {
|
||||
// Reset static emoticon caches.
|
||||
parent::$emoticontexts = array();
|
||||
parent::$emoticonimgs = array();
|
||||
// Use this context for filtering.
|
||||
$this->context = \context_system::instance();
|
||||
// Define FORMAT_HTML as only one filtering in DB.
|
||||
set_config('formats', implode(',', array(FORMAT_HTML)), 'filter_emoticon');
|
||||
/**
|
||||
* Get a copy of the filter configured for testing.
|
||||
*
|
||||
* @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 = [];
|
||||
parent::$emoticonimgs = [];
|
||||
// Use this context for filtering.
|
||||
$this->context = context_system::instance();
|
||||
// Define FORMAT_HTML as only one filtering in DB.
|
||||
set_config('formats', implode(',', [FORMAT_HTML]), 'filter_emoticon');
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -14,28 +14,32 @@
|
||||
// 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_glossary;
|
||||
|
||||
use cache;
|
||||
use cache_store;
|
||||
use core\output\html_writer;
|
||||
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.
|
||||
* This filter provides automatic linking to glossary entries, aliases and categories when found inside every Moodle text.
|
||||
*
|
||||
* @package filter
|
||||
* @subpackage glossary
|
||||
* NOTE: multilang glossary entries are not compatible with this filter.
|
||||
*
|
||||
* @package filter_glossary
|
||||
* @copyright 2004 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Glossary linking filter class.
|
||||
*
|
||||
* NOTE: multilang glossary entries are not compatible with this filter.
|
||||
*/
|
||||
class filter_glossary extends moodle_text_filter {
|
||||
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', []);
|
||||
@@ -44,7 +48,7 @@ class filter_glossary extends moodle_text_filter {
|
||||
|
||||
/**
|
||||
* Get all the concepts for this context.
|
||||
* @return filterobject[] the concepts, and filterobjects.
|
||||
* @return filter_object[] the concepts, and filterobjects.
|
||||
*/
|
||||
protected function get_all_concepts() {
|
||||
global $USER;
|
||||
@@ -72,7 +76,7 @@ class filter_glossary extends moodle_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 filter_glossary extends moodle_text_filter {
|
||||
return [];
|
||||
}
|
||||
|
||||
$conceptlist = array();
|
||||
$conceptlist = [];
|
||||
|
||||
foreach ($allconcepts as $concepts) {
|
||||
foreach ($concepts as $concept) {
|
||||
$conceptlist[] = new filterobject($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 filter_glossary extends moodle_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 moodle_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 moodle_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('&', '&', $title), // Undo the s() mangling.
|
||||
'class' => 'glossary autolink concept glossaryid' . $concept->glossaryid,
|
||||
'data-entryid' => $concept->id,
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
// This flag is optionally set by resource_pluginfile()
|
||||
@@ -155,7 +175,8 @@ class filter_glossary extends moodle_text_filter {
|
||||
return [html_writer::start_tag('a', $attributes), '</a>', null];
|
||||
}
|
||||
|
||||
public function filter($text, array $options = array()) {
|
||||
#[\Override]
|
||||
public function filter($text, array $options = []) {
|
||||
global $GLOSSARY_EXCLUDEENTRY;
|
||||
|
||||
$conceptlist = $this->get_all_concepts();
|
||||
@@ -184,8 +205,8 @@ class filter_glossary extends moodle_text_filter {
|
||||
|
||||
/**
|
||||
* usort helper used in get_all_concepts above.
|
||||
* @param filterobject $filterobject0 first item to compare.
|
||||
* @param filterobject $filterobject1 second item to compare.
|
||||
* @param filter_object $filterobject0 first item to compare.
|
||||
* @param filter_object $filterobject1 second item to compare.
|
||||
* @return int -1, 0 or 1.
|
||||
*/
|
||||
private function sort_entries_by_length($filterobject0, $filterobject1) {
|
||||
+70
-54
@@ -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_glossary;
|
||||
|
||||
/**
|
||||
* Unit tests.
|
||||
*
|
||||
@@ -21,15 +23,9 @@
|
||||
* @category test
|
||||
* @copyright 2013 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \filter_glossary\text_filter
|
||||
*/
|
||||
|
||||
namespace filter_glossary;
|
||||
|
||||
/**
|
||||
* Test case for glossary.
|
||||
*/
|
||||
class filter_test extends \advanced_testcase {
|
||||
|
||||
final class text_filter_test extends \advanced_testcase {
|
||||
public function test_link_to_entry_with_alias(): void {
|
||||
global $CFG;
|
||||
$this->resetAfterTest(true);
|
||||
@@ -43,20 +39,25 @@ class filter_test extends \advanced_testcase {
|
||||
$context = \context_course::instance($course->id);
|
||||
|
||||
// Create a glossary.
|
||||
$glossary = $this->getDataGenerator()->create_module('glossary',
|
||||
array('course' => $course->id, 'mainglossary' => 1));
|
||||
$glossary = $this->getDataGenerator()->create_module(
|
||||
'glossary',
|
||||
['course' => $course->id, 'mainglossary' => 1]
|
||||
);
|
||||
|
||||
// Create two entries with ampersands and one normal entry.
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
|
||||
$normal = $generator->create_content($glossary, array('concept' => 'entry name'),
|
||||
array('first alias', 'second alias'));
|
||||
$normal = $generator->create_content(
|
||||
$glossary,
|
||||
['concept' => 'entry name'],
|
||||
['first alias', 'second alias']
|
||||
);
|
||||
|
||||
// Format text with all three entries in HTML.
|
||||
$html = '<p>First we have entry name, then we have it twp aliases first alias and second alias.</p>';
|
||||
$filtered = format_text($html, FORMAT_HTML, array('context' => $context));
|
||||
$filtered = format_text($html, FORMAT_HTML, ['context' => $context]);
|
||||
|
||||
// Find all the glossary links in the result.
|
||||
$matches = array();
|
||||
$matches = [];
|
||||
preg_match_all('~eid=([0-9]+).*?title="(.*?)"~', $filtered, $matches);
|
||||
|
||||
// There should be 3 glossary links.
|
||||
@@ -66,8 +67,8 @@ class filter_test extends \advanced_testcase {
|
||||
$this->assertEquals($normal->id, $matches[1][2]);
|
||||
|
||||
// Check text of title attribute.
|
||||
$this->assertEquals($glossary->name . ': entry name', $matches[2][0]);
|
||||
$this->assertEquals($glossary->name . ': first alias', $matches[2][1]);
|
||||
$this->assertEquals($glossary->name . ': entry name', $matches[2][0]);
|
||||
$this->assertEquals($glossary->name . ': first alias', $matches[2][1]);
|
||||
$this->assertEquals($glossary->name . ': second alias', $matches[2][2]);
|
||||
}
|
||||
|
||||
@@ -84,20 +85,22 @@ class filter_test extends \advanced_testcase {
|
||||
$context = \context_course::instance($course->id);
|
||||
|
||||
// Create a glossary.
|
||||
$glossary = $this->getDataGenerator()->create_module('glossary',
|
||||
array('course' => $course->id, 'mainglossary' => 1));
|
||||
$glossary = $this->getDataGenerator()->create_module(
|
||||
'glossary',
|
||||
['course' => $course->id, 'mainglossary' => 1]
|
||||
);
|
||||
|
||||
// Create two entries with ampersands and one normal entry.
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
|
||||
$shorter = $generator->create_content($glossary, array('concept' => 'Tim'));
|
||||
$longer = $generator->create_content($glossary, array('concept' => 'Time'));
|
||||
$shorter = $generator->create_content($glossary, ['concept' => 'Tim']);
|
||||
$longer = $generator->create_content($glossary, ['concept' => 'Time']);
|
||||
|
||||
// Format text with all three entries in HTML.
|
||||
$html = '<p>Time will tell</p>';
|
||||
$filtered = format_text($html, FORMAT_HTML, array('context' => $context));
|
||||
$filtered = format_text($html, FORMAT_HTML, ['context' => $context]);
|
||||
|
||||
// Find all the glossary links in the result.
|
||||
$matches = array();
|
||||
$matches = [];
|
||||
preg_match_all('~eid=([0-9]+).*?title="(.*?)"~', $filtered, $matches);
|
||||
|
||||
// There should be 1 glossary link to Time, not Tim.
|
||||
@@ -105,7 +108,7 @@ class filter_test extends \advanced_testcase {
|
||||
$this->assertEquals($longer->id, $matches[1][0]);
|
||||
|
||||
// Check text of title attribute.
|
||||
$this->assertEquals($glossary->name . ': Time', $matches[2][0]);
|
||||
$this->assertEquals($glossary->name . ': Time', $matches[2][0]);
|
||||
}
|
||||
|
||||
public function test_link_to_category(): void {
|
||||
@@ -121,20 +124,22 @@ class filter_test extends \advanced_testcase {
|
||||
$context = \context_course::instance($course->id);
|
||||
|
||||
// Create a glossary.
|
||||
$glossary = $this->getDataGenerator()->create_module('glossary',
|
||||
array('course' => $course->id, 'mainglossary' => 1));
|
||||
$glossary = $this->getDataGenerator()->create_module(
|
||||
'glossary',
|
||||
['course' => $course->id, 'mainglossary' => 1]
|
||||
);
|
||||
|
||||
// Create two entries with ampersands and one normal entry.
|
||||
/** @var \mod_glossary_generator $generator */
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
|
||||
$category = $generator->create_category($glossary, array('name' => 'My category', 'usedynalink' => 1));
|
||||
$category = $generator->create_category($glossary, ['name' => 'My category', 'usedynalink' => 1]);
|
||||
|
||||
// Format text with all three entries in HTML.
|
||||
$html = '<p>This is My category you know.</p>';
|
||||
$filtered = format_text($html, FORMAT_HTML, array('context' => $context));
|
||||
$filtered = format_text($html, FORMAT_HTML, ['context' => $context]);
|
||||
|
||||
// Find all the glossary links in the result.
|
||||
$matches = array();
|
||||
$matches = [];
|
||||
preg_match_all('~hook=([0-9]+).*?title="(.*?)"~', $filtered, $matches);
|
||||
|
||||
// There should be 1 glossary link.
|
||||
@@ -159,22 +164,24 @@ class filter_test extends \advanced_testcase {
|
||||
$context = \context_course::instance($course->id);
|
||||
|
||||
// Create a glossary.
|
||||
$glossary = $this->getDataGenerator()->create_module('glossary',
|
||||
array('course' => $course->id, 'mainglossary' => 1));
|
||||
$glossary = $this->getDataGenerator()->create_module(
|
||||
'glossary',
|
||||
['course' => $course->id, 'mainglossary' => 1]
|
||||
);
|
||||
|
||||
// Create two entries with ampersands and one normal entry.
|
||||
/** @var \mod_glossary_generator $generator */
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
|
||||
$normal = $generator->create_content($glossary, array('concept' => 'normal'));
|
||||
$amp1 = $generator->create_content($glossary, array('concept' => 'A&B'));
|
||||
$amp2 = $generator->create_content($glossary, array('concept' => 'C&D'));
|
||||
$normal = $generator->create_content($glossary, ['concept' => 'normal']);
|
||||
$amp1 = $generator->create_content($glossary, ['concept' => 'A&B']);
|
||||
$amp2 = $generator->create_content($glossary, ['concept' => 'C&D']);
|
||||
|
||||
// Format text with all three entries in HTML.
|
||||
$html = '<p>A&B C&D normal</p>';
|
||||
$filtered = format_text($html, FORMAT_HTML, array('context' => $context));
|
||||
$filtered = format_text($html, FORMAT_HTML, ['context' => $context]);
|
||||
|
||||
// Find all the glossary links in the result.
|
||||
$matches = array();
|
||||
$matches = [];
|
||||
preg_match_all('~eid=([0-9]+).*?title="(.*?)"~', $filtered, $matches);
|
||||
|
||||
// There should be 3 glossary links.
|
||||
@@ -205,22 +212,24 @@ class filter_test extends \advanced_testcase {
|
||||
$context = \context_course::instance($course->id);
|
||||
|
||||
// Create a glossary.
|
||||
$glossary = $this->getDataGenerator()->create_module('glossary',
|
||||
array('course' => $course->id, 'mainglossary' => 1));
|
||||
$glossary = $this->getDataGenerator()->create_module(
|
||||
'glossary',
|
||||
['course' => $course->id, 'mainglossary' => 1]
|
||||
);
|
||||
|
||||
// Create two entries with ampersands and one normal entry.
|
||||
/** @var \mod_glossary_generator $generator */
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
|
||||
$simple = $generator->create_content($glossary, array('concept' => 'simple'));
|
||||
$withbrackets = $generator->create_content($glossary, array('concept' => 'more complex (perhaps)'));
|
||||
$test2 = $generator->create_content($glossary, array('concept' => 'Test (2)'));
|
||||
$simple = $generator->create_content($glossary, ['concept' => 'simple']);
|
||||
$withbrackets = $generator->create_content($glossary, ['concept' => 'more complex (perhaps)']);
|
||||
$test2 = $generator->create_content($glossary, ['concept' => 'Test (2)']);
|
||||
|
||||
// Format text with all three entries in HTML.
|
||||
$html = '<p>Some thigns are simple. Others are more complex (perhaps). Test (2).</p>';
|
||||
$filtered = format_text($html, FORMAT_HTML, array('context' => $context));
|
||||
$filtered = format_text($html, FORMAT_HTML, ['context' => $context]);
|
||||
|
||||
// Find all the glossary links in the result.
|
||||
$matches = array();
|
||||
$matches = [];
|
||||
preg_match_all('~eid=([0-9]+).*?title="(.*?)"~', $filtered, $matches);
|
||||
|
||||
// There should be 3 glossary links.
|
||||
@@ -249,24 +258,29 @@ class filter_test extends \advanced_testcase {
|
||||
$context = \context_course::instance($course->id);
|
||||
|
||||
// Create a glossary.
|
||||
$glossary = $this->getDataGenerator()->create_module('glossary',
|
||||
array('course' => $course->id, 'mainglossary' => 1));
|
||||
$glossary = $this->getDataGenerator()->create_module(
|
||||
'glossary',
|
||||
['course' => $course->id, 'mainglossary' => 1]
|
||||
);
|
||||
|
||||
// Create two entries with ampersands and one normal entry.
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
|
||||
$tobeexcluded = $generator->create_content($glossary, array('concept' => 'entry name'),
|
||||
array('first alias', 'second alias'));
|
||||
$normal = $generator->create_content($glossary, array('concept' => 'other entry'));
|
||||
$tobeexcluded = $generator->create_content(
|
||||
$glossary,
|
||||
['concept' => 'entry name'],
|
||||
['first alias', 'second alias']
|
||||
);
|
||||
$normal = $generator->create_content($glossary, ['concept' => 'other entry']);
|
||||
|
||||
// Format text with all three entries in HTML.
|
||||
$html = '<p>First we have entry name, then we have it twp aliases first alias and second alias. ' .
|
||||
'In this case, those should not be linked, but this other entry should be.</p>';
|
||||
$GLOSSARY_EXCLUDEENTRY = $tobeexcluded->id;
|
||||
$filtered = format_text($html, FORMAT_HTML, array('context' => $context));
|
||||
$filtered = format_text($html, FORMAT_HTML, ['context' => $context]);
|
||||
$GLOSSARY_EXCLUDEENTRY = null;
|
||||
|
||||
// Find all the glossary links in the result.
|
||||
$matches = array();
|
||||
$matches = [];
|
||||
preg_match_all('~eid=([0-9]+).*?title="(.*?)"~', $filtered, $matches);
|
||||
|
||||
// There should be 1 glossary links.
|
||||
@@ -288,22 +302,24 @@ class filter_test extends \advanced_testcase {
|
||||
$context = \context_course::instance($course->id);
|
||||
|
||||
// Create a glossary.
|
||||
$glossary = $this->getDataGenerator()->create_module('glossary',
|
||||
array('course' => $course->id, 'mainglossary' => 1));
|
||||
$glossary = $this->getDataGenerator()->create_module(
|
||||
'glossary',
|
||||
['course' => $course->id, 'mainglossary' => 1]
|
||||
);
|
||||
|
||||
// Create two entries with ampersands and one normal entry.
|
||||
/** @var \mod_glossary_generator $generator */
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
|
||||
$category = $generator->create_category($glossary, array('name' => 'My category', 'usedynalink' => 1));
|
||||
$category = $generator->create_category($glossary, ['name' => 'My category', 'usedynalink' => 1]);
|
||||
|
||||
// Format text with all three entries in HTML.
|
||||
$html = '<p>This is My category you know.</p>';
|
||||
$GLOSSARY_EXCLUDEENTRY = $category->id;
|
||||
$filtered = format_text($html, FORMAT_HTML, array('context' => $context));
|
||||
$filtered = format_text($html, FORMAT_HTML, ['context' => $context]);
|
||||
$GLOSSARY_EXCLUDEENTRY = null;
|
||||
|
||||
// Find all the glossary links in the result.
|
||||
$matches = array();
|
||||
$matches = [];
|
||||
preg_match_all('~hook=([0-9]+).*?title="(.*?)"~', $filtered, $matches);
|
||||
|
||||
// There should be 1 glossary link.
|
||||
@@ -14,72 +14,3 @@
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* A Moodle form base class for editing local filter settings.
|
||||
*
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
|
||||
* @package core
|
||||
* @subpackage filter
|
||||
*/
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir . '/formslib.php');
|
||||
|
||||
abstract class filter_local_settings_form extends moodleform {
|
||||
protected $filter;
|
||||
protected $context;
|
||||
|
||||
public function __construct($submiturl, $filter, $context) {
|
||||
$this->filter = $filter;
|
||||
$this->context = $context;
|
||||
parent::__construct($submiturl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the form definition. Rather than overriding this method, you
|
||||
* should probably override definition_inner instead.
|
||||
*
|
||||
* This method adds the necessary hidden fields and submit buttons,
|
||||
* and calls definition_inner to insert the custom controls in the appropriate place.
|
||||
*/
|
||||
public function definition() {
|
||||
$mform = $this->_form;
|
||||
|
||||
$this->definition_inner($mform);
|
||||
|
||||
$mform->addElement('hidden', 'contextid');
|
||||
$mform->setType('contextid', PARAM_INT);
|
||||
$mform->setDefault('contextid', $this->context->id);
|
||||
|
||||
$mform->addElement('hidden', 'filter');
|
||||
$mform->setType('filter', PARAM_SAFEPATH);
|
||||
$mform->setDefault('filter', $this->filter);
|
||||
|
||||
$this->add_action_buttons();
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method to add your form controls.
|
||||
* @param $mform the form we are building. $this->_form, but passed in for convenience.
|
||||
*/
|
||||
abstract protected function definition_inner($mform);
|
||||
|
||||
/**
|
||||
* Override this method to save the settings to the database. The default
|
||||
* implementation will probably be sufficient for most simple cases.
|
||||
* @param object $data the form data that was submitted.
|
||||
*/
|
||||
public function save_changes($data) {
|
||||
$data = (array) $data;
|
||||
unset($data['filter']);
|
||||
unset($data['contextid']);
|
||||
foreach ($data as $name => $value) {
|
||||
if ($value !== '') {
|
||||
filter_set_local_config($this->filter, $this->context->id, $name, $value);
|
||||
} else {
|
||||
filter_unset_local_config($this->filter, $this->context->id, $name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@ if ($forfilter) {
|
||||
if (!filter_has_local_settings($forfilter)) {
|
||||
throw new \moodle_exception('filterdoesnothavelocalconfig', 'error', $forfilter);
|
||||
}
|
||||
require_once($CFG->dirroot . '/filter/local_settings_form.php');
|
||||
require_once($CFG->dirroot . '/filter/' . $forfilter . '/filterlocalsettings.php');
|
||||
$formname = $forfilter . '_filter_local_settings_form';
|
||||
$settingsform = new $formname($CFG->wwwroot . '/filter/manage.php', $forfilter, $context);
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
// 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_mathjaxloader;
|
||||
|
||||
use core\url;
|
||||
|
||||
/**
|
||||
* This filter provides automatic support for MathJax
|
||||
*
|
||||
@@ -21,15 +25,8 @@
|
||||
* @copyright 2013 Damyon Wiese (damyon@moodle.com)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Mathjax filtering
|
||||
*/
|
||||
class filter_mathjaxloader extends moodle_text_filter {
|
||||
|
||||
/*
|
||||
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
|
||||
@@ -41,7 +38,7 @@ class filter_mathjaxloader extends moodle_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).
|
||||
@@ -72,39 +69,29 @@ class filter_mathjaxloader extends moodle_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 moodle_url($url, array('delayStartupUntil' => 'configured'));
|
||||
|
||||
$page->requires->js($url);
|
||||
|
||||
$config = get_config('filter_mathjaxloader', 'mathjaxconfig');
|
||||
$wwwroot = new moodle_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');
|
||||
@@ -151,7 +138,7 @@ class filter_mathjaxloader extends moodle_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) {
|
||||
@@ -208,8 +195,10 @@ class filter_mathjaxloader extends moodle_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);
|
||||
|
||||
@@ -224,7 +213,7 @@ class filter_mathjaxloader extends moodle_text_filter {
|
||||
|
||||
++$i;
|
||||
}
|
||||
return array($text, $changesdone);
|
||||
return [$text, $changesdone];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -240,10 +229,12 @@ class filter_mathjaxloader extends moodle_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
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -255,7 +246,7 @@ class filter_mathjaxloader extends moodle_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 = ['<', '>'];
|
||||
return str_replace($search, $replace, $matches[0]);
|
||||
@@ -16,11 +16,7 @@
|
||||
|
||||
namespace filter_mathjaxloader;
|
||||
|
||||
use filter_mathjaxloader;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot.'/filter/mathjaxloader/filter.php');
|
||||
use core\context\system as context_system;
|
||||
|
||||
/**
|
||||
* Unit tests for the MathJax loader filter.
|
||||
@@ -29,19 +25,18 @@ require_once($CFG->dirroot.'/filter/mathjaxloader/filter.php');
|
||||
* @category test
|
||||
* @copyright 2018 Markku Riekkinen
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \filter_mathjaxloader\text_filter
|
||||
*/
|
||||
class filtermath_test extends \advanced_testcase {
|
||||
|
||||
final class filtermath_test extends \advanced_testcase {
|
||||
/**
|
||||
* Test the functionality of {@link filter_mathjaxloader::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 {
|
||||
$filter = new filter_mathjaxloader(\context_system::instance(), []);
|
||||
$filter = new text_filter(context_system::instance(), []);
|
||||
$this->assertEquals($expected, $filter->filter($inputtext));
|
||||
}
|
||||
|
||||
@@ -50,53 +45,70 @@ class filtermath_test extends \advanced_testcase {
|
||||
*
|
||||
* @return array of [inputtext, expectedoutput] tuples.
|
||||
*/
|
||||
public 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 \\).',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
+5
-15
@@ -16,13 +16,6 @@
|
||||
|
||||
namespace filter_mathjaxloader;
|
||||
|
||||
use filter_mathjaxloader;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot.'/filter/mathjaxloader/filter.php');
|
||||
|
||||
/**
|
||||
* Unit tests for the MathJax loader filter.
|
||||
*
|
||||
@@ -30,20 +23,18 @@ require_once($CFG->dirroot.'/filter/mathjaxloader/filter.php');
|
||||
* @category test
|
||||
* @copyright 2017 David Mudrak <david@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \filter_mathjaxloader\text_filter
|
||||
*/
|
||||
class filter_test extends \advanced_testcase {
|
||||
|
||||
final class text_filter_test extends \advanced_testcase {
|
||||
/**
|
||||
* Test the functionality of {@link filter_mathjaxloader::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 {
|
||||
|
||||
$filter = new filter_mathjaxloader(\context_system::instance(), []);
|
||||
$filter = new text_filter(\context_system::instance(), []);
|
||||
$this->assertEquals($mathjaxlangcode, $filter->map_language_code($moodlelangcode));
|
||||
}
|
||||
|
||||
@@ -52,8 +43,7 @@ class filter_test extends \advanced_testcase {
|
||||
*
|
||||
* @return array of [moodlelangcode, mathjaxcode] tuples
|
||||
*/
|
||||
public 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).
|
||||
@@ -14,19 +14,13 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Media plugin filtering
|
||||
*
|
||||
* This filter will replace any links to a media file with
|
||||
* a media plugin that plays that media inline
|
||||
*
|
||||
* @package filter
|
||||
* @subpackage mediaplugin
|
||||
* @copyright 2004 onwards Martin Dougiamas {@link http://moodle.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace filter_mediaplugin;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
use core\context;
|
||||
use core\url;
|
||||
use core_media_manager;
|
||||
use core_media_player_native;
|
||||
use moodle_page;
|
||||
|
||||
/**
|
||||
* Automatic media embedding filter class.
|
||||
@@ -34,12 +28,12 @@ defined('MOODLE_INTERNAL') || die();
|
||||
* 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
|
||||
*/
|
||||
class filter_mediaplugin extends moodle_text_filter {
|
||||
class text_filter extends \core_filters\text_filter {
|
||||
/** @var bool True if currently filtering trusted text */
|
||||
private $trusted;
|
||||
|
||||
@@ -61,11 +55,12 @@ class filter_mediaplugin extends moodle_text_filter {
|
||||
$mediamanager = core_media_manager::instance($page);
|
||||
}
|
||||
|
||||
public function filter($text, array $options = array()) {
|
||||
#[\Override]
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -75,7 +70,7 @@ class filter_mediaplugin extends moodle_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);
|
||||
@@ -97,14 +92,14 @@ class filter_mediaplugin extends moodle_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);
|
||||
@@ -115,8 +110,10 @@ class filter_mediaplugin extends moodle_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]);
|
||||
@@ -152,7 +149,7 @@ class filter_mediaplugin extends moodle_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.
|
||||
}
|
||||
|
||||
@@ -215,11 +212,11 @@ class filter_mediaplugin extends moodle_text_filter {
|
||||
// Find all sources both as <video src=""> and as embedded <source> tags.
|
||||
$urls = [];
|
||||
if (preg_match('/^<[^>]*\bsrc="(.*?)"/im', $fulltext, $matches)) {
|
||||
$urls[] = new moodle_url($matches[1]);
|
||||
$urls[] = new url($matches[1]);
|
||||
}
|
||||
if (preg_match_all('/<source\b[^>]*\bsrc="(.*?)"/im', $fulltext, $matches)) {
|
||||
foreach ($matches[1] as $url) {
|
||||
$urls[] = new moodle_url($url);
|
||||
$urls[] = new url($url);
|
||||
}
|
||||
}
|
||||
// Extract width/height/title attributes and call embed_alternatives to find a suitable media player.
|
||||
@@ -26,7 +26,6 @@
|
||||
*/
|
||||
|
||||
require(__DIR__ . '/../../../config.php');
|
||||
require_once($CFG->dirroot . '/filter/mediaplugin/filter.php');
|
||||
|
||||
// Only available to site admins.
|
||||
require_login();
|
||||
@@ -45,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(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.
|
||||
|
||||
@@ -14,45 +14,36 @@
|
||||
// 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_mediaplugin;
|
||||
|
||||
/**
|
||||
* Unit test for the filter_mediaplugin
|
||||
*
|
||||
* @package filter_mediaplugin
|
||||
* @category phpunit
|
||||
* @category test
|
||||
* @copyright 2011 Rossiani Wijaya <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \filter_mediaplugin\text_filter
|
||||
*/
|
||||
|
||||
namespace filter_mediaplugin;
|
||||
|
||||
use filter_mediaplugin;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/filter/mediaplugin/filter.php'); // Include the code to test
|
||||
|
||||
|
||||
class filter_test extends \advanced_testcase {
|
||||
|
||||
function test_filter_mediaplugin_link(): void {
|
||||
final class filter_test extends \advanced_testcase {
|
||||
public function test_text_filter_link(): void {
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
// We need to enable the media plugins.
|
||||
\core\plugininfo\media::set_enabled_plugins('vimeo,youtube,videojs,html5video,html5audio');
|
||||
|
||||
$filterplugin = new filter_mediaplugin(null, array());
|
||||
$filterplugin = new text_filter(null, []);
|
||||
|
||||
$longurl = '<a href="http://moodle/.mp4">my test file</a>';
|
||||
$longhref = '';
|
||||
|
||||
do {
|
||||
$longhref .= 'a';
|
||||
} while(strlen($longhref) + strlen($longurl) < 4095);
|
||||
} while (strlen($longhref) + strlen($longurl) < 4095);
|
||||
|
||||
$longurl = '<a href="http://moodle/' . $longhref . '.mp4">my test file</a>';
|
||||
|
||||
$validtexts = array (
|
||||
$validtexts = [
|
||||
'<a href="http://moodle.org/testfile/test.mp3">test mp3</a>',
|
||||
'<a href="http://moodle.org/testfile/test.ogg">test ogg</a>',
|
||||
'<a id="movie player" class="center" href="http://moodle.org/testfile/test.mp4">test mp4</a>',
|
||||
@@ -78,12 +69,12 @@ class filter_test extends \advanced_testcase {
|
||||
</a>',
|
||||
'<a href="http://www.youtube.com/watch?v=JghQgA2HMX8?d=200x200" >youtube\'s</a>',
|
||||
// Test a long URL under 4096 characters.
|
||||
$longurl
|
||||
);
|
||||
$longurl,
|
||||
];
|
||||
|
||||
//test for valid link
|
||||
// Test for valid link.
|
||||
foreach ($validtexts as $text) {
|
||||
$msg = "Testing text: ". $text;
|
||||
$msg = "Testing text: " . $text;
|
||||
$filter = $filterplugin->filter($text);
|
||||
$this->assertNotEquals($text, $filter, $msg);
|
||||
}
|
||||
@@ -92,15 +83,18 @@ class filter_test extends \advanced_testcase {
|
||||
$longurl = substr_replace($longurl, 'http://pushover4096chars', $insertpoint, 0);
|
||||
|
||||
$originalurl = '<p>Some text.</p><pre style="color: rgb(0, 0, 0); line-height: normal;">' .
|
||||
'<a href="https://www.youtube.com/watch?v=uUhWl9Lm3OM">Valid link</a></pre><pre style="color: rgb(0, 0, 0); line-height: normal;">';
|
||||
'<a href="https://www.youtube.com/watch?v=uUhWl9Lm3OM">Valid link</a>' .
|
||||
'</pre><pre style="color: rgb(0, 0, 0); line-height: normal;">';
|
||||
$paddedurl = str_pad($originalurl, 6000, 'z');
|
||||
$validpaddedurl = '<p>Some text.</p><pre style="color: rgb(0, 0, 0); line-height: normal;"><span class="mediaplugin mediaplugin_youtube">
|
||||
$validpaddedurl = '<p>Some text.</p>' .
|
||||
'<pre style="color: rgb(0, 0, 0); line-height: normal;">' .
|
||||
'<span class="mediaplugin mediaplugin_youtube">
|
||||
<iframe title="Valid link" width="640" height="360" style="border:0;"
|
||||
src="https://www.youtube.com/embed/uUhWl9Lm3OM?rel=0&wmode=transparent" allow="fullscreen" loading="lazy"></iframe>
|
||||
</span></pre><pre style="color: rgb(0, 0, 0); line-height: normal;">';
|
||||
$validpaddedurl = str_pad($validpaddedurl, 6000 + (strlen($validpaddedurl) - strlen($originalurl)), 'z');
|
||||
|
||||
$invalidtexts = array(
|
||||
$invalidtexts = [
|
||||
'<a class="_blanktarget">href="http://moodle.org/testfile/test.mp3"</a>',
|
||||
'<a>test test</a>',
|
||||
'<a >test test</a>',
|
||||
@@ -117,18 +111,18 @@ class filter_test extends \advanced_testcase {
|
||||
'<ahref="http://moodle.org/testfile/test.mp3">test mp3</a>',
|
||||
'<aclass="content" href="http://moodle.org/testfile/test.mp3">test mp3</a>',
|
||||
// Test a long URL over 4096 characters.
|
||||
$longurl
|
||||
);
|
||||
$longurl,
|
||||
];
|
||||
|
||||
//test for invalid link
|
||||
// Test for invalid link.
|
||||
foreach ($invalidtexts as $text) {
|
||||
$msg = "Testing text: ". $text;
|
||||
$msg = "Testing text: " . $text;
|
||||
$filter = $filterplugin->filter($text);
|
||||
$this->assertEquals($text, $filter, $msg);
|
||||
}
|
||||
|
||||
// Valid mediaurl followed by a longurl.
|
||||
$precededlongurl = '<a href="http://moodle.org/testfile/test.mp3">test.mp3</a>'. $longurl;
|
||||
$precededlongurl = '<a href="http://moodle.org/testfile/test.mp3">test.mp3</a>' . $longurl;
|
||||
$filter = $filterplugin->filter($precededlongurl);
|
||||
$this->assertEquals(1, substr_count($filter, '</audio>'));
|
||||
$this->assertStringContainsString($longurl, $filter);
|
||||
|
||||
@@ -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
|
||||
@@ -15,62 +14,53 @@
|
||||
// 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_multilang;
|
||||
|
||||
/**
|
||||
* Implementation of the Moodle filter API for the Multi-lang filter.
|
||||
*
|
||||
* Given XML multilinguage text, return relevant text according to
|
||||
* current language:
|
||||
* - look for multilang blocks in the text.
|
||||
* - if there exists texts in the currently active language, print them.
|
||||
* - else, if there exists texts in the current parent language, print them.
|
||||
* - else, print the first language in the text.
|
||||
* Please note that English texts are not used as default anymore!
|
||||
*
|
||||
* This version is based on original multilang filter by Gaetan Frenoy,
|
||||
* rewritten by Eloy and skodak.
|
||||
*
|
||||
* Following new syntax is not compatible with old one:
|
||||
* <span lang="XX" class="multilang">one lang</span><span lang="YY" class="multilang">another language</span>
|
||||
*
|
||||
* @package filter_multilang
|
||||
* @copyright Gaetan Frenoy <gaetan@frenoy.net>
|
||||
* @copyright 2004 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
// Given XML multilinguage text, return relevant text according to
|
||||
// current language:
|
||||
// - look for multilang blocks in the text.
|
||||
// - if there exists texts in the currently active language, print them.
|
||||
// - else, if there exists texts in the current parent language, print them.
|
||||
// - else, print the first language in the text.
|
||||
// Please note that English texts are not used as default anymore!
|
||||
//
|
||||
// This version is based on original multilang filter by Gaetan Frenoy,
|
||||
// rewritten by Eloy and skodak.
|
||||
//
|
||||
// Following new syntax is not compatible with old one:
|
||||
// <span lang="XX" class="multilang">one lang</span><span lang="YY" class="multilang">another language</span>
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of the Moodle filter API for the Multi-lang filter.
|
||||
*
|
||||
* @copyright Gaetan Frenoy <gaetan@frenoy.net>
|
||||
* @copyright 2004 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class filter_multilang extends moodle_text_filter {
|
||||
function filter($text, array $options = array()) {
|
||||
class text_filter extends \core_filters\text_filter {
|
||||
#[\Override]
|
||||
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;
|
||||
}
|
||||
@@ -90,7 +80,7 @@ class filter_multilang extends moodle_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];
|
||||
+14
-28
@@ -14,36 +14,18 @@
|
||||
// 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_multilang;
|
||||
|
||||
/**
|
||||
* Unit tests.
|
||||
* Tests for filter_multilang.
|
||||
*
|
||||
* @package filter_multilang
|
||||
* @category test
|
||||
* @copyright 2019 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \filter_multilang\text_filter
|
||||
*/
|
||||
|
||||
namespace filter_multilang;
|
||||
|
||||
use filter_multilang;
|
||||
|
||||
/**
|
||||
* Tests for filter_multilang.
|
||||
*
|
||||
* @copyright 2019 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class filter_test extends \advanced_testcase {
|
||||
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
// Enable glossary filter at top level.
|
||||
filter_set_global_state('multilang', TEXTFILTER_ON);
|
||||
}
|
||||
|
||||
final class text_filter_test extends \advanced_testcase {
|
||||
/**
|
||||
* Setup parent language relationship.
|
||||
*
|
||||
@@ -62,7 +44,7 @@ class filter_test extends \advanced_testcase {
|
||||
/**
|
||||
* Data provider for multi-language filtering tests.
|
||||
*/
|
||||
public function multilang_testcases() {
|
||||
public static function multilang_testcases(): array {
|
||||
return [
|
||||
'Basic case EN' => [
|
||||
'English',
|
||||
@@ -79,7 +61,7 @@ class filter_test extends \advanced_testcase {
|
||||
'<span lang="fr" class="multilang">Français</span><span class="multilang" lang="en">English</span>',
|
||||
'en',
|
||||
],
|
||||
'Reversed input order EN' => [
|
||||
'Reversed input order FR' => [
|
||||
'Français',
|
||||
'<span lang="fr" class="multilang">Français</span><span class="multilang" lang="en">English</span>',
|
||||
'fr',
|
||||
@@ -87,7 +69,7 @@ class filter_test extends \advanced_testcase {
|
||||
'Fallback to parent when child not present' => [
|
||||
'Français',
|
||||
'<span lang="en" class="multilang">English</span><span lang="fr" class="multilang">Français</span>',
|
||||
'fr_ca', ['fr_ca' => 'fr']
|
||||
'fr_ca', ['fr_ca' => 'fr'],
|
||||
],
|
||||
'Both parent and child language present, using child' => [
|
||||
'Québécois',
|
||||
@@ -129,13 +111,17 @@ class filter_test extends \advanced_testcase {
|
||||
* Tests the filtering of multi-language strings.
|
||||
*
|
||||
* @dataProvider multilang_testcases
|
||||
*
|
||||
* @param string $expectedoutput The expected filter output.
|
||||
* @param string $input the input that is filtererd.
|
||||
* @param string $targetlang the laguage to set as the current languge .
|
||||
* @param array $parentlangs Array child lang => parent lang. E.g. ['es_co' => 'es', 'es_mx' => 'es'].
|
||||
*/
|
||||
public function test_filtering($expectedoutput, $input, $targetlang, $parentlangs = []): void {
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
// Enable glossary filter at top level.
|
||||
filter_set_global_state('multilang', TEXTFILTER_ON);
|
||||
|
||||
global $SESSION;
|
||||
$SESSION->forcelang = $targetlang;
|
||||
|
||||
@@ -143,7 +129,7 @@ class filter_test extends \advanced_testcase {
|
||||
$this->setup_parent_language($child, $parent);
|
||||
}
|
||||
|
||||
$filtered = format_text($input, FORMAT_HTML, array('context' => \context_system::instance()));
|
||||
$filtered = format_text($input, FORMAT_HTML, ['context' => \context_system::instance()]);
|
||||
$this->assertEquals($expectedoutput, $filtered);
|
||||
}
|
||||
}
|
||||
Vendored
+25
-26
@@ -27,7 +27,6 @@
|
||||
namespace core_filters\external;
|
||||
|
||||
use core_external\external_api;
|
||||
use core_filters\external;
|
||||
use externallib_advanced_testcase;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
@@ -44,11 +43,11 @@ require_once($CFG->dirroot . '/webservice/tests/helpers.php');
|
||||
* @copyright 2017 Juan Leyva
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @since Moodle 3.4
|
||||
* @covers \core_filters\external\get_available_in_context
|
||||
*/
|
||||
class external_test extends externallib_advanced_testcase {
|
||||
|
||||
final class get_available_in_context_test extends externallib_advanced_testcase {
|
||||
/**
|
||||
* Test get_available_in_context_system
|
||||
* Test execute
|
||||
*/
|
||||
public function test_get_available_in_context_system(): void {
|
||||
global $DB;
|
||||
@@ -57,7 +56,7 @@ class external_test extends externallib_advanced_testcase {
|
||||
$this->setAdminUser();
|
||||
|
||||
$this->expectException('moodle_exception');
|
||||
external::get_available_in_context(array(array('contextlevel' => 'system', 'instanceid' => 0)));
|
||||
get_available_in_context::execute([['contextlevel' => 'system', 'instanceid' => 0]]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,8 +76,8 @@ class external_test extends externallib_advanced_testcase {
|
||||
filter_set_global_state($filter, TEXTFILTER_DISABLED);
|
||||
}
|
||||
|
||||
$result = external::get_available_in_context(array(array('contextlevel' => 'coursecat', 'instanceid' => $category->id)));
|
||||
$result = external_api::clean_returnvalue(external::get_available_in_context_returns(), $result);
|
||||
$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']);
|
||||
|
||||
@@ -87,8 +86,8 @@ class external_test extends externallib_advanced_testcase {
|
||||
$firstfilter = key($allfilters);
|
||||
filter_set_global_state($firstfilter, TEXTFILTER_ON);
|
||||
|
||||
$result = external::get_available_in_context(array(array('contextlevel' => 'coursecat', 'instanceid' => $category->id)));
|
||||
$result = external_api::clean_returnvalue(external::get_available_in_context_returns(), $result);
|
||||
$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.
|
||||
$this->assertEquals(TEXTFILTER_INHERIT, $result['filters'][0]['localstate']); // Inherits the parent context status.
|
||||
@@ -96,8 +95,8 @@ class external_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 = external::get_available_in_context(array(array('contextlevel' => 'coursecat', 'instanceid' => $category->id)));
|
||||
$result = external_api::clean_returnvalue(external::get_available_in_context_returns(), $result);
|
||||
$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.
|
||||
$this->assertEquals(TEXTFILTER_OFF, $result['filters'][0]['localstate']); // It is not available in this context.
|
||||
@@ -121,8 +120,8 @@ class external_test extends externallib_advanced_testcase {
|
||||
filter_set_global_state($filter, TEXTFILTER_DISABLED);
|
||||
}
|
||||
|
||||
$result = external::get_available_in_context(array(array('contextlevel' => 'course', 'instanceid' => $course->id)));
|
||||
$result = external_api::clean_returnvalue(external::get_available_in_context_returns(), $result);
|
||||
$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']);
|
||||
|
||||
@@ -131,8 +130,8 @@ class external_test extends externallib_advanced_testcase {
|
||||
$firstfilter = key($allfilters);
|
||||
filter_set_global_state($firstfilter, TEXTFILTER_ON);
|
||||
|
||||
$result = external::get_available_in_context(array(array('contextlevel' => 'course', 'instanceid' => $course->id)));
|
||||
$result = external_api::clean_returnvalue(external::get_available_in_context_returns(), $result);
|
||||
$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.
|
||||
$this->assertEquals(TEXTFILTER_INHERIT, $result['filters'][0]['localstate']); // Inherits the parent context status.
|
||||
@@ -140,8 +139,8 @@ class external_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 = external::get_available_in_context(array(array('contextlevel' => 'course', 'instanceid' => $course->id)));
|
||||
$result = external_api::clean_returnvalue(external::get_available_in_context_returns(), $result);
|
||||
$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.
|
||||
$this->assertEquals(TEXTFILTER_OFF, $result['filters'][0]['localstate']); // It is not available in this context.
|
||||
@@ -159,7 +158,7 @@ class external_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();
|
||||
@@ -167,8 +166,8 @@ class external_test extends externallib_advanced_testcase {
|
||||
filter_set_global_state($filter, TEXTFILTER_DISABLED);
|
||||
}
|
||||
|
||||
$result = external::get_available_in_context(array(array('contextlevel' => 'module', 'instanceid' => $forum->cmid)));
|
||||
$result = external_api::clean_returnvalue(external::get_available_in_context_returns(), $result);
|
||||
$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']);
|
||||
|
||||
@@ -177,8 +176,8 @@ class external_test extends externallib_advanced_testcase {
|
||||
$firstfilter = key($allfilters);
|
||||
filter_set_global_state($firstfilter, TEXTFILTER_ON);
|
||||
|
||||
$result = external::get_available_in_context(array(array('contextlevel' => 'module', 'instanceid' => $forum->cmid)));
|
||||
$result = external_api::clean_returnvalue(external::get_available_in_context_returns(), $result);
|
||||
$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.
|
||||
$this->assertEquals(TEXTFILTER_INHERIT, $result['filters'][0]['localstate']); // Inherits the parent context status.
|
||||
@@ -186,8 +185,8 @@ class external_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 = external::get_available_in_context(array(array('contextlevel' => 'module', 'instanceid' => $forum->cmid)));
|
||||
$result = external_api::clean_returnvalue(external::get_available_in_context_returns(), $result);
|
||||
$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.
|
||||
$this->assertEquals(TEXTFILTER_OFF, $result['filters'][0]['localstate']); // It is not available in this context.
|
||||
@@ -196,8 +195,8 @@ class external_test extends externallib_advanced_testcase {
|
||||
// Try user without permission, warning expected.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->setUser($user);
|
||||
$result = external::get_available_in_context(array(array('contextlevel' => 'module', 'instanceid' => $forum->cmid)));
|
||||
$result = external_api::clean_returnvalue(external::get_available_in_context_returns(), $result);
|
||||
$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']);
|
||||
$this->assertEquals($forum->cmid, $result['warnings'][0]['itemid']);
|
||||
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// 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_tex;
|
||||
|
||||
use core\context\system as context_system;
|
||||
use core\exception\coding_exception;
|
||||
use core\output\actions\popup_action;
|
||||
use core\url;
|
||||
use core_useragent;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Moodle - Filter for converting TeX expressions to cached gif images
|
||||
*
|
||||
* This Moodle text filter converts TeX expressions delimited
|
||||
* by either $$...$$ or by <tex...>...</tex> tags to gif images using
|
||||
* mimetex.cgi obtained from http: *www.forkosh.com/mimetex.html authored by
|
||||
* John Forkosh [email protected]. Several binaries of this areincluded with
|
||||
* this distribution.
|
||||
* 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_tex
|
||||
* @subpackage tex
|
||||
* @copyright 2004 Zbigniew Fiedorowicz [email protected]
|
||||
* Originally based on code provided by Bruno Vernier [email protected]
|
||||
* @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 = []) {
|
||||
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) &&
|
||||
(strpos($text, '\\(') === false) &&
|
||||
(!preg_match('/\[tex/i', $text))
|
||||
) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
$text .= ' ';
|
||||
preg_match_all('/\$(\$\$+?)([^\$])/s', $text, $matches);
|
||||
for ($i = 0; $i < count($matches[0]); $i++) {
|
||||
$replacement = str_replace('$', '$', $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
|
||||
// or [tex] TeX expression [/tex] // somtime it's more comfortable than <tex>.
|
||||
$rules = [
|
||||
'<tex(?:\s+alt=["\'](.*?)["\'])?>(.+?)<\/tex>',
|
||||
'\$\$(.+?)\$\$',
|
||||
'\\\\\[(.+?)\\\\\]',
|
||||
'\\\\\((.+?)\\\\\)',
|
||||
'\\[tex\\](.+?)\\[\/tex\\]',
|
||||
];
|
||||
$megarule = '/' . implode('|', $rules) . '/is';
|
||||
preg_match_all($megarule, $text, $matches);
|
||||
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);
|
||||
$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);
|
||||
}
|
||||
|
||||
// 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 === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Sanitize the decoded string, because $this->get_image_markup() injects the final string between script tags.
|
||||
$texexp = clean_param($texexp, PARAM_TEXT);
|
||||
|
||||
$md5 = md5($texexp);
|
||||
if (!$DB->record_exists("cache_filters", ["filter" => "tex", "md5key" => $md5])) {
|
||||
$texcache = new stdClass();
|
||||
$texcache->filter = 'tex';
|
||||
$texcache->version = 1;
|
||||
$texcache->md5key = $md5;
|
||||
$texcache->rawtext = $texexp;
|
||||
$texcache->timemodified = time();
|
||||
$DB->insert_record("cache_filters", $texcache, false);
|
||||
}
|
||||
$convertformat = get_config('filter_tex', 'convertformat');
|
||||
if ($convertformat == 'svg' && !core_useragent::supports_svg()) {
|
||||
$convertformat = 'png';
|
||||
}
|
||||
$filename = $md5 . ".{$convertformat}";
|
||||
$text = str_replace($matches[0][$i], $this->get_image_markup($filename, $texexp, 0, 0, $align, $alt), $text);
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create image link.
|
||||
*
|
||||
* @param string $imagefile name of file
|
||||
* @param string $tex TeX notation (html entities already decoded)
|
||||
* @param int $height O means automatic
|
||||
* @param int $width O means automatic
|
||||
* @param string $align
|
||||
* @param string $alt
|
||||
* @return string HTML markup
|
||||
*/
|
||||
protected function get_image_markup(
|
||||
string $imagefile,
|
||||
string $tex,
|
||||
int $height,
|
||||
int $width,
|
||||
string $align,
|
||||
string $alt,
|
||||
): string {
|
||||
global $CFG, $OUTPUT;
|
||||
|
||||
if (!$imagefile) {
|
||||
throw new coding_exception('Image file argument empty in get_image_markup()');
|
||||
}
|
||||
|
||||
// Work out any necessary inline style.
|
||||
$rules = [];
|
||||
if ($align !== 'middle') {
|
||||
$rules[] = 'vertical-align:' . $align . ';';
|
||||
}
|
||||
if ($height) {
|
||||
$rules[] = 'height:' . $height . 'px;';
|
||||
}
|
||||
if ($width) {
|
||||
$rules[] = 'width:' . $width . 'px;';
|
||||
}
|
||||
if (!empty($rules)) {
|
||||
$style = ' style="' . implode('', $rules) . '" ';
|
||||
} else {
|
||||
$style = '';
|
||||
}
|
||||
|
||||
// Prepare the title attribute.
|
||||
// Note that we retain the title tag as TeX format rather than using
|
||||
// the alt text, even if supplied. The alt text is intended for blind
|
||||
// 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) . '"';
|
||||
|
||||
if ($alt === '') {
|
||||
$alt = s($tex);
|
||||
} else {
|
||||
$alt = s(html_entity_decode($tex, ENT_QUOTES, 'UTF-8'));
|
||||
}
|
||||
|
||||
// Build the output.
|
||||
$anchorcontents = "<img class=\"texrender\" $title alt=\"$alt\" src=\"";
|
||||
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/>";
|
||||
|
||||
$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', ['texexp' => $tex]);
|
||||
$action = new popup_action('click', $link, 'popup', ['width' => 320, 'height' => 240]);
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
@@ -1,216 +0,0 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Moodle - Filter for converting TeX expressions to cached gif images
|
||||
*
|
||||
* This Moodle text filter converts TeX expressions delimited
|
||||
* by either $$...$$ or by <tex...>...</tex> tags to gif images using
|
||||
* mimetex.cgi obtained from http: *www.forkosh.com/mimetex.html authored by
|
||||
* John Forkosh [email protected]. Several binaries of this areincluded with
|
||||
* this distribution.
|
||||
* 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
|
||||
* @subpackage tex
|
||||
* @copyright 2004 Zbigniew Fiedorowicz [email protected]
|
||||
* Originally based on code provided by Bruno Vernier [email protected]
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die;
|
||||
|
||||
require_once($CFG->libdir . '/classes/useragent.php');
|
||||
|
||||
/**
|
||||
* Create TeX image link.
|
||||
*
|
||||
* @param string $imagefile name of file
|
||||
* @param string $tex TeX notation (html entities already decoded)
|
||||
* @param int $height O means automatic
|
||||
* @param int $width O means automatic
|
||||
* @param string $align
|
||||
* @param string $alt
|
||||
* @return string HTML markup
|
||||
*/
|
||||
function filter_text_image($imagefile, $tex, $height, $width, $align, $alt) {
|
||||
global $CFG, $OUTPUT;
|
||||
|
||||
if (!$imagefile) {
|
||||
throw new coding_exception('image file argument empty in filter_text_image()');
|
||||
}
|
||||
|
||||
// Work out any necessary inline style.
|
||||
$rules = array();
|
||||
if ($align !== 'middle') {
|
||||
$rules[] = 'vertical-align:' . $align . ';';
|
||||
}
|
||||
if ($height) {
|
||||
$rules[] = 'height:' . $height . 'px;';
|
||||
}
|
||||
if ($width) {
|
||||
$rules[] = 'width:' . $width . 'px;';
|
||||
}
|
||||
if (!empty($rules)) {
|
||||
$style = ' style="' . implode('', $rules) . '" ';
|
||||
} else {
|
||||
$style = '';
|
||||
}
|
||||
|
||||
// Prepare the title attribute.
|
||||
// Note that we retain the title tag as TeX format rather than using
|
||||
// the alt text, even if supplied. The alt text is intended for blind
|
||||
// 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).'"';
|
||||
|
||||
if ($alt === '') {
|
||||
$alt = s($tex);
|
||||
} else {
|
||||
$alt = s(html_entity_decode($tex, ENT_QUOTES, 'UTF-8'));
|
||||
}
|
||||
|
||||
// Build the output.
|
||||
$anchorcontents = "<img class=\"texrender\" $title alt=\"$alt\" src=\"";
|
||||
if ($CFG->slasharguments) { // Use this method if possible for better 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())) {
|
||||
$link = '/filter/tex/texdebug.php';
|
||||
$action = null;
|
||||
} else {
|
||||
$link = new moodle_url('/filter/tex/displaytex.php', array('texexp'=>$tex));
|
||||
$action = new popup_action('click', $link, 'popup', array('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!!
|
||||
$output = "<span class=\"MathJax_Preview\">$output</span><script type=\"math/tex\">$tex</script>";
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* TeX filtering class.
|
||||
*/
|
||||
class filter_tex extends moodle_text_filter {
|
||||
function filter($text, array $options = array()) {
|
||||
|
||||
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) &&
|
||||
(strpos($text, '\\(') === false) &&
|
||||
(!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('$','$', $matches[1][$i]).$matches[2][$i];
|
||||
$text = str_replace($matches[0][$i], $replacement, $text);
|
||||
}
|
||||
|
||||
// <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(
|
||||
'<tex(?:\s+alt=["\'](.*?)["\'])?>(.+?)<\/tex>',
|
||||
'\$\$(.+?)\$\$',
|
||||
'\\\\\[(.+?)\\\\\]',
|
||||
'\\\\\((.+?)\\\\\)',
|
||||
'\\[tex\\](.+?)\\[\/tex\\]'
|
||||
);
|
||||
$megarule = '/' . implode('|', $rules) . '/is';
|
||||
preg_match_all($megarule, $text, $matches);
|
||||
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
|
||||
$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);
|
||||
}
|
||||
|
||||
// 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 === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Sanitize the decoded string, because filter_text_image() injects the final string between script tags.
|
||||
$texexp = clean_param($texexp, PARAM_TEXT);
|
||||
|
||||
$md5 = md5($texexp);
|
||||
if (!$DB->record_exists("cache_filters", array("filter"=>"tex", "md5key"=>$md5))) {
|
||||
$texcache = new stdClass();
|
||||
$texcache->filter = 'tex';
|
||||
$texcache->version = 1;
|
||||
$texcache->md5key = $md5;
|
||||
$texcache->rawtext = $texexp;
|
||||
$texcache->timemodified = time();
|
||||
$DB->insert_record("cache_filters", $texcache, false);
|
||||
}
|
||||
$convertformat = get_config('filter_tex', 'convertformat');
|
||||
if ($convertformat == 'svg' && !core_useragent::supports_svg()) {
|
||||
$convertformat = 'png';
|
||||
}
|
||||
$filename = $md5.".{$convertformat}";
|
||||
$text = str_replace( $matches[0][$i], filter_text_image($filename, $texexp, 0, 0, $align, $alt), $text);
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Unit test for the filter_tex
|
||||
*
|
||||
* @package filter_tex
|
||||
* @copyright 2014 Damyon Wiese
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace filter_tex;
|
||||
|
||||
use filter_tex;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/filter/tex/filter.php');
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for filter_tex.
|
||||
*
|
||||
* Test the delimiter parsing used by the tex filter.
|
||||
*
|
||||
* @copyright 2014 Damyon Wiese
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class filter_test extends \advanced_testcase {
|
||||
|
||||
protected $filter;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->resetAfterTest(true);
|
||||
$this->filter = new filter_tex(\context_system::instance(), array());
|
||||
}
|
||||
|
||||
function run_with_delimiters($start, $end, $filtershouldrun) {
|
||||
$pre = 'Some pre text';
|
||||
$post = 'Some post text';
|
||||
$equation = ' \sum{a^b} ';
|
||||
|
||||
$before = $pre . $start . $equation . $end . $post;
|
||||
|
||||
$after = trim($this->filter->filter($before));
|
||||
|
||||
if ($filtershouldrun) {
|
||||
$this->assertNotEquals($after, $before);
|
||||
} else {
|
||||
$this->assertEquals($after, $before);
|
||||
}
|
||||
}
|
||||
|
||||
function test_delimiters(): void {
|
||||
// First test the list of supported delimiters.
|
||||
$this->run_with_delimiters('$$', '$$', true);
|
||||
$this->run_with_delimiters('\\(', '\\)', true);
|
||||
$this->run_with_delimiters('\\[', '\\]', true);
|
||||
$this->run_with_delimiters('[tex]', '[/tex]', true);
|
||||
$this->run_with_delimiters('<tex>', '</tex>', true);
|
||||
$this->run_with_delimiters('<tex alt="nonsense">', '</tex>', true);
|
||||
// Now test some cases that shouldn't be executed.
|
||||
$this->run_with_delimiters('<textarea>', '</textarea>', false);
|
||||
$this->run_with_delimiters('$', '$', false);
|
||||
$this->run_with_delimiters('(', ')', false);
|
||||
$this->run_with_delimiters('[', ']', false);
|
||||
$this->run_with_delimiters('$$', '\\]', false);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// 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_tex;
|
||||
|
||||
use core\context\system as context_system;
|
||||
|
||||
/**
|
||||
* Unit tests for text_filter.
|
||||
*
|
||||
* Test the delimiter parsing used by the tex filter.
|
||||
*
|
||||
* @package filter_tex
|
||||
* @copyright 2014 Damyon Wiese
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \filter_tex\text_filter
|
||||
*/
|
||||
final class text_filter_test extends \advanced_testcase {
|
||||
/**
|
||||
* Test the delimeter support.
|
||||
*
|
||||
* @param string $start
|
||||
* @param string $end
|
||||
* @param bool $filtershouldrun
|
||||
* @dataProvider delimiter_provider
|
||||
*/
|
||||
public function test_delimiter_support(
|
||||
string $start,
|
||||
string $end,
|
||||
bool $filtershouldrun,
|
||||
): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$filter = new text_filter(context_system::instance(), []);
|
||||
|
||||
$pre = 'Some pre text';
|
||||
$post = 'Some post text';
|
||||
$equation = ' \sum{a^b} ';
|
||||
|
||||
$before = $pre . $start . $equation . $end . $post;
|
||||
|
||||
$after = trim($filter->filter($before));
|
||||
|
||||
if ($filtershouldrun) {
|
||||
$this->assertNotEquals($after, $before);
|
||||
} else {
|
||||
$this->assertEquals($after, $before);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for delimeters.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function delimiter_provider(): array {
|
||||
return [
|
||||
// First test the list of supported delimiters.
|
||||
['$$', '$$', true],
|
||||
['\\(', '\\)', true],
|
||||
['\\[', '\\]', true],
|
||||
['[tex]', '[/tex]', true],
|
||||
['<tex>', '</tex>', true],
|
||||
['<tex alt="nonsense">', '</tex>', true],
|
||||
|
||||
// Now test some cases that shouldn't be executed.
|
||||
['<textarea>', '</textarea>', false],
|
||||
['$', '$', false],
|
||||
['(', ')', false],
|
||||
['[', ']', false],
|
||||
['$$', '\\]', false],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
@@ -33,11 +35,11 @@
|
||||
* @copyright 2004 Hannes Gassert <hannes at mediagonal dot ch>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class filter_tidy extends moodle_text_filter {
|
||||
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 filter_tidy extends moodle_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);
|
||||
}
|
||||
@@ -23,18 +23,12 @@ namespace filter_tidy;
|
||||
* @category test
|
||||
* @copyright 2024 Andrew Lyons <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \filter_tidy
|
||||
* @covers \filter_tidy\text_filter
|
||||
*/
|
||||
final class filter_tidy_test extends \advanced_testcase {
|
||||
final class text_filter_test extends \advanced_testcase {
|
||||
/** @var string Locale */
|
||||
protected string $locale;
|
||||
|
||||
#[\Override]
|
||||
public static function setUpBeforeClass(): void {
|
||||
parent::setUpBeforeClass();
|
||||
require_once(__DIR__ . '/../filter.php');
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
@@ -59,7 +53,7 @@ final class filter_tidy_test extends \advanced_testcase {
|
||||
string $text,
|
||||
string $expected,
|
||||
): void {
|
||||
$filter = new \filter_tidy(\core\context\system::instance(), []);
|
||||
$filter = new text_filter(\core\context\system::instance(), []);
|
||||
$this->assertEquals($expected, $filter->filter($text));
|
||||
$this->assertEquals(
|
||||
\core\locale::standardise_locale($this->locale),
|
||||
@@ -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
|
||||
@@ -15,40 +14,30 @@
|
||||
// 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_urltolink;
|
||||
|
||||
/**
|
||||
* Filter converting URLs in the text to HTML links
|
||||
*
|
||||
* @package filter
|
||||
* @subpackage urltolink
|
||||
* @package filter_urltolink
|
||||
* @copyright 2010 David Mudrak <david@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
class filter_urltolink extends moodle_text_filter {
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Apply the filter to the text
|
||||
*
|
||||
* @see 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()) {
|
||||
#[\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 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')))) {
|
||||
@@ -57,26 +46,22 @@ class filter_urltolink extends moodle_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">
|
||||
//<a href="blah">
|
||||
//<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">
|
||||
// <a href="blah">
|
||||
// <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
|
||||
@@ -86,10 +71,10 @@ class filter_urltolink extends moodle_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})';
|
||||
@@ -108,7 +93,7 @@ class filter_urltolink extends moodle_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.
|
||||
@@ -145,33 +130,32 @@ class filter_urltolink extends moodle_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, 'filter_urltolink_img_callback', $text);
|
||||
$text = preg_replace_callback($search, [self::class, 'get_image_markup'], $text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Change links to images into embedded images.
|
||||
*
|
||||
* This plugin is intended for automatic conversion of image URLs when FORMAT_MOODLE used.
|
||||
*
|
||||
* @param $link
|
||||
* @return string
|
||||
*/
|
||||
function filter_urltolink_img_callback($link) {
|
||||
if ($link[1] !== $link[3]) {
|
||||
// this is not a link created by this filter, because the url does not match the text
|
||||
return $link[0];
|
||||
/**
|
||||
* Change links to images into embedded images.
|
||||
*
|
||||
* This plugin is intended for automatic conversion of image URLs when FORMAT_MOODLE used.
|
||||
*
|
||||
* @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.
|
||||
return $link[0];
|
||||
}
|
||||
return '<img class="filter_urltolink_image" alt="" src="' . $link[1] . '" />';
|
||||
}
|
||||
return '<img class="filter_urltolink_image" alt="" src="'.$link[1].'" />';
|
||||
}
|
||||
+85
-90
@@ -14,47 +14,44 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Unit test for the filter_urltolink
|
||||
*
|
||||
* @package filter_urltolink
|
||||
* @category phpunit
|
||||
* @copyright 2010 David Mudrak <david@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace filter_urltolink;
|
||||
|
||||
use filter_urltolink;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/filter/urltolink/filter.php'); // Include the code to test
|
||||
|
||||
|
||||
class filter_test extends \basic_testcase {
|
||||
|
||||
function get_convert_urls_into_links_test_cases() {
|
||||
/**
|
||||
* Unit test for the text_filter
|
||||
*
|
||||
* @package filter_urltolink
|
||||
* @category test
|
||||
* @copyright 2010 David Mudrak <david@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @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');
|
||||
|
||||
$texts = array (
|
||||
//just a url
|
||||
// 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',
|
||||
'www.moodle.org - URL' => '<a href="http://www.moodle.org" class="_blanktarget">www.moodle.org</a> - URL',
|
||||
//url with params
|
||||
// Url with params.
|
||||
'URL: http://moodle.org/s/i=1&j=2' => 'URL: <a href="http://moodle.org/s/i=1&j=2" class="_blanktarget">http://moodle.org/s/i=1&j=2</a>',
|
||||
//url with escaped params
|
||||
// Url with escaped params.
|
||||
'URL: www.moodle.org/s/i=1&j=2' => 'URL: <a href="http://www.moodle.org/s/i=1&j=2" class="_blanktarget">www.moodle.org/s/i=1&j=2</a>',
|
||||
//https url with params
|
||||
// Https url with params.
|
||||
'URL: https://moodle.org/s/i=1&j=2' => 'URL: <a href="https://moodle.org/s/i=1&j=2" class="_blanktarget">https://moodle.org/s/i=1&j=2</a>',
|
||||
//url with port and params
|
||||
// Url with port and params.
|
||||
'URL: http://moodle.org:8080/s/i=1' => 'URL: <a href="http://moodle.org:8080/s/i=1" class="_blanktarget">http://moodle.org:8080/s/i=1</a>',
|
||||
// URL with complex fragment.
|
||||
'Most voted issues: https://tracker.moodle.org/browse/MDL#selectedTab=com.atlassian.jira.plugin.system.project%3Apopularissues-panel' => 'Most voted issues: <a href="https://tracker.moodle.org/browse/MDL#selectedTab=com.atlassian.jira.plugin.system.project%3Apopularissues-panel" class="_blanktarget">https://tracker.moodle.org/browse/MDL#selectedTab=com.atlassian.jira.plugin.system.project%3Apopularissues-panel</a>',
|
||||
// Domain with more parts
|
||||
// Domain with more parts.
|
||||
'URL: www.bbc.co.uk.' => 'URL: <a href="http://www.bbc.co.uk" class="_blanktarget">www.bbc.co.uk</a>.',
|
||||
// URL in brackets.
|
||||
'(http://moodle.org) - URL' => '(<a href="http://moodle.org" class="_blanktarget">http://moodle.org</a>) - URL',
|
||||
@@ -74,70 +71,66 @@ class filter_test extends \basic_testcase {
|
||||
// URL in square brackets with anchor.
|
||||
'[http://moodle.org/main#anchor] - URL' => '[<a href="http://moodle.org/main#anchor" class="_blanktarget">http://moodle.org/main#anchor</a>] - URL',
|
||||
'[www.moodle.org/main#anchor] - URL' => '[<a href="http://www.moodle.org/main#anchor" class="_blanktarget">www.moodle.org/main#anchor</a>] - URL',
|
||||
//brackets within the url
|
||||
// Brackets within the url.
|
||||
'URL: http://cc.org/url_(withpar)_go/?i=2' => 'URL: <a href="http://cc.org/url_(withpar)_go/?i=2" class="_blanktarget">http://cc.org/url_(withpar)_go/?i=2</a>',
|
||||
'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
|
||||
'http://en.wikipedia.org/wiki/Slash_%28punctuation%29'=>'<a href="http://en.wikipedia.org/wiki/Slash_%28punctuation%29" class="_blanktarget">http://en.wikipedia.org/wiki/Slash_%28punctuation%29</a>',
|
||||
//anchor tag
|
||||
// Escaped brackets in url.
|
||||
'http://en.wikipedia.org/wiki/Slash_%28punctuation%29' => '<a href="http://en.wikipedia.org/wiki/Slash_%28punctuation%29" class="_blanktarget">http://en.wikipedia.org/wiki/Slash_%28punctuation%29</a>',
|
||||
// Anchor tag.
|
||||
'URL: <a href="http://moodle.org">http://moodle.org</a>' => 'URL: <a href="http://moodle.org">http://moodle.org</a>',
|
||||
'URL: <a href="http://moodle.org">www.moodle.org</a>' => 'URL: <a href="http://moodle.org">www.moodle.org</a>',
|
||||
'URL: <a href="http://moodle.org"> http://moodle.org</a>' => 'URL: <a href="http://moodle.org"> http://moodle.org</a>',
|
||||
'URL: <a href="http://moodle.org"> www.moodle.org</a>' => 'URL: <a href="http://moodle.org"> www.moodle.org</a>',
|
||||
//escaped anchor tag. Commented out as part of MDL-21183
|
||||
//htmlspecialchars('escaped anchor tag <a href="http://moodle.org">www.moodle.org</a>') => 'escaped anchor tag <a href="http://moodle.org"> www.moodle.org</a>',
|
||||
//trailing fullstop
|
||||
// Trailing fullstop.
|
||||
'URL: http://moodle.org/s/i=1&j=2.' => 'URL: <a href="http://moodle.org/s/i=1&j=2" class="_blanktarget">http://moodle.org/s/i=1&j=2</a>.',
|
||||
'URL: www.moodle.org/s/i=1&j=2.' => 'URL: <a href="http://www.moodle.org/s/i=1&j=2" class="_blanktarget">www.moodle.org/s/i=1&j=2</a>.',
|
||||
//trailing unmatched bracket
|
||||
// Trailing unmatched bracket.
|
||||
'URL: http://moodle.org)<br />' => 'URL: <a href="http://moodle.org" class="_blanktarget">http://moodle.org</a>)<br />',
|
||||
//partially escaped html
|
||||
// Partially escaped html.
|
||||
'URL: <p>text www.moodle.org</p> text' => 'URL: <p>text <a href="http://www.moodle.org" class="_blanktarget">www.moodle.org</a></p> text',
|
||||
//decimal url parameter
|
||||
// Decimal url parameter.
|
||||
'URL: www.moodle.org?u=1.23' => 'URL: <a href="http://www.moodle.org?u=1.23" class="_blanktarget">www.moodle.org?u=1.23</a>',
|
||||
//escaped space in url
|
||||
// Escaped space in url.
|
||||
'URL: www.moodle.org?u=test+param&' => 'URL: <a href="http://www.moodle.org?u=test+param&" class="_blanktarget">www.moodle.org?u=test+param&</a>',
|
||||
//multiple urls
|
||||
// Multiple urls.
|
||||
'URL: http://moodle.org www.moodle.org'
|
||||
=> 'URL: <a href="http://moodle.org" class="_blanktarget">http://moodle.org</a> <a href="http://www.moodle.org" class="_blanktarget">www.moodle.org</a>',
|
||||
//containing anchor tags including a class parameter and a url to convert
|
||||
// Containing anchor tags including a class parameter and a url to convert.
|
||||
'URL: <a href="http://moodle.org">http://moodle.org</a> www.moodle.org <a class="customclass" href="http://moodle.org">http://moodle.org</a>'
|
||||
=> 'URL: <a href="http://moodle.org">http://moodle.org</a> <a href="http://www.moodle.org" class="_blanktarget">www.moodle.org</a> <a class="customclass" href="http://moodle.org">http://moodle.org</a>',
|
||||
//subdomain
|
||||
// Subdomain.
|
||||
'http://subdomain.moodle.org - URL' => '<a href="http://subdomain.moodle.org" class="_blanktarget">http://subdomain.moodle.org</a> - URL',
|
||||
//multiple subdomains
|
||||
// Multiple subdomains.
|
||||
'http://subdomain.subdomain.moodle.org - URL' => '<a href="http://subdomain.subdomain.moodle.org" class="_blanktarget">http://subdomain.subdomain.moodle.org</a> - URL',
|
||||
//looks almost like a link but isnt
|
||||
'This contains http, http:// and www but no actual links.'=>'This contains http, http:// and www but no actual links.',
|
||||
//no link at all
|
||||
'This is a story about moodle.coming to a cinema near you.'=>'This is a story about moodle.coming to a cinema near you.',
|
||||
//URLs containing utf 8 characters
|
||||
'http://Iñtërnâtiônàlizætiøn.com?ô=nëø'=>'<a href="http://Iñtërnâtiônàlizætiøn.com?ô=nëø" class="_blanktarget">http://Iñtërnâtiônàlizætiøn.com?ô=nëø</a>',
|
||||
'www.Iñtërnâtiônàlizætiøn.com?ô=nëø'=>'<a href="http://www.Iñtërnâtiônàlizætiøn.com?ô=nëø" class="_blanktarget">www.Iñtërnâtiônàlizætiøn.com?ô=nëø</a>',
|
||||
//text containing utf 8 characters outside of a url
|
||||
'Iñtërnâtiônàlizætiøn is important to http://moodle.org'=>'Iñtërnâtiônàlizætiøn is important to <a href="http://moodle.org" class="_blanktarget">http://moodle.org</a>',
|
||||
//too hard to identify without additional regexs
|
||||
// Looks almost like a link but isnt.
|
||||
'This contains http, http:// and www but no actual links.' => 'This contains http, http:// and www but no actual links.',
|
||||
// No link at all.
|
||||
'This is a story about moodle.coming to a cinema near you.' => 'This is a story about moodle.coming to a cinema near you.',
|
||||
// URLs containing utf 8 characters.
|
||||
'http://Iñtërnâtiônàlizætiøn.com?ô=nëø' => '<a href="http://Iñtërnâtiônàlizætiøn.com?ô=nëø" class="_blanktarget">http://Iñtërnâtiônàlizætiøn.com?ô=nëø</a>',
|
||||
'www.Iñtërnâtiônàlizætiøn.com?ô=nëø' => '<a href="http://www.Iñtërnâtiônàlizætiøn.com?ô=nëø" class="_blanktarget">www.Iñtërnâtiônàlizætiøn.com?ô=nëø</a>',
|
||||
// Text containing utf 8 characters outside of a url.
|
||||
'Iñtërnâtiônàlizætiøn is important to http://moodle.org' => 'Iñtërnâtiônàlizætiøn is important to <a href="http://moodle.org" class="_blanktarget">http://moodle.org</a>',
|
||||
// Too hard to identify without additional regexs.
|
||||
'moodle.org' => 'moodle.org',
|
||||
//some text with no link between related html tags
|
||||
// Some text with no link between related html tags.
|
||||
'<b>no link here</b>' => '<b>no link here</b>',
|
||||
//some text with a link between related html tags
|
||||
// Some text with a link between related html tags.
|
||||
'<b>a link here www.moodle.org</b>' => '<b>a link here <a href="http://www.moodle.org" class="_blanktarget">www.moodle.org</a></b>',
|
||||
//some text containing a link within unrelated tags
|
||||
// Some text containing a link within unrelated tags.
|
||||
'<br />This is some text. www.moodle.com then some more text<br />' => '<br />This is some text. <a href="http://www.moodle.com" class="_blanktarget">www.moodle.com</a> then some more text<br />',
|
||||
//check we aren't modifying img tags
|
||||
// Check we aren't modifying img tags.
|
||||
'image<img src="http://moodle.org/logo/logo-240x60.gif" />' => 'image<img src="http://moodle.org/logo/logo-240x60.gif" />',
|
||||
'image<img src="www.moodle.org/logo/logo-240x60.gif" />' => 'image<img src="www.moodle.org/logo/logo-240x60.gif" />',
|
||||
'image<img src="http://www.example.com/logo.gif" />' => 'image<img src="http://www.example.com/logo.gif" />',
|
||||
//and another url within one tag
|
||||
// And another url within one tag.
|
||||
'<td background="http://moodle.org"> </td>' => '<td background="http://moodle.org"> </td>',
|
||||
'<td background="www.moodle.org"> </td>' => '<td background="www.moodle.org"> </td>',
|
||||
'<form name="input" action="http://moodle.org/submit.asp" method="get">'=>'<form name="input" action="http://moodle.org/submit.asp" method="get">',
|
||||
'<form name="input" action="http://moodle.org/submit.asp" method="get">' => '<form name="input" action="http://moodle.org/submit.asp" method="get">',
|
||||
'<input type="submit" value="Go to http://moodle.org">' => '<input type="submit" value="Go to http://moodle.org">',
|
||||
'<td background="https://www.moodle.org"> </td>' => '<td background="https://www.moodle.org"> </td>',
|
||||
// CSS URLs.
|
||||
@@ -145,16 +138,14 @@ class 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 <img src="http://moodle.org/logo/logo-240x60.gif" />' => 'partially escaped img tag <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 <img src="http://moodle.org/logo/logo-240x60.gif" />',
|
||||
//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¶m2=1' => 'URL: <a href="http://127.0.0.1/path/to?param=value_with%28parenthesis%29¶m2=1" class="_blanktarget">http://127.0.0.1/path/to?param=value_with%28parenthesis%29¶m2=1</a>',
|
||||
'URL: www.localhost.com/path/to?param=value_with%28parenthesis%29¶m2=1' => 'URL: <a href="http://www.localhost.com/path/to?param=value_with%28parenthesis%29¶m2=1" class="_blanktarget">www.localhost.com/path/to?param=value_with%28parenthesis%29¶m2=1</a>',
|
||||
// Test URL less than 4096 characters in size is converted to link.
|
||||
@@ -165,7 +156,6 @@ class filter_test extends \basic_testcase {
|
||||
'URL: <span style="kasd"> my link to http://google.com </span>' => 'URL: <span style="kasd"> my link to <a href="http://google.com" class="_blanktarget">http://google.com</a> </span>',
|
||||
// Nested tags test.
|
||||
'<b><i>www.google.com</i></b>' => '<b><i><a href="http://www.google.com" class="_blanktarget">www.google.com</a></i></b>',
|
||||
'<input type="submit" value="Go to http://moodle.org">' => '<input type="submit" value="Go to http://moodle.org">',
|
||||
// Test realistic content.
|
||||
'<p><span style="color: rgb(37, 37, 37); font-family: sans-serif; line-height: 22.3999996185303px;">Lorem ipsum amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut http://google.com aliquip ex ea <a href="http://google.com">commodo consequat</a>. Duis aute irure in reprehenderit in excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia https://docs.google.com/document/d/BrokenLinkPleaseAyacDHc_Ov8aoskoSVQsfmLHP_jYAkRMk/edit?usp=sharing https://docs.google.com/document/d/BrokenLinkPleaseAyacDHc_Ov8aoskoSVQsfmLHP_jYAkRMk/edit?usp=sharing mollit anim id est laborum.</span><br></p>'
|
||||
=>
|
||||
@@ -183,41 +173,46 @@ class filter_test extends \basic_testcase {
|
||||
// Test 'nolink' class.
|
||||
'URL: <span class="nolink">http://moodle.org</span>' => 'URL: <span class="nolink">http://moodle.org</span>',
|
||||
'<span class="nolink">URL: http://moodle.org</span>' => '<span class="nolink">URL: http://moodle.org</span>',
|
||||
//URLs in Javascript. Commented out as part of MDL-21183
|
||||
//'var url="http://moodle.org";'=>'var url="http://moodle.org";',
|
||||
//'var url = "http://moodle.org";'=>'var url = "http://moodle.org";',
|
||||
//'var url="www.moodle.org";'=>'var url="www.moodle.org";',
|
||||
//'var url = "www.moodle.org";'=>'var url = "www.moodle.org";',
|
||||
//doctype. do we care about this failing?
|
||||
//'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN http://www.w3.org/TR/html4/strict.dtd">'=>'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN http://www.w3.org/TR/html4/strict.dtd">'
|
||||
);
|
||||
];
|
||||
|
||||
$data = array();
|
||||
// phpcs:enable
|
||||
|
||||
$data = [];
|
||||
foreach ($texts as $text => $correctresult) {
|
||||
$data[] = array($text, $correctresult);
|
||||
$data[] = [$text, $correctresult];
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the convert_urls_into_links method.
|
||||
*
|
||||
* @dataProvider get_convert_urls_into_links_test_cases
|
||||
* @param string $text
|
||||
* @param string $correctresult
|
||||
*/
|
||||
function test_convert_urls_into_links($text, $correctresult): void {
|
||||
$testablefilter = new testable_filter_urltolink();
|
||||
public function test_convert_urls_into_links($text, $correctresult): void {
|
||||
$testablefilter = $this->get_testable_text_filter();
|
||||
|
||||
$testablefilter->convert_urls_into_links($text);
|
||||
$this->assertEquals($correctresult, $text);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test subclass that makes all the protected methods we want to test public.
|
||||
*/
|
||||
class testable_filter_urltolink extends filter_urltolink {
|
||||
public function __construct() {
|
||||
}
|
||||
public function convert_urls_into_links(&$text) {
|
||||
parent::convert_urls_into_links($text);
|
||||
/**
|
||||
* Get a copy of the filter configured for testing.
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -441,9 +441,7 @@ class component {
|
||||
$keyclasses = [
|
||||
\core\exception\moodle_exception::class,
|
||||
\core\output\bootstrap_renderer::class,
|
||||
\core\lang_string::class,
|
||||
\renderable::class,
|
||||
\core\url::class,
|
||||
\core_filters\filter_manager::class,
|
||||
];
|
||||
foreach ($keyclasses as $classname) {
|
||||
if (!array_key_exists($classname, $cache['classmap'])) {
|
||||
|
||||
@@ -158,4 +158,30 @@ $legacyclasses = [
|
||||
\progress_trace::class => 'output/progress_trace.php',
|
||||
\progress_trace_buffer::class => 'output/progress_trace/progress_trace_buffer.php',
|
||||
\text_progress_trace::class => 'output/progress_trace/text_progress_trace.php',
|
||||
|
||||
// Filters subsystem.
|
||||
\filter_manager::class => [
|
||||
'core_filters',
|
||||
'filter_manager.php',
|
||||
],
|
||||
\filterobject::class => [
|
||||
'core_filters',
|
||||
'filter_object.php',
|
||||
],
|
||||
\moodle_text_filter::class => [
|
||||
'core_filters',
|
||||
'text_filter.php',
|
||||
],
|
||||
\null_filter_manager::class => [
|
||||
'core_filters',
|
||||
'null_filter_manager.php',
|
||||
],
|
||||
\performance_measuring_filter_manager::class => [
|
||||
'core_filters',
|
||||
'performance_measuring_filter_manager.php',
|
||||
],
|
||||
\filter_local_settings_form::class => [
|
||||
'core_filters',
|
||||
'form/local_settings_form.php',
|
||||
],
|
||||
];
|
||||
|
||||
+1
-2
@@ -2832,8 +2832,7 @@ $functions = array(
|
||||
|
||||
// Filters functions.
|
||||
'core_filters_get_available_in_context' => array(
|
||||
'classname' => 'core_filters\external',
|
||||
'methodname' => 'get_available_in_context',
|
||||
'classname' => 'core_filters\external\get_available_in_context',
|
||||
'description' => 'Returns the filters available in the given contexts.',
|
||||
'type' => 'read',
|
||||
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
|
||||
|
||||
+1
-575
@@ -22,8 +22,6 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/** The states a filter can be in, stored in the filter_active table. */
|
||||
define('TEXTFILTER_ON', 1);
|
||||
/** The states a filter can be in, stored in the filter_active table. */
|
||||
@@ -41,578 +39,6 @@ define('TEXTFILTER_DISABLED', -9999);
|
||||
define('TEXTFILTER_EXCL_SEPARATOR', chr(0x1F) . '%' . chr(0x1F));
|
||||
|
||||
|
||||
/**
|
||||
* Class to manage the filtering of strings. It is intended that this class is
|
||||
* only used by weblib.php. Client code should probably be using the
|
||||
* format_text and format_string functions.
|
||||
*
|
||||
* This class is a singleton.
|
||||
*
|
||||
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class filter_manager {
|
||||
/**
|
||||
* @var moodle_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();
|
||||
|
||||
/**
|
||||
* @var moodle_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();
|
||||
|
||||
/** @var array Exploded version of $CFG->stringfilters. */
|
||||
protected $stringfilternames = array();
|
||||
|
||||
/** @var filter_manager Holds the singleton instance. */
|
||||
protected static $singletoninstance;
|
||||
|
||||
/**
|
||||
* Constructor. Protected. Use {@link instance()} instead.
|
||||
*/
|
||||
protected function __construct() {
|
||||
$this->stringfilternames = filter_get_string_filters();
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method. Use this to get the filter manager.
|
||||
*
|
||||
* @return filter_manager the singleton instance.
|
||||
*/
|
||||
public static function instance() {
|
||||
global $CFG;
|
||||
if (is_null(self::$singletoninstance)) {
|
||||
if (!empty($CFG->perfdebug) and $CFG->perfdebug > 7) {
|
||||
self::$singletoninstance = new performance_measuring_filter_manager();
|
||||
} else {
|
||||
self::$singletoninstance = new self();
|
||||
}
|
||||
}
|
||||
return self::$singletoninstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the caches, usually to be called between unit tests
|
||||
*/
|
||||
public static function reset_caches() {
|
||||
if (self::$singletoninstance) {
|
||||
self::$singletoninstance->unload_all_filters();
|
||||
}
|
||||
self::$singletoninstance = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unloads all filters and other cached information
|
||||
*/
|
||||
protected function unload_all_filters() {
|
||||
$this->textfilters = array();
|
||||
$this->stringfilters = array();
|
||||
$this->stringfilternames = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all the filters required by this context.
|
||||
*
|
||||
* @param context $context the context.
|
||||
*/
|
||||
protected function load_filters($context) {
|
||||
$filters = filter_get_active_in_context($context);
|
||||
$this->textfilters[$context->id] = array();
|
||||
$this->stringfilters[$context->id] = array();
|
||||
foreach ($filters as $filtername => $localconfig) {
|
||||
$filter = $this->make_filter_object($filtername, $context, $localconfig);
|
||||
if (is_null($filter)) {
|
||||
continue;
|
||||
}
|
||||
$this->textfilters[$context->id][$filtername] = $filter;
|
||||
if (in_array($filtername, $this->stringfilternames)) {
|
||||
$this->stringfilters[$context->id][$filtername] = $filter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method for creating a filter.
|
||||
*
|
||||
* @param string $filtername The filter name, for example 'tex'.
|
||||
* @param context $context context object.
|
||||
* @param array $localconfig array of local configuration variables for this filter.
|
||||
* @return ?moodle_text_filter The filter, or null, if this type of filter is
|
||||
* not recognised or could not be created.
|
||||
*/
|
||||
protected function make_filter_object($filtername, $context, $localconfig) {
|
||||
global $CFG;
|
||||
$path = $CFG->dirroot .'/filter/'. $filtername .'/filter.php';
|
||||
if (!is_readable($path)) {
|
||||
return null;
|
||||
}
|
||||
include_once($path);
|
||||
|
||||
$filterclassname = 'filter_' . $filtername;
|
||||
if (class_exists($filterclassname)) {
|
||||
return new $filterclassname($context, $localconfig);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a list of filters to some content.
|
||||
* @param string $text
|
||||
* @param moodle_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.
|
||||
* @return string $text
|
||||
*/
|
||||
protected function apply_filter_chain($text, $filterchain, array $options = array(),
|
||||
array $skipfilters = null) {
|
||||
if (!isset($options['stage'])) {
|
||||
$filtermethod = 'filter';
|
||||
} else if (in_array($options['stage'], ['pre_format', 'pre_clean', 'post_clean', 'string'], true)) {
|
||||
$filtermethod = 'filter_stage_' . $options['stage'];
|
||||
} else {
|
||||
$filtermethod = 'filter';
|
||||
debugging('Invalid filter stage specified in options: ' . $options['stage'], DEBUG_DEVELOPER);
|
||||
}
|
||||
if ($text === null || $text === '') {
|
||||
// Nothing to filter.
|
||||
return '';
|
||||
}
|
||||
foreach ($filterchain as $filtername => $filter) {
|
||||
if ($skipfilters !== null && in_array($filtername, $skipfilters)) {
|
||||
continue;
|
||||
}
|
||||
$text = $filter->$filtermethod($text, $options);
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the filters that apply to a given context for calls to format_text.
|
||||
*
|
||||
* @param context $context
|
||||
* @return moodle_text_filter[] A text filter
|
||||
*/
|
||||
protected function get_text_filters($context) {
|
||||
if (!isset($this->textfilters[$context->id])) {
|
||||
$this->load_filters($context);
|
||||
}
|
||||
return $this->textfilters[$context->id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the filters that apply to a given context for calls to format_string.
|
||||
*
|
||||
* @param context $context the context.
|
||||
* @return moodle_text_filter[] A text filter
|
||||
*/
|
||||
protected function get_string_filters($context) {
|
||||
if (!isset($this->stringfilters[$context->id])) {
|
||||
$this->load_filters($context);
|
||||
}
|
||||
return $this->stringfilters[$context->id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter some text
|
||||
*
|
||||
* @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.
|
||||
* @return string resulting text
|
||||
*/
|
||||
public function filter_text($text, $context, array $options = array(),
|
||||
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);
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter a piece of string
|
||||
*
|
||||
* @param string $string The text to filter
|
||||
* @param context $context the context.
|
||||
* @return string resulting string
|
||||
*/
|
||||
public function filter_string($string, $context) {
|
||||
return $this->apply_filter_chain($string, $this->get_string_filters($context), ['stage' => 'string']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Since Moodle 3.0 MDL-50491. This was used by the old text filtering system, but no more.
|
||||
*/
|
||||
public function text_filtering_hash() {
|
||||
throw new coding_exception('filter_manager::text_filtering_hash() can not be used any more');
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup page with filters requirements and other prepare stuff.
|
||||
*
|
||||
* This method is used by {@see format_text()} and {@see format_string()}
|
||||
* in order to allow filters to setup any page requirement (js, css...)
|
||||
* or perform any action needed to get them prepared before filtering itself
|
||||
* happens by calling to each every active setup() method.
|
||||
*
|
||||
* Note it's executed for each piece of text filtered, so filter implementations
|
||||
* are responsible of controlling the cardinality of the executions that may
|
||||
* be different depending of the stuff to prepare.
|
||||
*
|
||||
* @param moodle_page $page the page we are going to add requirements to.
|
||||
* @param context $context the context which contents are going to be filtered.
|
||||
* @since Moodle 2.3
|
||||
*/
|
||||
public function setup_page_for_filters($page, $context) {
|
||||
$filters = $this->get_text_filters($context);
|
||||
foreach ($filters as $filter) {
|
||||
$filter->setup($page, $context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the page for globally available filters.
|
||||
*
|
||||
* This helps setting up the page for filters which may be applied to
|
||||
* the page, even if they do not belong to the current context, or are
|
||||
* not yet visible because the content is lazily added (ajax). This method
|
||||
* always uses to the system context which determines the globally
|
||||
* available filters.
|
||||
*
|
||||
* This should only ever be called once per request.
|
||||
*
|
||||
* @param moodle_page $page The page.
|
||||
* @since Moodle 3.2
|
||||
*/
|
||||
public function setup_page_for_globally_available_filters($page) {
|
||||
$context = context_system::instance();
|
||||
$filterdata = filter_get_globally_enabled_filters_with_config();
|
||||
foreach ($filterdata as $name => $config) {
|
||||
if (isset($this->textfilters[$context->id][$name])) {
|
||||
$filter = $this->textfilters[$context->id][$name];
|
||||
} else {
|
||||
$filter = $this->make_filter_object($name, $context, $config);
|
||||
if (is_null($filter)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$filter->setup($page, $context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Filter manager subclass that does nothing. Having this simplifies the logic
|
||||
* of format_text, etc.
|
||||
*
|
||||
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class null_filter_manager {
|
||||
/**
|
||||
* As for the equivalent {@link 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
|
||||
* @return string resulting text.
|
||||
*/
|
||||
public function filter_text($text, $context, array $options = array(),
|
||||
array $skipfilters = null) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* As for the equivalent {@link filter_manager} method.
|
||||
*
|
||||
* @param string $string The text to filter
|
||||
* @param context $context not used.
|
||||
* @return string resulting string
|
||||
*/
|
||||
public function filter_string($string, $context) {
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* As for the equivalent {@link filter_manager} method.
|
||||
*
|
||||
* @deprecated Since Moodle 3.0 MDL-50491.
|
||||
*/
|
||||
public function text_filtering_hash() {
|
||||
throw new coding_exception('filter_manager::text_filtering_hash() can not be used any more');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Filter manager subclass that tracks how much work it does.
|
||||
*
|
||||
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class performance_measuring_filter_manager extends filter_manager {
|
||||
/** @var int number of filter objects created. */
|
||||
protected $filterscreated = 0;
|
||||
|
||||
/** @var int number of calls to filter_text. */
|
||||
protected $textsfiltered = 0;
|
||||
|
||||
/** @var int number of calls to filter_string. */
|
||||
protected $stringsfiltered = 0;
|
||||
|
||||
protected function unload_all_filters() {
|
||||
parent::unload_all_filters();
|
||||
$this->filterscreated = 0;
|
||||
$this->textsfiltered = 0;
|
||||
$this->stringsfiltered = 0;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!isset($options['stage']) || $options['stage'] === 'post_clean') {
|
||||
$this->textsfiltered++;
|
||||
}
|
||||
return parent::filter_text($text, $context, $options, $skipfilters);
|
||||
}
|
||||
|
||||
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 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',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Base class for text filters. You just need to override this class and
|
||||
* implement the filter method.
|
||||
*
|
||||
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
abstract class moodle_text_filter {
|
||||
/** @var context The context we are in. */
|
||||
protected $context;
|
||||
|
||||
/** @var array Any local configuration for this filter in this context. */
|
||||
protected $localconfig;
|
||||
|
||||
/**
|
||||
* Set any context-specific configuration for this filter.
|
||||
*
|
||||
* @param context $context The current context.
|
||||
* @param array $localconfig Any context-specific configuration for this filter.
|
||||
*/
|
||||
public function __construct($context, array $localconfig) {
|
||||
$this->context = $context;
|
||||
$this->localconfig = $localconfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Since Moodle 3.0 MDL-50491. This was used by the old text filtering system, but no more.
|
||||
*/
|
||||
public function hash() {
|
||||
throw new coding_exception('moodle_text_filter::hash() can not be used any more');
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup page with filter requirements and other prepare stuff.
|
||||
*
|
||||
* Override this method if the filter needs to setup page
|
||||
* requirements or needs other stuff to be executed.
|
||||
*
|
||||
* Note this method is invoked from {@see setup_page_for_filters()}
|
||||
* for each piece of text being filtered, so it is responsible
|
||||
* for controlling its own execution cardinality.
|
||||
*
|
||||
* @param moodle_page $page the page we are going to add requirements to.
|
||||
* @param context $context the context which contents are going to be filtered.
|
||||
* @since Moodle 2.3
|
||||
*/
|
||||
public function setup($page, $context) {
|
||||
// Override me, if needed.
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this function to actually implement the filtering.
|
||||
*
|
||||
* Filter developers must make sure that filtering done after text cleaning
|
||||
* does not introduce security vulnerabilities.
|
||||
*
|
||||
* @param string $text some HTML content to process.
|
||||
* @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());
|
||||
|
||||
/**
|
||||
* Filter text before changing format to HTML.
|
||||
*
|
||||
* @param string $text
|
||||
* @param array $options
|
||||
* @return string
|
||||
*/
|
||||
public function filter_stage_pre_format(string $text, array $options): string {
|
||||
// NOTE: override if necessary.
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter HTML text before sanitising text.
|
||||
*
|
||||
* NOTE: this is called even if $options['noclean'] is true and text is not cleaned.
|
||||
*
|
||||
* @param string $text
|
||||
* @param array $options
|
||||
* @return string
|
||||
*/
|
||||
public function filter_stage_pre_clean(string $text, array $options): string {
|
||||
// NOTE: override if necessary.
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter HTML text at the very end after text is sanitised.
|
||||
*
|
||||
* NOTE: this is called even if $options['noclean'] is true and text is not cleaned.
|
||||
*
|
||||
* @param string $text
|
||||
* @param array $options
|
||||
* @return string
|
||||
*/
|
||||
public function filter_stage_post_clean(string $text, array $options): string {
|
||||
// NOTE: override if necessary.
|
||||
return $this->filter($text, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter simple text coming from format_string().
|
||||
*
|
||||
* Note that unless $CFG->formatstringstriptags is disabled
|
||||
* HTML tags are not expected in returned value.
|
||||
*
|
||||
* @param string $text
|
||||
* @param array $options
|
||||
* @return string
|
||||
*/
|
||||
public function filter_stage_string(string $text, array $options): string {
|
||||
// NOTE: override if necessary.
|
||||
return $this->filter($text, $options);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is just a little object to define a phrase and some instructions
|
||||
* for how to process it. Filters can create an array of these to pass
|
||||
* to the @{link filter_phrases()} function below.
|
||||
*
|
||||
* Note that although the fields here are public, you almost certainly should
|
||||
* never use that. All that is supported is contructing new instances of this
|
||||
* class, and then passing an array of them to filter_phrases.
|
||||
*
|
||||
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class filterobject {
|
||||
/** @var string this is the phrase that should be matched. */
|
||||
public $phrase;
|
||||
|
||||
/** @var bool whether to match complete words. If true, 'T' won't be matched in 'Tim'. */
|
||||
public $fullmatch;
|
||||
|
||||
/** @var bool whether the match needs to be case sensitive. */
|
||||
public $casesensitive;
|
||||
|
||||
/** @var string HTML to insert before any match. */
|
||||
public $hreftagbegin;
|
||||
/** @var string HTML to insert after any match. */
|
||||
public $hreftagend;
|
||||
|
||||
/** @var null|string replacement text to go inside begin and end. If not set,
|
||||
* the body of the replacement will be the original phrase.
|
||||
*/
|
||||
public $replacementphrase;
|
||||
|
||||
/** @var null|string once initialised, holds the regexp for matching this phrase. */
|
||||
public $workregexp = null;
|
||||
|
||||
/** @var null|string once initialised, holds the mangled HTML to replace the regexp with. */
|
||||
public $workreplacementphrase = null;
|
||||
|
||||
/** @var null|callable hold a replacement function to be called. */
|
||||
public $replacementcallback;
|
||||
|
||||
/** @var null|array data to be passed to $replacementcallback. */
|
||||
public $replacementcallbackdata;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $phrase this is the phrase that should be matched.
|
||||
* @param string $hreftagbegin HTML to insert before any match. Default '<span class="highlight">'.
|
||||
* @param string $hreftagend HTML to insert after any match. Default '</span>'.
|
||||
* @param bool $casesensitive whether the match needs to be case sensitive
|
||||
* @param bool $fullmatch whether to match complete words. If true, 'T' won't be matched in 'Tim'.
|
||||
* @param mixed $replacementphrase replacement text to go inside begin and end. If not set,
|
||||
* the body of the replacement will be the original phrase.
|
||||
* @param callback $replacementcallback if set, then this will be called just before
|
||||
* $hreftagbegin, $hreftagend and $replacementphrase are needed, so they can be computed only if required.
|
||||
* The call made is
|
||||
* 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).
|
||||
*/
|
||||
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;
|
||||
$this->hreftagend = $hreftagend;
|
||||
$this->casesensitive = !empty($casesensitive);
|
||||
$this->fullmatch = !empty($fullmatch);
|
||||
$this->replacementphrase = $replacementphrase;
|
||||
$this->replacementcallback = $replacementcallback;
|
||||
$this->replacementcallbackdata = $replacementcallbackdata;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up the name of this filter
|
||||
*
|
||||
@@ -644,7 +70,7 @@ function filter_get_name($filter) {
|
||||
function filter_get_all_installed() {
|
||||
$filternames = array();
|
||||
foreach (core_component::get_plugin_list('filter') as $filter => $fulldir) {
|
||||
if (is_readable("$fulldir/filter.php")) {
|
||||
if (class_exists("\\filter_{$filter}\\text_filter") || is_readable("$fulldir/filter.php")) {
|
||||
$filternames[$filter] = filter_get_name($filter);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user