Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

Differences Between: [Versions 310 and 400] [Versions 311 and 400] [Versions 39 and 400] [Versions 400 and 401] [Versions 400 and 402] [Versions 400 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Weblib tests.
  19   *
  20   * @package    core
  21   * @category   phpunit
  22   * @copyright  &copy; 2006 The Open University
  23   * @author     T.J.Hunt@open.ac.uk
  24   * @license    http://www.gnu.org/copyleft/gpl.html GNU Public License
  25   */
  26  
  27  class weblib_test extends advanced_testcase {
  28  
  29      /**
  30       * @covers ::format_string
  31       */
  32      public function test_format_string() {
  33          global $CFG;
  34  
  35          // Ampersands.
  36          $this->assertSame("&amp; &amp;&amp;&amp;&amp;&amp; &amp;&amp;", format_string("& &&&&& &&"));
  37          $this->assertSame("ANother &amp; &amp;&amp;&amp;&amp;&amp; Category", format_string("ANother & &&&&& Category"));
  38          $this->assertSame("ANother &amp; &amp;&amp;&amp;&amp;&amp; Category", format_string("ANother & &&&&& Category", true));
  39          $this->assertSame("Nick's Test Site &amp; Other things", format_string("Nick's Test Site & Other things", true));
  40          $this->assertSame("& < > \" '", format_string("& < > \" '", true, ['escape' => false]));
  41  
  42          // String entities.
  43          $this->assertSame("&quot;", format_string("&quot;"));
  44  
  45          // Digital entities.
  46          $this->assertSame("&11234;", format_string("&11234;"));
  47  
  48          // Unicode entities.
  49          $this->assertSame("&#4475;", format_string("&#4475;"));
  50  
  51          // < and > signs.
  52          $originalformatstringstriptags = $CFG->formatstringstriptags;
  53  
  54          $CFG->formatstringstriptags = false;
  55          $this->assertSame('x &lt; 1', format_string('x < 1'));
  56          $this->assertSame('x &gt; 1', format_string('x > 1'));
  57          $this->assertSame('x &lt; 1 and x &gt; 0', format_string('x < 1 and x > 0'));
  58  
  59          $CFG->formatstringstriptags = true;
  60          $this->assertSame('x &lt; 1', format_string('x < 1'));
  61          $this->assertSame('x &gt; 1', format_string('x > 1'));
  62          $this->assertSame('x &lt; 1 and x &gt; 0', format_string('x < 1 and x > 0'));
  63  
  64          $CFG->formatstringstriptags = $originalformatstringstriptags;
  65      }
  66  
  67      /**
  68       * The format string static caching should include the filters option to make
  69       * sure filters are correctly applied when requested.
  70       */
  71      public function test_format_string_static_caching_with_filters() {
  72          global $CFG;
  73  
  74          $this->resetAfterTest(true);
  75          $this->setAdminUser();
  76          $generator = $this->getDataGenerator();
  77          $course = $generator->create_course();
  78          $user = $generator->create_user();
  79          $rawstring = '<span lang="en" class="multilang">English</span><span lang="ca" class="multilang">Catalan</span>';
  80          $expectednofilter = strip_tags($rawstring);
  81          $expectedfilter = 'English';
  82          $striplinks = true;
  83          $context = context_course::instance($course->id);
  84          $options = [
  85              'context' => $context,
  86              'escape' => true,
  87              'filter' => false
  88          ];
  89  
  90          $this->setUser($user);
  91  
  92          // Format the string without filters. It should just strip the
  93          // links.
  94          $nofilterresult = format_string($rawstring, $striplinks, $options);
  95          $this->assertEquals($expectednofilter, $nofilterresult);
  96  
  97          // Add the multilang filter. Make sure it's enabled globally.
  98          $CFG->filterall = true;
  99          $CFG->stringfilters = 'multilang';
 100          filter_set_global_state('multilang', TEXTFILTER_ON);
 101          filter_set_local_state('multilang', $context->id, TEXTFILTER_ON);
 102          // This time we want to apply the filters.
 103          $options['filter'] = true;
 104          $filterresult = format_string($rawstring, $striplinks, $options);
 105          $this->assertMatchesRegularExpression("/$expectedfilter/", $filterresult);
 106  
 107          filter_set_local_state('multilang', $context->id, TEXTFILTER_OFF);
 108  
 109          // Confirm that we get back the cached string. The result should be
 110          // the same as the filtered text above even though we've disabled the
 111          // multilang filter in between.
 112          $cachedresult = format_string($rawstring, $striplinks, $options);
 113          $this->assertMatchesRegularExpression("/$expectedfilter/", $cachedresult);
 114      }
 115  
 116      /**
 117       * @covers ::s
 118       */
 119      public function test_s() {
 120          // Special cases.
 121          $this->assertSame('0', s(0));
 122          $this->assertSame('0', s('0'));
 123          $this->assertSame('0', s(false));
 124          $this->assertSame('', s(null));
 125  
 126          // Normal cases.
 127          $this->assertSame('This Breaks &quot; Strict', s('This Breaks " Strict'));
 128          $this->assertSame('This Breaks &lt;a&gt;&quot; Strict&lt;/a&gt;', s('This Breaks <a>" Strict</a>'));
 129  
 130          // Unicode characters.
 131          $this->assertSame('Café', s('Café'));
 132          $this->assertSame('一, 二, 三', s('一, 二, 三'));
 133  
 134          // Don't escape already-escaped numeric entities. (Note, this behaviour
 135          // may not be desirable. Perhaps we should remove these tests and that
 136          // functionality, but we can only do that if we understand why it was added.)
 137          $this->assertSame('An entity: &#x09ff;.', s('An entity: &#x09ff;.'));
 138          $this->assertSame('An entity: &#1073;.', s('An entity: &#1073;.'));
 139          $this->assertSame('An entity: &amp;amp;.', s('An entity: &amp;.'));
 140          $this->assertSame('Not an entity: &amp;amp;#x09ff;.', s('Not an entity: &amp;#x09ff;.'));
 141  
 142          // Test all ASCII characters (0-127).
 143          for ($i = 0; $i <= 127; $i++) {
 144              $character = chr($i);
 145              $result = s($character);
 146              switch ($character) {
 147                  case '"' :
 148                      $this->assertSame('&quot;', $result);
 149                      break;
 150                  case '&' :
 151                      $this->assertSame('&amp;', $result);
 152                      break;
 153                  case "'" :
 154                      $this->assertSame('&#039;', $result);
 155                      break;
 156                  case '<' :
 157                      $this->assertSame('&lt;', $result);
 158                      break;
 159                  case '>' :
 160                      $this->assertSame('&gt;', $result);
 161                      break;
 162                  default:
 163                      $this->assertSame($character, $result);
 164                      break;
 165              }
 166          }
 167      }
 168  
 169      /**
 170       * @covers ::format_text_email
 171       */
 172      public function test_format_text_email() {
 173          $this->assertSame("This is a TEST\n",
 174              format_text_email('<p>This is a <strong>test</strong></p>', FORMAT_HTML));
 175          $this->assertSame("This is a TEST\n",
 176              format_text_email('<p class="frogs">This is a <strong class=\'fishes\'>test</strong></p>', FORMAT_HTML));
 177          $this->assertSame('& so is this',
 178              format_text_email('&amp; so is this', FORMAT_HTML));
 179          $this->assertSame('Two bullets: ' . core_text::code2utf8(8226) . ' ' . core_text::code2utf8(8226),
 180              format_text_email('Two bullets: &#x2022; &#8226;', FORMAT_HTML));
 181          $this->assertSame(core_text::code2utf8(0x7fd2).core_text::code2utf8(0x7fd2),
 182              format_text_email('&#x7fd2;&#x7FD2;', FORMAT_HTML));
 183      }
 184  
 185      /**
 186       * @covers ::obfuscate_email
 187       */
 188      public function test_obfuscate_email() {
 189          $email = 'some.user@example.com';
 190          $obfuscated = obfuscate_email($email);
 191          $this->assertNotSame($email, $obfuscated);
 192          $back = core_text::entities_to_utf8(urldecode($email), true);
 193          $this->assertSame($email, $back);
 194      }
 195  
 196      /**
 197       * @covers ::obfuscate_text
 198       */
 199      public function test_obfuscate_text() {
 200          $text = 'Žluťoučký koníček 32131';
 201          $obfuscated = obfuscate_text($text);
 202          $this->assertNotSame($text, $obfuscated);
 203          $back = core_text::entities_to_utf8($obfuscated, true);
 204          $this->assertSame($text, $back);
 205      }
 206  
 207      /**
 208       * @covers ::highlight
 209       */
 210      public function test_highlight() {
 211          $this->assertSame('This is <span class="highlight">good</span>',
 212                  highlight('good', 'This is good'));
 213  
 214          $this->assertSame('<span class="highlight">span</span>',
 215                  highlight('SpaN', 'span'));
 216  
 217          $this->assertSame('<span class="highlight">SpaN</span>',
 218                  highlight('span', 'SpaN'));
 219  
 220          $this->assertSame('<span><span class="highlight">span</span></span>',
 221                  highlight('span', '<span>span</span>'));
 222  
 223          $this->assertSame('He <span class="highlight">is</span> <span class="highlight">good</span>',
 224                  highlight('good is', 'He is good'));
 225  
 226          $this->assertSame('This is <span class="highlight">good</span>',
 227                  highlight('+good', 'This is good'));
 228  
 229          $this->assertSame('This is good',
 230                  highlight('-good', 'This is good'));
 231  
 232          $this->assertSame('This is goodness',
 233                  highlight('+good', 'This is goodness'));
 234  
 235          $this->assertSame('This is <span class="highlight">good</span>ness',
 236                  highlight('good', 'This is goodness'));
 237  
 238          $this->assertSame('<p><b>test</b> <b>1</b></p><p><b>1</b></p>',
 239                  highlight('test 1', '<p>test 1</p><p>1</p>', false, '<b>', '</b>'));
 240  
 241          $this->assertSame('<p><b>test</b> <b>1</b></p><p><b>1</b></p>',
 242                      highlight('test +1', '<p>test 1</p><p>1</p>', false, '<b>', '</b>'));
 243  
 244          $this->assertSame('<p><b>test</b> 1</p><p>1</p>',
 245                      highlight('test -1', '<p>test 1</p><p>1</p>', false, '<b>', '</b>'));
 246      }
 247  
 248      /**
 249       * @covers ::replace_ampersands_not_followed_by_entity
 250       */
 251      public function test_replace_ampersands() {
 252          $this->assertSame("This &amp; that &nbsp;", replace_ampersands_not_followed_by_entity("This & that &nbsp;"));
 253          $this->assertSame("This &amp;nbsp that &nbsp;", replace_ampersands_not_followed_by_entity("This &nbsp that &nbsp;"));
 254      }
 255  
 256      /**
 257       * @covers ::strip_links
 258       */
 259      public function test_strip_links() {
 260          $this->assertSame('this is a link', strip_links('this is a <a href="http://someaddress.com/query">link</a>'));
 261      }
 262  
 263      /**
 264       * @covers ::wikify_links
 265       */
 266      public function test_wikify_links() {
 267          $this->assertSame('this is a link [ http://someaddress.com/query ]', wikify_links('this is a <a href="http://someaddress.com/query">link</a>'));
 268      }
 269  
 270      /**
 271       * @covers ::clean_text
 272       */
 273      public function test_clean_text() {
 274          $text = "lala <applet>xx</applet>";
 275          $this->assertSame($text, clean_text($text, FORMAT_PLAIN));
 276          $this->assertSame('lala xx', clean_text($text, FORMAT_MARKDOWN));
 277          $this->assertSame('lala xx', clean_text($text, FORMAT_MOODLE));
 278          $this->assertSame('lala xx', clean_text($text, FORMAT_HTML));
 279      }
 280  
 281      /**
 282       * @covers ::qualified_me
 283       */
 284      public function test_qualified_me() {
 285          global $PAGE, $FULLME, $CFG;
 286          $this->resetAfterTest();
 287  
 288          $PAGE = new moodle_page();
 289  
 290          $FULLME = $CFG->wwwroot.'/course/view.php?id=1&xx=yy';
 291          $this->assertSame($FULLME, qualified_me());
 292  
 293          $PAGE->set_url('/course/view.php', array('id'=>1));
 294          $this->assertSame($CFG->wwwroot.'/course/view.php?id=1', qualified_me());
 295      }
 296  
 297      /**
 298       * @covers \null_progress_trace
 299       */
 300      public function test_null_progress_trace() {
 301          $this->resetAfterTest(false);
 302  
 303          $trace = new null_progress_trace();
 304          $trace->output('do');
 305          $trace->output('re', 1);
 306          $trace->output('mi', 2);
 307          $trace->finished();
 308          $output = ob_get_contents();
 309          $this->assertSame('', $output);
 310          $this->expectOutputString('');
 311      }
 312  
 313      /**
 314       * @covers \null_progress_trace
 315       */
 316      public function test_text_progress_trace() {
 317          $this->resetAfterTest(false);
 318  
 319          $trace = new text_progress_trace();
 320          $trace->output('do');
 321          $trace->output('re', 1);
 322          $trace->output('mi', 2);
 323          $trace->finished();
 324          $this->expectOutputString("do\n  re\n    mi\n");
 325      }
 326  
 327      /**
 328       * @covers \html_progress_trace
 329       */
 330      public function test_html_progress_trace() {
 331          $this->resetAfterTest(false);
 332  
 333          $trace = new html_progress_trace();
 334          $trace->output('do');
 335          $trace->output('re', 1);
 336          $trace->output('mi', 2);
 337          $trace->finished();
 338          $this->expectOutputString("<p>do</p>\n<p>&#160;&#160;re</p>\n<p>&#160;&#160;&#160;&#160;mi</p>\n");
 339      }
 340  
 341      /**
 342       * @covers \html_list_progress_trace
 343       */
 344      public function test_html_list_progress_trace() {
 345          $this->resetAfterTest(false);
 346  
 347          $trace = new html_list_progress_trace();
 348          $trace->output('do');
 349          $trace->output('re', 1);
 350          $trace->output('mi', 2);
 351          $trace->finished();
 352          $this->expectOutputString("<ul>\n<li>do<ul>\n<li>re<ul>\n<li>mi</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n");
 353      }
 354  
 355      /**
 356       * @covers \progress_trace_buffer
 357       */
 358      public function test_progress_trace_buffer() {
 359          $this->resetAfterTest(false);
 360  
 361          $trace = new progress_trace_buffer(new html_progress_trace());
 362          ob_start();
 363          $trace->output('do');
 364          $trace->output('re', 1);
 365          $trace->output('mi', 2);
 366          $trace->finished();
 367          $output = ob_get_contents();
 368          ob_end_clean();
 369          $this->assertSame("<p>do</p>\n<p>&#160;&#160;re</p>\n<p>&#160;&#160;&#160;&#160;mi</p>\n", $output);
 370          $this->assertSame($output, $trace->get_buffer());
 371  
 372          $trace = new progress_trace_buffer(new html_progress_trace(), false);
 373          $trace->output('do');
 374          $trace->output('re', 1);
 375          $trace->output('mi', 2);
 376          $trace->finished();
 377          $this->assertSame("<p>do</p>\n<p>&#160;&#160;re</p>\n<p>&#160;&#160;&#160;&#160;mi</p>\n", $trace->get_buffer());
 378          $this->assertSame("<p>do</p>\n<p>&#160;&#160;re</p>\n<p>&#160;&#160;&#160;&#160;mi</p>\n", $trace->get_buffer());
 379          $trace->reset_buffer();
 380          $this->assertSame('', $trace->get_buffer());
 381          $this->expectOutputString('');
 382      }
 383  
 384      /**
 385       * @covers \combined_progress_trace
 386       */
 387      public function test_combined_progress_trace() {
 388          $this->resetAfterTest(false);
 389  
 390          $trace1 = new progress_trace_buffer(new html_progress_trace(), false);
 391          $trace2 = new progress_trace_buffer(new text_progress_trace(), false);
 392  
 393          $trace = new combined_progress_trace(array($trace1, $trace2));
 394          $trace->output('do');
 395          $trace->output('re', 1);
 396          $trace->output('mi', 2);
 397          $trace->finished();
 398          $this->assertSame("<p>do</p>\n<p>&#160;&#160;re</p>\n<p>&#160;&#160;&#160;&#160;mi</p>\n", $trace1->get_buffer());
 399          $this->assertSame("do\n  re\n    mi\n", $trace2->get_buffer());
 400          $this->expectOutputString('');
 401      }
 402  
 403      /**
 404       * @covers ::set_debugging
 405       */
 406      public function test_set_debugging() {
 407          global $CFG;
 408  
 409          $this->resetAfterTest();
 410  
 411          $this->assertEquals(DEBUG_DEVELOPER, $CFG->debug);
 412          $this->assertTrue($CFG->debugdeveloper);
 413          $this->assertNotEmpty($CFG->debugdisplay);
 414  
 415          set_debugging(DEBUG_DEVELOPER, true);
 416          $this->assertEquals(DEBUG_DEVELOPER, $CFG->debug);
 417          $this->assertTrue($CFG->debugdeveloper);
 418          $this->assertNotEmpty($CFG->debugdisplay);
 419  
 420          set_debugging(DEBUG_DEVELOPER, false);
 421          $this->assertEquals(DEBUG_DEVELOPER, $CFG->debug);
 422          $this->assertTrue($CFG->debugdeveloper);
 423          $this->assertEmpty($CFG->debugdisplay);
 424  
 425          set_debugging(-1);
 426          $this->assertEquals(-1, $CFG->debug);
 427          $this->assertTrue($CFG->debugdeveloper);
 428  
 429          set_debugging(DEBUG_ALL);
 430          $this->assertEquals(DEBUG_ALL, $CFG->debug);
 431          $this->assertFalse($CFG->debugdeveloper);
 432  
 433          set_debugging(DEBUG_NORMAL);
 434          $this->assertEquals(DEBUG_NORMAL, $CFG->debug);
 435          $this->assertFalse($CFG->debugdeveloper);
 436  
 437          set_debugging(DEBUG_MINIMAL);
 438          $this->assertEquals(DEBUG_MINIMAL, $CFG->debug);
 439          $this->assertFalse($CFG->debugdeveloper);
 440  
 441          set_debugging(DEBUG_NONE);
 442          $this->assertEquals(DEBUG_NONE, $CFG->debug);
 443          $this->assertFalse($CFG->debugdeveloper);
 444      }
 445  
 446      /**
 447       * @covers ::strip_pluginfile_content
 448       */
 449      public function test_strip_pluginfile_content() {
 450          $source = <<<SOURCE
 451  Hello!
 452  
 453  I'm writing to you from the Moodle Majlis in Muscat, Oman, where we just had several days of Moodle community goodness.
 454  
 455  URL outside a tag: https://moodle.org/logo/logo-240x60.gif
 456  Plugin url outside a tag: @@PLUGINFILE@@/logo-240x60.gif
 457  
 458  External link 1:<img src='https://moodle.org/logo/logo-240x60.gif' alt='Moodle'/>
 459  External link 2:<img alt="Moodle" src="https://moodle.org/logo/logo-240x60.gif"/>
 460  Internal link 1:<img src='@@PLUGINFILE@@/logo-240x60.gif' alt='Moodle'/>
 461  Internal link 2:<img alt="Moodle" src="@@PLUGINFILE@@logo-240x60.gif"/>
 462  Anchor link 1:<a href="@@PLUGINFILE@@logo-240x60.gif" alt="bananas">Link text</a>
 463  Anchor link 2:<a title="bananas" href="../logo-240x60.gif">Link text</a>
 464  Anchor + ext. img:<a title="bananas" href="../logo-240x60.gif"><img alt="Moodle" src="@@PLUGINFILE@@logo-240x60.gif"/></a>
 465  Ext. anchor + img:<a href="@@PLUGINFILE@@logo-240x60.gif"><img alt="Moodle" src="https://moodle.org/logo/logo-240x60.gif"/></a>
 466  SOURCE;
 467          $expected = <<<EXPECTED
 468  Hello!
 469  
 470  I'm writing to you from the Moodle Majlis in Muscat, Oman, where we just had several days of Moodle community goodness.
 471  
 472  URL outside a tag: https://moodle.org/logo/logo-240x60.gif
 473  Plugin url outside a tag: @@PLUGINFILE@@/logo-240x60.gif
 474  
 475  External link 1:<img src="https://moodle.org/logo/logo-240x60.gif" alt="Moodle" />
 476  External link 2:<img alt="Moodle" src="https://moodle.org/logo/logo-240x60.gif" />
 477  Internal link 1:
 478  Internal link 2:
 479  Anchor link 1:Link text
 480  Anchor link 2:<a title="bananas" href="../logo-240x60.gif">Link text</a>
 481  Anchor + ext. img:<a title="bananas" href="../logo-240x60.gif"></a>
 482  Ext. anchor + img:<img alt="Moodle" src="https://moodle.org/logo/logo-240x60.gif" />
 483  EXPECTED;
 484          $this->assertSame($expected, strip_pluginfile_content($source));
 485      }
 486  
 487      /**
 488       * @covers \purify_html
 489       */
 490      public function test_purify_html_ruby() {
 491  
 492          $this->resetAfterTest();
 493  
 494          $ruby =
 495              "<p><ruby><rb>京都</rb><rp>(</rp><rt>きょうと</rt><rp>)</rp></ruby>は" .
 496              "<ruby><rb>日本</rb><rp>(</rp><rt>にほん</rt><rp>)</rp></ruby>の" .
 497              "<ruby><rb>都</rb><rp>(</rp><rt>みやこ</rt><rp>)</rp></ruby>です。</p>";
 498          $illegal = '<script src="//code.jquery.com/jquery-1.11.3.min.js"></script>';
 499  
 500          $cleaned = purify_html($ruby . $illegal);
 501          $this->assertEquals($ruby, $cleaned);
 502  
 503      }
 504  
 505      /**
 506       * Tests for content_to_text.
 507       *
 508       * @param string    $content   The content
 509       * @param int|false $format    The content format
 510       * @param string    $expected  Expected value
 511       * @dataProvider provider_content_to_text
 512       * @covers ::content_to_text
 513       */
 514      public function test_content_to_text($content, $format, $expected) {
 515          $content = content_to_text($content, $format);
 516          $this->assertEquals($expected, $content);
 517      }
 518  
 519      /**
 520       * Data provider for test_content_to_text.
 521       */
 522      public static function provider_content_to_text() {
 523          return array(
 524              array('asd', false, 'asd'),
 525              // Trim '\r\n '.
 526              array("Note that:\n\n3 > 1 ", FORMAT_PLAIN, "Note that:\n\n3 > 1"),
 527              array("Note that:\n\n3 > 1\r\n", FORMAT_PLAIN, "Note that:\n\n3 > 1"),
 528              // Multiple spaces to one.
 529              array('<span class="eheh">京都</span>  ->  hehe', FORMAT_HTML, '京都 -> hehe'),
 530              array('<span class="eheh">京都</span>  ->  hehe', false, '京都 -> hehe'),
 531              array('asd    asd', false, 'asd asd'),
 532              // From markdown to html and html to text.
 533              array('asd __lera__ con la', FORMAT_MARKDOWN, 'asd LERA con la'),
 534              // HTML to text.
 535              array('<p class="frogs">This is a <strong class=\'fishes\'>test</strong></p>', FORMAT_HTML, 'This is a TEST'),
 536              array("<span lang='en' class='multilang'>english</span>
 537  <span lang='ca' class='multilang'>català</span>
 538  <span lang='es' class='multilang'>español</span>
 539  <span lang='fr' class='multilang'>français</span>", FORMAT_HTML, "english català español français")
 540          );
 541      }
 542  
 543      /**
 544       * Data provider for validate_email() function.
 545       *
 546       * @return array Returns aray of test data for the test_validate_email function
 547       */
 548      public function data_validate_email() {
 549          return [
 550              // Test addresses that should pass.
 551              [
 552                  'email' => 'moodle@example.com',
 553                  'result' => true
 554              ],
 555              [
 556                  'email' => 'moodle@localhost.local',
 557                  'result' => true
 558              ],
 559              [
 560                  'email' => 'verp_email+is=mighty@moodle.org',
 561                  'result' => true
 562              ],
 563              [
 564                  'email' => "but_potentially'dangerous'too@example.org",
 565                  'result' => true
 566              ],
 567              [
 568                  'email' => 'posts+AAAAAAAAAAIAAAAAAAAGQQAAAAABFSXz1eM/P/lR2bYyljM+@posts.moodle.org',
 569                  'result' => true
 570              ],
 571  
 572              // Test addresses that should NOT pass.
 573              [
 574                  'email' => 'moodle@localhost',
 575                  'result' => false
 576              ],
 577              [
 578                  'email' => '"attacker\\" -oQ/tmp/ -X/var/www/vhost/moodle/backdoor.php  some"@email.com',
 579                  'result' => false
 580              ],
 581              [
 582                  'email' => "moodle@example.com>\r\nRCPT TO:<victim@example.com",
 583                  'result' => false
 584              ],
 585              [
 586                  'email' => 'greater>than@example.com',
 587                  'result' => false
 588              ],
 589              [
 590                  'email' => 'less<than@example.com',
 591                  'result' => false
 592              ],
 593              [
 594                  'email' => '"this<is>validbutwerejectit"@example.com',
 595                  'result' => false
 596              ],
 597  
 598              // Empty e-mail addresess are not valid.
 599              [
 600                  'email' => '',
 601                  'result' => false,
 602              ],
 603              [
 604                  'email' => null,
 605                  'result' => false,
 606              ],
 607              [
 608                  'email' => false,
 609                  'result' => false,
 610              ],
 611  
 612              // Extra email addresses from Wikipedia page on Email Addresses.
 613              // Valid.
 614              [
 615                  'email' => 'simple@example.com',
 616                  'result' => true
 617              ],
 618              [
 619                  'email' => 'very.common@example.com',
 620                  'result' => true
 621              ],
 622              [
 623                  'email' => 'disposable.style.email.with+symbol@example.com',
 624                  'result' => true
 625              ],
 626              [
 627                  'email' => 'other.email-with-hyphen@example.com',
 628                  'result' => true
 629              ],
 630              [
 631                  'email' => 'fully-qualified-domain@example.com',
 632                  'result' => true
 633              ],
 634              [
 635                  'email' => 'user.name+tag+sorting@example.com',
 636                  'result' => true
 637              ],
 638              // One-letter local-part.
 639              [
 640                  'email' => 'x@example.com',
 641                  'result' => true
 642              ],
 643              [
 644                  'email' => 'example-indeed@strange-example.com',
 645                  'result' => true
 646              ],
 647              // See the List of Internet top-level domains.
 648              [
 649                  'email' => 'example@s.example',
 650                  'result' => true
 651              ],
 652              // Quoted double dot.
 653              [
 654                  'email' => '"john..doe"@example.org',
 655                  'result' => true
 656              ],
 657  
 658              // Invalid.
 659              // No @ character.
 660              [
 661                  'email' => 'Abc.example.com',
 662                  'result' => false
 663              ],
 664              // Only one @ is allowed outside quotation marks.
 665              [
 666                  'email' => 'A@b@c@example.com',
 667                  'result' => false
 668              ],
 669              // None of the special characters in this local-part are allowed outside quotation marks.
 670              [
 671                  'email' => 'a"b(c)d,e:f;g<h>i[j\k]l@example.com',
 672                  'result' => false
 673              ],
 674              // Quoted strings must be dot separated or the only element making up the local-part.
 675              [
 676                  'email' => 'just"not"right@example.com',
 677                  'result' => false
 678              ],
 679              // Spaces, quotes, and backslashes may only exist when within quoted strings and preceded by a backslash.
 680              [
 681                  'email' => 'this is"not\allowed@example.com',
 682                  'result' => false
 683              ],
 684              // Even if escaped (preceded by a backslash), spaces, quotes, and backslashes must still be contained by quotes.
 685              [
 686                  'email' => 'this\ still\"not\\allowed@example.com',
 687                  'result' => false
 688              ],
 689              // Local part is longer than 64 characters.
 690              [
 691                  'email' => '1234567890123456789012345678901234567890123456789012345678901234+x@example.com',
 692                  'result' => false
 693              ],
 694          ];
 695      }
 696  
 697      /**
 698       * Tests valid and invalid email address using the validate_email() function.
 699       *
 700       * @param string $email the email address to test
 701       * @param boolean $result Expected result (true or false)
 702       * @dataProvider    data_validate_email
 703       * @covers ::validate_email
 704       */
 705      public function test_validate_email($email, $result) {
 706          if ($result) {
 707              $this->assertTrue(validate_email($email));
 708          } else {
 709              $this->assertFalse(validate_email($email));
 710          }
 711      }
 712  
 713      /**
 714       * Data provider for test_get_file_argument.
 715       */
 716      public static function provider_get_file_argument() {
 717          return array(
 718              // Serving SCORM content w/o HTTP GET params.
 719              array(array(
 720                      'SERVER_SOFTWARE' => 'Apache',
 721                      'SERVER_PORT' => '80',
 722                      'REQUEST_METHOD' => 'GET',
 723                      'REQUEST_URI' => '/pluginfile.php/3854/mod_scorm/content/1/swf.html',
 724                      'SCRIPT_NAME' => '/pluginfile.php',
 725                      'PATH_INFO' => '/3854/mod_scorm/content/1/swf.html',
 726                  ), 0, '/3854/mod_scorm/content/1/swf.html'),
 727              array(array(
 728                      'SERVER_SOFTWARE' => 'Apache',
 729                      'SERVER_PORT' => '80',
 730                      'REQUEST_METHOD' => 'GET',
 731                      'REQUEST_URI' => '/pluginfile.php/3854/mod_scorm/content/1/swf.html',
 732                      'SCRIPT_NAME' => '/pluginfile.php',
 733                      'PATH_INFO' => '/3854/mod_scorm/content/1/swf.html',
 734                  ), 1, '/3854/mod_scorm/content/1/swf.html'),
 735              // Serving SCORM content w/ HTTP GET 'file' as first param.
 736              array(array(
 737                      'SERVER_SOFTWARE' => 'Apache',
 738                      'SERVER_PORT' => '80',
 739                      'REQUEST_METHOD' => 'GET',
 740                      'REQUEST_URI' => '/pluginfile.php/3854/mod_scorm/content/1/swf.html?file=video_.swf',
 741                      'SCRIPT_NAME' => '/pluginfile.php',
 742                      'PATH_INFO' => '/3854/mod_scorm/content/1/swf.html',
 743                  ), 0, '/3854/mod_scorm/content/1/swf.html'),
 744              array(array(
 745                      'SERVER_SOFTWARE' => 'Apache',
 746                      'SERVER_PORT' => '80',
 747                      'REQUEST_METHOD' => 'GET',
 748                      'REQUEST_URI' => '/pluginfile.php/3854/mod_scorm/content/1/swf.html?file=video_.swf',
 749                      'SCRIPT_NAME' => '/pluginfile.php',
 750                      'PATH_INFO' => '/3854/mod_scorm/content/1/swf.html',
 751                  ), 1, '/3854/mod_scorm/content/1/swf.html'),
 752              // Serving SCORM content w/ HTTP GET 'file' not as first param.
 753              array(array(
 754                      'SERVER_SOFTWARE' => 'Apache',
 755                      'SERVER_PORT' => '80',
 756                      'REQUEST_METHOD' => 'GET',
 757                      'REQUEST_URI' => '/pluginfile.php/3854/mod_scorm/content/1/swf.html?foo=bar&file=video_.swf',
 758                      'SCRIPT_NAME' => '/pluginfile.php',
 759                      'PATH_INFO' => '/3854/mod_scorm/content/1/swf.html',
 760                  ), 0, '/3854/mod_scorm/content/1/swf.html'),
 761              array(array(
 762                      'SERVER_SOFTWARE' => 'Apache',
 763                      'SERVER_PORT' => '80',
 764                      'REQUEST_METHOD' => 'GET',
 765                      'REQUEST_URI' => '/pluginfile.php/3854/mod_scorm/content/1/swf.html?foo=bar&file=video_.swf',
 766                      'SCRIPT_NAME' => '/pluginfile.php',
 767                      'PATH_INFO' => '/3854/mod_scorm/content/1/swf.html',
 768                  ), 1, '/3854/mod_scorm/content/1/swf.html'),
 769              // Serving content from a generic activity w/ HTTP GET 'file', still forcing slash arguments.
 770              array(array(
 771                      'SERVER_SOFTWARE' => 'Apache',
 772                      'SERVER_PORT' => '80',
 773                      'REQUEST_METHOD' => 'GET',
 774                      'REQUEST_URI' => '/pluginfile.php/3854/whatever/content/1/swf.html?file=video_.swf',
 775                      'SCRIPT_NAME' => '/pluginfile.php',
 776                      'PATH_INFO' => '/3854/whatever/content/1/swf.html',
 777                  ), 0, '/3854/whatever/content/1/swf.html'),
 778              array(array(
 779                      'SERVER_SOFTWARE' => 'Apache',
 780                      'SERVER_PORT' => '80',
 781                      'REQUEST_METHOD' => 'GET',
 782                      'REQUEST_URI' => '/pluginfile.php/3854/whatever/content/1/swf.html?file=video_.swf',
 783                      'SCRIPT_NAME' => '/pluginfile.php',
 784                      'PATH_INFO' => '/3854/whatever/content/1/swf.html',
 785                  ), 1, '/3854/whatever/content/1/swf.html'),
 786              // Serving content from a generic activity w/ HTTP GET 'file', still forcing slash arguments (edge case).
 787              array(array(
 788                      'SERVER_SOFTWARE' => 'Apache',
 789                      'SERVER_PORT' => '80',
 790                      'REQUEST_METHOD' => 'GET',
 791                      'REQUEST_URI' => '/pluginfile.php/?file=video_.swf',
 792                      'SCRIPT_NAME' => '/pluginfile.php',
 793                      'PATH_INFO' => '/',
 794                  ), 0, 'video_.swf'),
 795              array(array(
 796                      'SERVER_SOFTWARE' => 'Apache',
 797                      'SERVER_PORT' => '80',
 798                      'REQUEST_METHOD' => 'GET',
 799                      'REQUEST_URI' => '/pluginfile.php/?file=video_.swf',
 800                      'SCRIPT_NAME' => '/pluginfile.php',
 801                      'PATH_INFO' => '/',
 802                  ), 1, 'video_.swf'),
 803              // Serving content from a generic activity w/ HTTP GET 'file', w/o forcing slash arguments.
 804              array(array(
 805                      'SERVER_SOFTWARE' => 'Apache',
 806                      'SERVER_PORT' => '80',
 807                      'REQUEST_METHOD' => 'GET',
 808                      'REQUEST_URI' => '/pluginfile.php?file=%2F3854%2Fwhatever%2Fcontent%2F1%2Fswf.html%3Ffile%3Dvideo_.swf',
 809                      'SCRIPT_NAME' => '/pluginfile.php',
 810                  ), 0, '/3854/whatever/content/1/swf.html?file=video_.swf'),
 811              array(array(
 812                      'SERVER_SOFTWARE' => 'Apache',
 813                      'SERVER_PORT' => '80',
 814                      'REQUEST_METHOD' => 'GET',
 815                      'REQUEST_URI' => '/pluginfile.php?file=%2F3854%2Fwhatever%2Fcontent%2F1%2Fswf.html%3Ffile%3Dvideo_.swf',
 816                      'SCRIPT_NAME' => '/pluginfile.php',
 817                  ), 1, '/3854/whatever/content/1/swf.html?file=video_.swf'),
 818          );
 819      }
 820  
 821      /**
 822       * Tests for get_file_argument() function.
 823       *
 824       * @param array $server mockup for $_SERVER.
 825       * @param string $cfgslasharguments slasharguments setting.
 826       * @param string|false $expected Expected value.
 827       * @dataProvider provider_get_file_argument
 828       * @covers ::get_file_argument
 829       */
 830      public function test_get_file_argument($server, $cfgslasharguments, $expected) {
 831          global $CFG;
 832  
 833          // Overwrite the related settings.
 834          $currentsetting = $CFG->slasharguments;
 835          $CFG->slasharguments = $cfgslasharguments;
 836          // Mock global $_SERVER.
 837          $currentserver = isset($_SERVER) ? $_SERVER : null;
 838          $_SERVER = $server;
 839          initialise_fullme();
 840          if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
 841              $this->fail('Only HTTP GET mocked request allowed.');
 842          }
 843          if (empty($_SERVER['REQUEST_URI'])) {
 844              $this->fail('Invalid HTTP GET mocked request.');
 845          }
 846          // Mock global $_GET.
 847          $currentget = isset($_GET) ? $_GET : null;
 848          $_GET = array();
 849          $querystring = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY);
 850          if (!empty($querystring)) {
 851              $_SERVER['QUERY_STRING'] = $querystring;
 852              parse_str($querystring, $_GET);
 853          }
 854  
 855          $this->assertEquals($expected, get_file_argument());
 856  
 857          // Restore the current settings and global values.
 858          $CFG->slasharguments = $currentsetting;
 859          if (is_null($currentserver)) {
 860              unset($_SERVER);
 861          } else {
 862              $_SERVER = $currentserver;
 863          }
 864          if (is_null($currentget)) {
 865              unset($_GET);
 866          } else {
 867              $_GET = $currentget;
 868          }
 869      }
 870  
 871      /**
 872       * Tests for extract_draft_file_urls_from_text() function.
 873       *
 874       * @covers ::extract_draft_file_urls_from_text
 875       */
 876      public function test_extract_draft_file_urls_from_text() {
 877          global $CFG;
 878  
 879          $url1 = "{$CFG->wwwroot}/draftfile.php/5/user/draft/99999999/test1.jpg";
 880          $url2 = "{$CFG->wwwroot}/draftfile.php/5/user/draft/99999998/test2.jpg";
 881  
 882          $html = "<p>This is a test.</p><p><img src=\"$url1}\" alt=\"\" role=\"presentation\"></p>
 883                  <br>Test content.<p></p><p><img src=\"{$url2}\" alt=\"\" width=\"2048\" height=\"1536\"
 884                  role=\"presentation\" class=\"img-fluid atto_image_button_text-bottom\"><br></p>";
 885          $draftareas = array(
 886              array(
 887                  'urlbase' => 'draftfile.php',
 888                  'contextid' => '5',
 889                  'component' => 'user',
 890                  'filearea' => 'draft',
 891                  'itemid' => '99999999',
 892                  'filename' => 'test1.jpg',
 893                  0 => "{$CFG->wwwroot}/draftfile.php/5/user/draft/99999999/test1.jpg",
 894                  1 => 'draftfile.php',
 895                  2 => '5',
 896                  3 => 'user',
 897                  4 => 'draft',
 898                  5 => '99999999',
 899                  6 => 'test1.jpg'
 900              ),
 901              array(
 902                  'urlbase' => 'draftfile.php',
 903                  'contextid' => '5',
 904                  'component' => 'user',
 905                  'filearea' => 'draft',
 906                  'itemid' => '99999998',
 907                  'filename' => 'test2.jpg',
 908                  0 => "{$CFG->wwwroot}/draftfile.php/5/user/draft/99999998/test2.jpg",
 909                  1 => 'draftfile.php',
 910                  2 => '5',
 911                  3 => 'user',
 912                  4 => 'draft',
 913                  5 => '99999998',
 914                  6 => 'test2.jpg'
 915              )
 916          );
 917          $extracteddraftareas = extract_draft_file_urls_from_text($html, false, 5, 'user', 'draft');
 918          $this->assertEquals($draftareas, $extracteddraftareas);
 919      }
 920  
 921      /**
 922       * @covers ::print_password_policy
 923       */
 924      public function test_print_password_policy() {
 925          $this->resetAfterTest(true);
 926          global $CFG;
 927  
 928          $policydisabled = '';
 929  
 930          // Set password policy to disabled.
 931          $CFG->passwordpolicy = false;
 932  
 933          // Check for empty response.
 934          $this->assertEquals($policydisabled, print_password_policy());
 935  
 936          // Now set the policy to enabled with every control disabled.
 937          $CFG->passwordpolicy = true;
 938          $CFG->minpasswordlength = 0;
 939          $CFG->minpassworddigits = 0;
 940          $CFG->minpasswordlower = 0;
 941          $CFG->minpasswordupper = 0;
 942          $CFG->minpasswordnonalphanum = 0;
 943          $CFG->maxconsecutiveidentchars = 0;
 944  
 945          // Check for empty response.
 946          $this->assertEquals($policydisabled, print_password_policy());
 947  
 948          // Now enable some controls, and check that the policy responds with policy text.
 949          $CFG->minpasswordlength = 8;
 950          $CFG->minpassworddigits = 1;
 951          $CFG->minpasswordlower = 1;
 952          $CFG->minpasswordupper = 1;
 953          $CFG->minpasswordnonalphanum = 1;
 954          $CFG->maxconsecutiveidentchars = 1;
 955  
 956          $this->assertNotEquals($policydisabled, print_password_policy());
 957      }
 958  }