Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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

   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   * Unit tests for format_text defined in weblib.php.
  19   *
  20   * @package   core
  21   * @category  test
  22   * @copyright 2015 The Open University
  23   * @license   http://www.gnu.org/copyleft/gpl.html GNU Public License
  24   */
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  
  29  /**
  30   * Unit tests for format_text defined in weblib.php.
  31   *
  32   * @copyright 2015 The Open University
  33   * @license   http://www.gnu.org/copyleft/gpl.html GNU Public License
  34   */
  35  class core_weblib_format_text_testcase extends advanced_testcase {
  36  
  37      public function test_format_text_format_html() {
  38          $this->resetAfterTest();
  39          filter_set_global_state('emoticon', TEXTFILTER_ON);
  40          $this->assertRegExp('~^<p><img class="icon emoticon" alt="smile" title="smile" ' .
  41                  'src="https://www.example.com/moodle/theme/image.php/_s/boost/core/1/s/smiley" /></p>$~',
  42                  format_text('<p>:-)</p>', FORMAT_HTML));
  43      }
  44  
  45      public function test_format_text_format_html_no_filters() {
  46          $this->resetAfterTest();
  47          filter_set_global_state('emoticon', TEXTFILTER_ON);
  48          $this->assertEquals('<p>:-)</p>',
  49                  format_text('<p>:-)</p>', FORMAT_HTML, array('filter' => false)));
  50      }
  51  
  52      public function test_format_text_format_plain() {
  53          // Note FORMAT_PLAIN does not filter ever, no matter we ask for filtering.
  54          $this->resetAfterTest();
  55          filter_set_global_state('emoticon', TEXTFILTER_ON);
  56          $this->assertEquals(':-)',
  57                  format_text(':-)', FORMAT_PLAIN));
  58      }
  59  
  60      public function test_format_text_format_plain_no_filters() {
  61          $this->resetAfterTest();
  62          filter_set_global_state('emoticon', TEXTFILTER_ON);
  63          $this->assertEquals(':-)',
  64                  format_text(':-)', FORMAT_PLAIN, array('filter' => false)));
  65      }
  66  
  67      public function test_format_text_format_markdown() {
  68          $this->resetAfterTest();
  69          filter_set_global_state('emoticon', TEXTFILTER_ON);
  70          $this->assertRegExp('~^<p><em><img class="icon emoticon" alt="smile" title="smile" ' .
  71                  'src="https://www.example.com/moodle/theme/image.php/_s/boost/core/1/s/smiley" />' .
  72                  '</em></p>\n$~',
  73                  format_text('*:-)*', FORMAT_MARKDOWN));
  74      }
  75  
  76      public function test_format_text_format_markdown_nofilter() {
  77          $this->resetAfterTest();
  78          filter_set_global_state('emoticon', TEXTFILTER_ON);
  79          $this->assertEquals("<p><em>:-)</em></p>\n",
  80                  format_text('*:-)*', FORMAT_MARKDOWN, array('filter' => false)));
  81      }
  82  
  83      public function test_format_text_format_moodle() {
  84          $this->resetAfterTest();
  85          filter_set_global_state('emoticon', TEXTFILTER_ON);
  86          $this->assertRegExp('~^<div class="text_to_html"><p>' .
  87                  '<img class="icon emoticon" alt="smile" title="smile" ' .
  88                  'src="https://www.example.com/moodle/theme/image.php/_s/boost/core/1/s/smiley" /></p></div>$~',
  89                  format_text('<p>:-)</p>', FORMAT_MOODLE));
  90      }
  91  
  92      public function test_format_text_format_moodle_no_filters() {
  93          $this->resetAfterTest();
  94          filter_set_global_state('emoticon', TEXTFILTER_ON);
  95          $this->assertEquals('<div class="text_to_html"><p>:-)</p></div>',
  96                  format_text('<p>:-)</p>', FORMAT_MOODLE, array('filter' => false)));
  97      }
  98  
  99      public function test_format_text_overflowdiv() {
 100          $this->assertEquals('<div class="no-overflow"><p>Hello world</p></div>',
 101                  format_text('<p>Hello world</p>', FORMAT_HTML, array('overflowdiv' => true)));
 102      }
 103  
 104      /**
 105       * Test adding blank target attribute to links
 106       *
 107       * @dataProvider format_text_blanktarget_testcases
 108       * @param string $link The link to add target="_blank" to
 109       * @param string $expected The expected filter value
 110       */
 111      public function test_format_text_blanktarget($link, $expected) {
 112          $actual = format_text($link, FORMAT_MOODLE, array('blanktarget' => true, 'filter' => false, 'noclean' => true));
 113          $this->assertEquals($expected, $actual);
 114      }
 115  
 116      /**
 117       * Data provider for the test_format_text_blanktarget testcase
 118       *
 119       * @return array of testcases
 120       */
 121      public function format_text_blanktarget_testcases() {
 122          return [
 123              'Simple link' => [
 124                  '<a href="https://www.youtube.com/watch?v=JeimE8Wz6e4">Hey, that\'s pretty good!</a>',
 125                  '<div class="text_to_html"><a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" target="_blank"' .
 126                      ' rel="noreferrer">Hey, that\'s pretty good!</a></div>'
 127              ],
 128              'Link with rel' => [
 129                  '<a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" rel="nofollow">Hey, that\'s pretty good!</a>',
 130                  '<div class="text_to_html"><a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" rel="nofollow noreferrer"' .
 131                      ' target="_blank">Hey, that\'s pretty good!</a></div>'
 132              ],
 133              'Link with rel noreferrer' => [
 134                  '<a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" rel="noreferrer">Hey, that\'s pretty good!</a>',
 135                  '<div class="text_to_html"><a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" rel="noreferrer"' .
 136                   ' target="_blank">Hey, that\'s pretty good!</a></div>'
 137              ],
 138              'Link with target' => [
 139                  '<a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" target="_self">Hey, that\'s pretty good!</a>',
 140                  '<div class="text_to_html"><a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" target="_self">' .
 141                      'Hey, that\'s pretty good!</a></div>'
 142              ],
 143              'Link with target blank' => [
 144                  '<a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" target="_blank">Hey, that\'s pretty good!</a>',
 145                  '<div class="text_to_html"><a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" target="_blank"' .
 146                      ' rel="noreferrer">Hey, that\'s pretty good!</a></div>'
 147              ],
 148              'Link with Frank\'s casket inscription' => [
 149                  '<a href="https://en.wikipedia.org/wiki/Franks_Casket">ᚠᛁᛋᚳ᛫ᚠᛚᚩᛞᚢ᛫ᚪᚻᚩᚠᚩᚾᚠᛖᚱᚷ ᛖᚾᛒᛖᚱᛁᚷ ᚹᚪᚱᚦᚷᚪ᛬ᛋᚱᛁᚳᚷᚱᚩᚱᚾᚦᚫᚱᚻᛖᚩᚾᚷᚱᛖᚢᛏᚷᛁᛋᚹᚩᛗ ᚻ' .
 150                      'ᚱᚩᚾᚫᛋᛒᚪᚾ ᛗᚫᚷᛁᚠᛁᛋᚳ᛫ᚠᛚᚩᛞᚢ᛫ᚪᚻᚩᚠᚩᚾᚠᛖᚱᚷ ᛖᚾᛒᛖᚱᛁᚷ ᚹᚪᚱᚦᚷᚪ᛬ᛋᚱᛁᚳᚷᚱᚩᚱᚾᚦᚫᚱᚻᛖᚩᚾᚷᚱᛖᚢᛏᚷᛁᛋᚹᚩᛗ ᚻᚱᚩᚾᚫᛋᛒᚪᚾ ᛗᚫᚷᛁ</a>',
 151                  '<div class="text_to_html"><a href="https://en.wikipedia.org/wiki/Franks_Casket" target="_blank" ' .
 152                      'rel="noreferrer">ᚠᛁᛋᚳ᛫ᚠᛚᚩᛞᚢ᛫ᚪᚻᚩᚠᚩᚾᚠᛖᚱᚷ ᛖᚾᛒᛖᚱᛁᚷ ᚹᚪᚱᚦᚷᚪ᛬ᛋᚱᛁᚳᚷᚱᚩᚱᚾᚦᚫᚱᚻᛖᚩᚾᚷᚱᛖᚢᛏᚷᛁᛋᚹᚩᛗ ᚻᚱᚩᚾᚫᛋᛒᚪᚾ ᛗᚫᚷᛁᚠᛁᛋᚳ᛫ᚠᛚᚩᛞᚢ᛫ᚪᚻᚩᚠᚩᚾᚠᛖᚱᚷ ᛖᚾ' .
 153                      'ᛒᛖᚱᛁᚷ ᚹᚪᚱᚦᚷᚪ᛬ᛋᚱᛁᚳᚷᚱᚩᚱᚾᚦᚫᚱᚻᛖᚩᚾᚷᚱᛖᚢᛏᚷᛁᛋᚹᚩᛗ ᚻᚱᚩᚾᚫᛋᛒᚪᚾ ᛗᚫᚷᛁ</a></div>'
 154               ],
 155              'No link' => [
 156                  'Some very boring text written with the Latin script',
 157                  '<div class="text_to_html">Some very boring text written with the Latin script</div>'
 158              ],
 159              'No link with Thror\'s map runes' => [
 160                  'ᛋᛏᚫᚾᛞ ᛒᚣ ᚦᛖ ᚷᚱᛖᚣ ᛋᛏᚩᚾᛖ ᚻᚹᛁᛚᛖ ᚦᛖ ᚦᚱᚢᛋᚻ ᚾᚩᚳᛋ ᚫᚾᛞ ᚦᛖ ᛋᛖᛏᛏᛁᚾᚷ ᛋᚢᚾ ᚹᛁᚦ ᚦᛖ ᛚᚫᛋᛏ ᛚᛁᚷᚻᛏ ᚩᚠ ᛞᚢᚱᛁᚾᛋ ᛞᚫᚣ ᚹᛁᛚᛚ ᛋᚻᛁᚾᛖ ᚢᛈᚩᚾ ᚦᛖ ᚳᛖᚣᚻᚩᛚᛖ',
 161                  '<div class="text_to_html">ᛋᛏᚫᚾᛞ ᛒᚣ ᚦᛖ ᚷᚱᛖᚣ ᛋᛏᚩᚾᛖ ᚻᚹᛁᛚᛖ ᚦᛖ ᚦᚱᚢᛋᚻ ᚾᚩᚳᛋ ᚫᚾᛞ ᚦᛖ ᛋᛖᛏᛏᛁᚾᚷ ᛋᚢᚾ ᚹᛁᚦ ᚦᛖ ᛚᚫᛋᛏ ᛚᛁᚷᚻᛏ ᚩᚠ ᛞᚢᚱᛁᚾᛋ ᛞᚫᚣ ᚹ' .
 162                  'ᛁᛚᛚ ᛋᚻᛁᚾᛖ ᚢᛈᚩᚾ ᚦᛖ ᚳᛖᚣᚻᚩᛚᛖ</div>'
 163              ]
 164          ];
 165      }
 166  
 167      /**
 168       * Test ability to force cleaning of otherwise non-cleaned content.
 169       *
 170       * @dataProvider format_text_cleaning_testcases
 171       *
 172       * @param string $input Input text
 173       * @param string $nocleaned Expected output of format_text() with noclean=true
 174       * @param string $cleaned Expected output of format_text() with noclean=false
 175       */
 176      public function test_format_text_cleaning($input, $nocleaned, $cleaned) {
 177          global $CFG;
 178          $this->resetAfterTest();
 179  
 180          $CFG->forceclean = false;
 181          $actual = format_text($input, FORMAT_HTML, ['filter' => false, 'noclean' => false]);
 182          $this->assertEquals($cleaned, $actual);
 183  
 184          $CFG->forceclean = true;
 185          $actual = format_text($input, FORMAT_HTML, ['filter' => false, 'noclean' => false]);
 186          $this->assertEquals($cleaned, $actual);
 187  
 188          $CFG->forceclean = false;
 189          $actual = format_text($input, FORMAT_HTML, ['filter' => false, 'noclean' => true]);
 190          $this->assertEquals($nocleaned, $actual);
 191  
 192          $CFG->forceclean = true;
 193          $actual = format_text($input, FORMAT_HTML, ['filter' => false, 'noclean' => true]);
 194          $this->assertEquals($cleaned, $actual);
 195      }
 196  
 197      /**
 198       * Data provider for the test_format_text_cleaning testcase
 199       *
 200       * @return array of testcases (string)testcasename => [(string)input, (string)nocleaned, (string)cleaned]
 201       */
 202      public function format_text_cleaning_testcases() {
 203          return [
 204              'JavaScript' => [
 205                  'Hello <script type="text/javascript">alert("XSS");</script> world',
 206                  'Hello <script type="text/javascript">alert("XSS");</script> world',
 207                  'Hello  world',
 208              ],
 209              'Inline frames' => [
 210                  'Let us go phishing! <iframe src="https://1.2.3.4/google.com"></iframe>',
 211                  'Let us go phishing! <iframe src="https://1.2.3.4/google.com"></iframe>',
 212                  'Let us go phishing! ',
 213              ],
 214              'Malformed A tags' => [
 215                  '<a onmouseover="alert(document.cookie)">xxs link</a>',
 216                  '<a onmouseover="alert(document.cookie)">xxs link</a>',
 217                  '<a>xxs link</a>',
 218              ],
 219              'Malformed IMG tags' => [
 220                  '<IMG """><SCRIPT>alert("XSS")</SCRIPT>">',
 221                  '<IMG """><SCRIPT>alert("XSS")</SCRIPT>">',
 222                  '"&gt;',
 223              ],
 224              'On error alert' => [
 225                  '<IMG SRC=/ onerror="alert(String.fromCharCode(88,83,83))"></img>',
 226                  '<IMG SRC=/ onerror="alert(String.fromCharCode(88,83,83))"></img>',
 227                  '<img src="/" alt="" />',
 228              ],
 229              'IMG onerror and javascript alert encode' => [
 230                  '<img src=x onerror="&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000083&#0000083&#0000039&#0000041">',
 231                  '<img src=x onerror="&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000083&#0000083&#0000039&#0000041">',
 232                  '<img src="x" alt="x" />',
 233              ],
 234              'DIV background-image' => [
 235                  '<DIV STYLE="background-image: url(javascript:alert(\'XSS\'))">',
 236                  '<DIV STYLE="background-image: url(javascript:alert(\'XSS\'))">',
 237                  '<div></div>',
 238              ],
 239          ];
 240      }
 241  }