PageRenderTime 69ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/tests/filelib_test.php

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