MDL-60161 filelib: Unit test for duplicate curl headers.
[moodle.git] / lib / tests / filelib_test.php
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/>.
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  */
26 defined('MOODLE_INTERNAL') || die();
28 global $CFG;
29 require_once($CFG->libdir . '/filelib.php');
30 require_once($CFG->dirroot . '/repository/lib.php');
32 class core_filelib_testcase extends advanced_testcase {
33     public function test_format_postdata_for_curlcall() {
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);
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);
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);
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);
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     }
83     public function test_download_file_content() {
84         global $CFG;
86         // Test http success first.
87         $testhtml = $this->getExternalTestFileUrl('/test.html');
89         $contents = download_file_content($testhtml);
90         $this->assertSame('47250a973d1b88d9445f94db4ef2c97a', md5($contents));
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);
100         $result = download_file_content($testhtml, null, null, false, 300, 20, false, null, true);
101         $this->assertSame($contents, $result);
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);
111         // Test https success.
112         $testhtml = $this->getExternalTestFileUrl('/test.html', true);
114         $contents = download_file_content($testhtml, null, null, false, 300, 20, true);
115         $this->assertSame('47250a973d1b88d9445f94db4ef2c97a', md5($contents));
117         $contents = download_file_content($testhtml);
118         $this->assertSame('47250a973d1b88d9445f94db4ef2c97a', md5($contents));
120         // Now 404.
121         $testhtml = $this->getExternalTestFileUrl('/test.html_nonexistent');
123         $contents = download_file_content($testhtml);
124         $this->assertFalse($contents);
125         $this->assertDebuggingCalled();
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);
135         // Invalid url.
136         $testhtml = $this->getExternalTestFileUrl('/test.html');
137         $testhtml = str_replace('http://', 'ftp://', $testhtml);
139         $contents = download_file_content($testhtml);
140         $this->assertFalse($contents);
142         // Test standard redirects.
143         $testurl = $this->getExternalTestFileUrl('/test_redir.php');
145         $contents = download_file_content("$testurl?redir=2");
146         $this->assertSame('done', $contents);
148         $response = download_file_content("$testurl?redir=2", null, null, true);
149         $this->assertInstanceOf('stdClass', $response);
150         $this->assertSame('200', $response->status);
151         $this->assertTrue(is_array($response->headers));
152         $this->assertRegExp('|^HTTP/1\.[01] 200 OK$|', rtrim($response->response_code));
153         $this->assertSame('done', $response->results);
154         $this->assertSame('', $response->error);
156         // Commented out this block if there are performance problems.
157         /*
158         $contents = download_file_content("$testurl?redir=6");
159         $this->assertFalse(false, $contents);
160         $this->assertDebuggingCalled();
161         $response = download_file_content("$testurl?redir=6", null, null, true);
162         $this->assertInstanceOf('stdClass', $response);
163         $this->assertSame('0', $response->status);
164         $this->assertTrue(is_array($response->headers));
165         $this->assertFalse($response->results);
166         $this->assertNotEmpty($response->error);
167         */
169         // Test relative redirects.
170         $testurl = $this->getExternalTestFileUrl('/test_relative_redir.php');
172         $contents = download_file_content("$testurl");
173         $this->assertSame('done', $contents);
175         $contents = download_file_content("$testurl?unused=xxx");
176         $this->assertSame('done', $contents);
177     }
179     /**
180      * Test curl basics.
181      */
182     public function test_curl_basics() {
183         global $CFG;
185         // Test HTTP success.
186         $testhtml = $this->getExternalTestFileUrl('/test.html');
188         $curl = new curl();
189         $contents = $curl->get($testhtml);
190         $this->assertSame('47250a973d1b88d9445f94db4ef2c97a', md5($contents));
191         $this->assertSame(0, $curl->get_errno());
193         $curl = new curl();
194         $tofile = "$CFG->tempdir/test.html";
195         @unlink($tofile);
196         $fp = fopen($tofile, 'w');
197         $result = $curl->get($testhtml, array(), array('CURLOPT_FILE'=>$fp));
198         $this->assertTrue($result);
199         fclose($fp);
200         $this->assertFileExists($tofile);
201         $this->assertSame($contents, file_get_contents($tofile));
202         @unlink($tofile);
204         $curl = new curl();
205         $tofile = "$CFG->tempdir/test.html";
206         @unlink($tofile);
207         $result = $curl->download_one($testhtml, array(), array('filepath'=>$tofile));
208         $this->assertTrue($result);
209         $this->assertFileExists($tofile);
210         $this->assertSame($contents, file_get_contents($tofile));
211         @unlink($tofile);
213         // Test 404 request.
214         $curl = new curl();
215         $contents = $curl->get($this->getExternalTestFileUrl('/i.do.not.exist'));
216         $response = $curl->getResponse();
217         $this->assertSame('404 Not Found', reset($response));
218         $this->assertSame(0, $curl->get_errno());
219     }
221     /**
222      * Test a curl basic request with security enabled.
223      */
224     public function test_curl_basics_with_security_helper() {
225         $this->resetAfterTest();
227         // Test a request with a basic hostname filter applied.
228         $testhtml = $this->getExternalTestFileUrl('/test.html');
229         $url = new moodle_url($testhtml);
230         $host = $url->get_host();
231         set_config('curlsecurityblockedhosts', $host); // Blocks $host.
233         // Create curl with the default security enabled. We expect this to be blocked.
234         $curl = new curl();
235         $contents = $curl->get($testhtml);
236         $expected = $curl->get_security()->get_blocked_url_string();
237         $this->assertSame($expected, $contents);
238         $this->assertSame(0, $curl->get_errno());
240         // Now, create a curl using the 'ignoresecurity' override.
241         // We expect this request to pass, despite the admin setting having been set earlier.
242         $curl = new curl(['ignoresecurity' => true]);
243         $contents = $curl->get($testhtml);
244         $this->assertSame('47250a973d1b88d9445f94db4ef2c97a', md5($contents));
245         $this->assertSame(0, $curl->get_errno());
247         // Now, try injecting a mock security helper into curl. This will override the default helper.
248         $mockhelper = $this->getMockBuilder('\core\files\curl_security_helper')->getMock();
250         // Make the mock return a different string.
251         $mockhelper->expects($this->any())->method('get_blocked_url_string')->will($this->returnValue('You shall not pass'));
253         // And make the mock security helper block all URLs. This helper instance doesn't care about config.
254         $mockhelper->expects($this->any())->method('url_is_blocked')->will($this->returnValue(true));
256         $curl = new curl(['securityhelper' => $mockhelper]);
257         $contents = $curl->get($testhtml);
258         $this->assertSame('You shall not pass', $curl->get_security()->get_blocked_url_string());
259         $this->assertSame($curl->get_security()->get_blocked_url_string(), $contents);
260     }
262     public function test_curl_redirects() {
263         global $CFG;
265         // Test full URL redirects.
266         $testurl = $this->getExternalTestFileUrl('/test_redir.php');
268         $curl = new curl();
269         $contents = $curl->get("$testurl?redir=2", array(), array('CURLOPT_MAXREDIRS'=>2));
270         $response = $curl->getResponse();
271         $this->assertSame('200 OK', reset($response));
272         $this->assertSame(0, $curl->get_errno());
273         $this->assertSame(2, $curl->info['redirect_count']);
274         $this->assertSame('done', $contents);
276         $curl = new curl();
277         $curl->emulateredirects = true;
278         $contents = $curl->get("$testurl?redir=2", array(), array('CURLOPT_MAXREDIRS'=>2));
279         $response = $curl->getResponse();
280         $this->assertSame('200 OK', reset($response));
281         $this->assertSame(0, $curl->get_errno());
282         $this->assertSame(2, $curl->info['redirect_count']);
283         $this->assertSame('done', $contents);
285         // This test was failing for people behind Squid proxies. Squid does not
286         // fully support HTTP 1.1, so converts things to HTTP 1.0, where the name
287         // of the status code is different.
288         reset($response);
289         if (key($response) === 'HTTP/1.0') {
290             $responsecode302 = '302 Moved Temporarily';
291         } else {
292             $responsecode302 = '302 Found';
293         }
295         $curl = new curl();
296         $contents = $curl->get("$testurl?redir=3", array(), array('CURLOPT_FOLLOWLOCATION'=>0));
297         $response = $curl->getResponse();
298         $this->assertSame($responsecode302, reset($response));
299         $this->assertSame(0, $curl->get_errno());
300         $this->assertSame(302, $curl->info['http_code']);
301         $this->assertSame('', $contents);
303         $curl = new curl();
304         $curl->emulateredirects = true;
305         $contents = $curl->get("$testurl?redir=3", array(), array('CURLOPT_FOLLOWLOCATION'=>0));
306         $response = $curl->getResponse();
307         $this->assertSame($responsecode302, reset($response));
308         $this->assertSame(0, $curl->get_errno());
309         $this->assertSame(302, $curl->info['http_code']);
310         $this->assertSame('', $contents);
312         $curl = new curl();
313         $contents = $curl->get("$testurl?redir=2", array(), array('CURLOPT_MAXREDIRS'=>1));
314         $this->assertSame(CURLE_TOO_MANY_REDIRECTS, $curl->get_errno());
315         $this->assertNotEmpty($contents);
317         $curl = new curl();
318         $curl->emulateredirects = true;
319         $contents = $curl->get("$testurl?redir=2", array(), array('CURLOPT_MAXREDIRS'=>1));
320         $this->assertSame(CURLE_TOO_MANY_REDIRECTS, $curl->get_errno());
321         $this->assertNotEmpty($contents);
323         $curl = new curl();
324         $tofile = "$CFG->tempdir/test.html";
325         @unlink($tofile);
326         $fp = fopen($tofile, 'w');
327         $result = $curl->get("$testurl?redir=1", array(), array('CURLOPT_FILE'=>$fp));
328         $this->assertTrue($result);
329         fclose($fp);
330         $this->assertFileExists($tofile);
331         $this->assertSame('done', file_get_contents($tofile));
332         @unlink($tofile);
334         $curl = new curl();
335         $curl->emulateredirects = true;
336         $tofile = "$CFG->tempdir/test.html";
337         @unlink($tofile);
338         $fp = fopen($tofile, 'w');
339         $result = $curl->get("$testurl?redir=1", array(), array('CURLOPT_FILE'=>$fp));
340         $this->assertTrue($result);
341         fclose($fp);
342         $this->assertFileExists($tofile);
343         $this->assertSame('done', file_get_contents($tofile));
344         @unlink($tofile);
346         $curl = new curl();
347         $tofile = "$CFG->tempdir/test.html";
348         @unlink($tofile);
349         $result = $curl->download_one("$testurl?redir=1", array(), array('filepath'=>$tofile));
350         $this->assertTrue($result);
351         $this->assertFileExists($tofile);
352         $this->assertSame('done', file_get_contents($tofile));
353         @unlink($tofile);
355         $curl = new curl();
356         $curl->emulateredirects = true;
357         $tofile = "$CFG->tempdir/test.html";
358         @unlink($tofile);
359         $result = $curl->download_one("$testurl?redir=1", array(), array('filepath'=>$tofile));
360         $this->assertTrue($result);
361         $this->assertFileExists($tofile);
362         $this->assertSame('done', file_get_contents($tofile));
363         @unlink($tofile);
364     }
366     public function test_curl_relative_redirects() {
367         // Test relative location redirects.
368         $testurl = $this->getExternalTestFileUrl('/test_relative_redir.php');
370         $curl = new curl();
371         $contents = $curl->get($testurl);
372         $response = $curl->getResponse();
373         $this->assertSame('200 OK', reset($response));
374         $this->assertSame(0, $curl->get_errno());
375         $this->assertSame(1, $curl->info['redirect_count']);
376         $this->assertSame('done', $contents);
378         $curl = new curl();
379         $curl->emulateredirects = true;
380         $contents = $curl->get($testurl);
381         $response = $curl->getResponse();
382         $this->assertSame('200 OK', reset($response));
383         $this->assertSame(0, $curl->get_errno());
384         $this->assertSame(1, $curl->info['redirect_count']);
385         $this->assertSame('done', $contents);
387         // Test different redirect types.
388         $testurl = $this->getExternalTestFileUrl('/test_relative_redir.php');
390         $curl = new curl();
391         $contents = $curl->get("$testurl?type=301");
392         $response = $curl->getResponse();
393         $this->assertSame('200 OK', reset($response));
394         $this->assertSame(0, $curl->get_errno());
395         $this->assertSame(1, $curl->info['redirect_count']);
396         $this->assertSame('done', $contents);
398         $curl = new curl();
399         $curl->emulateredirects = true;
400         $contents = $curl->get("$testurl?type=301");
401         $response = $curl->getResponse();
402         $this->assertSame('200 OK', reset($response));
403         $this->assertSame(0, $curl->get_errno());
404         $this->assertSame(1, $curl->info['redirect_count']);
405         $this->assertSame('done', $contents);
407         $curl = new curl();
408         $contents = $curl->get("$testurl?type=302");
409         $response = $curl->getResponse();
410         $this->assertSame('200 OK', reset($response));
411         $this->assertSame(0, $curl->get_errno());
412         $this->assertSame(1, $curl->info['redirect_count']);
413         $this->assertSame('done', $contents);
415         $curl = new curl();
416         $curl->emulateredirects = true;
417         $contents = $curl->get("$testurl?type=302");
418         $response = $curl->getResponse();
419         $this->assertSame('200 OK', reset($response));
420         $this->assertSame(0, $curl->get_errno());
421         $this->assertSame(1, $curl->info['redirect_count']);
422         $this->assertSame('done', $contents);
424         $curl = new curl();
425         $contents = $curl->get("$testurl?type=303");
426         $response = $curl->getResponse();
427         $this->assertSame('200 OK', reset($response));
428         $this->assertSame(0, $curl->get_errno());
429         $this->assertSame(1, $curl->info['redirect_count']);
430         $this->assertSame('done', $contents);
432         $curl = new curl();
433         $curl->emulateredirects = true;
434         $contents = $curl->get("$testurl?type=303");
435         $response = $curl->getResponse();
436         $this->assertSame('200 OK', reset($response));
437         $this->assertSame(0, $curl->get_errno());
438         $this->assertSame(1, $curl->info['redirect_count']);
439         $this->assertSame('done', $contents);
441         $curl = new curl();
442         $contents = $curl->get("$testurl?type=307");
443         $response = $curl->getResponse();
444         $this->assertSame('200 OK', reset($response));
445         $this->assertSame(0, $curl->get_errno());
446         $this->assertSame(1, $curl->info['redirect_count']);
447         $this->assertSame('done', $contents);
449         $curl = new curl();
450         $curl->emulateredirects = true;
451         $contents = $curl->get("$testurl?type=307");
452         $response = $curl->getResponse();
453         $this->assertSame('200 OK', reset($response));
454         $this->assertSame(0, $curl->get_errno());
455         $this->assertSame(1, $curl->info['redirect_count']);
456         $this->assertSame('done', $contents);
458         $curl = new curl();
459         $contents = $curl->get("$testurl?type=308");
460         $response = $curl->getResponse();
461         $this->assertSame('200 OK', reset($response));
462         $this->assertSame(0, $curl->get_errno());
463         $this->assertSame(1, $curl->info['redirect_count']);
464         $this->assertSame('done', $contents);
466         $curl = new curl();
467         $curl->emulateredirects = true;
468         $contents = $curl->get("$testurl?type=308");
469         $response = $curl->getResponse();
470         $this->assertSame('200 OK', reset($response));
471         $this->assertSame(0, $curl->get_errno());
472         $this->assertSame(1, $curl->info['redirect_count']);
473         $this->assertSame('done', $contents);
475     }
477     public function test_curl_proxybypass() {
478         global $CFG;
479         $testurl = $this->getExternalTestFileUrl('/test.html');
481         $oldproxy = $CFG->proxyhost;
482         $oldproxybypass = $CFG->proxybypass;
484         // Test without proxy bypass and inaccessible proxy.
485         $CFG->proxyhost = 'i.do.not.exist';
486         $CFG->proxybypass = '';
487         $curl = new curl();
488         $contents = $curl->get($testurl);
489         $this->assertNotEquals(0, $curl->get_errno());
490         $this->assertNotEquals('47250a973d1b88d9445f94db4ef2c97a', md5($contents));
492         // Test with proxy bypass.
493         $testurlhost = parse_url($testurl, PHP_URL_HOST);
494         $CFG->proxybypass = $testurlhost;
495         $curl = new curl();
496         $contents = $curl->get($testurl);
497         $this->assertSame(0, $curl->get_errno());
498         $this->assertSame('47250a973d1b88d9445f94db4ef2c97a', md5($contents));
500         $CFG->proxyhost = $oldproxy;
501         $CFG->proxybypass = $oldproxybypass;
502     }
504     /**
505      * Test that duplicate lines in the curl header are removed.
506      */
507     public function test_duplicate_curl_header() {
508         $testurl = $this->getExternalTestFileUrl('/test_post.php');
510         $curl = new curl();
511         $headerdata = 'Accept: application/json';
512         $header = [$headerdata, $headerdata];
513         $this->assertCount(2, $header);
514         $curl->setHeader($header);
515         $this->assertCount(1, $curl->header);
516         $this->assertEquals($headerdata, $curl->header[0]);
517     }
519     public function test_curl_post() {
520         $testurl = $this->getExternalTestFileUrl('/test_post.php');
522         // Test post request.
523         $curl = new curl();
524         $contents = $curl->post($testurl, 'data=moodletest');
525         $response = $curl->getResponse();
526         $this->assertSame('200 OK', reset($response));
527         $this->assertSame(0, $curl->get_errno());
528         $this->assertSame('OK', $contents);
530         // Test 100 requests.
531         $curl = new curl();
532         $curl->setHeader('Expect: 100-continue');
533         $contents = $curl->post($testurl, 'data=moodletest');
534         $response = $curl->getResponse();
535         $this->assertSame('200 OK', reset($response));
536         $this->assertSame(0, $curl->get_errno());
537         $this->assertSame('OK', $contents);
538     }
540     public function test_curl_file() {
541         $this->resetAfterTest();
542         $testurl = $this->getExternalTestFileUrl('/test_file.php');
544         $fs = get_file_storage();
545         $filerecord = array(
546             'contextid' => context_system::instance()->id,
547             'component' => 'test',
548             'filearea' => 'curl_post',
549             'itemid' => 0,
550             'filepath' => '/',
551             'filename' => 'test.txt'
552         );
553         $teststring = 'moodletest';
554         $testfile = $fs->create_file_from_string($filerecord, $teststring);
556         // Test post with file.
557         $data = array('testfile' => $testfile);
558         $curl = new curl();
559         $contents = $curl->post($testurl, $data);
560         $this->assertSame('OK', $contents);
561     }
563     public function test_curl_file_name() {
564         $this->resetAfterTest();
565         $testurl = $this->getExternalTestFileUrl('/test_file_name.php');
567         $fs = get_file_storage();
568         $filerecord = array(
569             'contextid' => context_system::instance()->id,
570             'component' => 'test',
571             'filearea' => 'curl_post',
572             'itemid' => 0,
573             'filepath' => '/',
574             'filename' => 'test.txt'
575         );
576         $teststring = 'moodletest';
577         $testfile = $fs->create_file_from_string($filerecord, $teststring);
579         // Test post with file.
580         $data = array('testfile' => $testfile);
581         $curl = new curl();
582         $contents = $curl->post($testurl, $data);
583         $this->assertSame('OK', $contents);
584     }
586     public function test_curl_protocols() {
588         // HTTP and HTTPS requests were verified in previous requests. Now check
589         // that we can selectively disable some protocols.
590         $curl = new curl();
592         // Other protocols than HTTP(S) are disabled by default.
593         $testurl = 'file:///';
594         $curl->get($testurl);
595         $this->assertNotEmpty($curl->error);
596         $this->assertEquals(CURLE_UNSUPPORTED_PROTOCOL, $curl->errno);
598         $testurl = 'ftp://nowhere';
599         $curl->get($testurl);
600         $this->assertNotEmpty($curl->error);
601         $this->assertEquals(CURLE_UNSUPPORTED_PROTOCOL, $curl->errno);
603         $testurl = 'telnet://somewhere';
604         $curl->get($testurl);
605         $this->assertNotEmpty($curl->error);
606         $this->assertEquals(CURLE_UNSUPPORTED_PROTOCOL, $curl->errno);
608         // Protocols are also disabled during redirections.
609         $testurl = $this->getExternalTestFileUrl('/test_redir_proto.php');
610         $curl->get($testurl, array('proto' => 'file'));
611         $this->assertNotEmpty($curl->error);
612         $this->assertEquals(CURLE_UNSUPPORTED_PROTOCOL, $curl->errno);
614         $testurl = $this->getExternalTestFileUrl('/test_redir_proto.php');
615         $curl->get($testurl, array('proto' => 'ftp'));
616         $this->assertNotEmpty($curl->error);
617         $this->assertEquals(CURLE_UNSUPPORTED_PROTOCOL, $curl->errno);
619         $testurl = $this->getExternalTestFileUrl('/test_redir_proto.php');
620         $curl->get($testurl, array('proto' => 'telnet'));
621         $this->assertNotEmpty($curl->error);
622         $this->assertEquals(CURLE_UNSUPPORTED_PROTOCOL, $curl->errno);
623     }
625     /**
626      * Testing prepare draft area
627      *
628      * @copyright 2012 Dongsheng Cai {@link http://dongsheng.org}
629      * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
630      */
631     public function test_prepare_draft_area() {
632         global $USER, $DB;
634         $this->resetAfterTest(true);
636         $generator = $this->getDataGenerator();
637         $user = $generator->create_user();
638         $usercontext = context_user::instance($user->id);
639         $USER = $DB->get_record('user', array('id'=>$user->id));
641         $repositorypluginname = 'user';
643         $args = array();
644         $args['type'] = $repositorypluginname;
645         $repos = repository::get_instances($args);
646         $userrepository = reset($repos);
647         $this->assertInstanceOf('repository', $userrepository);
649         $fs = get_file_storage();
651         $syscontext = context_system::instance();
652         $component = 'core';
653         $filearea  = 'unittest';
654         $itemid    = 0;
655         $filepath  = '/';
656         $filename  = 'test.txt';
657         $sourcefield = 'Copyright stuff';
659         $filerecord = array(
660             'contextid' => $syscontext->id,
661             'component' => $component,
662             'filearea'  => $filearea,
663             'itemid'    => $itemid,
664             'filepath'  => $filepath,
665             'filename'  => $filename,
666             'source'    => $sourcefield,
667         );
668         $ref = $fs->pack_reference($filerecord);
669         $originalfile = $fs->create_file_from_string($filerecord, 'Test content');
670         $fileid = $originalfile->get_id();
671         $this->assertInstanceOf('stored_file', $originalfile);
673         // Create a user private file.
674         $userfilerecord = new stdClass;
675         $userfilerecord->contextid = $usercontext->id;
676         $userfilerecord->component = 'user';
677         $userfilerecord->filearea  = 'private';
678         $userfilerecord->itemid    = 0;
679         $userfilerecord->filepath  = '/';
680         $userfilerecord->filename  = 'userfile.txt';
681         $userfilerecord->source    = 'test';
682         $userfile = $fs->create_file_from_string($userfilerecord, 'User file content');
683         $userfileref = $fs->pack_reference($userfilerecord);
685         $filerefrecord = clone((object)$filerecord);
686         $filerefrecord->filename = 'testref.txt';
688         // Create a file reference.
689         $fileref = $fs->create_file_from_reference($filerefrecord, $userrepository->id, $userfileref);
690         $this->assertInstanceOf('stored_file', $fileref);
691         $this->assertEquals($userrepository->id, $fileref->get_repository_id());
692         $this->assertSame($userfile->get_contenthash(), $fileref->get_contenthash());
693         $this->assertEquals($userfile->get_filesize(), $fileref->get_filesize());
694         $this->assertRegExp('#' . $userfile->get_filename(). '$#', $fileref->get_reference_details());
696         $draftitemid = 0;
697         file_prepare_draft_area($draftitemid, $syscontext->id, $component, $filearea, $itemid);
699         $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid);
700         $this->assertCount(3, $draftfiles);
702         $draftfile = $fs->get_file($usercontext->id, 'user', 'draft', $draftitemid, $filepath, $filename);
703         $source = unserialize($draftfile->get_source());
704         $this->assertSame($ref, $source->original);
705         $this->assertSame($sourcefield, $source->source);
707         $draftfileref = $fs->get_file($usercontext->id, 'user', 'draft', $draftitemid, $filepath, $filerefrecord->filename);
708         $this->assertInstanceOf('stored_file', $draftfileref);
709         $this->assertTrue($draftfileref->is_external_file());
711         // Change some information.
712         $author = 'Dongsheng Cai';
713         $draftfile->set_author($author);
714         $newsourcefield = 'Get from Flickr';
715         $license = 'GPLv3';
716         $draftfile->set_license($license);
717         // If you want to really just change source field, do this.
718         $source = unserialize($draftfile->get_source());
719         $newsourcefield = 'From flickr';
720         $source->source = $newsourcefield;
721         $draftfile->set_source(serialize($source));
723         // Save changed file.
724         file_save_draft_area_files($draftitemid, $syscontext->id, $component, $filearea, $itemid);
726         $file = $fs->get_file($syscontext->id, $component, $filearea, $itemid, $filepath, $filename);
728         // Make sure it's the original file id.
729         $this->assertEquals($fileid, $file->get_id());
730         $this->assertInstanceOf('stored_file', $file);
731         $this->assertSame($author, $file->get_author());
732         $this->assertSame($license, $file->get_license());
733         $this->assertEquals($newsourcefield, $file->get_source());
734     }
736     /**
737      * Testing deleting original files.
738      *
739      * @copyright 2012 Dongsheng Cai {@link http://dongsheng.org}
740      * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
741      */
742     public function test_delete_original_file_from_draft() {
743         global $USER, $DB;
745         $this->resetAfterTest(true);
747         $generator = $this->getDataGenerator();
748         $user = $generator->create_user();
749         $usercontext = context_user::instance($user->id);
750         $USER = $DB->get_record('user', array('id'=>$user->id));
752         $repositorypluginname = 'user';
754         $args = array();
755         $args['type'] = $repositorypluginname;
756         $repos = repository::get_instances($args);
757         $userrepository = reset($repos);
758         $this->assertInstanceOf('repository', $userrepository);
760         $fs = get_file_storage();
761         $syscontext = context_system::instance();
763         $filecontent = 'User file content';
765         // Create a user private file.
766         $userfilerecord = new stdClass;
767         $userfilerecord->contextid = $usercontext->id;
768         $userfilerecord->component = 'user';
769         $userfilerecord->filearea  = 'private';
770         $userfilerecord->itemid    = 0;
771         $userfilerecord->filepath  = '/';
772         $userfilerecord->filename  = 'userfile.txt';
773         $userfilerecord->source    = 'test';
774         $userfile = $fs->create_file_from_string($userfilerecord, $filecontent);
775         $userfileref = $fs->pack_reference($userfilerecord);
776         $contenthash = $userfile->get_contenthash();
778         $filerecord = array(
779             'contextid' => $syscontext->id,
780             'component' => 'core',
781             'filearea'  => 'phpunit',
782             'itemid'    => 0,
783             'filepath'  => '/',
784             'filename'  => 'test.txt',
785         );
786         // Create a file reference.
787         $fileref = $fs->create_file_from_reference($filerecord, $userrepository->id, $userfileref);
788         $this->assertInstanceOf('stored_file', $fileref);
789         $this->assertEquals($userrepository->id, $fileref->get_repository_id());
790         $this->assertSame($userfile->get_contenthash(), $fileref->get_contenthash());
791         $this->assertEquals($userfile->get_filesize(), $fileref->get_filesize());
792         $this->assertRegExp('#' . $userfile->get_filename(). '$#', $fileref->get_reference_details());
794         $draftitemid = 0;
795         file_prepare_draft_area($draftitemid, $usercontext->id, 'user', 'private', 0);
796         $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid);
797         $this->assertCount(2, $draftfiles);
798         $draftfile = $fs->get_file($usercontext->id, 'user', 'draft', $draftitemid, $userfilerecord->filepath, $userfilerecord->filename);
799         $draftfile->delete();
800         // Save changed file.
801         file_save_draft_area_files($draftitemid, $usercontext->id, 'user', 'private', 0);
803         // The file reference should be a regular moodle file now.
804         $fileref = $fs->get_file($syscontext->id, 'core', 'phpunit', 0, '/', 'test.txt');
805         $this->assertFalse($fileref->is_external_file());
806         $this->assertSame($contenthash, $fileref->get_contenthash());
807         $this->assertEquals($filecontent, $fileref->get_content());
808     }
810     /**
811      * Tests the strip_double_headers function in the curl class.
812      */
813     public function test_curl_strip_double_headers() {
814         // Example from issue tracker.
815         $mdl30648example = <<<EOF
816 HTTP/1.0 407 Proxy Authentication Required
817 Server: squid/2.7.STABLE9
818 Date: Thu, 08 Dec 2011 14:44:33 GMT
819 Content-Type: text/html
820 Content-Length: 1275
821 X-Squid-Error: ERR_CACHE_ACCESS_DENIED 0
822 Proxy-Authenticate: Basic realm="Squid proxy-caching web server"
823 X-Cache: MISS from homer.lancs.ac.uk
824 X-Cache-Lookup: NONE from homer.lancs.ac.uk:3128
825 Via: 1.0 homer.lancs.ac.uk:3128 (squid/2.7.STABLE9)
826 Connection: close
828 HTTP/1.0 200 OK
829 Server: Apache
830 X-Lb-Nocache: true
831 Cache-Control: private, max-age=15, no-transform
832 ETag: "4d69af5d8ba873ea9192c489e151bd7b"
833 Content-Type: text/html
834 Date: Thu, 08 Dec 2011 14:44:53 GMT
835 Set-Cookie: BBC-UID=c4de2e109c8df6a51de627cee11b214bd4fb6054a030222488317afb31b343360MoodleBot/1.0; expires=Mon, 07-Dec-15 14:44:53 GMT; path=/; domain=bbc.co.uk
836 X-Cache-Action: MISS
837 X-Cache-Age: 0
838 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
839 X-Cache: MISS from ww
841 <html>...
842 EOF;
843         $mdl30648expected = <<<EOF
844 HTTP/1.0 200 OK
845 Server: Apache
846 X-Lb-Nocache: true
847 Cache-Control: private, max-age=15, no-transform
848 ETag: "4d69af5d8ba873ea9192c489e151bd7b"
849 Content-Type: text/html
850 Date: Thu, 08 Dec 2011 14:44:53 GMT
851 Set-Cookie: BBC-UID=c4de2e109c8df6a51de627cee11b214bd4fb6054a030222488317afb31b343360MoodleBot/1.0; expires=Mon, 07-Dec-15 14:44:53 GMT; path=/; domain=bbc.co.uk
852 X-Cache-Action: MISS
853 X-Cache-Age: 0
854 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
855 X-Cache: MISS from ww
857 <html>...
858 EOF;
859         // For HTTP, replace the \n with \r\n.
860         $mdl30648example = preg_replace("~(?!<\r)\n~", "\r\n", $mdl30648example);
861         $mdl30648expected = preg_replace("~(?!<\r)\n~", "\r\n", $mdl30648expected);
863         // Test stripping works OK.
864         $this->assertSame($mdl30648expected, curl::strip_double_headers($mdl30648example));
865         // Test it does nothing to the 'plain' data.
866         $this->assertSame($mdl30648expected, curl::strip_double_headers($mdl30648expected));
868         // Example from OU proxy.
869         $httpsexample = <<<EOF
870 HTTP/1.0 200 Connection established
872 HTTP/1.1 200 OK
873 Date: Fri, 22 Feb 2013 17:14:23 GMT
874 Server: Apache/2
875 X-Powered-By: PHP/5.3.3-7+squeeze14
876 Content-Type: text/xml
877 Connection: close
878 Content-Encoding: gzip
879 Transfer-Encoding: chunked
881 <?xml version="1.0" encoding="ISO-8859-1" ?>
882 <rss version="2.0">...
883 EOF;
884         $httpsexpected = <<<EOF
885 HTTP/1.1 200 OK
886 Date: Fri, 22 Feb 2013 17:14:23 GMT
887 Server: Apache/2
888 X-Powered-By: PHP/5.3.3-7+squeeze14
889 Content-Type: text/xml
890 Connection: close
891 Content-Encoding: gzip
892 Transfer-Encoding: chunked
894 <?xml version="1.0" encoding="ISO-8859-1" ?>
895 <rss version="2.0">...
896 EOF;
897         // For HTTP, replace the \n with \r\n.
898         $httpsexample = preg_replace("~(?!<\r)\n~", "\r\n", $httpsexample);
899         $httpsexpected = preg_replace("~(?!<\r)\n~", "\r\n", $httpsexpected);
901         // Test stripping works OK.
902         $this->assertSame($httpsexpected, curl::strip_double_headers($httpsexample));
903         // Test it does nothing to the 'plain' data.
904         $this->assertSame($httpsexpected, curl::strip_double_headers($httpsexpected));
905     }
907     /**
908      * Tests the get_mimetype_description function.
909      */
910     public function test_get_mimetype_description() {
911         $this->resetAfterTest();
913         // Test example type (.doc).
914         $this->assertEquals(get_string('application/msword', 'mimetypes'),
915                 get_mimetype_description(array('filename' => 'test.doc')));
917         // Test an unknown file type.
918         $this->assertEquals(get_string('document/unknown', 'mimetypes'),
919                 get_mimetype_description(array('filename' => 'test.frog')));
921         // Test a custom filetype with no lang string specified.
922         core_filetypes::add_type('frog', 'application/x-frog', 'document');
923         $this->assertEquals('application/x-frog',
924                 get_mimetype_description(array('filename' => 'test.frog')));
926         // Test custom description.
927         core_filetypes::update_type('frog', 'frog', 'application/x-frog', 'document',
928                 array(), '', 'Froggy file');
929         $this->assertEquals('Froggy file',
930                 get_mimetype_description(array('filename' => 'test.frog')));
932         // Test custom description using multilang filter.
933         filter_manager::reset_caches();
934         filter_set_global_state('multilang', TEXTFILTER_ON);
935         filter_set_applies_to_strings('multilang', true);
936         core_filetypes::update_type('frog', 'frog', 'application/x-frog', 'document',
937                 array(), '', '<span lang="en" class="multilang">Green amphibian</span>' .
938                 '<span lang="fr" class="multilang">Amphibian vert</span>');
939         $this->assertEquals('Green amphibian',
940                 get_mimetype_description(array('filename' => 'test.frog')));
941     }
943     /**
944      * Tests the get_mimetypes_array function.
945      */
946     public function test_get_mimetypes_array() {
947         $mimeinfo = get_mimetypes_array();
949         // Test example MIME type (doc).
950         $this->assertEquals('application/msword', $mimeinfo['doc']['type']);
951         $this->assertEquals('document', $mimeinfo['doc']['icon']);
952         $this->assertEquals(array('document'), $mimeinfo['doc']['groups']);
953         $this->assertFalse(isset($mimeinfo['doc']['string']));
954         $this->assertFalse(isset($mimeinfo['doc']['defaulticon']));
955         $this->assertFalse(isset($mimeinfo['doc']['customdescription']));
957         // Check the less common fields using other examples.
958         $this->assertEquals('image', $mimeinfo['png']['string']);
959         $this->assertEquals(true, $mimeinfo['txt']['defaulticon']);
960     }
962     /**
963      * Tests for get_mimetype_for_sending function.
964      */
965     public function test_get_mimetype_for_sending() {
966         // Without argument.
967         $this->assertEquals('application/octet-stream', get_mimetype_for_sending());
969         // Argument is null.
970         $this->assertEquals('application/octet-stream', get_mimetype_for_sending(null));
972         // Filename having no extension.
973         $this->assertEquals('application/octet-stream', get_mimetype_for_sending('filenamewithoutextension'));
975         // Test using the extensions listed from the get_mimetypes_array function.
976         $mimetypes = get_mimetypes_array();
977         foreach ($mimetypes as $ext => $info) {
978             if ($ext === 'xxx') {
979                 $this->assertEquals('application/octet-stream', get_mimetype_for_sending('SampleFile.' . $ext));
980             } else {
981                 $this->assertEquals($info['type'], get_mimetype_for_sending('SampleFile.' . $ext));
982             }
983         }
984     }
986     /**
987      * Test curl agent settings.
988      */
989     public function test_curl_useragent() {
990         $curl = new testable_curl();
991         $options = $curl->get_options();
992         $this->assertNotEmpty($options);
994         $curl->call_apply_opt($options);
995         $this->assertTrue(in_array('User-Agent: MoodleBot/1.0', $curl->header));
996         $this->assertFalse(in_array('User-Agent: Test/1.0', $curl->header));
998         $options['CURLOPT_USERAGENT'] = 'Test/1.0';
999         $curl->call_apply_opt($options);
1000         $this->assertTrue(in_array('User-Agent: Test/1.0', $curl->header));
1001         $this->assertFalse(in_array('User-Agent: MoodleBot/1.0', $curl->header));
1003         $curl->set_option('CURLOPT_USERAGENT', 'AnotherUserAgent/1.0');
1004         $curl->call_apply_opt();
1005         $this->assertTrue(in_array('User-Agent: AnotherUserAgent/1.0', $curl->header));
1006         $this->assertFalse(in_array('User-Agent: Test/1.0', $curl->header));
1008         $curl->set_option('CURLOPT_USERAGENT', 'AnotherUserAgent/1.1');
1009         $options = $curl->get_options();
1010         $curl->call_apply_opt($options);
1011         $this->assertTrue(in_array('User-Agent: AnotherUserAgent/1.1', $curl->header));
1012         $this->assertFalse(in_array('User-Agent: AnotherUserAgent/1.0', $curl->header));
1014         $curl->unset_option('CURLOPT_USERAGENT');
1015         $curl->call_apply_opt();
1016         $this->assertTrue(in_array('User-Agent: MoodleBot/1.0', $curl->header));
1018         // Finally, test it via exttests, to ensure the agent is sent properly.
1019         // Matching.
1020         $testurl = $this->getExternalTestFileUrl('/test_agent.php');
1021         $extcurl = new curl();
1022         $contents = $extcurl->get($testurl, array(), array('CURLOPT_USERAGENT' => 'AnotherUserAgent/1.2'));
1023         $response = $extcurl->getResponse();
1024         $this->assertSame('200 OK', reset($response));
1025         $this->assertSame(0, $extcurl->get_errno());
1026         $this->assertSame('OK', $contents);
1027         // Not matching.
1028         $contents = $extcurl->get($testurl, array(), array('CURLOPT_USERAGENT' => 'NonMatchingUserAgent/1.2'));
1029         $response = $extcurl->getResponse();
1030         $this->assertSame('200 OK', reset($response));
1031         $this->assertSame(0, $extcurl->get_errno());
1032         $this->assertSame('', $contents);
1033     }
1035     /**
1036      * Test file_rewrite_pluginfile_urls.
1037      */
1038     public function test_file_rewrite_pluginfile_urls() {
1040         $syscontext = context_system::instance();
1041         $originaltext = 'Fake test with an image <img src="@@PLUGINFILE@@/image.png">';
1043         // Do the rewrite.
1044         $finaltext = file_rewrite_pluginfile_urls($originaltext, 'pluginfile.php', $syscontext->id, 'user', 'private', 0);
1045         $this->assertContains("pluginfile.php", $finaltext);
1047         // Now undo.
1048         $options = array('reverse' => true);
1049         $finaltext = file_rewrite_pluginfile_urls($finaltext, 'pluginfile.php', $syscontext->id, 'user', 'private', 0, $options);
1051         // Compare the final text is the same that the original.
1052         $this->assertEquals($originaltext, $finaltext);
1053     }
1055     /**
1056      * Helpter function to create draft files
1057      *
1058      * @param  array  $filedata data for the file record (to not use defaults)
1059      * @return stored_file the stored file instance
1060      */
1061     public static function create_draft_file($filedata = array()) {
1062         global $USER;
1064         self::setAdminUser();
1065         $fs = get_file_storage();
1067         $filerecord = array(
1068             'component' => 'user',
1069             'filearea'  => 'draft',
1070             'itemid'    => isset($filedata['itemid']) ? $filedata['itemid'] : file_get_unused_draft_itemid(),
1071             'author'    => isset($filedata['author']) ? $filedata['author'] : fullname($USER),
1072             'filepath'  => isset($filedata['filepath']) ? $filedata['filepath'] : '/',
1073             'filename'  => isset($filedata['filename']) ? $filedata['filename'] : 'file.txt',
1074         );
1076         if (isset($filedata['contextid'])) {
1077             $filerecord['contextid'] = $filedata['contextid'];
1078         } else {
1079             $usercontext = context_user::instance($USER->id);
1080             $filerecord['contextid'] = $usercontext->id;
1081         }
1082         $source = isset($filedata['source']) ? $filedata['source'] : serialize((object)array('source' => 'From string'));
1083         $content = isset($filedata['content']) ? $filedata['content'] : 'some content here';
1085         $file = $fs->create_file_from_string($filerecord, $content);
1086         $file->set_source($source);
1088         return $file;
1089     }
1091     /**
1092      * Test file_merge_files_from_draft_area_into_filearea
1093      */
1094     public function test_file_merge_files_from_draft_area_into_filearea() {
1095         global $USER, $CFG;
1097         $this->resetAfterTest(true);
1098         $this->setAdminUser();
1099         $fs = get_file_storage();
1100         $usercontext = context_user::instance($USER->id);
1102         // Create a draft file.
1103         $filename = 'data.txt';
1104         $filerecord = array(
1105             'filename'  => $filename,
1106         );
1107         $file = self::create_draft_file($filerecord);
1108         $draftitemid = $file->get_itemid();
1110         $maxbytes = $CFG->userquota;
1111         $maxareabytes = $CFG->userquota;
1112         $options = array('subdirs' => 1,
1113                          'maxbytes' => $maxbytes,
1114                          'maxfiles' => -1,
1115                          'areamaxbytes' => $maxareabytes);
1117         // Add new file.
1118         file_merge_files_from_draft_area_into_filearea($draftitemid, $usercontext->id, 'user', 'private', 0, $options);
1120         $files = $fs->get_area_files($usercontext->id, 'user', 'private', 0);
1121         // Directory and file.
1122         $this->assertCount(2, $files);
1123         $found = false;
1124         foreach ($files as $file) {
1125             if (!$file->is_directory()) {
1126                 $found = true;
1127                 $this->assertEquals($filename, $file->get_filename());
1128                 $this->assertEquals('some content here', $file->get_content());
1129             }
1130         }
1131         $this->assertTrue($found);
1133         // Add two more files.
1134         $filerecord = array(
1135             'itemid'  => $draftitemid,
1136             'filename'  => 'second.txt',
1137         );
1138         self::create_draft_file($filerecord);
1139         $filerecord = array(
1140             'itemid'  => $draftitemid,
1141             'filename'  => 'third.txt',
1142         );
1143         $file = self::create_draft_file($filerecord);
1145         file_merge_files_from_draft_area_into_filearea($file->get_itemid(), $usercontext->id, 'user', 'private', 0, $options);
1147         $files = $fs->get_area_files($usercontext->id, 'user', 'private', 0);
1148         $this->assertCount(4, $files);
1150         // Update contents of one file.
1151         $filerecord = array(
1152             'filename'  => 'second.txt',
1153             'content'  => 'new content',
1154         );
1155         $file = self::create_draft_file($filerecord);
1156         file_merge_files_from_draft_area_into_filearea($file->get_itemid(), $usercontext->id, 'user', 'private', 0, $options);
1158         $files = $fs->get_area_files($usercontext->id, 'user', 'private', 0);
1159         $this->assertCount(4, $files);
1160         $found = false;
1161         foreach ($files as $file) {
1162             if ($file->get_filename() == 'second.txt') {
1163                 $found = true;
1164                 $this->assertEquals('new content', $file->get_content());
1165             }
1166         }
1167         $this->assertTrue($found);
1169         // Update author.
1170         // Set different author in the current file.
1171         foreach ($files as $file) {
1172             if ($file->get_filename() == 'second.txt') {
1173                 $file->set_author('Nobody');
1174             }
1175         }
1176         $filerecord = array(
1177             'filename'  => 'second.txt',
1178         );
1179         $file = self::create_draft_file($filerecord);
1181         file_merge_files_from_draft_area_into_filearea($file->get_itemid(), $usercontext->id, 'user', 'private', 0, $options);
1183         $files = $fs->get_area_files($usercontext->id, 'user', 'private', 0);
1184         $this->assertCount(4, $files);
1185         $found = false;
1186         foreach ($files as $file) {
1187             if ($file->get_filename() == 'second.txt') {
1188                 $found = true;
1189                 $this->assertEquals(fullname($USER), $file->get_author());
1190             }
1191         }
1192         $this->assertTrue($found);
1194     }
1196     /**
1197      * Test max area bytes for file_merge_files_from_draft_area_into_filearea
1198      */
1199     public function test_file_merge_files_from_draft_area_into_filearea_max_area_bytes() {
1200         global $USER;
1202         $this->resetAfterTest(true);
1203         $this->setAdminUser();
1204         $fs = get_file_storage();
1206         $file = self::create_draft_file();
1207         $options = array('subdirs' => 1,
1208                          'maxbytes' => 5,
1209                          'maxfiles' => -1,
1210                          'areamaxbytes' => 10);
1212         // Add new file.
1213         file_merge_files_from_draft_area_into_filearea($file->get_itemid(), $file->get_contextid(), 'user', 'private', 0, $options);
1214         $usercontext = context_user::instance($USER->id);
1215         $files = $fs->get_area_files($usercontext->id, 'user', 'private', 0);
1216         $this->assertCount(0, $files);
1217     }
1219     /**
1220      * Test max file bytes for file_merge_files_from_draft_area_into_filearea
1221      */
1222     public function test_file_merge_files_from_draft_area_into_filearea_max_file_bytes() {
1223         global $USER;
1225         $this->resetAfterTest(true);
1226         $this->setAdminUser();
1227         $fs = get_file_storage();
1229         $file = self::create_draft_file();
1230         $options = array('subdirs' => 1,
1231                          'maxbytes' => 1,
1232                          'maxfiles' => -1,
1233                          'areamaxbytes' => 100);
1235         // Add new file.
1236         file_merge_files_from_draft_area_into_filearea($file->get_itemid(), $file->get_contextid(), 'user', 'private', 0, $options);
1237         $usercontext = context_user::instance($USER->id);
1238         // Check we only get the base directory, not a new file.
1239         $files = $fs->get_area_files($usercontext->id, 'user', 'private', 0);
1240         $this->assertCount(1, $files);
1241         $file = array_shift($files);
1242         $this->assertTrue($file->is_directory());
1243     }
1245     /**
1246      * Test max file number for file_merge_files_from_draft_area_into_filearea
1247      */
1248     public function test_file_merge_files_from_draft_area_into_filearea_max_files() {
1249         global $USER;
1251         $this->resetAfterTest(true);
1252         $this->setAdminUser();
1253         $fs = get_file_storage();
1255         $file = self::create_draft_file();
1256         $options = array('subdirs' => 1,
1257                          'maxbytes' => 1000,
1258                          'maxfiles' => 0,
1259                          'areamaxbytes' => 1000);
1261         // Add new file.
1262         file_merge_files_from_draft_area_into_filearea($file->get_itemid(), $file->get_contextid(), 'user', 'private', 0, $options);
1263         $usercontext = context_user::instance($USER->id);
1264         // Check we only get the base directory, not a new file.
1265         $files = $fs->get_area_files($usercontext->id, 'user', 'private', 0);
1266         $this->assertCount(1, $files);
1267         $file = array_shift($files);
1268         $this->assertTrue($file->is_directory());
1269     }
1271     /**
1272      * Test file_get_draft_area_info.
1273      */
1274     public function test_file_get_draft_area_info() {
1275         global $USER;
1277         $this->resetAfterTest(true);
1278         $this->setAdminUser();
1279         $fs = get_file_storage();
1281         $filerecord = array(
1282             'filename'  => 'one.txt',
1283         );
1284         $file = self::create_draft_file($filerecord);
1285         $size = $file->get_filesize();
1286         $draftitemid = $file->get_itemid();
1287         // Add another file.
1288         $filerecord = array(
1289             'itemid'  => $draftitemid,
1290             'filename'  => 'second.txt',
1291         );
1292         $file = self::create_draft_file($filerecord);
1293         $size += $file->get_filesize();
1295         // Create directory.
1296         $usercontext = context_user::instance($USER->id);
1297         $dir = $fs->create_directory($usercontext->id, 'user', 'draft', $draftitemid, '/testsubdir/');
1298         // Add file to directory.
1299         $filerecord = array(
1300             'itemid'  => $draftitemid,
1301             'filename' => 'third.txt',
1302             'filepath' => '/testsubdir/',
1303         );
1304         $file = self::create_draft_file($filerecord);
1305         $size += $file->get_filesize();
1307         $fileinfo = file_get_draft_area_info($draftitemid);
1308         $this->assertEquals(3, $fileinfo['filecount']);
1309         $this->assertEquals($size, $fileinfo['filesize']);
1310         $this->assertEquals(1, $fileinfo['foldercount']);   // Directory created.
1311         $this->assertEquals($size, $fileinfo['filesize_without_references']);
1313         // Now get files from just one folder.
1314         $fileinfo = file_get_draft_area_info($draftitemid, '/testsubdir/');
1315         $this->assertEquals(1, $fileinfo['filecount']);
1316         $this->assertEquals($file->get_filesize(), $fileinfo['filesize']);
1317         $this->assertEquals(0, $fileinfo['foldercount']);   // No subdirectories inside the directory.
1318         $this->assertEquals($file->get_filesize(), $fileinfo['filesize_without_references']);
1320         // Check we get the same results if we call file_get_file_area_info.
1321         $fileinfo = file_get_file_area_info($usercontext->id, 'user', 'draft', $draftitemid);
1322         $this->assertEquals(3, $fileinfo['filecount']);
1323         $this->assertEquals($size, $fileinfo['filesize']);
1324         $this->assertEquals(1, $fileinfo['foldercount']);   // Directory created.
1325         $this->assertEquals($size, $fileinfo['filesize_without_references']);
1326     }
1328     /**
1329      * Test file_get_file_area_info.
1330      */
1331     public function test_file_get_file_area_info() {
1332         global $USER;
1334         $this->resetAfterTest(true);
1335         $this->setAdminUser();
1336         $fs = get_file_storage();
1338         $filerecord = array(
1339             'filename'  => 'one.txt',
1340         );
1341         $file = self::create_draft_file($filerecord);
1342         $size = $file->get_filesize();
1343         $draftitemid = $file->get_itemid();
1344         // Add another file.
1345         $filerecord = array(
1346             'itemid'  => $draftitemid,
1347             'filename'  => 'second.txt',
1348         );
1349         $file = self::create_draft_file($filerecord);
1350         $size += $file->get_filesize();
1352         // Create directory.
1353         $usercontext = context_user::instance($USER->id);
1354         $dir = $fs->create_directory($usercontext->id, 'user', 'draft', $draftitemid, '/testsubdir/');
1355         // Add file to directory.
1356         $filerecord = array(
1357             'itemid'  => $draftitemid,
1358             'filename' => 'third.txt',
1359             'filepath' => '/testsubdir/',
1360         );
1361         $file = self::create_draft_file($filerecord);
1362         $size += $file->get_filesize();
1364         // Add files to user private file area.
1365         $options = array('subdirs' => 1, 'maxfiles' => 3);
1366         file_merge_files_from_draft_area_into_filearea($draftitemid, $file->get_contextid(), 'user', 'private', 0, $options);
1368         $fileinfo = file_get_file_area_info($usercontext->id, 'user', 'private');
1369         $this->assertEquals(3, $fileinfo['filecount']);
1370         $this->assertEquals($size, $fileinfo['filesize']);
1371         $this->assertEquals(1, $fileinfo['foldercount']);   // Directory created.
1372         $this->assertEquals($size, $fileinfo['filesize_without_references']);
1374         // Now get files from just one folder.
1375         $fileinfo = file_get_file_area_info($usercontext->id, 'user', 'private', 0, '/testsubdir/');
1376         $this->assertEquals(1, $fileinfo['filecount']);
1377         $this->assertEquals($file->get_filesize(), $fileinfo['filesize']);
1378         $this->assertEquals(0, $fileinfo['foldercount']);   // No subdirectories inside the directory.
1379         $this->assertEquals($file->get_filesize(), $fileinfo['filesize_without_references']);
1380     }
1382     /**
1383      * Test confirming that draft files not referenced in the editor text are removed.
1384      */
1385     public function test_file_remove_editor_orphaned_files() {
1386         global $USER, $CFG;
1387         $this->resetAfterTest(true);
1388         $this->setAdminUser();
1390         // Create three draft files.
1391         $filerecord = ['filename'  => 'file1.png'];
1392         $file = self::create_draft_file($filerecord);
1393         $draftitemid = $file->get_itemid();
1395         $filerecord['itemid'] = $draftitemid;
1397         $filerecord['filename'] = 'file2.png';
1398         self::create_draft_file($filerecord);
1400         $filerecord['filename'] = 'file 3.png';
1401         self::create_draft_file($filerecord);
1403         // Confirm the user drafts area lists 3 files.
1404         $fs = get_file_storage();
1405         $usercontext = context_user::instance($USER->id);
1406         $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'itemid', 0);
1407         $this->assertCount(3, $draftfiles);
1409         // Now, spoof some editor text content, referencing 2 of the files; one requiring name encoding, one not.
1410         $editor = [
1411             'itemid' => $draftitemid,
1412             'text' => '
1413                 <img src="'.$CFG->wwwroot.'/draftfile.php/'.$usercontext->id.'/user/draft/'.$draftitemid.'/file%203.png" alt="">
1414                 <img src="'.$CFG->wwwroot.'/draftfile.php/'.$usercontext->id.'/user/draft/'.$draftitemid.'/file1.png" alt="">'
1415         ];
1417         // Run the remove orphaned drafts function and confirm that only the referenced files remain in the user drafts.
1418         $expected = ['file1.png', 'file 3.png']; // The drafts we expect will not be removed (are referenced in the online text).
1419         file_remove_editor_orphaned_files($editor);
1420         $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'itemid', 0);
1421         $this->assertCount(2, $draftfiles);
1422         foreach ($draftfiles as $file) {
1423             $this->assertContains($file->get_filename(), $expected);
1424         }
1425     }
1428 /**
1429  * Test-specific class to allow easier testing of curl functions.
1430  *
1431  * @copyright 2015 Dave Cooper
1432  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1433  */
1434 class testable_curl extends curl {
1435     /**
1436      * Accessor for private options array using reflection.
1437      *
1438      * @return array
1439      */
1440     public function get_options() {
1441         // Access to private property.
1442         $rp = new ReflectionProperty('curl', 'options');
1443         $rp->setAccessible(true);
1444         return $rp->getValue($this);
1445     }
1447     /**
1448      * Setter for private options array using reflection.
1449      *
1450      * @param array $options
1451      */
1452     public function set_options($options) {
1453         // Access to private property.
1454         $rp = new ReflectionProperty('curl', 'options');
1455         $rp->setAccessible(true);
1456         $rp->setValue($this, $options);
1457     }
1459     /**
1460      * Setter for individual option.
1461      * @param string $option
1462      * @param string $value
1463      */
1464     public function set_option($option, $value) {
1465         $options = $this->get_options();
1466         $options[$option] = $value;
1467         $this->set_options($options);
1468     }
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     }
1480     /**
1481      * Wrapper to access the private curl::apply_opt() method using reflection.
1482      *
1483      * @param array $options
1484      * @return resource The curl handle
1485      */
1486     public function call_apply_opt($options = null) {
1487         // Access to private method.
1488         $rm = new ReflectionMethod('curl', 'apply_opt');
1489         $rm->setAccessible(true);
1490         $ch = curl_init();
1491         return $rm->invoke($this, $ch, $options);
1492     }