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 /lib/filelib.php.
  19   *
  20   * @package   core_files
  21   * @category  phpunit
  22   * @copyright 2009 Jerome Mouneyrac
  23   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  global $CFG;
  29  require_once($CFG->libdir . '/filelib.php');
  30  require_once($CFG->dirroot . '/repository/lib.php');
  31  
  32  class core_filelib_testcase extends advanced_testcase {
  33      public function test_format_postdata_for_curlcall() {
  34  
  35          // POST params with just simple types.
  36          $postdatatoconvert = array( 'userid' => 1, 'roleid' => 22, 'name' => 'john');
  37          $expectedresult = "userid=1&roleid=22&name=john";
  38          $postdata = format_postdata_for_curlcall($postdatatoconvert);
  39          $this->assertEquals($expectedresult, $postdata);
  40  
  41          // POST params with a string containing & character.
  42          $postdatatoconvert = array( 'name' => 'john&emilie', 'roleid' => 22);
  43          $expectedresult = "name=john%26emilie&roleid=22"; // Urlencode: '%26' => '&'.
  44          $postdata = format_postdata_for_curlcall($postdatatoconvert);
  45          $this->assertEquals($expectedresult, $postdata);
  46  
  47          // POST params with an empty value.
  48          $postdatatoconvert = array( 'name' => null, 'roleid' => 22);
  49          $expectedresult = "name=&roleid=22";
  50          $postdata = format_postdata_for_curlcall($postdatatoconvert);
  51          $this->assertEquals($expectedresult, $postdata);
  52  
  53          // POST params with complex types.
  54          $postdatatoconvert = array( 'users' => array(
  55              array(
  56                  'id' => 2,
  57                  'customfields' => array(
  58                      array
  59                      (
  60                          'type' => 'Color',
  61                          'value' => 'violet'
  62                      )
  63                  )
  64              )
  65          )
  66          );
  67          $expectedresult = "users[0][id]=2&users[0][customfields][0][type]=Color&users[0][customfields][0][value]=violet";
  68          $postdata = format_postdata_for_curlcall($postdatatoconvert);
  69          $this->assertEquals($expectedresult, $postdata);
  70  
  71          // POST params with other complex types.
  72          $postdatatoconvert = array ('members' =>
  73          array(
  74              array('groupid' => 1, 'userid' => 1)
  75          , array('groupid' => 1, 'userid' => 2)
  76          )
  77          );
  78          $expectedresult = "members[0][groupid]=1&members[0][userid]=1&members[1][groupid]=1&members[1][userid]=2";
  79          $postdata = format_postdata_for_curlcall($postdatatoconvert);
  80          $this->assertEquals($expectedresult, $postdata);
  81      }
  82  
  83      public function test_download_file_content() {
  84          global $CFG;
  85  
  86          // Test http success first.
  87          $testhtml = $this->getExternalTestFileUrl('/test.html');
  88  
  89          $contents = download_file_content($testhtml);
  90          $this->assertSame('47250a973d1b88d9445f94db4ef2c97a', md5($contents));
  91  
  92          $tofile = "$CFG->tempdir/test.html";
  93          @unlink($tofile);
  94          $result = download_file_content($testhtml, null, null, false, 300, 20, false, $tofile);
  95          $this->assertTrue($result);
  96          $this->assertFileExists($tofile);
  97          $this->assertSame(file_get_contents($tofile), $contents);
  98          @unlink($tofile);
  99  
 100          $result = download_file_content($testhtml, null, null, false, 300, 20, false, null, true);
 101          $this->assertSame($contents, $result);
 102  
 103          $response = download_file_content($testhtml, null, null, true);
 104          $this->assertInstanceOf('stdClass', $response);
 105          $this->assertSame('200', $response->status);
 106          $this->assertTrue(is_array($response->headers));
 107          $this->assertRegExp('|^HTTP/1\.[01] 200 OK$|', rtrim($response->response_code));
 108          $this->assertSame($contents, $response->results);
 109          $this->assertSame('', $response->error);
 110  
 111          // Test https success.
 112          $testhtml = $this->getExternalTestFileUrl('/test.html', true);
 113  
 114          $contents = download_file_content($testhtml, null, null, false, 300, 20, true);
 115          $this->assertSame('47250a973d1b88d9445f94db4ef2c97a', md5($contents));
 116  
 117          $contents = download_file_content($testhtml);
 118          $this->assertSame('47250a973d1b88d9445f94db4ef2c97a', md5($contents));
 119  
 120          // Now 404.
 121          $testhtml = $this->getExternalTestFileUrl('/test.html_nonexistent');
 122  
 123          $contents = download_file_content($testhtml);
 124          $this->assertFalse($contents);
 125          $this->assertDebuggingCalled();
 126  
 127          $response = download_file_content($testhtml, null, null, true);
 128          $this->assertInstanceOf('stdClass', $response);
 129          $this->assertSame('404', $response->status);
 130          $this->assertTrue(is_array($response->headers));
 131          $this->assertRegExp('|^HTTP/1\.[01] 404 Not Found$|', rtrim($response->response_code));
 132          // Do not test the response starts with DOCTYPE here because some servers may return different headers.
 133          $this->assertSame('', $response->error);
 134  
 135          // Invalid url.
 136          $testhtml = $this->getExternalTestFileUrl('/test.html');
 137          $testhtml = str_replace('http://', 'ftp://', $testhtml);
 138  
 139          $contents = download_file_content($testhtml);
 140          $this->assertFalse($contents);
 141  
 142          // Test standard redirects.
 143          $testurl = $this->getExternalTestFileUrl('/test_redir.php');
 144  
 145          $contents = download_file_content("$testurl?redir=2");
 146          $this->assertSame('done', $contents);
 147  
 148          $contents = download_file_content("$testurl?redir=2&verbose=1");
 149          $this->assertSame('done', $contents);
 150  
 151          $response = download_file_content("$testurl?redir=2", null, null, true);
 152          $this->assertInstanceOf('stdClass', $response);
 153          $this->assertSame('200', $response->status);
 154          $this->assertTrue(is_array($response->headers));
 155          $this->assertRegExp('|^HTTP/1\.[01] 200 OK$|', rtrim($response->response_code));
 156          $this->assertSame('done', $response->results);
 157          $this->assertSame('', $response->error);
 158  
 159          $response = download_file_content("$testurl?redir=2&verbose=1", null, null, true);
 160          $this->assertInstanceOf('stdClass', $response);
 161          $this->assertSame('200', $response->status);
 162          $this->assertTrue(is_array($response->headers));
 163          $this->assertRegExp('|^HTTP/1\.[01] 200 OK$|', rtrim($response->response_code));
 164          $this->assertSame('done', $response->results);
 165          $this->assertSame('', $response->error);
 166  
 167          // Commented out this block if there are performance problems.
 168          /*
 169          $contents = download_file_content("$testurl?redir=6");
 170          $this->assertFalse(false, $contents);
 171          $this->assertDebuggingCalled();
 172          $response = download_file_content("$testurl?redir=6", null, null, true);
 173          $this->assertInstanceOf('stdClass', $response);
 174          $this->assertSame('0', $response->status);
 175          $this->assertTrue(is_array($response->headers));
 176          $this->assertFalse($response->results);
 177          $this->assertNotEmpty($response->error);
 178          */
 179  
 180          // Test relative redirects.
 181          $testurl = $this->getExternalTestFileUrl('/test_relative_redir.php');
 182  
 183          $contents = download_file_content("$testurl");
 184          $this->assertSame('done', $contents);
 185  
 186          $contents = download_file_content("$testurl?unused=xxx");
 187          $this->assertSame('done', $contents);
 188      }
 189  
 190      /**
 191       * Test curl basics.
 192       */
 193      public function test_curl_basics() {
 194          global $CFG;
 195  
 196          // Test HTTP success.
 197          $testhtml = $this->getExternalTestFileUrl('/test.html');
 198  
 199          $curl = new curl();
 200          $contents = $curl->get($testhtml);
 201          $this->assertSame('47250a973d1b88d9445f94db4ef2c97a', md5($contents));
 202          $this->assertSame(0, $curl->get_errno());
 203  
 204          $curl = new curl();
 205          $tofile = "$CFG->tempdir/test.html";
 206          @unlink($tofile);
 207          $fp = fopen($tofile, 'w');
 208          $result = $curl->get($testhtml, array(), array('CURLOPT_FILE'=>$fp));
 209          $this->assertTrue($result);
 210          fclose($fp);
 211          $this->assertFileExists($tofile);
 212          $this->assertSame($contents, file_get_contents($tofile));
 213          @unlink($tofile);
 214  
 215          $curl = new curl();
 216          $tofile = "$CFG->tempdir/test.html";
 217          @unlink($tofile);
 218          $result = $curl->download_one($testhtml, array(), array('filepath'=>$tofile));
 219          $this->assertTrue($result);
 220          $this->assertFileExists($tofile);
 221          $this->assertSame($contents, file_get_contents($tofile));
 222          @unlink($tofile);
 223  
 224          // Test 404 request.
 225          $curl = new curl();
 226          $contents = $curl->get($this->getExternalTestFileUrl('/i.do.not.exist'));
 227          $response = $curl->getResponse();
 228          $this->assertSame('404 Not Found', reset($response));
 229          $this->assertSame(0, $curl->get_errno());
 230      }
 231  
 232      /**
 233       * Test a curl basic request with security enabled.
 234       */
 235      public function test_curl_basics_with_security_helper() {
 236          $this->resetAfterTest();
 237  
 238          // Test a request with a basic hostname filter applied.
 239          $testhtml = $this->getExternalTestFileUrl('/test.html');
 240          $url = new moodle_url($testhtml);
 241          $host = $url->get_host();
 242          set_config('curlsecurityblockedhosts', $host); // Blocks $host.
 243  
 244          // Create curl with the default security enabled. We expect this to be blocked.
 245          $curl = new curl();
 246          $contents = $curl->get($testhtml);
 247          $expected = $curl->get_security()->get_blocked_url_string();
 248          $this->assertSame($expected, $contents);
 249          $this->assertSame(0, $curl->get_errno());
 250  
 251          // Now, create a curl using the 'ignoresecurity' override.
 252          // We expect this request to pass, despite the admin setting having been set earlier.
 253          $curl = new curl(['ignoresecurity' => true]);
 254          $contents = $curl->get($testhtml);
 255          $this->assertSame('47250a973d1b88d9445f94db4ef2c97a', md5($contents));
 256          $this->assertSame(0, $curl->get_errno());
 257  
 258          // Now, try injecting a mock security helper into curl. This will override the default helper.
 259          $mockhelper = $this->getMockBuilder('\core\files\curl_security_helper')->getMock();
 260  
 261          // Make the mock return a different string.
 262          $mockhelper->expects($this->any())->method('get_blocked_url_string')->will($this->returnValue('You shall not pass'));
 263  
 264          // And make the mock security helper block all URLs. This helper instance doesn't care about config.
 265          $mockhelper->expects($this->any())->method('url_is_blocked')->will($this->returnValue(true));
 266  
 267          $curl = new curl(['securityhelper' => $mockhelper]);
 268          $contents = $curl->get($testhtml);
 269          $this->assertSame('You shall not pass', $curl->get_security()->get_blocked_url_string());
 270          $this->assertSame($curl->get_security()->get_blocked_url_string(), $contents);
 271      }
 272  
 273      public function test_curl_redirects() {
 274          global $CFG;
 275  
 276          $testurl = $this->getExternalTestFileUrl('/test_redir.php');
 277  
 278          $curl = new curl();
 279          $contents = $curl->get("$testurl?redir=2", array(), array('CURLOPT_MAXREDIRS'=>2));
 280          $response = $curl->getResponse();
 281          $this->assertSame('200 OK', reset($response));
 282          $this->assertSame(0, $curl->get_errno());
 283          $this->assertSame(2, $curl->info['redirect_count']);
 284          $this->assertSame('done', $contents);
 285  
 286          // All redirects are emulated now. Enabling "emulateredirects" explicitly does not have effect.
 287          $curl = new curl();
 288          $curl->emulateredirects = true;
 289          $contents = $curl->get("$testurl?redir=2", array(), array('CURLOPT_MAXREDIRS'=>2));
 290          $response = $curl->getResponse();
 291          $this->assertSame('200 OK', reset($response));
 292          $this->assertSame(0, $curl->get_errno());
 293          $this->assertSame(2, $curl->info['redirect_count']);
 294          $this->assertSame('done', $contents);
 295  
 296          // All redirects are emulated now. Attempting to disable "emulateredirects" explicitly causes warning.
 297          $curl = new curl();
 298          $curl->emulateredirects = false;
 299          $contents = $curl->get("$testurl?redir=2", array(), array('CURLOPT_MAXREDIRS' => 2));
 300          $response = $curl->getResponse();
 301          $this->assertDebuggingCalled('Attempting to disable emulated redirects has no effect any more!');
 302          $this->assertSame('200 OK', reset($response));
 303          $this->assertSame(0, $curl->get_errno());
 304          $this->assertSame(2, $curl->info['redirect_count']);
 305          $this->assertSame('done', $contents);
 306  
 307          // This test was failing for people behind Squid proxies. Squid does not
 308          // fully support HTTP 1.1, so converts things to HTTP 1.0, where the name
 309          // of the status code is different.
 310          reset($response);
 311          if (key($response) === 'HTTP/1.0') {
 312              $responsecode302 = '302 Moved Temporarily';
 313          } else {
 314              $responsecode302 = '302 Found';
 315          }
 316  
 317          $curl = new curl();
 318          $contents = $curl->get("$testurl?redir=3", array(), array('CURLOPT_FOLLOWLOCATION'=>0));
 319          $response = $curl->getResponse();
 320          $this->assertSame($responsecode302, reset($response));
 321          $this->assertSame(0, $curl->get_errno());
 322          $this->assertSame(302, $curl->info['http_code']);
 323          $this->assertSame('', $contents);
 324  
 325          $curl = new curl();
 326          $contents = $curl->get("$testurl?redir=2", array(), array('CURLOPT_MAXREDIRS'=>1));
 327          $this->assertSame(CURLE_TOO_MANY_REDIRECTS, $curl->get_errno());
 328          $this->assertNotEmpty($contents);
 329  
 330          $curl = new curl();
 331          $tofile = "$CFG->tempdir/test.html";
 332          @unlink($tofile);
 333          $fp = fopen($tofile, 'w');
 334          $result = $curl->get("$testurl?redir=1", array(), array('CURLOPT_FILE'=>$fp));
 335          $this->assertTrue($result);
 336          fclose($fp);
 337          $this->assertFileExists($tofile);
 338          $this->assertSame('done', file_get_contents($tofile));
 339          @unlink($tofile);
 340  
 341          $curl = new curl();
 342          $tofile = "$CFG->tempdir/test.html";
 343          @unlink($tofile);
 344          $fp = fopen($tofile, 'w');
 345          $result = $curl->get("$testurl?redir=1&verbose=1", array(), array('CURLOPT_FILE' => $fp));
 346          $this->assertTrue($result);
 347          fclose($fp);
 348          $this->assertFileExists($tofile);
 349          $this->assertSame('done', file_get_contents($tofile));
 350          @unlink($tofile);
 351  
 352          $curl = new curl();
 353          $tofile = "$CFG->tempdir/test.html";
 354          @unlink($tofile);
 355          $result = $curl->download_one("$testurl?redir=1", array(), array('filepath'=>$tofile));
 356          $this->assertTrue($result);
 357          $this->assertFileExists($tofile);
 358          $this->assertSame('done', file_get_contents($tofile));
 359          @unlink($tofile);
 360  
 361          $curl = new curl();
 362          $tofile = "$CFG->tempdir/test.html";
 363          @unlink($tofile);
 364          $result = $curl->download_one("$testurl?redir=1&verbose=1", array(), array('filepath' => $tofile));
 365          $this->assertTrue($result);
 366          $this->assertFileExists($tofile);
 367          $this->assertSame('done', file_get_contents($tofile));
 368          @unlink($tofile);
 369      }
 370  
 371      /**
 372       * Test that redirects to blocked hosts are blocked.
 373       */
 374      public function test_curl_blocked_redirect() {
 375          $this->resetAfterTest();
 376  
 377          $testurl = $this->getExternalTestFileUrl('/test_redir.php');
 378  
 379          // Block a host.
 380          // Note: moodle.com is the URL redirected to when test_redir.php has the param extdest=1 set.
 381          set_config('curlsecurityblockedhosts', 'moodle.com');
 382  
 383          // Redirecting to a non-blocked host should resolve.
 384          $curl = new curl();
 385          $contents = $curl->get("{$testurl}?redir=2");
 386          $response = $curl->getResponse();
 387          $this->assertSame('200 OK', reset($response));
 388          $this->assertSame(0, $curl->get_errno());
 389  
 390          // Redirecting to the blocked host should fail.
 391          $curl = new curl();
 392          $blockedstring = $curl->get_security()->get_blocked_url_string();
 393          $contents = $curl->get("{$testurl}?redir=1&extdest=1");
 394          $this->assertSame($blockedstring, $contents);
 395          $this->assertSame(0, $curl->get_errno());
 396  
 397          // Redirecting to the blocked host after multiple successful redirects should also fail.
 398          $curl = new curl();
 399          $contents = $curl->get("{$testurl}?redir=3&extdest=1");
 400          $this->assertSame($blockedstring, $contents);
 401          $this->assertSame(0, $curl->get_errno());
 402      }
 403  
 404      public function test_curl_relative_redirects() {
 405          // Test relative location redirects.
 406          $testurl = $this->getExternalTestFileUrl('/test_relative_redir.php');
 407  
 408          $curl = new curl();
 409          $contents = $curl->get($testurl);
 410          $response = $curl->getResponse();
 411          $this->assertSame('200 OK', reset($response));
 412          $this->assertSame(0, $curl->get_errno());
 413          $this->assertSame(1, $curl->info['redirect_count']);
 414          $this->assertSame('done', $contents);
 415  
 416          // Test different redirect types.
 417          $testurl = $this->getExternalTestFileUrl('/test_relative_redir.php');
 418  
 419          $curl = new curl();
 420          $contents = $curl->get("$testurl?type=301");
 421          $response = $curl->getResponse();
 422          $this->assertSame('200 OK', reset($response));
 423          $this->assertSame(0, $curl->get_errno());
 424          $this->assertSame(1, $curl->info['redirect_count']);
 425          $this->assertSame('done', $contents);
 426  
 427          $curl = new curl();
 428          $contents = $curl->get("$testurl?type=302");
 429          $response = $curl->getResponse();
 430          $this->assertSame('200 OK', reset($response));
 431          $this->assertSame(0, $curl->get_errno());
 432          $this->assertSame(1, $curl->info['redirect_count']);
 433          $this->assertSame('done', $contents);
 434  
 435          $curl = new curl();
 436          $contents = $curl->get("$testurl?type=303");
 437          $response = $curl->getResponse();
 438          $this->assertSame('200 OK', reset($response));
 439          $this->assertSame(0, $curl->get_errno());
 440          $this->assertSame(1, $curl->info['redirect_count']);
 441          $this->assertSame('done', $contents);
 442  
 443          $curl = new curl();
 444          $contents = $curl->get("$testurl?type=307");
 445          $response = $curl->getResponse();
 446          $this->assertSame('200 OK', reset($response));
 447          $this->assertSame(0, $curl->get_errno());
 448          $this->assertSame(1, $curl->info['redirect_count']);
 449          $this->assertSame('done', $contents);
 450  
 451          $curl = new curl();
 452          $contents = $curl->get("$testurl?type=308");
 453          $response = $curl->getResponse();
 454          $this->assertSame('200 OK', reset($response));
 455          $this->assertSame(0, $curl->get_errno());
 456          $this->assertSame(1, $curl->info['redirect_count']);
 457          $this->assertSame('done', $contents);
 458      }
 459  
 460      public function test_curl_proxybypass() {
 461          global $CFG;
 462          $testurl = $this->getExternalTestFileUrl('/test.html');
 463  
 464          $oldproxy = $CFG->proxyhost;
 465          $oldproxybypass = $CFG->proxybypass;
 466  
 467          // Test without proxy bypass and inaccessible proxy.
 468          $CFG->proxyhost = 'i.do.not.exist';
 469          $CFG->proxybypass = '';
 470          $curl = new curl();
 471          $contents = $curl->get($testurl);
 472          $this->assertNotEquals(0, $curl->get_errno());
 473          $this->assertNotEquals('47250a973d1b88d9445f94db4ef2c97a', md5($contents));
 474  
 475          // Test with proxy bypass.
 476          $testurlhost = parse_url($testurl, PHP_URL_HOST);
 477          $CFG->proxybypass = $testurlhost;
 478          $curl = new curl();
 479          $contents = $curl->get($testurl);
 480          $this->assertSame(0, $curl->get_errno());
 481          $this->assertSame('47250a973d1b88d9445f94db4ef2c97a', md5($contents));
 482  
 483          $CFG->proxyhost = $oldproxy;
 484          $CFG->proxybypass = $oldproxybypass;
 485      }
 486  
 487      /**
 488       * Test that duplicate lines in the curl header are removed.
 489       */
 490      public function test_duplicate_curl_header() {
 491          $testurl = $this->getExternalTestFileUrl('/test_post.php');
 492  
 493          $curl = new curl();
 494          $headerdata = 'Accept: application/json';
 495          $header = [$headerdata, $headerdata];
 496          $this->assertCount(2, $header);
 497          $curl->setHeader($header);
 498          $this->assertCount(1, $curl->header);
 499          $this->assertEquals($headerdata, $curl->header[0]);
 500      }
 501  
 502      public function test_curl_post() {
 503          $testurl = $this->getExternalTestFileUrl('/test_post.php');
 504  
 505          // Test post request.
 506          $curl = new curl();
 507          $contents = $curl->post($testurl, 'data=moodletest');
 508          $response = $curl->getResponse();
 509          $this->assertSame('200 OK', reset($response));
 510          $this->assertSame(0, $curl->get_errno());
 511          $this->assertSame('OK', $contents);
 512  
 513          // Test 100 requests.
 514          $curl = new curl();
 515          $curl->setHeader('Expect: 100-continue');
 516          $contents = $curl->post($testurl, 'data=moodletest');
 517          $response = $curl->getResponse();
 518          $this->assertSame('200 OK', reset($response));
 519          $this->assertSame(0, $curl->get_errno());
 520          $this->assertSame('OK', $contents);
 521      }
 522  
 523      public function test_curl_file() {
 524          $this->resetAfterTest();
 525          $testurl = $this->getExternalTestFileUrl('/test_file.php');
 526  
 527          $fs = get_file_storage();
 528          $filerecord = array(
 529              'contextid' => context_system::instance()->id,
 530              'component' => 'test',
 531              'filearea' => 'curl_post',
 532              'itemid' => 0,
 533              'filepath' => '/',
 534              'filename' => 'test.txt'
 535          );
 536          $teststring = 'moodletest';
 537          $testfile = $fs->create_file_from_string($filerecord, $teststring);
 538  
 539          // Test post with file.
 540          $data = array('testfile' => $testfile);
 541          $curl = new curl();
 542          $contents = $curl->post($testurl, $data);
 543          $this->assertSame('OK', $contents);
 544      }
 545  
 546      public function test_curl_file_name() {
 547          $this->resetAfterTest();
 548          $testurl = $this->getExternalTestFileUrl('/test_file_name.php');
 549  
 550          $fs = get_file_storage();
 551          $filerecord = array(
 552              'contextid' => context_system::instance()->id,
 553              'component' => 'test',
 554              'filearea' => 'curl_post',
 555              'itemid' => 0,
 556              'filepath' => '/',
 557              'filename' => 'test.txt'
 558          );
 559          $teststring = 'moodletest';
 560          $testfile = $fs->create_file_from_string($filerecord, $teststring);
 561  
 562          // Test post with file.
 563          $data = array('testfile' => $testfile);
 564          $curl = new curl();
 565          $contents = $curl->post($testurl, $data);
 566          $this->assertSame('OK', $contents);
 567      }
 568  
 569      public function test_curl_protocols() {
 570  
 571          // HTTP and HTTPS requests were verified in previous requests. Now check
 572          // that we can selectively disable some protocols.
 573          $curl = new curl();
 574  
 575          // Other protocols than HTTP(S) are disabled by default.
 576          $testurl = 'file:///';
 577          $curl->get($testurl);
 578          $this->assertNotEmpty($curl->error);
 579          $this->assertEquals(CURLE_UNSUPPORTED_PROTOCOL, $curl->errno);
 580  
 581          $testurl = 'ftp://nowhere';
 582          $curl->get($testurl);
 583          $this->assertNotEmpty($curl->error);
 584          $this->assertEquals(CURLE_UNSUPPORTED_PROTOCOL, $curl->errno);
 585  
 586          $testurl = 'telnet://somewhere';
 587          $curl->get($testurl);
 588          $this->assertNotEmpty($curl->error);
 589          $this->assertEquals(CURLE_UNSUPPORTED_PROTOCOL, $curl->errno);
 590  
 591          // Protocols are also disabled during redirections.
 592          $testurl = $this->getExternalTestFileUrl('/test_redir_proto.php');
 593          $curl->get($testurl, array('proto' => 'file'));
 594          $this->assertNotEmpty($curl->error);
 595          $this->assertEquals(CURLE_UNSUPPORTED_PROTOCOL, $curl->errno);
 596  
 597          $testurl = $this->getExternalTestFileUrl('/test_redir_proto.php');
 598          $curl->get($testurl, array('proto' => 'ftp'));
 599          $this->assertNotEmpty($curl->error);
 600          $this->assertEquals(CURLE_UNSUPPORTED_PROTOCOL, $curl->errno);
 601  
 602          $testurl = $this->getExternalTestFileUrl('/test_redir_proto.php');
 603          $curl->get($testurl, array('proto' => 'telnet'));
 604          $this->assertNotEmpty($curl->error);
 605          $this->assertEquals(CURLE_UNSUPPORTED_PROTOCOL, $curl->errno);
 606      }
 607  
 608      /**
 609       * Testing prepare draft area
 610       *
 611       * @copyright 2012 Dongsheng Cai {@link http://dongsheng.org}
 612       * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 613       */
 614      public function test_prepare_draft_area() {
 615          global $USER, $DB;
 616  
 617          $this->resetAfterTest(true);
 618  
 619          $generator = $this->getDataGenerator();
 620          $user = $generator->create_user();
 621          $usercontext = context_user::instance($user->id);
 622          $USER = $DB->get_record('user', array('id'=>$user->id));
 623  
 624          $repositorypluginname = 'user';
 625  
 626          $args = array();
 627          $args['type'] = $repositorypluginname;
 628          $repos = repository::get_instances($args);
 629          $userrepository = reset($repos);
 630          $this->assertInstanceOf('repository', $userrepository);
 631  
 632          $fs = get_file_storage();
 633  
 634          $syscontext = context_system::instance();
 635          $component = 'core';
 636          $filearea  = 'unittest';
 637          $itemid    = 0;
 638          $filepath  = '/';
 639          $filename  = 'test.txt';
 640          $sourcefield = 'Copyright stuff';
 641  
 642          $filerecord = array(
 643              'contextid' => $syscontext->id,
 644              'component' => $component,
 645              'filearea'  => $filearea,
 646              'itemid'    => $itemid,
 647              'filepath'  => $filepath,
 648              'filename'  => $filename,
 649              'source'    => $sourcefield,
 650          );
 651          $ref = $fs->pack_reference($filerecord);
 652          $originalfile = $fs->create_file_from_string($filerecord, 'Test content');
 653          $fileid = $originalfile->get_id();
 654          $this->assertInstanceOf('stored_file', $originalfile);
 655  
 656          // Create a user private file.
 657          $userfilerecord = new stdClass;
 658          $userfilerecord->contextid = $usercontext->id;
 659          $userfilerecord->component = 'user';
 660          $userfilerecord->filearea  = 'private';
 661          $userfilerecord->itemid    = 0;
 662          $userfilerecord->filepath  = '/';
 663          $userfilerecord->filename  = 'userfile.txt';
 664          $userfilerecord->source    = 'test';
 665          $userfile = $fs->create_file_from_string($userfilerecord, 'User file content');
 666          $userfileref = $fs->pack_reference($userfilerecord);
 667  
 668          $filerefrecord = clone((object)$filerecord);
 669          $filerefrecord->filename = 'testref.txt';
 670  
 671          // Create a file reference.
 672          $fileref = $fs->create_file_from_reference($filerefrecord, $userrepository->id, $userfileref);
 673          $this->assertInstanceOf('stored_file', $fileref);
 674          $this->assertEquals($userrepository->id, $fileref->get_repository_id());
 675          $this->assertSame($userfile->get_contenthash(), $fileref->get_contenthash());
 676          $this->assertEquals($userfile->get_filesize(), $fileref->get_filesize());
 677          $this->assertRegExp('#' . $userfile->get_filename(). '$#', $fileref->get_reference_details());
 678  
 679          $draftitemid = 0;
 680          file_prepare_draft_area($draftitemid, $syscontext->id, $component, $filearea, $itemid);
 681  
 682          $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid);
 683          $this->assertCount(3, $draftfiles);
 684  
 685          $draftfile = $fs->get_file($usercontext->id, 'user', 'draft', $draftitemid, $filepath, $filename);
 686          $source = unserialize($draftfile->get_source());
 687          $this->assertSame($ref, $source->original);
 688          $this->assertSame($sourcefield, $source->source);
 689  
 690          $draftfileref = $fs->get_file($usercontext->id, 'user', 'draft', $draftitemid, $filepath, $filerefrecord->filename);
 691          $this->assertInstanceOf('stored_file', $draftfileref);
 692          $this->assertTrue($draftfileref->is_external_file());
 693  
 694          // Change some information.
 695          $author = 'Dongsheng Cai';
 696          $draftfile->set_author($author);
 697          $newsourcefield = 'Get from Flickr';
 698          $license = 'GPLv3';
 699          $draftfile->set_license($license);
 700          // If you want to really just change source field, do this.
 701          $source = unserialize($draftfile->get_source());
 702          $newsourcefield = 'From flickr';
 703          $source->source = $newsourcefield;
 704          $draftfile->set_source(serialize($source));
 705  
 706          // Save changed file.
 707          file_save_draft_area_files($draftitemid, $syscontext->id, $component, $filearea, $itemid);
 708  
 709          $file = $fs->get_file($syscontext->id, $component, $filearea, $itemid, $filepath, $filename);
 710  
 711          // Make sure it's the original file id.
 712          $this->assertEquals($fileid, $file->get_id());
 713          $this->assertInstanceOf('stored_file', $file);
 714          $this->assertSame($author, $file->get_author());
 715          $this->assertSame($license, $file->get_license());
 716          $this->assertEquals($newsourcefield, $file->get_source());
 717      }
 718  
 719      /**
 720       * Testing deleting original files.
 721       *
 722       * @copyright 2012 Dongsheng Cai {@link http://dongsheng.org}
 723       * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 724       */
 725      public function test_delete_original_file_from_draft() {
 726          global $USER, $DB;
 727  
 728          $this->resetAfterTest(true);
 729  
 730          $generator = $this->getDataGenerator();
 731          $user = $generator->create_user();
 732          $usercontext = context_user::instance($user->id);
 733          $USER = $DB->get_record('user', array('id'=>$user->id));
 734  
 735          $repositorypluginname = 'user';
 736  
 737          $args = array();
 738          $args['type'] = $repositorypluginname;
 739          $repos = repository::get_instances($args);
 740          $userrepository = reset($repos);
 741          $this->assertInstanceOf('repository', $userrepository);
 742  
 743          $fs = get_file_storage();
 744          $syscontext = context_system::instance();
 745  
 746          $filecontent = 'User file content';
 747  
 748          // Create a user private file.
 749          $userfilerecord = new stdClass;
 750          $userfilerecord->contextid = $usercontext->id;
 751          $userfilerecord->component = 'user';
 752          $userfilerecord->filearea  = 'private';
 753          $userfilerecord->itemid    = 0;
 754          $userfilerecord->filepath  = '/';
 755          $userfilerecord->filename  = 'userfile.txt';
 756          $userfilerecord->source    = 'test';
 757          $userfile = $fs->create_file_from_string($userfilerecord, $filecontent);
 758          $userfileref = $fs->pack_reference($userfilerecord);
 759          $contenthash = $userfile->get_contenthash();
 760  
 761          $filerecord = array(
 762              'contextid' => $syscontext->id,
 763              'component' => 'core',
 764              'filearea'  => 'phpunit',
 765              'itemid'    => 0,
 766              'filepath'  => '/',
 767              'filename'  => 'test.txt',
 768          );
 769          // Create a file reference.
 770          $fileref = $fs->create_file_from_reference($filerecord, $userrepository->id, $userfileref);
 771          $this->assertInstanceOf('stored_file', $fileref);
 772          $this->assertEquals($userrepository->id, $fileref->get_repository_id());
 773          $this->assertSame($userfile->get_contenthash(), $fileref->get_contenthash());
 774          $this->assertEquals($userfile->get_filesize(), $fileref->get_filesize());
 775          $this->assertRegExp('#' . $userfile->get_filename(). '$#', $fileref->get_reference_details());
 776  
 777          $draftitemid = 0;
 778          file_prepare_draft_area($draftitemid, $usercontext->id, 'user', 'private', 0);
 779          $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid);
 780          $this->assertCount(2, $draftfiles);
 781          $draftfile = $fs->get_file($usercontext->id, 'user', 'draft', $draftitemid, $userfilerecord->filepath, $userfilerecord->filename);
 782          $draftfile->delete();
 783          // Save changed file.
 784          file_save_draft_area_files($draftitemid, $usercontext->id, 'user', 'private', 0);
 785  
 786          // The file reference should be a regular moodle file now.
 787          $fileref = $fs->get_file($syscontext->id, 'core', 'phpunit', 0, '/', 'test.txt');
 788          $this->assertFalse($fileref->is_external_file());
 789          $this->assertSame($contenthash, $fileref->get_contenthash());
 790          $this->assertEquals($filecontent, $fileref->get_content());
 791      }
 792  
 793      /**
 794       * Test avoid file merging when working with draft areas.
 795       */
 796      public function test_ignore_file_merging_in_draft_area() {
 797          global $USER, $DB;
 798  
 799          $this->resetAfterTest(true);
 800  
 801          $generator = $this->getDataGenerator();
 802          $user = $generator->create_user();
 803          $usercontext = context_user::instance($user->id);
 804          $USER = $DB->get_record('user', array('id' => $user->id));
 805  
 806          $repositorypluginname = 'user';
 807  
 808          $args = array();
 809          $args['type'] = $repositorypluginname;
 810          $repos = repository::get_instances($args);
 811          $userrepository = reset($repos);
 812          $this->assertInstanceOf('repository', $userrepository);
 813  
 814          $fs = get_file_storage();
 815          $syscontext = context_system::instance();
 816  
 817          $filecontent = 'User file content';
 818  
 819          // Create a user private file.
 820          $userfilerecord = new stdClass;
 821          $userfilerecord->contextid = $usercontext->id;
 822          $userfilerecord->component = 'user';
 823          $userfilerecord->filearea  = 'private';
 824          $userfilerecord->itemid    = 0;
 825          $userfilerecord->filepath  = '/';
 826          $userfilerecord->filename  = 'userfile.txt';
 827          $userfilerecord->source    = 'test';
 828          $userfile = $fs->create_file_from_string($userfilerecord, $filecontent);
 829          $userfileref = $fs->pack_reference($userfilerecord);
 830          $contenthash = $userfile->get_contenthash();
 831  
 832          $filerecord = array(
 833              'contextid' => $syscontext->id,
 834              'component' => 'core',
 835              'filearea'  => 'phpunit',
 836              'itemid'    => 0,
 837              'filepath'  => '/',
 838              'filename'  => 'test.txt',
 839          );
 840          // Create a file reference.
 841          $fileref = $fs->create_file_from_reference($filerecord, $userrepository->id, $userfileref);
 842          $this->assertCount(2, $fs->get_area_files($usercontext->id, 'user', 'private'));    // 2 because includes the '.' file.
 843  
 844          // Save using empty draft item id, all files will be deleted.
 845          file_save_draft_area_files(0, $usercontext->id, 'user', 'private', 0);
 846          $this->assertCount(0, $fs->get_area_files($usercontext->id, 'user', 'private'));
 847  
 848          // Create a file again.
 849          $userfile = $fs->create_file_from_string($userfilerecord, $filecontent);
 850          $this->assertCount(2, $fs->get_area_files($usercontext->id, 'user', 'private'));
 851  
 852          // Save without merge.
 853          file_save_draft_area_files(IGNORE_FILE_MERGE, $usercontext->id, 'user', 'private', 0);
 854          $this->assertCount(2, $fs->get_area_files($usercontext->id, 'user', 'private'));
 855          // Save again, this time including some inline text.
 856          $inlinetext = 'Some text <img src="@@PLUGINFILE@@/file.png">';
 857          $text = file_save_draft_area_files(IGNORE_FILE_MERGE, $usercontext->id, 'user', 'private', 0, null, $inlinetext);
 858          $this->assertCount(2, $fs->get_area_files($usercontext->id, 'user', 'private'));
 859          $this->assertEquals($inlinetext, $text);
 860      }
 861  
 862      /**
 863       * Tests the strip_double_headers function in the curl class.
 864       */
 865      public function test_curl_strip_double_headers() {
 866          // Example from issue tracker.
 867          $mdl30648example = <<<EOF
 868  HTTP/1.0 407 Proxy Authentication Required
 869  Server: squid/2.7.STABLE9
 870  Date: Thu, 08 Dec 2011 14:44:33 GMT
 871  Content-Type: text/html
 872  Content-Length: 1275
 873  X-Squid-Error: ERR_CACHE_ACCESS_DENIED 0
 874  Proxy-Authenticate: Basic realm="Squid proxy-caching web server"
 875  X-Cache: MISS from homer.lancs.ac.uk
 876  X-Cache-Lookup: NONE from homer.lancs.ac.uk:3128
 877  Via: 1.0 homer.lancs.ac.uk:3128 (squid/2.7.STABLE9)
 878  Connection: close
 879  
 880  HTTP/1.0 200 OK
 881  Server: Apache
 882  X-Lb-Nocache: true
 883  Cache-Control: private, max-age=15, no-transform
 884  ETag: "4d69af5d8ba873ea9192c489e151bd7b"
 885  Content-Type: text/html
 886  Date: Thu, 08 Dec 2011 14:44:53 GMT
 887  Set-Cookie: BBC-UID=c4de2e109c8df6a51de627cee11b214bd4fb6054a030222488317afb31b343360MoodleBot/1.0; expires=Mon, 07-Dec-15 14:44:53 GMT; path=/; domain=bbc.co.uk
 888  X-Cache-Action: MISS
 889  X-Cache-Age: 0
 890  Vary: Cookie,X-Country,X-Ip-is-uk-combined,X-Ip-is-advertise-combined,X-Ip_is_uk_combined,X-Ip_is_advertise_combined, X-GeoIP
 891  X-Cache: MISS from ww
 892  
 893  <html>...
 894  EOF;
 895          $mdl30648expected = <<<EOF
 896  HTTP/1.0 200 OK
 897  Server: Apache
 898  X-Lb-Nocache: true
 899  Cache-Control: private, max-age=15, no-transform
 900  ETag: "4d69af5d8ba873ea9192c489e151bd7b"
 901  Content-Type: text/html
 902  Date: Thu, 08 Dec 2011 14:44:53 GMT
 903  Set-Cookie: BBC-UID=c4de2e109c8df6a51de627cee11b214bd4fb6054a030222488317afb31b343360MoodleBot/1.0; expires=Mon, 07-Dec-15 14:44:53 GMT; path=/; domain=bbc.co.uk
 904  X-Cache-Action: MISS
 905  X-Cache-Age: 0
 906  Vary: Cookie,X-Country,X-Ip-is-uk-combined,X-Ip-is-advertise-combined,X-Ip_is_uk_combined,X-Ip_is_advertise_combined, X-GeoIP
 907  X-Cache: MISS from ww
 908  
 909  <html>...
 910  EOF;
 911          // For HTTP, replace the \n with \r\n.
 912          $mdl30648example = preg_replace("~(?!<\r)\n~", "\r\n", $mdl30648example);
 913          $mdl30648expected = preg_replace("~(?!<\r)\n~", "\r\n", $mdl30648expected);
 914  
 915          // Test stripping works OK.
 916          $this->assertSame($mdl30648expected, curl::strip_double_headers($mdl30648example));
 917          // Test it does nothing to the 'plain' data.
 918          $this->assertSame($mdl30648expected, curl::strip_double_headers($mdl30648expected));
 919  
 920          // Example from OU proxy.
 921          $httpsexample = <<<EOF
 922  HTTP/1.0 200 Connection established
 923  
 924  HTTP/1.1 200 OK
 925  Date: Fri, 22 Feb 2013 17:14:23 GMT
 926  Server: Apache/2
 927  X-Powered-By: PHP/5.3.3-7+squeeze14
 928  Content-Type: text/xml
 929  Connection: close
 930  Content-Encoding: gzip
 931  Transfer-Encoding: chunked
 932  
 933  <?xml version="1.0" encoding="ISO-8859-1" ?>
 934  <rss version="2.0">...
 935  EOF;
 936          $httpsexpected = <<<EOF
 937  HTTP/1.1 200 OK
 938  Date: Fri, 22 Feb 2013 17:14:23 GMT
 939  Server: Apache/2
 940  X-Powered-By: PHP/5.3.3-7+squeeze14
 941  Content-Type: text/xml
 942  Connection: close
 943  Content-Encoding: gzip
 944  Transfer-Encoding: chunked
 945  
 946  <?xml version="1.0" encoding="ISO-8859-1" ?>
 947  <rss version="2.0">...
 948  EOF;
 949          // For HTTP, replace the \n with \r\n.
 950          $httpsexample = preg_replace("~(?!<\r)\n~", "\r\n", $httpsexample);
 951          $httpsexpected = preg_replace("~(?!<\r)\n~", "\r\n", $httpsexpected);
 952  
 953          // Test stripping works OK.
 954          $this->assertSame($httpsexpected, curl::strip_double_headers($httpsexample));
 955          // Test it does nothing to the 'plain' data.
 956          $this->assertSame($httpsexpected, curl::strip_double_headers($httpsexpected));
 957      }
 958  
 959      /**
 960       * Tests the get_mimetype_description function.
 961       */
 962      public function test_get_mimetype_description() {
 963          $this->resetAfterTest();
 964  
 965          // Test example type (.doc).
 966          $this->assertEquals(get_string('application/msword', 'mimetypes'),
 967                  get_mimetype_description(array('filename' => 'test.doc')));
 968  
 969          // Test an unknown file type.
 970          $this->assertEquals(get_string('document/unknown', 'mimetypes'),
 971                  get_mimetype_description(array('filename' => 'test.frog')));
 972  
 973          // Test a custom filetype with no lang string specified.
 974          core_filetypes::add_type('frog', 'application/x-frog', 'document');
 975          $this->assertEquals('application/x-frog',
 976                  get_mimetype_description(array('filename' => 'test.frog')));
 977  
 978          // Test custom description.
 979          core_filetypes::update_type('frog', 'frog', 'application/x-frog', 'document',
 980                  array(), '', 'Froggy file');
 981          $this->assertEquals('Froggy file',
 982                  get_mimetype_description(array('filename' => 'test.frog')));
 983  
 984          // Test custom description using multilang filter.
 985          filter_manager::reset_caches();
 986          filter_set_global_state('multilang', TEXTFILTER_ON);
 987          filter_set_applies_to_strings('multilang', true);
 988          core_filetypes::update_type('frog', 'frog', 'application/x-frog', 'document',
 989                  array(), '', '<span lang="en" class="multilang">Green amphibian</span>' .
 990                  '<span lang="fr" class="multilang">Amphibian vert</span>');
 991          $this->assertEquals('Green amphibian',
 992                  get_mimetype_description(array('filename' => 'test.frog')));
 993      }
 994  
 995      /**
 996       * Tests the get_mimetypes_array function.
 997       */
 998      public function test_get_mimetypes_array() {
 999          $mimeinfo = get_mimetypes_array();
1000  
1001          // Test example MIME type (doc).
1002          $this->assertEquals('application/msword', $mimeinfo['doc']['type']);
1003          $this->assertEquals('document', $mimeinfo['doc']['icon']);
1004          $this->assertEquals(array('document'), $mimeinfo['doc']['groups']);
1005          $this->assertFalse(isset($mimeinfo['doc']['string']));
1006          $this->assertFalse(isset($mimeinfo['doc']['defaulticon']));
1007          $this->assertFalse(isset($mimeinfo['doc']['customdescription']));
1008  
1009          // Check the less common fields using other examples.
1010          $this->assertEquals('image', $mimeinfo['png']['string']);
1011          $this->assertEquals(true, $mimeinfo['txt']['defaulticon']);
1012      }
1013  
1014      /**
1015       * Tests for get_mimetype_for_sending function.
1016       */
1017      public function test_get_mimetype_for_sending() {
1018          // Without argument.
1019          $this->assertEquals('application/octet-stream', get_mimetype_for_sending());
1020  
1021          // Argument is null.
1022          $this->assertEquals('application/octet-stream', get_mimetype_for_sending(null));
1023  
1024          // Filename having no extension.
1025          $this->assertEquals('application/octet-stream', get_mimetype_for_sending('filenamewithoutextension'));
1026  
1027          // Test using the extensions listed from the get_mimetypes_array function.
1028          $mimetypes = get_mimetypes_array();
1029          foreach ($mimetypes as $ext => $info) {
1030              if ($ext === 'xxx') {
1031                  $this->assertEquals('application/octet-stream', get_mimetype_for_sending('SampleFile.' . $ext));
1032              } else {
1033                  $this->assertEquals($info['type'], get_mimetype_for_sending('SampleFile.' . $ext));
1034              }
1035          }
1036      }
1037  
1038      /**
1039       * Test curl agent settings.
1040       */
1041      public function test_curl_useragent() {
1042          $curl = new testable_curl();
1043          $options = $curl->get_options();
1044          $this->assertNotEmpty($options);
1045  
1046          $moodlebot = \core_useragent::get_moodlebot_useragent();
1047  
1048          $curl->call_apply_opt($options);
1049          $this->assertTrue(in_array("User-Agent: $moodlebot", $curl->header));
1050          $this->assertFalse(in_array('User-Agent: Test/1.0', $curl->header));
1051  
1052          $options['CURLOPT_USERAGENT'] = 'Test/1.0';
1053          $curl->call_apply_opt($options);
1054          $this->assertTrue(in_array('User-Agent: Test/1.0', $curl->header));
1055          $this->assertFalse(in_array("User-Agent: $moodlebot", $curl->header));
1056  
1057          $curl->set_option('CURLOPT_USERAGENT', 'AnotherUserAgent/1.0');
1058          $curl->call_apply_opt();
1059          $this->assertTrue(in_array('User-Agent: AnotherUserAgent/1.0', $curl->header));
1060          $this->assertFalse(in_array('User-Agent: Test/1.0', $curl->header));
1061  
1062          $curl->set_option('CURLOPT_USERAGENT', 'AnotherUserAgent/1.1');
1063          $options = $curl->get_options();
1064          $curl->call_apply_opt($options);
1065          $this->assertTrue(in_array('User-Agent: AnotherUserAgent/1.1', $curl->header));
1066          $this->assertFalse(in_array('User-Agent: AnotherUserAgent/1.0', $curl->header));
1067  
1068          $curl->unset_option('CURLOPT_USERAGENT');
1069          $curl->call_apply_opt();
1070          $this->assertTrue(in_array("User-Agent: $moodlebot", $curl->header));
1071  
1072          // Finally, test it via exttests, to ensure the agent is sent properly.
1073          // Matching.
1074          $testurl = $this->getExternalTestFileUrl('/test_agent.php');
1075          $extcurl = new curl();
1076          $contents = $extcurl->get($testurl, array(), array('CURLOPT_USERAGENT' => 'AnotherUserAgent/1.2'));
1077          $response = $extcurl->getResponse();
1078          $this->assertSame('200 OK', reset($response));
1079          $this->assertSame(0, $extcurl->get_errno());
1080          $this->assertSame('OK', $contents);
1081          // Not matching.
1082          $contents = $extcurl->get($testurl, array(), array('CURLOPT_USERAGENT' => 'NonMatchingUserAgent/1.2'));
1083          $response = $extcurl->getResponse();
1084          $this->assertSame('200 OK', reset($response));
1085          $this->assertSame(0, $extcurl->get_errno());
1086          $this->assertSame('', $contents);
1087      }
1088  
1089      /**
1090       * Test file_rewrite_pluginfile_urls.
1091       */
1092      public function test_file_rewrite_pluginfile_urls() {
1093  
1094          $syscontext = context_system::instance();
1095          $originaltext = 'Fake test with an image <img src="@@PLUGINFILE@@/image.png">';
1096  
1097          // Do the rewrite.
1098          $finaltext = file_rewrite_pluginfile_urls($originaltext, 'pluginfile.php', $syscontext->id, 'user', 'private', 0);
1099          $this->assertStringContainsString("pluginfile.php", $finaltext);
1100  
1101          // Now undo.
1102          $options = array('reverse' => true);
1103          $finaltext = file_rewrite_pluginfile_urls($finaltext, 'pluginfile.php', $syscontext->id, 'user', 'private', 0, $options);
1104  
1105          // Compare the final text is the same that the original.
1106          $this->assertEquals($originaltext, $finaltext);
1107      }
1108  
1109      /**
1110       * Test file_rewrite_pluginfile_urls with includetoken.
1111       */
1112      public function test_file_rewrite_pluginfile_urls_includetoken() {
1113          global $USER, $CFG;
1114  
1115          $CFG->slasharguments = true;
1116  
1117          $this->resetAfterTest();
1118  
1119          $syscontext = context_system::instance();
1120          $originaltext = 'Fake test with an image <img src="@@PLUGINFILE@@/image.png">';
1121          $options = ['includetoken' => true];
1122  
1123          // Rewrite the content. This will generate a new token.
1124          $finaltext = file_rewrite_pluginfile_urls(
1125                  $originaltext, 'pluginfile.php', $syscontext->id, 'user', 'private', 0, $options);
1126  
1127          $token = get_user_key('core_files', $USER->id);
1128          $expectedurl = new \moodle_url("/tokenpluginfile.php/{$token}/{$syscontext->id}/user/private/0/image.png");
1129          $expectedtext = "Fake test with an image <img src=\"{$expectedurl}\">";
1130          $this->assertEquals($expectedtext, $finaltext);
1131  
1132          // Do it again - the second time will use an existing token.
1133          $finaltext = file_rewrite_pluginfile_urls(
1134                  $originaltext, 'pluginfile.php', $syscontext->id, 'user', 'private', 0, $options);
1135          $this->assertEquals($expectedtext, $finaltext);
1136  
1137          // Now undo.
1138          $options['reverse'] = true;
1139          $finaltext = file_rewrite_pluginfile_urls($finaltext, 'pluginfile.php', $syscontext->id, 'user', 'private', 0, $options);
1140  
1141          // Compare the final text is the same that the original.
1142          $this->assertEquals($originaltext, $finaltext);
1143  
1144          // Now indicates a user different than $USER.
1145          $user = $this->getDataGenerator()->create_user();
1146          $options = ['includetoken' => $user->id];
1147  
1148          // Rewrite the content. This will generate a new token.
1149          $finaltext = file_rewrite_pluginfile_urls(
1150                  $originaltext, 'pluginfile.php', $syscontext->id, 'user', 'private', 0, $options);
1151  
1152          $token = get_user_key('core_files', $user->id);
1153          $expectedurl = new \moodle_url("/tokenpluginfile.php/{$token}/{$syscontext->id}/user/private/0/image.png");
1154          $expectedtext = "Fake test with an image <img src=\"{$expectedurl}\">";
1155          $this->assertEquals($expectedtext, $finaltext);
1156      }
1157  
1158      /**
1159       * Test file_rewrite_pluginfile_urls with includetoken with slasharguments disabled..
1160       */
1161      public function test_file_rewrite_pluginfile_urls_includetoken_no_slashargs() {
1162          global $USER, $CFG;
1163  
1164          $CFG->slasharguments = false;
1165  
1166          $this->resetAfterTest();
1167  
1168          $syscontext = context_system::instance();
1169          $originaltext = 'Fake test with an image <img src="@@PLUGINFILE@@/image.png">';
1170          $options = ['includetoken' => true];
1171  
1172          // Rewrite the content. This will generate a new token.
1173          $finaltext = file_rewrite_pluginfile_urls(
1174                  $originaltext, 'pluginfile.php', $syscontext->id, 'user', 'private', 0, $options);
1175  
1176          $token = get_user_key('core_files', $USER->id);
1177          $expectedurl = new \moodle_url("/tokenpluginfile.php");
1178          $expectedurl .= "?token={$token}&file=/{$syscontext->id}/user/private/0/image.png";
1179          $expectedtext = "Fake test with an image <img src=\"{$expectedurl}\">";
1180          $this->assertEquals($expectedtext, $finaltext);
1181  
1182          // Do it again - the second time will use an existing token.
1183          $finaltext = file_rewrite_pluginfile_urls(
1184                  $originaltext, 'pluginfile.php', $syscontext->id, 'user', 'private', 0, $options);
1185          $this->assertEquals($expectedtext, $finaltext);
1186  
1187          // Now undo.
1188          $options['reverse'] = true;
1189          $finaltext = file_rewrite_pluginfile_urls($finaltext, 'pluginfile.php', $syscontext->id, 'user', 'private', 0, $options);
1190  
1191          // Compare the final text is the same that the original.
1192          $this->assertEquals($originaltext, $finaltext);
1193      }
1194  
1195      /**
1196       * Helpter function to create draft files
1197       *
1198       * @param  array  $filedata data for the file record (to not use defaults)
1199       * @return stored_file the stored file instance
1200       */
1201      public static function create_draft_file($filedata = array()) {
1202          global $USER;
1203  
1204          $fs = get_file_storage();
1205  
1206          $filerecord = array(
1207              'component' => 'user',
1208              'filearea'  => 'draft',
1209              'itemid'    => isset($filedata['itemid']) ? $filedata['itemid'] : file_get_unused_draft_itemid(),
1210              'author'    => isset($filedata['author']) ? $filedata['author'] : fullname($USER),
1211              'filepath'  => isset($filedata['filepath']) ? $filedata['filepath'] : '/',
1212              'filename'  => isset($filedata['filename']) ? $filedata['filename'] : 'file.txt',
1213          );
1214  
1215          if (isset($filedata['contextid'])) {
1216              $filerecord['contextid'] = $filedata['contextid'];
1217          } else {
1218              $usercontext = context_user::instance($USER->id);
1219              $filerecord['contextid'] = $usercontext->id;
1220          }
1221          $source = isset($filedata['source']) ? $filedata['source'] : serialize((object)array('source' => 'From string'));
1222          $content = isset($filedata['content']) ? $filedata['content'] : 'some content here';
1223  
1224          $file = $fs->create_file_from_string($filerecord, $content);
1225          $file->set_source($source);
1226  
1227          return $file;
1228      }
1229  
1230      /**
1231       * Test file_merge_files_from_draft_area_into_filearea
1232       */
1233      public function test_file_merge_files_from_draft_area_into_filearea() {
1234          global $USER, $CFG;
1235  
1236          $this->resetAfterTest(true);
1237          $this->setAdminUser();
1238          $fs = get_file_storage();
1239          $usercontext = context_user::instance($USER->id);
1240  
1241          // Create a draft file.
1242          $filename = 'data.txt';
1243          $filerecord = array(
1244              'filename'  => $filename,
1245          );
1246          $file = self::create_draft_file($filerecord);
1247          $draftitemid = $file->get_itemid();
1248  
1249          $maxbytes = $CFG->userquota;
1250          $maxareabytes = $CFG->userquota;
1251          $options = array('subdirs' => 1,
1252                           'maxbytes' => $maxbytes,
1253                           'maxfiles' => -1,
1254                           'areamaxbytes' => $maxareabytes);
1255  
1256          // Add new file.
1257          file_merge_files_from_draft_area_into_filearea($draftitemid, $usercontext->id, 'user', 'private', 0, $options);
1258  
1259          $files = $fs->get_area_files($usercontext->id, 'user', 'private', 0);
1260          // Directory and file.
1261          $this->assertCount(2, $files);
1262          $found = false;
1263          foreach ($files as $file) {
1264              if (!$file->is_directory()) {
1265                  $found = true;
1266                  $this->assertEquals($filename, $file->get_filename());
1267                  $this->assertEquals('some content here', $file->get_content());
1268              }
1269          }
1270          $this->assertTrue($found);
1271  
1272          // Add two more files.
1273          $filerecord = array(
1274              'itemid'  => $draftitemid,
1275              'filename'  => 'second.txt',
1276          );
1277          self::create_draft_file($filerecord);
1278          $filerecord = array(
1279              'itemid'  => $draftitemid,
1280              'filename'  => 'third.txt',
1281          );
1282          $file = self::create_draft_file($filerecord);
1283  
1284          file_merge_files_from_draft_area_into_filearea($file->get_itemid(), $usercontext->id, 'user', 'private', 0, $options);
1285  
1286          $files = $fs->get_area_files($usercontext->id, 'user', 'private', 0);
1287          $this->assertCount(4, $files);
1288  
1289          // Update contents of one file.
1290          $filerecord = array(
1291              'filename'  => 'second.txt',
1292              'content'  => 'new content',
1293          );
1294          $file = self::create_draft_file($filerecord);
1295          file_merge_files_from_draft_area_into_filearea($file->get_itemid(), $usercontext->id, 'user', 'private', 0, $options);
1296  
1297          $files = $fs->get_area_files($usercontext->id, 'user', 'private', 0);
1298          $this->assertCount(4, $files);
1299          $found = false;
1300          foreach ($files as $file) {
1301              if ($file->get_filename() == 'second.txt') {
1302                  $found = true;
1303                  $this->assertEquals('new content', $file->get_content());
1304              }
1305          }
1306          $this->assertTrue($found);
1307  
1308          // Update author.
1309          // Set different author in the current file.
1310          foreach ($files as $file) {
1311              if ($file->get_filename() == 'second.txt') {
1312                  $file->set_author('Nobody');
1313              }
1314          }
1315          $filerecord = array(
1316              'filename'  => 'second.txt',
1317          );
1318          $file = self::create_draft_file($filerecord);
1319  
1320          file_merge_files_from_draft_area_into_filearea($file->get_itemid(), $usercontext->id, 'user', 'private', 0, $options);
1321  
1322          $files = $fs->get_area_files($usercontext->id, 'user', 'private', 0);
1323          $this->assertCount(4, $files);
1324          $found = false;
1325          foreach ($files as $file) {
1326              if ($file->get_filename() == 'second.txt') {
1327                  $found = true;
1328                  $this->assertEquals(fullname($USER), $file->get_author());
1329              }
1330          }
1331          $this->assertTrue($found);
1332  
1333      }
1334  
1335      /**
1336       * Test max area bytes for file_merge_files_from_draft_area_into_filearea
1337       */
1338      public function test_file_merge_files_from_draft_area_into_filearea_max_area_bytes() {
1339          global $USER;
1340  
1341          $this->resetAfterTest(true);
1342          $this->setAdminUser();
1343          $fs = get_file_storage();
1344  
1345          $file = self::create_draft_file();
1346          $options = array('subdirs' => 1,
1347                           'maxbytes' => 5,
1348                           'maxfiles' => -1,
1349                           'areamaxbytes' => 10);
1350  
1351          // Add new file.
1352          file_merge_files_from_draft_area_into_filearea($file->get_itemid(), $file->get_contextid(), 'user', 'private', 0, $options);
1353          $usercontext = context_user::instance($USER->id);
1354          $files = $fs->get_area_files($usercontext->id, 'user', 'private', 0);
1355          $this->assertCount(0, $files);
1356      }
1357  
1358      /**
1359       * Test max file bytes for file_merge_files_from_draft_area_into_filearea
1360       */
1361      public function test_file_merge_files_from_draft_area_into_filearea_max_file_bytes() {
1362          global $USER;
1363  
1364          $this->resetAfterTest(true);
1365          // The admin has no restriction for max file uploads, so use a normal user.
1366          $user = $this->getDataGenerator()->create_user();
1367          $this->setUser($user);
1368          $fs = get_file_storage();
1369  
1370          $file = self::create_draft_file();
1371          $options = array('subdirs' => 1,
1372                           'maxbytes' => 1,
1373                           'maxfiles' => -1,
1374                           'areamaxbytes' => 100);
1375  
1376          // Add new file.
1377          file_merge_files_from_draft_area_into_filearea($file->get_itemid(), $file->get_contextid(), 'user', 'private', 0, $options);
1378          $usercontext = context_user::instance($USER->id);
1379          // Check we only get the base directory, not a new file.
1380          $files = $fs->get_area_files($usercontext->id, 'user', 'private', 0);
1381          $this->assertCount(1, $files);
1382          $file = array_shift($files);
1383          $this->assertTrue($file->is_directory());
1384      }
1385  
1386      /**
1387       * Test max file number for file_merge_files_from_draft_area_into_filearea
1388       */
1389      public function test_file_merge_files_from_draft_area_into_filearea_max_files() {
1390          global $USER;
1391  
1392          $this->resetAfterTest(true);
1393          $this->setAdminUser();
1394          $fs = get_file_storage();
1395  
1396          $file = self::create_draft_file();
1397          $options = array('subdirs' => 1,
1398                           'maxbytes' => 1000,
1399                           'maxfiles' => 0,
1400                           'areamaxbytes' => 1000);
1401  
1402          // Add new file.
1403          file_merge_files_from_draft_area_into_filearea($file->get_itemid(), $file->get_contextid(), 'user', 'private', 0, $options);
1404          $usercontext = context_user::instance($USER->id);
1405          // Check we only get the base directory, not a new file.
1406          $files = $fs->get_area_files($usercontext->id, 'user', 'private', 0);
1407          $this->assertCount(1, $files);
1408          $file = array_shift($files);
1409          $this->assertTrue($file->is_directory());
1410      }
1411  
1412      /**
1413       * Test file_get_draft_area_info.
1414       */
1415      public function test_file_get_draft_area_info() {
1416          global $USER;
1417  
1418          $this->resetAfterTest(true);
1419          $this->setAdminUser();
1420          $fs = get_file_storage();
1421  
1422          $filerecord = array(
1423              'filename'  => 'one.txt',
1424          );
1425          $file = self::create_draft_file($filerecord);
1426          $size = $file->get_filesize();
1427          $draftitemid = $file->get_itemid();
1428          // Add another file.
1429          $filerecord = array(
1430              'itemid'  => $draftitemid,
1431              'filename'  => 'second.txt',
1432          );
1433          $file = self::create_draft_file($filerecord);
1434          $size += $file->get_filesize();
1435  
1436          // Create directory.
1437          $usercontext = context_user::instance($USER->id);
1438          $dir = $fs->create_directory($usercontext->id, 'user', 'draft', $draftitemid, '/testsubdir/');
1439          // Add file to directory.
1440          $filerecord = array(
1441              'itemid'  => $draftitemid,
1442              'filename' => 'third.txt',
1443              'filepath' => '/testsubdir/',
1444          );
1445          $file = self::create_draft_file($filerecord);
1446          $size += $file->get_filesize();
1447  
1448          $fileinfo = file_get_draft_area_info($draftitemid);
1449          $this->assertEquals(3, $fileinfo['filecount']);
1450          $this->assertEquals($size, $fileinfo['filesize']);
1451          $this->assertEquals(1, $fileinfo['foldercount']);   // Directory created.
1452          $this->assertEquals($size, $fileinfo['filesize_without_references']);
1453  
1454          // Now get files from just one folder.
1455          $fileinfo = file_get_draft_area_info($draftitemid, '/testsubdir/');
1456          $this->assertEquals(1, $fileinfo['filecount']);
1457          $this->assertEquals($file->get_filesize(), $fileinfo['filesize']);
1458          $this->assertEquals(0, $fileinfo['foldercount']);   // No subdirectories inside the directory.
1459          $this->assertEquals($file->get_filesize(), $fileinfo['filesize_without_references']);
1460  
1461          // Check we get the same results if we call file_get_file_area_info.
1462          $fileinfo = file_get_file_area_info($usercontext->id, 'user', 'draft', $draftitemid);
1463          $this->assertEquals(3, $fileinfo['filecount']);
1464          $this->assertEquals($size, $fileinfo['filesize']);
1465          $this->assertEquals(1, $fileinfo['foldercount']);   // Directory created.
1466          $this->assertEquals($size, $fileinfo['filesize_without_references']);
1467      }
1468  
1469      /**
1470       * Test file_get_file_area_info.
1471       */
1472      public function test_file_get_file_area_info() {
1473          global $USER;
1474  
1475          $this->resetAfterTest(true);
1476          $this->setAdminUser();
1477          $fs = get_file_storage();
1478  
1479          $filerecord = array(
1480              'filename'  => 'one.txt',
1481          );
1482          $file = self::create_draft_file($filerecord);
1483          $size = $file->get_filesize();
1484          $draftitemid = $file->get_itemid();
1485          // Add another file.
1486          $filerecord = array(
1487              'itemid'  => $draftitemid,
1488              'filename'  => 'second.txt',
1489          );
1490          $file = self::create_draft_file($filerecord);
1491          $size += $file->get_filesize();
1492  
1493          // Create directory.
1494          $usercontext = context_user::instance($USER->id);
1495          $dir = $fs->create_directory($usercontext->id, 'user', 'draft', $draftitemid, '/testsubdir/');
1496          // Add file to directory.
1497          $filerecord = array(
1498              'itemid'  => $draftitemid,
1499              'filename' => 'third.txt',
1500              'filepath' => '/testsubdir/',
1501          );
1502          $file = self::create_draft_file($filerecord);
1503          $size += $file->get_filesize();
1504  
1505          // Add files to user private file area.
1506          $options = array('subdirs' => 1, 'maxfiles' => 3);
1507          file_merge_files_from_draft_area_into_filearea($draftitemid, $file->get_contextid(), 'user', 'private', 0, $options);
1508  
1509          $fileinfo = file_get_file_area_info($usercontext->id, 'user', 'private');
1510          $this->assertEquals(3, $fileinfo['filecount']);
1511          $this->assertEquals($size, $fileinfo['filesize']);
1512          $this->assertEquals(1, $fileinfo['foldercount']);   // Directory created.
1513          $this->assertEquals($size, $fileinfo['filesize_without_references']);
1514  
1515          // Now get files from just one folder.
1516          $fileinfo = file_get_file_area_info($usercontext->id, 'user', 'private', 0, '/testsubdir/');
1517          $this->assertEquals(1, $fileinfo['filecount']);
1518          $this->assertEquals($file->get_filesize(), $fileinfo['filesize']);
1519          $this->assertEquals(0, $fileinfo['foldercount']);   // No subdirectories inside the directory.
1520          $this->assertEquals($file->get_filesize(), $fileinfo['filesize_without_references']);
1521      }
1522  
1523      /**
1524       * Test confirming that draft files not referenced in the editor text are removed.
1525       */
1526      public function test_file_remove_editor_orphaned_files() {
1527          global $USER, $CFG;
1528          $this->resetAfterTest(true);
1529          $this->setAdminUser();
1530  
1531          // Create three draft files.
1532          $filerecord = ['filename'  => 'file1.png'];
1533          $file = self::create_draft_file($filerecord);
1534          $draftitemid = $file->get_itemid();
1535  
1536          $filerecord['itemid'] = $draftitemid;
1537  
1538          $filerecord['filename'] = 'file2.png';
1539          self::create_draft_file($filerecord);
1540  
1541          $filerecord['filename'] = 'file 3.png';
1542          self::create_draft_file($filerecord);
1543  
1544          // Confirm the user drafts area lists 3 files.
1545          $fs = get_file_storage();
1546          $usercontext = context_user::instance($USER->id);
1547          $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'itemid', 0);
1548          $this->assertCount(3, $draftfiles);
1549  
1550          // Now, spoof some editor text content, referencing 2 of the files; one requiring name encoding, one not.
1551          $editor = [
1552              'itemid' => $draftitemid,
1553              'text' => '
1554                  <img src="'.$CFG->wwwroot.'/draftfile.php/'.$usercontext->id.'/user/draft/'.$draftitemid.'/file%203.png" alt="">
1555                  <img src="'.$CFG->wwwroot.'/draftfile.php/'.$usercontext->id.'/user/draft/'.$draftitemid.'/file1.png" alt="">'
1556          ];
1557  
1558          // Run the remove orphaned drafts function and confirm that only the referenced files remain in the user drafts.
1559          $expected = ['file1.png', 'file 3.png']; // The drafts we expect will not be removed (are referenced in the online text).
1560          file_remove_editor_orphaned_files($editor);
1561          $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'itemid', 0);
1562          $this->assertCount(2, $draftfiles);
1563          foreach ($draftfiles as $file) {
1564              $this->assertContains($file->get_filename(), $expected);
1565          }
1566      }
1567  
1568      /**
1569       * Test that all files in the draftarea are returned.
1570       */
1571      public function test_file_get_all_files_in_draftarea() {
1572          $this->resetAfterTest();
1573          $this->setAdminUser();
1574  
1575          $filerecord = ['filename' => 'basepic.jpg'];
1576          $file = self::create_draft_file($filerecord);
1577  
1578          $secondrecord = [
1579              'filename' => 'infolder.jpg',
1580              'filepath' => '/assignment/',
1581              'itemid' => $file->get_itemid()
1582          ];
1583          $file = self::create_draft_file($secondrecord);
1584  
1585          $thirdrecord = [
1586              'filename' => 'deeperfolder.jpg',
1587              'filepath' => '/assignment/pics/',
1588              'itemid' => $file->get_itemid()
1589          ];
1590          $file = self::create_draft_file($thirdrecord);
1591  
1592          $fourthrecord = [
1593              'filename' => 'differentimage.jpg',
1594              'filepath' => '/secondfolder/',
1595              'itemid' => $file->get_itemid()
1596          ];
1597          $file = self::create_draft_file($fourthrecord);
1598  
1599          // This record has the same name as the last record, but it's in a different folder.
1600          // Just checking this is also returned.
1601          $fifthrecord = [
1602              'filename' => 'differentimage.jpg',
1603              'filepath' => '/assignment/pics/',
1604              'itemid' => $file->get_itemid()
1605          ];
1606          $file = self::create_draft_file($fifthrecord);
1607  
1608          $allfiles = file_get_all_files_in_draftarea($file->get_itemid());
1609          $this->assertCount(5, $allfiles);
1610          $this->assertEquals($filerecord['filename'], $allfiles[0]->filename);
1611          $this->assertEquals($secondrecord['filename'], $allfiles[1]->filename);
1612          $this->assertEquals($thirdrecord['filename'], $allfiles[2]->filename);
1613          $this->assertEquals($fourthrecord['filename'], $allfiles[3]->filename);
1614          $this->assertEquals($fifthrecord['filename'], $allfiles[4]->filename);
1615      }
1616  
1617      public function test_file_copy_file_to_file_area() {
1618          // Create two files in different draft areas but owned by the same user.
1619          global $USER;
1620          $this->resetAfterTest(true);
1621          $this->setAdminUser();
1622  
1623          $filerecord = ['filename'  => 'file1.png', 'itemid' => file_get_unused_draft_itemid()];
1624          $file1 = self::create_draft_file($filerecord);
1625          $filerecord = ['filename'  => 'file2.png', 'itemid' => file_get_unused_draft_itemid()];
1626          $file2 = self::create_draft_file($filerecord);
1627  
1628          // Confirm one file in each draft area.
1629          $fs = get_file_storage();
1630          $usercontext = context_user::instance($USER->id);
1631          $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $file1->get_itemid(), 'itemid', 0);
1632          $this->assertCount(1, $draftfiles);
1633          $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $file2->get_itemid(), 'itemid', 0);
1634          $this->assertCount(1, $draftfiles);
1635  
1636          // Create file record.
1637          $filerecord = [
1638              'component' => $file2->get_component(),
1639              'filearea' => $file2->get_filearea(),
1640              'itemid' => $file2->get_itemid(),
1641              'contextid' => $file2->get_contextid(),
1642              'filepath' => '/',
1643              'filename' => $file2->get_filename()
1644          ];
1645  
1646          // Copy file2 into file1's draft area.
1647          file_copy_file_to_file_area($filerecord, $file2->get_filename(), $file1->get_itemid());
1648          $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $file1->get_itemid(), 'itemid', 0);
1649          $this->assertCount(2, $draftfiles);
1650          $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $file2->get_itemid(), 'itemid', 0);
1651          $this->assertCount(1, $draftfiles);
1652      }
1653  
1654      /**
1655       * Test file_is_draft_areas_limit_reached
1656       */
1657      public function test_file_is_draft_areas_limit_reached() {
1658          global $CFG;
1659          $this->resetAfterTest(true);
1660  
1661          $capacity = $CFG->draft_area_bucket_capacity = 5;
1662          $leak = $CFG->draft_area_bucket_leak = 0.2; // Leaks every 5 seconds.
1663  
1664          $generator = $this->getDataGenerator();
1665          $user = $generator->create_user();
1666  
1667          $this->setUser($user);
1668  
1669          $itemids = [];
1670          for ($i = 0; $i < $capacity; $i++) {
1671              $itemids[$i] = file_get_unused_draft_itemid();
1672          }
1673  
1674          // This test highly depends on time. We try to make sure that the test starts at the early moments on the second.
1675          // This was not needed if MDL-37327 was implemented.
1676          $after = time();
1677          while (time() === $after) {
1678              usleep(100000);
1679          }
1680  
1681          // Burst up to the capacity and make sure that the bucket allows it.
1682          for ($i = 0; $i < $capacity; $i++) {
1683              if ($i) {
1684                  sleep(1); // A little delay so we have different timemodified value for files.
1685              }
1686              $this->assertFalse(file_is_draft_areas_limit_reached($user->id));
1687              self::create_draft_file([
1688                  'filename' => 'file1.png',
1689                  'itemid' => $itemids[$i],
1690              ]);
1691          }
1692  
1693          // The bucket should be full after bursting.
1694          $this->assertTrue(file_is_draft_areas_limit_reached($user->id));
1695  
1696          // The bucket leaks so it shouldn't be full after a certain time.
1697          // Reiterating that this test could have been faster if MDL-37327 was implemented.
1698          sleep(ceil(1 / $leak) - ($capacity - 1));
1699          $this->assertFalse(file_is_draft_areas_limit_reached($user->id));
1700  
1701          // Only one item was leaked from the bucket. So the bucket should become full again if we add a single item to it.
1702          self::create_draft_file([
1703              'filename' => 'file2.png',
1704              'itemid' => $itemids[0],
1705          ]);
1706          $this->assertTrue(file_is_draft_areas_limit_reached($user->id));
1707  
1708          // The bucket leaks at a constant rate. It doesn't matter if it is filled as the result of bursting or not.
1709          sleep(ceil(1 / $leak));
1710          $this->assertFalse(file_is_draft_areas_limit_reached($user->id));
1711      }
1712  }
1713  
1714  /**
1715   * Test-specific class to allow easier testing of curl functions.
1716   *
1717   * @copyright 2015 Dave Cooper
1718   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1719   */
1720  class testable_curl extends curl {
1721      /**
1722       * Accessor for private options array using reflection.
1723       *
1724       * @return array
1725       */
1726      public function get_options() {
1727          // Access to private property.
1728          $rp = new ReflectionProperty('curl', 'options');
1729          $rp->setAccessible(true);
1730          return $rp->getValue($this);
1731      }
1732  
1733      /**
1734       * Setter for private options array using reflection.
1735       *
1736       * @param array $options
1737       */
1738      public function set_options($options) {
1739          // Access to private property.
1740          $rp = new ReflectionProperty('curl', 'options');
1741          $rp->setAccessible(true);
1742          $rp->setValue($this, $options);
1743      }
1744  
1745      /**
1746       * Setter for individual option.
1747       * @param string $option
1748       * @param string $value
1749       */
1750      public function set_option($option, $value) {
1751          $options = $this->get_options();
1752          $options[$option] = $value;
1753          $this->set_options($options);
1754      }
1755  
1756      /**
1757       * Unsets an option on the curl object
1758       * @param string $option
1759       */
1760      public function unset_option($option) {
1761          $options = $this->get_options();
1762          unset($options[$option]);
1763          $this->set_options($options);
1764      }
1765  
1766      /**
1767       * Wrapper to access the private curl::apply_opt() method using reflection.
1768       *
1769       * @param array $options
1770       * @return resource The curl handle
1771       */
1772      public function call_apply_opt($options = null) {
1773          // Access to private method.
1774          $rm = new ReflectionMethod('curl', 'apply_opt');
1775          $rm->setAccessible(true);
1776          $ch = curl_init();
1777          return $rm->invoke($this, $ch, $options);
1778      }
1779  }