diff --git a/admin/tool/mobile/tests/externallib_test.php b/admin/tool/mobile/tests/externallib_test.php index 9e7a02e44e2..0155a29c371 100644 --- a/admin/tool/mobile/tests/externallib_test.php +++ b/admin/tool/mobile/tests/externallib_test.php @@ -173,8 +173,10 @@ class externallib_test extends externallib_advanced_testcase { /** * Test get_config + * + * @covers \tool_mobile\external::get_config */ - public function test_get_config() { + public function test_get_config(): void { global $CFG, $SITE; require_once($CFG->dirroot . '/course/format/lib.php'); @@ -188,7 +190,11 @@ class externallib_test extends externallib_advanced_testcase { $result = \external_api::clean_returnvalue(external::get_config_returns(), $result); // SITE summary is null in phpunit which gets transformed to an empty string by format_text. - list($sitesummary, $unused) = external_format_text($SITE->summary, $SITE->summaryformat, \context_system::instance()->id); + [$sitesummary, $summaryformat] = external_format_text( + $SITE->summary, + $SITE->summaryformat, + \context_system::instance()->id + ); // Test default values. $context = \context_system::instance(); @@ -196,7 +202,7 @@ class externallib_test extends externallib_advanced_testcase { array('name' => 'fullname', 'value' => $SITE->fullname), array('name' => 'shortname', 'value' => $SITE->shortname), array('name' => 'summary', 'value' => $sitesummary), - array('name' => 'summaryformat', 'value' => FORMAT_HTML), + array('name' => 'summaryformat', 'value' => $summaryformat), array('name' => 'frontpage', 'value' => $CFG->frontpage), array('name' => 'frontpageloggedin', 'value' => $CFG->frontpageloggedin), array('name' => 'maxcategorydepth', 'value' => $CFG->maxcategorydepth), diff --git a/lib/external/classes/util.php b/lib/external/classes/util.php index bc6dbea9b8c..b49a1566633 100644 --- a/lib/external/classes/util.php +++ b/lib/external/classes/util.php @@ -169,7 +169,7 @@ class util { int $tokentype, stdClass $service, int $userid, - context $contextorid, + context $context, int $validuntil = 0, string $iprestriction = '' ): string { @@ -416,4 +416,174 @@ class util { } return $token; } + + /** + * Format the string to be returned properly as requested by the either the web service server, + * either by an internally call. + * The caller can change the format (raw) with the settings singleton + * All web service servers must set this singleton when parsing the $_GET and $_POST. + * + *
+ * Options are the same that in {@link format_string()} with some changes:
+ * filter : Can be set to false to force filters off, else observes {@link settings}.
+ *
+ *
+ * @param string|null $content The string to be filtered. Should be plain text, expect
+ * possibly for multilang tags.
+ * @param boolean $striplinks To strip any link in the result text. Moodle 1.8 default changed from false to true! MDL-8713
+ * @param context $contextorid The id of the context for the string or the context (affects filters).
+ * @param array $options options array/object or courseid
+ * @return string text
+ */
+ public static function format_string(
+ $content,
+ $context,
+ $striplinks = true,
+ $options = []
+ ) {
+ if ($content === null || $content === '') {
+ // Nothing to return.
+ // Note: It's common for the DB to return null, so we allow format_string to take a null,
+ // even though it is counter-intuitive.
+ return '';
+ }
+
+ // Get settings (singleton).
+ $settings = external_settings::get_instance();
+
+ if (!$settings->get_raw()) {
+ $options['context'] = $context;
+ $options['filter'] = isset($options['filter']) && !$options['filter'] ? false : $settings->get_filter();
+ return format_string($content, $striplinks, $options);
+ }
+
+ return $content;
+ }
+
+ /**
+ * Format the text to be returned properly as requested by the either the web service server,
+ * either by an internally call.
+ * The caller can change the format (raw, filter, file, fileurl) with the \core_external\settings singleton
+ * All web service servers must set this singleton when parsing the $_GET and $_POST.
+ *
+ *
+ * Options are the same that in {@link format_text()} with some changes in defaults to provide backwards compatibility:
+ * trusted : If true the string won't be cleaned. Default false.
+ * noclean : If true the string won't be cleaned only if trusted is also true. Default false.
+ * nocache : If true the string will not be cached and will be formatted every call. Default false.
+ * filter : Can be set to false to force filters off, else observes {@link \core_external\settings}.
+ * para : If true then the returned string will be wrapped in div tags.
+ * Default (different from format_text) false.
+ * Default changed because div tags are not commonly needed.
+ * newlines : If true then lines newline breaks will be converted to HTML newline breaks. Default true.
+ * context : Not used! Using contextid parameter instead.
+ * overflowdiv : If set to true the formatted text will be encased in a div with the class no-overflow before being
+ * returned. Default false.
+ * allowid : If true then id attributes will not be removed, even when using htmlpurifier. Default (different from
+ * format_text) true. Default changed id attributes are commonly needed.
+ * blanktarget : If true all tags will have target="_blank" added unless target is explicitly specified.
+ *
+ *
+ * @param string|null $text The content that may contain ULRs in need of rewriting.
+ * @param string|int|null $textformat The text format.
+ * @param context $context This parameter and the next two identify the file area to use.
+ * @param string|null $component
+ * @param string|null $filearea helps identify the file area.
+ * @param int|string|null $itemid helps identify the file area.
+ * @param array|stdClass|null $options text formatting options
+ * @return array text + textformat
+ */
+ public static function format_text(
+ $text,
+ $textformat,
+ $context,
+ $component = null,
+ $filearea = null,
+ $itemid = null,
+ $options = null
+ ) {
+ global $CFG;
+
+ if ($text === null || $text === '') {
+ // Nothing to return.
+ // Note: It's common for the DB to return null, so we allow format_string to take nulls,
+ // even though it is counter-intuitive.
+ return ['', $textformat ?? FORMAT_MOODLE];
+ }
+
+ if (empty($itemid)) {
+ $itemid = null;
+ }
+
+ // Get settings (singleton).
+ $settings = external_settings::get_instance();
+
+ if ($component && $filearea && $settings->get_fileurl()) {
+ require_once($CFG->libdir . "/filelib.php");
+ $text = file_rewrite_pluginfile_urls($text, $settings->get_file(), $context->id, $component, $filearea, $itemid);
+ }
+
+ // Note that $CFG->forceclean does not apply here if the client requests for the raw database content.
+ // This is consistent with web clients that are still able to load non-cleaned text into editors, too.
+
+ if (!$settings->get_raw()) {
+ $options = (array) $options;
+
+ // If context is passed in options, check that is the same to show a debug message.
+ if (isset($options['context'])) {
+ if (is_int($options['context'])) {
+ if ($options['context'] != $context->id) {
+ debugging(
+ 'Different contexts found in external_format_text parameters. $options[\'context\'] not allowed. ' .
+ 'Using $contextid parameter...',
+ DEBUG_DEVELOPER
+ );
+ }
+ } else if ($options['context'] instanceof context) {
+ if ($options['context']->id != $context->id) {
+ debugging(
+ 'Different contexts found in external_format_text parameters. $options[\'context\'] not allowed. ' .
+ 'Using $contextid parameter...',
+ DEBUG_DEVELOPER
+ );
+ }
+ }
+ }
+
+ $options['filter'] = isset($options['filter']) && !$options['filter'] ? false : $settings->get_filter();
+ $options['para'] = isset($options['para']) ? $options['para'] : false;
+ $options['context'] = $context;
+ $options['allowid'] = isset($options['allowid']) ? $options['allowid'] : true;
+
+ $text = format_text($text, $textformat, $options);
+ // Once converted to html (from markdown, plain... lets inform consumer this is already HTML).
+ $textformat = FORMAT_HTML;
+ }
+
+ // Note: The formats defined in weblib are strings.
+ return [$text, $textformat];
+ }
+
+ /**
+ * Validate text field format against known FORMAT_XXX
+ *
+ * @param array $format the format to validate
+ * @return the validated format
+ * @throws coding_exception
+ * @since Moodle 2.3
+ */
+ public static function validate_format($format) {
+ $allowedformats = array(FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN);
+ if (!in_array($format, $allowedformats)) {
+ throw new moodle_exception(
+ 'formatnotsupported',
+ 'webservice',
+ '',
+ null,
+ 'The format with value=' . $format . ' is not supported by this Moodle site'
+ );
+ }
+ return $format;
+ }
+
}
diff --git a/lib/external/tests/util_test.php b/lib/external/tests/util_test.php
index c5c54797507..3c5d2a5be1b 100644
--- a/lib/external/tests/util_test.php
+++ b/lib/external/tests/util_test.php
@@ -33,8 +33,12 @@ class util_test extends \advanced_testcase {
* Store the global DB for restore between tests.
*/
public function setUp(): void {
- global $DB;
+ global $CFG, $DB;
$this->db = $DB;
+ external_settings::reset();
+
+ // Note: This is retained for testing of the old functions.
+ require_once("{$CFG->libdir}/externallib.php");
}
/**
@@ -45,6 +49,7 @@ class util_test extends \advanced_testcase {
if ($this->db !== null) {
$DB = $this->db;
}
+ external_settings::reset();
}
/**
@@ -219,8 +224,178 @@ class util_test extends \advanced_testcase {
// Change token default time.
$this->setUser($user2);
set_config('tokenduration', DAYSECS);
- $token = util::external_generate_token_for_current_user($service);
+ $token = util::generate_token_for_current_user($service);
$timenow = time();
$this->assertLessThanOrEqual($timenow + DAYSECS, $token->validuntil);
}
+
+
+ /**
+ * Test the format_text function.
+ *
+ * @covers \core_external\util::format_text
+ */
+ public function test_format_text(): void {
+ $settings = external_settings::get_instance();
+
+ $currentraw = $settings->get_raw();
+ $currentfilter = $settings->get_filter();
+
+ $settings->set_raw(true);
+ $settings->set_filter(false);
+ $context = \context_system::instance();
+
+ $test = '$$ \pi $$';
+ $testformat = FORMAT_MARKDOWN;
+ $correct = [$test, $testformat];
+ $this->assertSame($correct, util::format_text($test, $testformat, $context, 'core', '', 0));
+
+ // Function external_format_text should work with context id or context instance.
+ $this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0), $correct);
+ $this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0), $correct);
+
+ $settings->set_raw(false);
+ $settings->set_filter(true);
+
+ $test = '$$ \pi $$';
+ $testformat = FORMAT_MARKDOWN;
+ $correct = ['$$ \pi $$
+', FORMAT_HTML, + ]; + $this->assertSame(util::format_text($test, $testformat, $context, 'core', '', 0), $correct); + + // Function external_format_text should work with context id or context instance. + $this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0), $correct); + $this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0), $correct); + + // Filters can be opted out from by the developer. + $test = '$$ \pi $$'; + $testformat = FORMAT_MARKDOWN; + $correct = ['$$ \pi $$
+', FORMAT_HTML, + ]; + $this->assertSame(util::format_text($test, $testformat, $context, 'core', '', 0, ['filter' => false]), $correct); + + // Function external_format_text should work with context id or context instance. + $this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0, ['filter' => false]), $correct); + $this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0, ['filter' => false]), $correct); + + $test = ''; + $testformat = FORMAT_HTML; + $correct = [$test, FORMAT_HTML]; + $options = ['allowid' => true]; + $this->assertSame(util::format_text($test, $testformat, $context, 'core', '', 0, $options), $correct); + // Function external_format_text should work with context id or context instance. + $this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0, $options), $correct); + $this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0, $options), $correct); + + $test = ''; + $testformat = FORMAT_HTML; + $correct = ['', FORMAT_HTML]; + $options = new \stdClass(); + $options->allowid = false; + $this->assertSame(util::format_text($test, $testformat, $context, 'core', '', 0, $options), $correct); + + // Function external_format_text should work with context id or context instance. + $this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0, $options), $correct); + $this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0, $options), $correct); + + $test = ''."\n".'Newline'; + $testformat = FORMAT_MOODLE; + $correct = [' Newline', FORMAT_HTML]; + $options = new \stdClass(); + $options->newlines = false; + $this->assertSame(util::format_text($test, $testformat, $context, 'core', '', 0, $options), $correct); + + // Function external_format_text should work with context id or context instance. + $this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0, $options), $correct); + $this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0, $options), $correct); + + $test = ''; + $testformat = FORMAT_MOODLE; + $correct = ['$$ \pi $$
-', FORMAT_HTML); - // Function external_format_text should work with context id or context instance. - $this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0), $correct); - $this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0), $correct); - - // Filters can be opted out from by the developer. - $test = '$$ \pi $$'; - $testformat = FORMAT_MARKDOWN; - $correct = array('$$ \pi $$
-', FORMAT_HTML); - // Function external_format_text should work with context id or context instance. - $this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0, ['filter' => false]), $correct); - $this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0, ['filter' => false]), $correct); - - $test = ''; - $testformat = FORMAT_HTML; - $correct = array($test, FORMAT_HTML); - $options = array('allowid' => true); - // Function external_format_text should work with context id or context instance. - $this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0, $options), $correct); - $this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0, $options), $correct); - - $test = ''; - $testformat = FORMAT_HTML; - $correct = array('', FORMAT_HTML); - $options = new \stdClass(); - $options->allowid = false; - // Function external_format_text should work with context id or context instance. - $this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0, $options), $correct); - $this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0, $options), $correct); - - $test = ''."\n".'Newline'; - $testformat = FORMAT_MOODLE; - $correct = array(' Newline', FORMAT_HTML); - $options = new \stdClass(); - $options->newlines = false; - // Function external_format_text should work with context id or context instance. - $this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0, $options), $correct); - $this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0, $options), $correct); - - $test = ''; - $testformat = FORMAT_MOODLE; - $correct = array('