__  __    __   __  _____      _            _          _____ _          _ _ 
 |  \/  |   \ \ / / |  __ \    (_)          | |        / ____| |        | | |
 | \  / |_ __\ V /  | |__) | __ ___   ____ _| |_ ___  | (___ | |__   ___| | |
 | |\/| | '__|> <   |  ___/ '__| \ \ / / _` | __/ _ \  \___ \| '_ \ / _ \ | |
 | |  | | |_ / . \  | |   | |  | |\ V / (_| | ||  __/  ____) | | | |  __/ | |
 |_|  |_|_(_)_/ \_\ |_|   |_|  |_| \_/ \__,_|\__\___| |_____/|_| |_|\___V 2.1
 if you need WebShell for Seo everyday contact me on Telegram
 Telegram Address : @jackleet
        
        
For_More_Tools: Telegram: @jackleet | Bulk Smtp support mail sender | Business Mail Collector | Mail Bouncer All Mail | Bulk Office Mail Validator | Html Letter private



Upload:

Command:

www-data@216.73.216.10: ~ $
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

/**
 * Unit tests for /lib/filelib.php.
 *
 * @package   core
 * @category  test
 * @copyright 2009 Jerome Mouneyrac
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

namespace core;

use core_filetypes;
use curl;
use repository;

defined('MOODLE_INTERNAL') || die();

global $CFG;
require_once($CFG->libdir . '/filelib.php');
require_once($CFG->dirroot . '/repository/lib.php');

/**
 * Unit tests for /lib/filelib.php.
 *
 * @package   core
 * @category  test
 * @copyright 2009 Jerome Mouneyrac
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
final class filelib_test extends \advanced_testcase {
    public function test_format_postdata_for_curlcall(): void {

        // POST params with just simple types.
        $postdatatoconvert = array( 'userid' => 1, 'roleid' => 22, 'name' => 'john');
        $expectedresult = "userid=1&roleid=22&name=john";
        $postdata = format_postdata_for_curlcall($postdatatoconvert);
        $this->assertEquals($expectedresult, $postdata);

        // POST params with a string containing & character.
        $postdatatoconvert = array( 'name' => 'john&emilie', 'roleid' => 22);
        $expectedresult = "name=john%26emilie&roleid=22"; // Urlencode: '%26' => '&'.
        $postdata = format_postdata_for_curlcall($postdatatoconvert);
        $this->assertEquals($expectedresult, $postdata);

        // POST params with an empty value.
        $postdatatoconvert = array( 'name' => null, 'roleid' => 22);
        $expectedresult = "name=&roleid=22";
        $postdata = format_postdata_for_curlcall($postdatatoconvert);
        $this->assertEquals($expectedresult, $postdata);

        // POST params with complex types.
        $postdatatoconvert = array( 'users' => array(
            array(
                'id' => 2,
                'customfields' => array(
                    array
                    (
                        'type' => 'Color',
                        'value' => 'violet'
                    )
                )
            )
        )
        );
        $expectedresult = "users[0][id]=2&users[0][customfields][0][type]=Color&users[0][customfields][0][value]=violet";
        $postdata = format_postdata_for_curlcall($postdatatoconvert);
        $this->assertEquals($expectedresult, $postdata);

        // POST params with other complex types.
        $postdatatoconvert = array ('members' =>
        array(
            array('groupid' => 1, 'userid' => 1)
        , array('groupid' => 1, 'userid' => 2)
        )
        );
        $expectedresult = "members[0][groupid]=1&members[0][userid]=1&members[1][groupid]=1&members[1][userid]=2";
        $postdata = format_postdata_for_curlcall($postdatatoconvert);
        $this->assertEquals($expectedresult, $postdata);
    }

    public function test_download_file_content(): void {
        global $CFG;

        // Test http success first.
        $testhtml = $this->getExternalTestFileUrl('/test.html');

        $contents = download_file_content($testhtml);
        $this->assertSame('47250a973d1b88d9445f94db4ef2c97a', md5($contents));

        $tofile = "$CFG->tempdir/test.html";
        @unlink($tofile);
        $result = download_file_content($testhtml, null, null, false, 300, 20, false, $tofile);
        $this->assertTrue($result);
        $this->assertFileExists($tofile);
        $this->assertSame(file_get_contents($tofile), $contents);
        @unlink($tofile);

        $result = download_file_content($testhtml, null, null, false, 300, 20, false, null, true);
        $this->assertSame($contents, $result);

        $response = download_file_content($testhtml, null, null, true);
        $this->assertInstanceOf('stdClass', $response);
        $this->assertSame('200', $response->status);
        $this->assertTrue(is_array($response->headers));
        $this->assertMatchesRegularExpression('|^HTTP/1\.[01] 200 OK$|', rtrim($response->response_code));
        $this->assertSame($contents, $response->results);
        $this->assertSame('', $response->error);

        // Test https success.
        $testhtml = $this->getExternalTestFileUrl('/test.html', true);

        $contents = download_file_content($testhtml, null, null, false, 300, 20, true);
        $this->assertSame('47250a973d1b88d9445f94db4ef2c97a', md5($contents));

        $contents = download_file_content($testhtml);
        $this->assertSame('47250a973d1b88d9445f94db4ef2c97a', md5($contents));

        // Now 404.
        $testhtml = $this->getExternalTestFileUrl('/test.html_nonexistent');

        $contents = download_file_content($testhtml);
        $this->assertFalse($contents);
        $this->assertDebuggingCalled();

        $response = download_file_content($testhtml, null, null, true);
        $this->assertInstanceOf('stdClass', $response);
        $this->assertSame('404', $response->status);
        $this->assertTrue(is_array($response->headers));
        $this->assertMatchesRegularExpression('|^HTTP/1\.[01] 404 Not Found$|', rtrim($response->response_code));
        // Do not test the response starts with DOCTYPE here because some servers may return different headers.
        $this->assertSame('', $response->error);

        // Invalid url.
        $testhtml = $this->getExternalTestFileUrl('/test.html');
        $testhtml = str_replace('http://', 'ftp://', $testhtml);

        $contents = download_file_content($testhtml);
        $this->assertFalse($contents);

        // Test standard redirects.
        $testurl = $this->getExternalTestFileUrl('/test_redir.php');

        $contents = download_file_content("$testurl?redir=2");
        $this->assertSame('done', $contents);

        $contents = download_file_content("$testurl?redir=2&verbose=1");
        $this->assertSame('done', $contents);

        $response = download_file_content("$testurl?redir=2", null, null, true);
        $this->assertInstanceOf('stdClass', $response);
        $this->assertSame('200', $response->status);
        $this->assertTrue(is_array($response->headers));
        $this->assertMatchesRegularExpression('|^HTTP/1\.[01] 200 OK$|', rtrim($response->response_code));
        $this->assertSame('done', $response->results);
        $this->assertSame('', $response->error);

        $response = download_file_content("$testurl?redir=2&verbose=1", null, null, true);
        $this->assertInstanceOf('stdClass', $response);
        $this->assertSame('200', $response->status);
        $this->assertTrue(is_array($response->headers));
        $this->assertMatchesRegularExpression('|^HTTP/1\.[01] 200 OK$|', rtrim($response->response_code));
        $this->assertSame('done', $response->results);
        $this->assertSame('', $response->error);

        // Commented out this block if there are performance problems.
        /*
        $contents = download_file_content("$testurl?redir=6");
        $this->assertFalse(false, $contents);
        $this->assertDebuggingCalled();
        $response = download_file_content("$testurl?redir=6", null, null, true);
        $this->assertInstanceOf('stdClass', $response);
        $this->assertSame('0', $response->status);
        $this->assertTrue(is_array($response->headers));
        $this->assertFalse($response->results);
        $this->assertNotEmpty($response->error);
        */

        // Test relative redirects.
        $testurl = $this->getExternalTestFileUrl('/test_relative_redir.php');

        $contents = download_file_content("$testurl");
        $this->assertSame('done', $contents);

        $contents = download_file_content("$testurl?unused=xxx");
        $this->assertSame('done', $contents);
    }

    /**
     * Test curl basics.
     */
    public function test_curl_basics(): void {
        global $CFG;

        // Test HTTP success.
        $testhtml = $this->getExternalTestFileUrl('/test.html');

        $curl = new \curl();
        $contents = $curl->get($testhtml);
        $this->assertSame('47250a973d1b88d9445f94db4ef2c97a', md5($contents));
        $this->assertSame(0, $curl->get_errno());

        $curl = new \curl();
        $tofile = "$CFG->tempdir/test.html";
        @unlink($tofile);
        $fp = fopen($tofile, 'w');
        $result = $curl->get($testhtml, array(), array('CURLOPT_FILE'=>$fp));
        $this->assertTrue($result);
        fclose($fp);
        $this->assertFileExists($tofile);
        $this->assertSame($contents, file_get_contents($tofile));
        @unlink($tofile);

        $curl = new \curl();
        $tofile = "$CFG->tempdir/test.html";
        @unlink($tofile);
        $result = $curl->download_one($testhtml, array(), array('filepath'=>$tofile));
        $this->assertTrue($result);
        $this->assertFileExists($tofile);
        $this->assertSame($contents, file_get_contents($tofile));
        @unlink($tofile);

        // Test 404 request.
        $curl = new \curl();
        $contents = $curl->get($this->getExternalTestFileUrl('/i.do.not.exist'));
        $response = $curl->getResponse();
        $this->assertSame('404 Not Found', reset($response));
        $this->assertSame(0, $curl->get_errno());
    }

    /**
     * Test a curl basic request with security enabled.
     */
    public function test_curl_basics_with_security_helper(): void {
        global $USER;

        $this->resetAfterTest();

        $sink = $this->redirectEvents();

        // Test a request with a basic hostname filter applied.
        $testhtml = $this->getExternalTestFileUrl('/test.html');
        $url = new \moodle_url($testhtml);
        $host = $url->get_host();
        set_config('curlsecurityblockedhosts', $host); // Blocks $host.

        // Create curl with the default security enabled. We expect this to be blocked.
        $curl = new \curl();
        $contents = $curl->get($testhtml);
        $expected = $curl->get_security()->get_blocked_url_string();
        $this->assertSame($expected, $contents);
        $this->assertSame(0, $curl->get_errno());
        $this->assertDebuggingCalled(
            "Blocked $testhtml: The URL is blocked. [user {$USER->id}]", DEBUG_NONE);

        $events = $sink->get_events();
        $this->assertCount(1, $events);
        $event = reset($events);

        $this->assertEquals('\core\event\url_blocked', $event->eventname);
        $this->assertEquals("Blocked $testhtml: $expected", $event->get_description());
        $this->assertEquals(\context_system::instance(), $event->get_context());
        $this->assertEquals($testhtml, $event->other['url']);
        $this->assertEventContextNotUsed($event);

        // Now, create a curl using the 'ignoresecurity' override.
        // We expect this request to pass, despite the admin setting having been set earlier.
        $curl = new \curl(['ignoresecurity' => true]);
        $contents = $curl->get($testhtml);
        $this->assertSame('47250a973d1b88d9445f94db4ef2c97a', md5($contents));
        $this->assertSame(0, $curl->get_errno());

        $events = $sink->get_events();
        $this->assertCount(1, $events);

        // Now, try injecting a mock security helper into curl. This will override the default helper.
        $mockhelper = $this->getMockBuilder('\core\files\curl_security_helper')->getMock();

        // Make the mock return a different string.
        $mockhelper->expects($this->any())->method('get_blocked_url_string')->will($this->returnValue('You shall not pass'));

        // And make the mock security helper block all URLs. This helper instance doesn't care about config.
        $mockhelper->expects($this->any())->method('url_is_blocked')->will($this->returnValue(true));

        $curl = new \curl(['securityhelper' => $mockhelper]);
        $contents = $curl->get($testhtml);
        $this->assertSame('You shall not pass', $curl->get_security()->get_blocked_url_string());
        $this->assertSame($curl->get_security()->get_blocked_url_string(), $contents);
        $this->assertDebuggingCalled();

        $events = $sink->get_events();
        $this->assertCount(2, $events);
    }

    public function test_curl_redirects(): void {
        global $CFG;

        $testurl = $this->getExternalTestFileUrl('/test_redir.php');

        $curl = new \curl();
        $contents = $curl->get("$testurl?redir=2", array(), array('CURLOPT_MAXREDIRS'=>2));
        $response = $curl->getResponse();
        $this->assertSame('200 OK', reset($response));
        $this->assertSame(0, $curl->get_errno());
        $this->assertSame(2, $curl->info['redirect_count']);
        $this->assertSame('done', $contents);

        // All redirects are emulated now. Enabling "emulateredirects" explicitly does not have effect.
        $curl = new \curl();
        $curl->emulateredirects = true;
        $contents = $curl->get("$testurl?redir=2", array(), array('CURLOPT_MAXREDIRS'=>2));
        $response = $curl->getResponse();
        $this->assertSame('200 OK', reset($response));
        $this->assertSame(0, $curl->get_errno());
        $this->assertSame(2, $curl->info['redirect_count']);
        $this->assertSame('done', $contents);

        // All redirects are emulated now. Attempting to disable "emulateredirects" explicitly causes warning.
        $curl = new \curl();
        $curl->emulateredirects = false;
        $contents = $curl->get("$testurl?redir=2", array(), array('CURLOPT_MAXREDIRS' => 2));
        $response = $curl->getResponse();
        $this->assertDebuggingCalled('Attempting to disable emulated redirects has no effect any more!');
        $this->assertSame('200 OK', reset($response));
        $this->assertSame(0, $curl->get_errno());
        $this->assertSame(2, $curl->info['redirect_count']);
        $this->assertSame('done', $contents);

        // This test was failing for people behind Squid proxies. Squid does not
        // fully support HTTP 1.1, so converts things to HTTP 1.0, where the name
        // of the status code is different.
        reset($response);
        if (key($response) === 'HTTP/1.0') {
            $responsecode302 = '302 Moved Temporarily';
        } else {
            $responsecode302 = '302 Found';
        }

        $curl = new \curl();
        $contents = $curl->get("$testurl?redir=3", array(), array('CURLOPT_FOLLOWLOCATION'=>0));
        $response = $curl->getResponse();
        $this->assertSame($responsecode302, reset($response));
        $this->assertSame(0, $curl->get_errno());
        $this->assertSame(302, $curl->info['http_code']);
        $this->assertSame('', $contents);

        $curl = new \curl();
        $contents = $curl->get("$testurl?redir=2", array(), array('CURLOPT_MAXREDIRS'=>1));
        $this->assertSame(CURLE_TOO_MANY_REDIRECTS, $curl->get_errno());
        $this->assertNotEmpty($contents);

        $curl = new \curl();
        $tofile = "$CFG->tempdir/test.html";
        @unlink($tofile);
        $fp = fopen($tofile, 'w');
        $result = $curl->get("$testurl?redir=1", array(), array('CURLOPT_FILE'=>$fp));
        $this->assertTrue($result);
        fclose($fp);
        $this->assertFileExists($tofile);
        $this->assertSame('done', file_get_contents($tofile));
        @unlink($tofile);

        $curl = new \curl();
        $tofile = "$CFG->tempdir/test.html";
        @unlink($tofile);
        $fp = fopen($tofile, 'w');
        $result = $curl->get("$testurl?redir=1&verbose=1", array(), array('CURLOPT_FILE' => $fp));
        $this->assertTrue($result);
        fclose($fp);
        $this->assertFileExists($tofile);
        $this->assertSame('done', file_get_contents($tofile));
        @unlink($tofile);

        $curl = new \curl();
        $tofile = "$CFG->tempdir/test.html";
        @unlink($tofile);
        $result = $curl->download_one("$testurl?redir=1", array(), array('filepath'=>$tofile));
        $this->assertTrue($result);
        $this->assertFileExists($tofile);
        $this->assertSame('done', file_get_contents($tofile));
        @unlink($tofile);

        $curl = new \curl();
        $tofile = "$CFG->tempdir/test.html";
        @unlink($tofile);
        $result = $curl->download_one("$testurl?redir=1&verbose=1", array(), array('filepath' => $tofile));
        $this->assertTrue($result);
        $this->assertFileExists($tofile);
        $this->assertSame('done', file_get_contents($tofile));
        @unlink($tofile);
    }

    /**
     * Test that redirects to blocked hosts are blocked.
     */
    public function test_curl_blocked_redirect(): void {
        $this->resetAfterTest();

        $testurl = $this->getExternalTestFileUrl('/test_redir.php');

        // Block a host.
        // Note: moodle.com is the URL redirected to when test_redir.php has the param extdest=1 set.
        set_config('curlsecurityblockedhosts', 'moodle.com');

        // Redirecting to a non-blocked host should resolve.
        $curl = new \curl();
        $contents = $curl->get("{$testurl}?redir=2");
        $response = $curl->getResponse();
        $this->assertSame('200 OK', reset($response));
        $this->assertSame(0, $curl->get_errno());

        // Redirecting to the blocked host should fail.
        $curl = new \curl();
        $blockedstring = $curl->get_security()->get_blocked_url_string();
        $contents = $curl->get("{$testurl}?redir=1&extdest=1");
        $this->assertSame($blockedstring, $contents);
        $this->assertSame(0, $curl->get_errno());
        $this->assertDebuggingCalled();

        // Redirecting to the blocked host after multiple successful redirects should also fail.
        $curl = new \curl();
        $contents = $curl->get("{$testurl}?redir=3&extdest=1");
        $this->assertSame($blockedstring, $contents);
        $this->assertSame(0, $curl->get_errno());
        $this->assertDebuggingCalled();
    }

    public function test_curl_relative_redirects(): void {
        // Test relative location redirects.
        $testurl = $this->getExternalTestFileUrl('/test_relative_redir.php');

        $curl = new \curl();
        $contents = $curl->get($testurl);
        $response = $curl->getResponse();
        $this->assertSame('200 OK', reset($response));
        $this->assertSame(0, $curl->get_errno());
        $this->assertSame(1, $curl->info['redirect_count']);
        $this->assertSame('done', $contents);

        // Test different redirect types.
        $testurl = $this->getExternalTestFileUrl('/test_relative_redir.php');

        $curl = new \curl();
        $contents = $curl->get("$testurl?type=301");
        $response = $curl->getResponse();
        $this->assertSame('200 OK', reset($response));
        $this->assertSame(0, $curl->get_errno());
        $this->assertSame(1, $curl->info['redirect_count']);
        $this->assertSame('done', $contents);

        $curl = new \curl();
        $contents = $curl->get("$testurl?type=302");
        $response = $curl->getResponse();
        $this->assertSame('200 OK', reset($response));
        $this->assertSame(0, $curl->get_errno());
        $this->assertSame(1, $curl->info['redirect_count']);
        $this->assertSame('done', $contents);

        $curl = new \curl();
        $contents = $curl->get("$testurl?type=303");
        $response = $curl->getResponse();
        $this->assertSame('200 OK', reset($response));
        $this->assertSame(0, $curl->get_errno());
        $this->assertSame(1, $curl->info['redirect_count']);
        $this->assertSame('done', $contents);

        $curl = new \curl();
        $contents = $curl->get("$testurl?type=307");
        $response = $curl->getResponse();
        $this->assertSame('200 OK', reset($response));
        $this->assertSame(0, $curl->get_errno());
        $this->assertSame(1, $curl->info['redirect_count']);
        $this->assertSame('done', $contents);

        $curl = new \curl();
        $contents = $curl->get("$testurl?type=308");
        $response = $curl->getResponse();
        $this->assertSame('200 OK', reset($response));
        $this->assertSame(0, $curl->get_errno());
        $this->assertSame(1, $curl->info['redirect_count']);
        $this->assertSame('done', $contents);
    }

    public function test_curl_proxybypass(): void {
        global $CFG;
        $testurl = $this->getExternalTestFileUrl('/test.html');

        $oldproxy = $CFG->proxyhost;
        $oldproxybypass = $CFG->proxybypass;

        // Test without proxy bypass and inaccessible proxy.
        $CFG->proxyhost = 'i.do.not.exist';
        $CFG->proxybypass = '';
        $curl = new \curl();
        $contents = $curl->get($testurl);
        $this->assertNotEquals(0, $curl->get_errno());
        $this->assertNotEquals('47250a973d1b88d9445f94db4ef2c97a', md5($contents));

        // Test with proxy bypass.
        $testurlhost = parse_url($testurl, PHP_URL_HOST);
        $CFG->proxybypass = $testurlhost;
        $curl = new \curl();
        $contents = $curl->get($testurl);
        $this->assertSame(0, $curl->get_errno());
        $this->assertSame('47250a973d1b88d9445f94db4ef2c97a', md5($contents));

        $CFG->proxyhost = $oldproxy;
        $CFG->proxybypass = $oldproxybypass;
    }

    /**
     * Test that duplicate lines in the curl header are removed.
     */
    public function test_duplicate_curl_header(): void {
        $testurl = $this->getExternalTestFileUrl('/test_post.php');

        $curl = new \curl();
        $headerdata = 'Accept: application/json';
        $header = [$headerdata, $headerdata];
        $this->assertCount(2, $header);
        $curl->setHeader($header);
        $this->assertCount(1, $curl->header);
        $this->assertEquals($headerdata, $curl->header[0]);
    }

    public function test_curl_post(): void {
        $testurl = $this->getExternalTestFileUrl('/test_post.php');

        // Test post request.
        $curl = new \curl();
        $contents = $curl->post($testurl, 'data=moodletest');
        $response = $curl->getResponse();
        $this->assertSame('200 OK', reset($response));
        $this->assertSame(0, $curl->get_errno());
        $this->assertSame('OK', $contents);

        // Test 100 requests.
        $curl = new \curl();
        $curl->setHeader('Expect: 100-continue');
        $contents = $curl->post($testurl, 'data=moodletest');
        $response = $curl->getResponse();
        $this->assertSame('200 OK', reset($response));
        $this->assertSame(0, $curl->get_errno());
        $this->assertSame('OK', $contents);
    }

    public function test_curl_file(): void {
        $this->resetAfterTest();
        $testurl = $this->getExternalTestFileUrl('/test_file.php');

        $fs = get_file_storage();
        $filerecord = array(
            'contextid' => \context_system::instance()->id,
            'component' => 'test',
            'filearea' => 'curl_post',
            'itemid' => 0,
            'filepath' => '/',
            'filename' => 'test.txt'
        );
        $teststring = 'moodletest';
        $testfile = $fs->create_file_from_string($filerecord, $teststring);

        // Test post with file.
        $data = array('testfile' => $testfile);
        $curl = new \curl();
        $contents = $curl->post($testurl, $data);
        $this->assertSame('OK', $contents);
    }

    public function test_curl_file_name(): void {
        $this->resetAfterTest();
        $testurl = $this->getExternalTestFileUrl('/test_file_name.php');

        $fs = get_file_storage();
        $filerecord = array(
            'contextid' => \context_system::instance()->id,
            'component' => 'test',
            'filearea' => 'curl_post',
            'itemid' => 0,
            'filepath' => '/',
            'filename' => 'test.txt'
        );
        $teststring = 'moodletest';
        $testfile = $fs->create_file_from_string($filerecord, $teststring);

        // Test post with file.
        $data = array('testfile' => $testfile);
        $curl = new \curl();
        $contents = $curl->post($testurl, $data);
        $this->assertSame('OK', $contents);
    }

    public function test_curl_protocols(): void {

        // HTTP and HTTPS requests were verified in previous requests. Now check
        // that we can selectively disable some protocols.
        $curl = new \curl();

        // Other protocols than HTTP(S) are disabled by default.
        $testurl = 'file:///';
        $curl->get($testurl);
        $this->assertNotEmpty($curl->error);
        $this->assertEquals(CURLE_UNSUPPORTED_PROTOCOL, $curl->errno);

        $testurl = 'ftp://nowhere';
        $curl->get($testurl);
        $this->assertNotEmpty($curl->error);
        $this->assertEquals(CURLE_UNSUPPORTED_PROTOCOL, $curl->errno);

        $testurl = 'telnet://somewhere';
        $curl->get($testurl);
        $this->assertNotEmpty($curl->error);
        $this->assertEquals(CURLE_UNSUPPORTED_PROTOCOL, $curl->errno);

        // Protocols are also disabled during redirections.
        $testurl = $this->getExternalTestFileUrl('/test_redir_proto.php');
        $curl->get($testurl, array('proto' => 'file'));
        $this->assertNotEmpty($curl->error);
        $this->assertEquals(CURLE_UNSUPPORTED_PROTOCOL, $curl->errno);

        $testurl = $this->getExternalTestFileUrl('/test_redir_proto.php');
        $curl->get($testurl, array('proto' => 'ftp'));
        $this->assertNotEmpty($curl->error);
        $this->assertEquals(CURLE_UNSUPPORTED_PROTOCOL, $curl->errno);

        $testurl = $this->getExternalTestFileUrl('/test_redir_proto.php');
        $curl->get($testurl, array('proto' => 'telnet'));
        $this->assertNotEmpty($curl->error);
        $this->assertEquals(CURLE_UNSUPPORTED_PROTOCOL, $curl->errno);
    }

    /**
     * Testing prepare draft area
     *
     * @copyright 2012 Dongsheng Cai {@link http://dongsheng.org}
     * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
     */
    public function test_prepare_draft_area(): void {
        global $USER, $DB;

        $this->resetAfterTest(true);

        $generator = $this->getDataGenerator();
        $user = $generator->create_user();
        $usercontext = \context_user::instance($user->id);
        $USER = $DB->get_record('user', array('id'=>$user->id));

        $repositorypluginname = 'user';

        $args = array();
        $args['type'] = $repositorypluginname;
        $repos = repository::get_instances($args);
        $userrepository = reset($repos);
        $this->assertInstanceOf('repository', $userrepository);

        $fs = get_file_storage();

        $syscontext = \context_system::instance();
        $component = 'core';
        $filearea  = 'unittest';
        $itemid    = 0;
        $filepath  = '/';
        $filename  = 'test.txt';
        $sourcefield = 'Copyright stuff';

        $filerecord = array(
            'contextid' => $syscontext->id,
            'component' => $component,
            'filearea'  => $filearea,
            'itemid'    => $itemid,
            'filepath'  => $filepath,
            'filename'  => $filename,
            'source'    => $sourcefield,
        );
        $ref = $fs->pack_reference($filerecord);
        $originalfile = $fs->create_file_from_string($filerecord, 'Test content');
        $fileid = $originalfile->get_id();
        $this->assertInstanceOf('stored_file', $originalfile);

        // Create a user private file.
        $userfilerecord = new \stdClass;
        $userfilerecord->contextid = $usercontext->id;
        $userfilerecord->component = 'user';
        $userfilerecord->filearea  = 'private';
        $userfilerecord->itemid    = 0;
        $userfilerecord->filepath  = '/';
        $userfilerecord->filename  = 'userfile.txt';
        $userfilerecord->source    = 'test';
        $userfile = $fs->create_file_from_string($userfilerecord, 'User file content');
        $userfileref = $fs->pack_reference($userfilerecord);

        $filerefrecord = clone((object)$filerecord);
        $filerefrecord->filename = 'testref.txt';

        // Create a file reference.
        $fileref = $fs->create_file_from_reference($filerefrecord, $userrepository->id, $userfileref);
        $this->assertInstanceOf('stored_file', $fileref);
        $this->assertEquals($userrepository->id, $fileref->get_repository_id());
        $this->assertSame($userfile->get_contenthash(), $fileref->get_contenthash());
        $this->assertEquals($userfile->get_filesize(), $fileref->get_filesize());
        $this->assertMatchesRegularExpression('#' . $userfile->get_filename(). '$#', $fileref->get_reference_details());

        $draftitemid = 0;
        file_prepare_draft_area($draftitemid, $syscontext->id, $component, $filearea, $itemid);

        $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid);
        $this->assertCount(3, $draftfiles);

        $draftfile = $fs->get_file($usercontext->id, 'user', 'draft', $draftitemid, $filepath, $filename);
        $source = unserialize($draftfile->get_source());
        $this->assertSame($ref, $source->original);
        $this->assertSame($sourcefield, $source->source);

        $draftfileref = $fs->get_file($usercontext->id, 'user', 'draft', $draftitemid, $filepath, $filerefrecord->filename);
        $this->assertInstanceOf('stored_file', $draftfileref);
        $this->assertTrue($draftfileref->is_external_file());

        // Change some information.
        $author = 'Dongsheng Cai';
        $draftfile->set_author($author);
        $newsourcefield = 'Get from Flickr';
        $license = 'GPLv3';
        $draftfile->set_license($license);
        // If you want to really just change source field, do this.
        $source = unserialize($draftfile->get_source());
        $newsourcefield = 'From flickr';
        $source->source = $newsourcefield;
        $draftfile->set_source(serialize($source));

        // Save changed file.
        file_save_draft_area_files($draftitemid, $syscontext->id, $component, $filearea, $itemid);

        $file = $fs->get_file($syscontext->id, $component, $filearea, $itemid, $filepath, $filename);

        // Make sure it's the original file id.
        $this->assertEquals($fileid, $file->get_id());
        $this->assertInstanceOf('stored_file', $file);
        $this->assertSame($author, $file->get_author());
        $this->assertSame($license, $file->get_license());
        $this->assertEquals($newsourcefield, $file->get_source());
    }

    /**
     * Testing deleting original files.
     *
     * @copyright 2012 Dongsheng Cai {@link http://dongsheng.org}
     * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
     */
    public function test_delete_original_file_from_draft(): void {
        global $USER, $DB;

        $this->resetAfterTest(true);

        $generator = $this->getDataGenerator();
        $user = $generator->create_user();
        $usercontext = \context_user::instance($user->id);
        $USER = $DB->get_record('user', array('id'=>$user->id));

        $repositorypluginname = 'user';

        $args = array();
        $args['type'] = $repositorypluginname;
        $repos = repository::get_instances($args);
        $userrepository = reset($repos);
        $this->assertInstanceOf('repository', $userrepository);

        $fs = get_file_storage();
        $syscontext = \context_system::instance();

        $filecontent = 'User file content';

        // Create a user private file.
        $userfilerecord = new \stdClass;
        $userfilerecord->contextid = $usercontext->id;
        $userfilerecord->component = 'user';
        $userfilerecord->filearea  = 'private';
        $userfilerecord->itemid    = 0;
        $userfilerecord->filepath  = '/';
        $userfilerecord->filename  = 'userfile.txt';
        $userfilerecord->source    = 'test';
        $userfile = $fs->create_file_from_string($userfilerecord, $filecontent);
        $userfileref = $fs->pack_reference($userfilerecord);
        $contenthash = $userfile->get_contenthash();

        $filerecord = array(
            'contextid' => $syscontext->id,
            'component' => 'core',
            'filearea'  => 'phpunit',
            'itemid'    => 0,
            'filepath'  => '/',
            'filename'  => 'test.txt',
        );
        // Create a file reference.
        $fileref = $fs->create_file_from_reference($filerecord, $userrepository->id, $userfileref);
        $this->assertInstanceOf('stored_file', $fileref);
        $this->assertEquals($userrepository->id, $fileref->get_repository_id());
        $this->assertSame($userfile->get_contenthash(), $fileref->get_contenthash());
        $this->assertEquals($userfile->get_filesize(), $fileref->get_filesize());
        $this->assertMatchesRegularExpression('#' . $userfile->get_filename(). '$#', $fileref->get_reference_details());

        $draftitemid = 0;
        file_prepare_draft_area($draftitemid, $usercontext->id, 'user', 'private', 0);
        $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid);
        $this->assertCount(2, $draftfiles);
        $draftfile = $fs->get_file($usercontext->id, 'user', 'draft', $draftitemid, $userfilerecord->filepath, $userfilerecord->filename);
        $draftfile->delete();
        // Save changed file.
        file_save_draft_area_files($draftitemid, $usercontext->id, 'user', 'private', 0);

        // The file reference should be a regular moodle file now.
        $fileref = $fs->get_file($syscontext->id, 'core', 'phpunit', 0, '/', 'test.txt');
        $this->assertFalse($fileref->is_external_file());
        $this->assertSame($contenthash, $fileref->get_contenthash());
        $this->assertEquals($filecontent, $fileref->get_content());
    }

    /**
     * Test avoid file merging when working with draft areas.
     */
    public function test_ignore_file_merging_in_draft_area(): void {
        global $USER, $DB;

        $this->resetAfterTest(true);

        $generator = $this->getDataGenerator();
        $user = $generator->create_user();
        $usercontext = \context_user::instance($user->id);
        $USER = $DB->get_record('user', array('id' => $user->id));

        $repositorypluginname = 'user';

        $args = array();
        $args['type'] = $repositorypluginname;
        $repos = repository::get_instances($args);
        $userrepository = reset($repos);
        $this->assertInstanceOf('repository', $userrepository);

        $fs = get_file_storage();
        $syscontext = \context_system::instance();

        $filecontent = 'User file content';

        // Create a user private file.
        $userfilerecord = new \stdClass;
        $userfilerecord->contextid = $usercontext->id;
        $userfilerecord->component = 'user';
        $userfilerecord->filearea  = 'private';
        $userfilerecord->itemid    = 0;
        $userfilerecord->filepath  = '/';
        $userfilerecord->filename  = 'userfile.txt';
        $userfilerecord->source    = 'test';
        $userfile = $fs->create_file_from_string($userfilerecord, $filecontent);
        $userfileref = $fs->pack_reference($userfilerecord);
        $contenthash = $userfile->get_contenthash();

        $filerecord = array(
            'contextid' => $syscontext->id,
            'component' => 'core',
            'filearea'  => 'phpunit',
            'itemid'    => 0,
            'filepath'  => '/',
            'filename'  => 'test.txt',
        );
        // Create a file reference.
        $fileref = $fs->create_file_from_reference($filerecord, $userrepository->id, $userfileref);
        $this->assertCount(2, $fs->get_area_files($usercontext->id, 'user', 'private'));    // 2 because includes the '.' file.

        // Save using empty draft item id, all files will be deleted.
        file_save_draft_area_files(0, $usercontext->id, 'user', 'private', 0);
        $this->assertCount(0, $fs->get_area_files($usercontext->id, 'user', 'private'));

        // Create a file again.
        $userfile = $fs->create_file_from_string($userfilerecord, $filecontent);
        $this->assertCount(2, $fs->get_area_files($usercontext->id, 'user', 'private'));

        // Save without merge.
        file_save_draft_area_files(IGNORE_FILE_MERGE, $usercontext->id, 'user', 'private', 0);
        $this->assertCount(2, $fs->get_area_files($usercontext->id, 'user', 'private'));
        // Save again, this time including some inline text.
        $inlinetext = 'Some text <img src="@@PLUGINFILE@@/file.png">';
        $text = file_save_draft_area_files(IGNORE_FILE_MERGE, $usercontext->id, 'user', 'private', 0, null, $inlinetext);
        $this->assertCount(2, $fs->get_area_files($usercontext->id, 'user', 'private'));
        $this->assertEquals($inlinetext, $text);
    }

    /**
     * Testing deleting file_save_draft_area_files won't accidentally wipe unintended files.
     */
    public function test_file_save_draft_area_files_itemid_cannot_be_false(): void {
        global $USER, $DB;
        $this->resetAfterTest();

        $generator = $this->getDataGenerator();
        $user = $generator->create_user();
        $usercontext = \context_user::instance($user->id);
        $USER = $DB->get_record('user', ['id' => $user->id]);

        $draftitemid = 0;
        file_prepare_draft_area($draftitemid, $usercontext->id, 'user', 'private', 0);

        // Call file_save_draft_area_files with itemid false - which could only happen due to a bug.
        // This should throw an exception.
        $this->expectExceptionMessage('file_save_draft_area_files was called with $itemid false. ' .
                'This suggests a bug, because it would wipe all (' . $usercontext->id . ', user, private) files.');
        file_save_draft_area_files($draftitemid, $usercontext->id, 'user', 'private', false);
    }

    /**
     * Tests the strip_double_headers function in the curl class.
     */
    public function test_curl_strip_double_headers(): void {
        // Example from issue tracker.
        $mdl30648example = <<<EOF
HTTP/1.0 407 Proxy Authentication Required
Server: squid/2.7.STABLE9
Date: Thu, 08 Dec 2011 14:44:33 GMT
Content-Type: text/html
Content-Length: 1275
X-Squid-Error: ERR_CACHE_ACCESS_DENIED 0
Proxy-Authenticate: Basic realm="Squid proxy-caching web server"
X-Cache: MISS from homer.lancs.ac.uk
X-Cache-Lookup: NONE from homer.lancs.ac.uk:3128
Via: 1.0 homer.lancs.ac.uk:3128 (squid/2.7.STABLE9)
Connection: close

HTTP/1.0 200 OK
Server: Apache
X-Lb-Nocache: true
Cache-Control: private, max-age=15, no-transform
ETag: "4d69af5d8ba873ea9192c489e151bd7b"
Content-Type: text/html
Date: Thu, 08 Dec 2011 14:44:53 GMT
Set-Cookie: BBC-UID=c4de2e109c8df6a51de627cee11b214bd4fb6054a030222488317afb31b343360MoodleBot/1.0; expires=Mon, 07-Dec-15 14:44:53 GMT; path=/; domain=bbc.co.uk
X-Cache-Action: MISS
X-Cache-Age: 0
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
X-Cache: MISS from ww

<html>...
EOF;
        $mdl30648expected = <<<EOF
HTTP/1.0 200 OK
Server: Apache
X-Lb-Nocache: true
Cache-Control: private, max-age=15, no-transform
ETag: "4d69af5d8ba873ea9192c489e151bd7b"
Content-Type: text/html
Date: Thu, 08 Dec 2011 14:44:53 GMT
Set-Cookie: BBC-UID=c4de2e109c8df6a51de627cee11b214bd4fb6054a030222488317afb31b343360MoodleBot/1.0; expires=Mon, 07-Dec-15 14:44:53 GMT; path=/; domain=bbc.co.uk
X-Cache-Action: MISS
X-Cache-Age: 0
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
X-Cache: MISS from ww

<html>...
EOF;
        // For HTTP, replace the \n with \r\n.
        $mdl30648example = preg_replace("~(?!<\r)\n~", "\r\n", $mdl30648example);
        $mdl30648expected = preg_replace("~(?!<\r)\n~", "\r\n", $mdl30648expected);

        // Test stripping works OK.
        $this->assertSame($mdl30648expected, \curl::strip_double_headers($mdl30648example));
        // Test it does nothing to the 'plain' data.
        $this->assertSame($mdl30648expected, \curl::strip_double_headers($mdl30648expected));

        // Example from OU proxy.
        $httpsexample = <<<EOF
HTTP/1.0 200 Connection established

HTTP/1.1 200 OK
Date: Fri, 22 Feb 2013 17:14:23 GMT
Server: Apache/2
X-Powered-By: PHP/5.3.3-7+squeeze14
Content-Type: text/xml
Connection: close
Content-Encoding: gzip
Transfer-Encoding: chunked

<?xml version="1.0" encoding="ISO-8859-1" ?>
<rss version="2.0">...
EOF;
        $httpsexpected = <<<EOF
HTTP/1.1 200 OK
Date: Fri, 22 Feb 2013 17:14:23 GMT
Server: Apache/2
X-Powered-By: PHP/5.3.3-7+squeeze14
Content-Type: text/xml
Connection: close
Content-Encoding: gzip
Transfer-Encoding: chunked

<?xml version="1.0" encoding="ISO-8859-1" ?>
<rss version="2.0">...
EOF;
        // For HTTP, replace the \n with \r\n.
        $httpsexample = preg_replace("~(?!<\r)\n~", "\r\n", $httpsexample);
        $httpsexpected = preg_replace("~(?!<\r)\n~", "\r\n", $httpsexpected);

        // Test stripping works OK.
        $this->assertSame($httpsexpected, \curl::strip_double_headers($httpsexample));
        // Test it does nothing to the 'plain' data.
        $this->assertSame($httpsexpected, \curl::strip_double_headers($httpsexpected));

        $httpsexample = <<<EOF
HTTP/1.0 200 Connection established

HTTP/2 200 OK
Date: Fri, 22 Feb 2013 17:14:23 GMT
Server: Apache/2
X-Powered-By: PHP/5.3.3-7+squeeze14
Content-Type: text/xml
Connection: close
Content-Encoding: gzip
Transfer-Encoding: chunked

<?xml version="1.0" encoding="ISO-8859-1" ?>
<rss version="2.0">...
EOF;
        $httpsexpected = <<<EOF
HTTP/2 200 OK
Date: Fri, 22 Feb 2013 17:14:23 GMT
Server: Apache/2
X-Powered-By: PHP/5.3.3-7+squeeze14
Content-Type: text/xml
Connection: close
Content-Encoding: gzip
Transfer-Encoding: chunked

<?xml version="1.0" encoding="ISO-8859-1" ?>
<rss version="2.0">...
EOF;
        // For HTTP, replace the \n with \r\n.
        $httpsexample = preg_replace("~(?!<\r)\n~", "\r\n", $httpsexample);
        $httpsexpected = preg_replace("~(?!<\r)\n~", "\r\n", $httpsexpected);

        // Test stripping works OK.
        $this->assertSame($httpsexpected, \curl::strip_double_headers($httpsexample));
        // Test it does nothing to the 'plain' data.
        $this->assertSame($httpsexpected, \curl::strip_double_headers($httpsexpected));

        $httpsexample = <<<EOF
HTTP/1.0 200 Connection established

HTTP/2.1 200 OK
Date: Fri, 22 Feb 2013 17:14:23 GMT
Server: Apache/2
X-Powered-By: PHP/5.3.3-7+squeeze14
Content-Type: text/xml
Connection: close
Content-Encoding: gzip
Transfer-Encoding: chunked

<?xml version="1.0" encoding="ISO-8859-1" ?>
<rss version="2.0">...
EOF;
        $httpsexpected = <<<EOF
HTTP/2.1 200 OK
Date: Fri, 22 Feb 2013 17:14:23 GMT
Server: Apache/2
X-Powered-By: PHP/5.3.3-7+squeeze14
Content-Type: text/xml
Connection: close
Content-Encoding: gzip
Transfer-Encoding: chunked

<?xml version="1.0" encoding="ISO-8859-1" ?>
<rss version="2.0">...
EOF;
        // For HTTP, replace the \n with \r\n.
        $httpsexample = preg_replace("~(?!<\r)\n~", "\r\n", $httpsexample);
        $httpsexpected = preg_replace("~(?!<\r)\n~", "\r\n", $httpsexpected);

        // Test stripping works OK.
        $this->assertSame($httpsexpected, \curl::strip_double_headers($httpsexample));
        // Test it does nothing to the 'plain' data.
        $this->assertSame($httpsexpected, \curl::strip_double_headers($httpsexpected));

        $httpsexample = <<<EOF
HTTP/1.1 200 Connection established

HTTP/3 200 OK
Date: Fri, 22 Feb 2013 17:14:23 GMT
Server: Apache/2
X-Powered-By: PHP/5.3.3-7+squeeze14
Content-Type: text/xml
Connection: close
Content-Encoding: gzip
Transfer-Encoding: chunked

<?xml version="1.0" encoding="ISO-8859-1" ?>
<rss version="2.0">...
EOF;
        $httpsexpected = <<<EOF
HTTP/3 200 OK
Date: Fri, 22 Feb 2013 17:14:23 GMT
Server: Apache/2
X-Powered-By: PHP/5.3.3-7+squeeze14
Content-Type: text/xml
Connection: close
Content-Encoding: gzip
Transfer-Encoding: chunked

<?xml version="1.0" encoding="ISO-8859-1" ?>
<rss version="2.0">...
EOF;
        // For HTTP, replace the \n with \r\n.
        $httpsexample = preg_replace("~(?!<\r)\n~", "\r\n", $httpsexample);
        $httpsexpected = preg_replace("~(?!<\r)\n~", "\r\n", $httpsexpected);

        // Test stripping works OK.
        $this->assertSame($httpsexpected, \curl::strip_double_headers($httpsexample));
        // Test it does nothing to the 'plain' data.
        $this->assertSame($httpsexpected, \curl::strip_double_headers($httpsexpected));

        $httpsexample = <<<EOF
HTTP/2 200 Connection established

HTTP/4 200 OK
Date: Fri, 22 Feb 2013 17:14:23 GMT
Server: Apache/2
X-Powered-By: PHP/5.3.3-7+squeeze14
Content-Type: text/xml
Connection: close
Content-Encoding: gzip
Transfer-Encoding: chunked

<?xml version="1.0" encoding="ISO-8859-1" ?>
<rss version="2.0">...
EOF;
        $httpsexpected = <<<EOF
HTTP/4 200 OK
Date: Fri, 22 Feb 2013 17:14:23 GMT
Server: Apache/2
X-Powered-By: PHP/5.3.3-7+squeeze14
Content-Type: text/xml
Connection: close
Content-Encoding: gzip
Transfer-Encoding: chunked

<?xml version="1.0" encoding="ISO-8859-1" ?>
<rss version="2.0">...
EOF;
        // For HTTP, replace the \n with \r\n.
        $httpsexample = preg_replace("~(?!<\r)\n~", "\r\n", $httpsexample);
        $httpsexpected = preg_replace("~(?!<\r)\n~", "\r\n", $httpsexpected);

        // Test stripping works OK.
        $this->assertSame($httpsexpected, \curl::strip_double_headers($httpsexample));
        // Test it does nothing to the 'plain' data.
        $this->assertSame($httpsexpected, \curl::strip_double_headers($httpsexpected));
    }

    /**
     * Tests the get_mimetype_description function.
     */
    public function test_get_mimetype_description(): void {
        $this->resetAfterTest();

        // Test example type (.doc).
        $this->assertEquals(get_string('application/msword', 'mimetypes'),
                get_mimetype_description(array('filename' => 'test.doc')));

        // Test an unknown file type.
        $this->assertEquals(get_string('document/unknown', 'mimetypes'),
                get_mimetype_description(array('filename' => 'test.frog')));

        // Test a custom filetype with no lang string specified.
        core_filetypes::add_type('frog', 'application/x-frog', 'document');
        $this->assertEquals('application/x-frog',
                get_mimetype_description(array('filename' => 'test.frog')));

        // Test custom description.
        core_filetypes::update_type('frog', 'frog', 'application/x-frog', 'document',
                array(), '', 'Froggy file');
        $this->assertEquals('Froggy file',
                get_mimetype_description(array('filename' => 'test.frog')));

        // Test custom description using multilang filter.
        \filter_manager::reset_caches();
        filter_set_global_state('multilang', TEXTFILTER_ON);
        filter_set_applies_to_strings('multilang', true);
        core_filetypes::update_type('frog', 'frog', 'application/x-frog', 'document',
                array(), '', '<span lang="en" class="multilang">Green amphibian</span>' .
                '<span lang="fr" class="multilang">Amphibian vert</span>');
        $this->assertEquals('Green amphibian',
                get_mimetype_description(array('filename' => 'test.frog')));
    }

    /**
     * Tests the get_mimetypes_array function.
     */
    public function test_get_mimetypes_array(): void {
        $mimeinfo = get_mimetypes_array();

        // Test example MIME type (doc).
        $this->assertEquals('application/msword', $mimeinfo['doc']['type']);
        $this->assertEquals('document', $mimeinfo['doc']['icon']);
        $this->assertEquals(array('document'), $mimeinfo['doc']['groups']);
        $this->assertFalse(isset($mimeinfo['doc']['string']));
        $this->assertFalse(isset($mimeinfo['doc']['defaulticon']));
        $this->assertFalse(isset($mimeinfo['doc']['customdescription']));

        // Check the less common fields using other examples.
        $this->assertEquals('image', $mimeinfo['png']['string']);
        $this->assertEquals(true, $mimeinfo['txt']['defaulticon']);
    }

    /**
     * Tests for get_mimetype_for_sending function.
     */
    public function test_get_mimetype_for_sending(): void {
        // Without argument.
        $this->assertEquals('application/octet-stream', get_mimetype_for_sending());

        // Argument is null.
        $this->assertEquals('application/octet-stream', get_mimetype_for_sending(null));

        // Filename having no extension.
        $this->assertEquals('application/octet-stream', get_mimetype_for_sending('filenamewithoutextension'));

        // Test using the extensions listed from the get_mimetypes_array function.
        $mimetypes = get_mimetypes_array();
        foreach ($mimetypes as $ext => $info) {
            if ($ext === 'xxx') {
                $this->assertEquals('application/octet-stream', get_mimetype_for_sending('SampleFile.' . $ext));
            } else {
                $this->assertEquals($info['type'], get_mimetype_for_sending('SampleFile.' . $ext));
            }
        }
    }

    /**
     * Test curl agent settings.
     */
    public function test_curl_useragent(): void {
        $curl = new testable_curl();
        $options = $curl->get_options();
        $this->assertNotEmpty($options);

        $moodlebot = \core_useragent::get_moodlebot_useragent();

        $curl->call_apply_opt($options);
        $this->assertTrue(in_array("User-Agent: $moodlebot", $curl->header));
        $this->assertFalse(in_array('User-Agent: Test/1.0', $curl->header));

        $options['CURLOPT_USERAGENT'] = 'Test/1.0';
        $curl->call_apply_opt($options);
        $this->assertTrue(in_array('User-Agent: Test/1.0', $curl->header));
        $this->assertFalse(in_array("User-Agent: $moodlebot", $curl->header));

        $curl->set_option('CURLOPT_USERAGENT', 'AnotherUserAgent/1.0');
        $curl->call_apply_opt();
        $this->assertTrue(in_array('User-Agent: AnotherUserAgent/1.0', $curl->header));
        $this->assertFalse(in_array('User-Agent: Test/1.0', $curl->header));

        $curl->set_option('CURLOPT_USERAGENT', 'AnotherUserAgent/1.1');
        $options = $curl->get_options();
        $curl->call_apply_opt($options);
        $this->assertTrue(in_array('User-Agent: AnotherUserAgent/1.1', $curl->header));
        $this->assertFalse(in_array('User-Agent: AnotherUserAgent/1.0', $curl->header));

        $curl->unset_option('CURLOPT_USERAGENT');
        $curl->call_apply_opt();
        $this->assertTrue(in_array("User-Agent: $moodlebot", $curl->header));

        // Finally, test it via exttests, to ensure the agent is sent properly.
        $testurl = $this->getExternalTestFileUrl('/test_agent.php');
        $extcurl = new \curl();

        // Matching (assert we don't receive an error, and get back the content "OK").
        $contents = $extcurl->get($testurl, array(), array('CURLOPT_USERAGENT' => 'AnotherUserAgent/1.2'));
        $this->assertSame(0, $extcurl->get_errno());
        $this->assertSame('OK', $contents);

        // Not matching (assert we don't receive an error, and get back empty content - not "OK").
        $contents = $extcurl->get($testurl, array(), array('CURLOPT_USERAGENT' => 'NonMatchingUserAgent/1.2'));
        $this->assertSame(0, $extcurl->get_errno());
        $this->assertSame('', $contents);
    }

    /**
     * Test file_rewrite_pluginfile_urls.
     */
    public function test_file_rewrite_pluginfile_urls(): void {

        $syscontext = \context_system::instance();
        $originaltext = 'Fake test with an image <img src="@@PLUGINFILE@@/image.png">';

        // Do the rewrite.
        $finaltext = file_rewrite_pluginfile_urls($originaltext, 'pluginfile.php', $syscontext->id, 'user', 'private', 0);
        $this->assertStringContainsString("pluginfile.php", $finaltext);

        // Now undo.
        $options = array('reverse' => true);
        $finaltext = file_rewrite_pluginfile_urls($finaltext, 'pluginfile.php', $syscontext->id, 'user', 'private', 0, $options);

        // Compare the final text is the same that the original.
        $this->assertEquals($originaltext, $finaltext);
    }

    /**
     * Test file_rewrite_pluginfile_urls with includetoken.
     */
    public function test_file_rewrite_pluginfile_urls_includetoken(): void {
        global $USER, $CFG;

        $CFG->slasharguments = true;

        $this->resetAfterTest();

        $syscontext = \context_system::instance();
        $originaltext = 'Fake test with an image <img src="@@PLUGINFILE@@/image.png">';
        $options = ['includetoken' => true];

        // Rewrite the content. This will generate a new token.
        $finaltext = file_rewrite_pluginfile_urls(
                $originaltext, 'pluginfile.php', $syscontext->id, 'user', 'private', 0, $options);

        $token = get_user_key('core_files', $USER->id);
        $expectedurl = new \moodle_url("/tokenpluginfile.php/{$token}/{$syscontext->id}/user/private/0/image.png");
        $expectedtext = "Fake test with an image <img src=\"{$expectedurl}\">";
        $this->assertEquals($expectedtext, $finaltext);

        // Do it again - the second time will use an existing token.
        $finaltext = file_rewrite_pluginfile_urls(
                $originaltext, 'pluginfile.php', $syscontext->id, 'user', 'private', 0, $options);
        $this->assertEquals($expectedtext, $finaltext);

        // Now undo.
        $options['reverse'] = true;
        $finaltext = file_rewrite_pluginfile_urls($finaltext, 'pluginfile.php', $syscontext->id, 'user', 'private', 0, $options);

        // Compare the final text is the same that the original.
        $this->assertEquals($originaltext, $finaltext);

        // Now indicates a user different than $USER.
        $user = $this->getDataGenerator()->create_user();
        $options = ['includetoken' => $user->id];

        // Rewrite the content. This will generate a new token.
        $finaltext = file_rewrite_pluginfile_urls(
                $originaltext, 'pluginfile.php', $syscontext->id, 'user', 'private', 0, $options);

        $token = get_user_key('core_files', $user->id);
        $expectedurl = new \moodle_url("/tokenpluginfile.php/{$token}/{$syscontext->id}/user/private/0/image.png");
        $expectedtext = "Fake test with an image <img src=\"{$expectedurl}\">";
        $this->assertEquals($expectedtext, $finaltext);
    }

    /**
     * Test file_rewrite_pluginfile_urls with includetoken with slasharguments disabled..
     */
    public function test_file_rewrite_pluginfile_urls_includetoken_no_slashargs(): void {
        global $USER, $CFG;

        $CFG->slasharguments = false;

        $this->resetAfterTest();

        $syscontext = \context_system::instance();
        $originaltext = 'Fake test with an image <img src="@@PLUGINFILE@@/image.png">';
        $options = ['includetoken' => true];

        // Rewrite the content. This will generate a new token.
        $finaltext = file_rewrite_pluginfile_urls(
                $originaltext, 'pluginfile.php', $syscontext->id, 'user', 'private', 0, $options);

        $token = get_user_key('core_files', $USER->id);
        $expectedurl = new \moodle_url("/tokenpluginfile.php");
        $expectedurl .= "?token={$token}&file=/{$syscontext->id}/user/private/0/image.png";
        $expectedtext = "Fake test with an image <img src=\"{$expectedurl}\">";
        $this->assertEquals($expectedtext, $finaltext);

        // Do it again - the second time will use an existing token.
        $finaltext = file_rewrite_pluginfile_urls(
                $originaltext, 'pluginfile.php', $syscontext->id, 'user', 'private', 0, $options);
        $this->assertEquals($expectedtext, $finaltext);

        // Now undo.
        $options['reverse'] = true;
        $finaltext = file_rewrite_pluginfile_urls($finaltext, 'pluginfile.php', $syscontext->id, 'user', 'private', 0, $options);

        // Compare the final text is the same that the original.
        $this->assertEquals($originaltext, $finaltext);
    }

    /**
     * Helpter function to create draft files
     *
     * @param  array  $filedata data for the file record (to not use defaults)
     * @return stored_file the stored file instance
     */
    public static function create_draft_file($filedata = array()) {
        global $USER;

        $fs = get_file_storage();

        $filerecord = array(
            'component' => 'user',
            'filearea'  => 'draft',
            'itemid'    => isset($filedata['itemid']) ? $filedata['itemid'] : file_get_unused_draft_itemid(),
            'author'    => isset($filedata['author']) ? $filedata['author'] : fullname($USER),
            'filepath'  => isset($filedata['filepath']) ? $filedata['filepath'] : '/',
            'filename'  => isset($filedata['filename']) ? $filedata['filename'] : 'file.txt',
        );

        if (isset($filedata['contextid'])) {
            $filerecord['contextid'] = $filedata['contextid'];
        } else {
            $usercontext = \context_user::instance($USER->id);
            $filerecord['contextid'] = $usercontext->id;
        }
        $source = isset($filedata['source']) ? $filedata['source'] : serialize((object)array('source' => 'From string'));
        $content = isset($filedata['content']) ? $filedata['content'] : 'some content here';

        $file = $fs->create_file_from_string($filerecord, $content);
        $file->set_source($source);

        return $file;
    }

    /**
     * Test file_merge_files_from_draft_area_into_filearea
     */
    public function test_file_merge_files_from_draft_area_into_filearea(): void {
        global $USER, $CFG;

        $this->resetAfterTest(true);
        $this->setAdminUser();
        $fs = get_file_storage();
        $usercontext = \context_user::instance($USER->id);

        // Create a draft file.
        $filename = 'data.txt';
        $filerecord = array(
            'filename'  => $filename,
        );
        $file = self::create_draft_file($filerecord);
        $draftitemid = $file->get_itemid();

        $maxbytes = $CFG->userquota;
        $maxareabytes = $CFG->userquota;
        $options = array('subdirs' => 1,
                         'maxbytes' => $maxbytes,
                         'maxfiles' => -1,
                         'areamaxbytes' => $maxareabytes);

        // Add new file.
        file_merge_files_from_draft_area_into_filearea($draftitemid, $usercontext->id, 'user', 'private', 0, $options);

        $files = $fs->get_area_files($usercontext->id, 'user', 'private', 0);
        // Directory and file.
        $this->assertCount(2, $files);
        $found = false;
        foreach ($files as $file) {
            if (!$file->is_directory()) {
                $found = true;
                $this->assertEquals($filename, $file->get_filename());
                $this->assertEquals('some content here', $file->get_content());
            }
        }
        $this->assertTrue($found);

        // Add two more files.
        $filerecord = array(
            'itemid'  => $draftitemid,
            'filename'  => 'second.txt',
        );
        self::create_draft_file($filerecord);
        $filerecord = array(
            'itemid'  => $draftitemid,
            'filename'  => 'third.txt',
        );
        $file = self::create_draft_file($filerecord);

        file_merge_files_from_draft_area_into_filearea($file->get_itemid(), $usercontext->id, 'user', 'private', 0, $options);

        $files = $fs->get_area_files($usercontext->id, 'user', 'private', 0);
        $this->assertCount(4, $files);

        // Update contents of one file.
        $filerecord = array(
            'filename'  => 'second.txt',
            'content'  => 'new content',
        );
        $file = self::create_draft_file($filerecord);
        file_merge_files_from_draft_area_into_filearea($file->get_itemid(), $usercontext->id, 'user', 'private', 0, $options);

        $files = $fs->get_area_files($usercontext->id, 'user', 'private', 0);
        $this->assertCount(4, $files);
        $found = false;
        foreach ($files as $file) {
            if ($file->get_filename() == 'second.txt') {
                $found = true;
                $this->assertEquals('new content', $file->get_content());
            }
        }
        $this->assertTrue($found);

        // Update author.
        // Set different author in the current file.
        foreach ($files as $file) {
            if ($file->get_filename() == 'second.txt') {
                $file->set_author('Nobody');
            }
        }
        $filerecord = array(
            'filename'  => 'second.txt',
        );
        $file = self::create_draft_file($filerecord);

        file_merge_files_from_draft_area_into_filearea($file->get_itemid(), $usercontext->id, 'user', 'private', 0, $options);

        $files = $fs->get_area_files($usercontext->id, 'user', 'private', 0);
        $this->assertCount(4, $files);
        $found = false;
        foreach ($files as $file) {
            if ($file->get_filename() == 'second.txt') {
                $found = true;
                $this->assertEquals(fullname($USER), $file->get_author());
            }
        }
        $this->assertTrue($found);

    }

    /**
     * Test max area bytes for file_merge_files_from_draft_area_into_filearea
     */
    public function test_file_merge_files_from_draft_area_into_filearea_max_area_bytes(): void {
        global $USER;

        $this->resetAfterTest(true);
        $this->setAdminUser();
        $fs = get_file_storage();

        $file = self::create_draft_file();
        $options = array('subdirs' => 1,
                         'maxbytes' => 5,
                         'maxfiles' => -1,
                         'areamaxbytes' => 10);

        // Add new file.
        file_merge_files_from_draft_area_into_filearea($file->get_itemid(), $file->get_contextid(), 'user', 'private', 0, $options);
        $usercontext = \context_user::instance($USER->id);
        $files = $fs->get_area_files($usercontext->id, 'user', 'private', 0);
        $this->assertCount(0, $files);
    }

    /**
     * Test max file bytes for file_merge_files_from_draft_area_into_filearea
     */
    public function test_file_merge_files_from_draft_area_into_filearea_max_file_bytes(): void {
        global $USER;

        $this->resetAfterTest(true);
        // The admin has no restriction for max file uploads, so use a normal user.
        $user = $this->getDataGenerator()->create_user();
        $this->setUser($user);
        $fs = get_file_storage();

        $file = self::create_draft_file();
        $options = array('subdirs' => 1,
                         'maxbytes' => 1,
                         'maxfiles' => -1,
                         'areamaxbytes' => 100);

        // Add new file.
        file_merge_files_from_draft_area_into_filearea($file->get_itemid(), $file->get_contextid(), 'user', 'private', 0, $options);
        $usercontext = \context_user::instance($USER->id);
        // Check we only get the base directory, not a new file.
        $files = $fs->get_area_files($usercontext->id, 'user', 'private', 0);
        $this->assertCount(1, $files);
        $file = array_shift($files);
        $this->assertTrue($file->is_directory());
    }

    /**
     * Test max file number for file_merge_files_from_draft_area_into_filearea
     */
    public function test_file_merge_files_from_draft_area_into_filearea_max_files(): void {
        global $USER;

        $this->resetAfterTest(true);
        $this->setAdminUser();
        $fs = get_file_storage();

        $file = self::create_draft_file();
        $options = array('subdirs' => 1,
                         'maxbytes' => 1000,
                         'maxfiles' => 0,
                         'areamaxbytes' => 1000);

        // Add new file.
        file_merge_files_from_draft_area_into_filearea($file->get_itemid(), $file->get_contextid(), 'user', 'private', 0, $options);
        $usercontext = \context_user::instance($USER->id);
        // Check we only get the base directory, not a new file.
        $files = $fs->get_area_files($usercontext->id, 'user', 'private', 0);
        $this->assertCount(1, $files);
        $file = array_shift($files);
        $this->assertTrue($file->is_directory());
    }

    /**
     * Test file_get_draft_area_info.
     */
    public function test_file_get_draft_area_info(): void {
        global $USER;

        $this->resetAfterTest(true);
        $this->setAdminUser();
        $fs = get_file_storage();

        $filerecord = array(
            'filename'  => 'one.txt',
        );
        $file = self::create_draft_file($filerecord);
        $size = $file->get_filesize();
        $draftitemid = $file->get_itemid();
        // Add another file.
        $filerecord = array(
            'itemid'  => $draftitemid,
            'filename'  => 'second.txt',
        );
        $file = self::create_draft_file($filerecord);
        $size += $file->get_filesize();

        // Create directory.
        $usercontext = \context_user::instance($USER->id);
        $dir = $fs->create_directory($usercontext->id, 'user', 'draft', $draftitemid, '/testsubdir/');
        // Add file to directory.
        $filerecord = array(
            'itemid'  => $draftitemid,
            'filename' => 'third.txt',
            'filepath' => '/testsubdir/',
        );
        $file = self::create_draft_file($filerecord);
        $size += $file->get_filesize();

        $fileinfo = file_get_draft_area_info($draftitemid);
        $this->assertEquals(3, $fileinfo['filecount']);
        $this->assertEquals($size, $fileinfo['filesize']);
        $this->assertEquals(1, $fileinfo['foldercount']);   // Directory created.
        $this->assertEquals($size, $fileinfo['filesize_without_references']);

        // Now get files from just one folder.
        $fileinfo = file_get_draft_area_info($draftitemid, '/testsubdir/');
        $this->assertEquals(1, $fileinfo['filecount']);
        $this->assertEquals($file->get_filesize(), $fileinfo['filesize']);
        $this->assertEquals(0, $fileinfo['foldercount']);   // No subdirectories inside the directory.
        $this->assertEquals($file->get_filesize(), $fileinfo['filesize_without_references']);

        // Check we get the same results if we call file_get_file_area_info.
        $fileinfo = file_get_file_area_info($usercontext->id, 'user', 'draft', $draftitemid);
        $this->assertEquals(3, $fileinfo['filecount']);
        $this->assertEquals($size, $fileinfo['filesize']);
        $this->assertEquals(1, $fileinfo['foldercount']);   // Directory created.
        $this->assertEquals($size, $fileinfo['filesize_without_references']);
    }

    /**
     * Test file_get_file_area_info.
     */
    public function test_file_get_file_area_info(): void {
        global $USER;

        $this->resetAfterTest(true);
        $this->setAdminUser();
        $fs = get_file_storage();

        $filerecord = array(
            'filename'  => 'one.txt',
        );
        $file = self::create_draft_file($filerecord);
        $size = $file->get_filesize();
        $draftitemid = $file->get_itemid();
        // Add another file.
        $filerecord = array(
            'itemid'  => $draftitemid,
            'filename'  => 'second.txt',
        );
        $file = self::create_draft_file($filerecord);
        $size += $file->get_filesize();

        // Create directory.
        $usercontext = \context_user::instance($USER->id);
        $dir = $fs->create_directory($usercontext->id, 'user', 'draft', $draftitemid, '/testsubdir/');
        // Add file to directory.
        $filerecord = array(
            'itemid'  => $draftitemid,
            'filename' => 'third.txt',
            'filepath' => '/testsubdir/',
        );
        $file = self::create_draft_file($filerecord);
        $size += $file->get_filesize();

        // Add files to user private file area.
        $options = array('subdirs' => 1, 'maxfiles' => 3);
        file_merge_files_from_draft_area_into_filearea($draftitemid, $file->get_contextid(), 'user', 'private', 0, $options);

        $fileinfo = file_get_file_area_info($usercontext->id, 'user', 'private');
        $this->assertEquals(3, $fileinfo['filecount']);
        $this->assertEquals($size, $fileinfo['filesize']);
        $this->assertEquals(1, $fileinfo['foldercount']);   // Directory created.
        $this->assertEquals($size, $fileinfo['filesize_without_references']);

        // Now get files from just one folder.
        $fileinfo = file_get_file_area_info($usercontext->id, 'user', 'private', 0, '/testsubdir/');
        $this->assertEquals(1, $fileinfo['filecount']);
        $this->assertEquals($file->get_filesize(), $fileinfo['filesize']);
        $this->assertEquals(0, $fileinfo['foldercount']);   // No subdirectories inside the directory.
        $this->assertEquals($file->get_filesize(), $fileinfo['filesize_without_references']);
    }

    /**
     * Test confirming that draft files not referenced in the editor text are removed.
     */
    public function test_file_remove_editor_orphaned_files(): void {
        global $USER, $CFG;
        $this->resetAfterTest(true);
        $this->setAdminUser();

        // Create three draft files.
        $filerecord = ['filename'  => 'file1.png'];
        $file = self::create_draft_file($filerecord);
        $draftitemid = $file->get_itemid();

        $filerecord['itemid'] = $draftitemid;

        $filerecord['filename'] = 'file2.png';
        self::create_draft_file($filerecord);

        $filerecord['filename'] = 'file 3.png';
        self::create_draft_file($filerecord);

        $filerecord['filename'] = 'file4.png';
        self::create_draft_file($filerecord);

        // Confirm the user drafts area lists 3 files.
        $fs = get_file_storage();
        $usercontext = \context_user::instance($USER->id);
        $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'itemid', 0);
        $this->assertCount(4, $draftfiles);

        // Now, spoof some editor text content, referencing 2 of the files; one requiring name encoding, one not.
        $editor = [
            'itemid' => $draftitemid,
            'text' => "
                <img src=\"{$CFG->wwwroot}/draftfile.php/{$usercontext->id}/user/draft/{$draftitemid}/file%203.png\" alt=\"\">
                <img src=\"{$CFG->wwwroot}/draftfile.php/{$usercontext->id}/user/draft/{$draftitemid}/file1.png\" alt=\"\">
                <span>{$CFG->wwwroot}/draftfile.php/{$usercontext->id}/user/draft/{$draftitemid}/file4.png</span>"
        ];

        // Run the remove orphaned drafts function and confirm that only the referenced files remain in the user drafts.
        // The drafts we expect will not be removed (are referenced in the online text).
        $expected = ['file1.png', 'file 3.png', 'file4.png'];
        file_remove_editor_orphaned_files($editor);
        $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'itemid', 0);
        $this->assertCount(3, $draftfiles);
        foreach ($draftfiles as $file) {
            $this->assertContains($file->get_filename(), $expected);
        }
    }

    /**
     * Test that all files in the draftarea are returned.
     */
    public function test_file_get_all_files_in_draftarea(): void {
        $this->resetAfterTest();
        $this->setAdminUser();

        $filerecord = ['filename' => 'basepic.jpg'];
        $file = self::create_draft_file($filerecord);

        $secondrecord = [
            'filename' => 'infolder.jpg',
            'filepath' => '/assignment/',
            'itemid' => $file->get_itemid()
        ];
        $file = self::create_draft_file($secondrecord);

        $thirdrecord = [
            'filename' => 'deeperfolder.jpg',
            'filepath' => '/assignment/pics/',
            'itemid' => $file->get_itemid()
        ];
        $file = self::create_draft_file($thirdrecord);

        $fourthrecord = [
            'filename' => 'differentimage.jpg',
            'filepath' => '/secondfolder/',
            'itemid' => $file->get_itemid()
        ];
        $file = self::create_draft_file($fourthrecord);

        // This record has the same name as the last record, but it's in a different folder.
        // Just checking this is also returned.
        $fifthrecord = [
            'filename' => 'differentimage.jpg',
            'filepath' => '/assignment/pics/',
            'itemid' => $file->get_itemid()
        ];
        $file = self::create_draft_file($fifthrecord);

        $allfiles = file_get_all_files_in_draftarea($file->get_itemid());
        $this->assertCount(5, $allfiles);
        $this->assertEquals($filerecord['filename'], $allfiles[0]->filename);
        $this->assertEquals($secondrecord['filename'], $allfiles[1]->filename);
        $this->assertEquals($thirdrecord['filename'], $allfiles[2]->filename);
        $this->assertEquals($fourthrecord['filename'], $allfiles[3]->filename);
        $this->assertEquals($fifthrecord['filename'], $allfiles[4]->filename);
    }

    /**
     * Test that zip files in the draftarea are returned.
     * @covers ::file_get_all_files_in_draftarea
     */
    public function test_file_get_all_files_in_draftarea_zip_files(): void {
        $this->resetAfterTest();
        $this->setAdminUser();

        $zip1 = ['filename' => 'basezip.zip'];
        $file = self::create_draft_file($zip1);

        $zip2 = [
            'filename' => 'infolder.zip',
            'filepath' => '/assignment/',
            'itemid' => $file->get_itemid(),
        ];
        $file = self::create_draft_file($zip2);

        $otherfile = [
            'filename' => 'otherfile.txt',
            'filepath' => '/secondfolder/',
            'itemid' => $file->get_itemid(),
        ];
        $file = self::create_draft_file($otherfile);

        $allfiles = file_get_all_files_in_draftarea($file->get_itemid());
        $this->assertCount(3, $allfiles);
        $this->assertEquals($zip1['filename'], $allfiles[0]->filename);
        $this->assertEquals($zip2['filename'], $allfiles[1]->filename);
        $this->assertEquals($otherfile['filename'], $allfiles[2]->filename);
    }

    public function test_file_copy_file_to_file_area(): void {
        // Create two files in different draft areas but owned by the same user.
        global $USER;
        $this->resetAfterTest(true);
        $this->setAdminUser();

        $filerecord = ['filename'  => 'file1.png', 'itemid' => file_get_unused_draft_itemid()];
        $file1 = self::create_draft_file($filerecord);
        $filerecord = ['filename'  => 'file2.png', 'itemid' => file_get_unused_draft_itemid()];
        $file2 = self::create_draft_file($filerecord);

        // Confirm one file in each draft area.
        $fs = get_file_storage();
        $usercontext = \context_user::instance($USER->id);
        $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $file1->get_itemid(), 'itemid', 0);
        $this->assertCount(1, $draftfiles);
        $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $file2->get_itemid(), 'itemid', 0);
        $this->assertCount(1, $draftfiles);

        // Create file record.
        $filerecord = [
            'component' => $file2->get_component(),
            'filearea' => $file2->get_filearea(),
            'itemid' => $file2->get_itemid(),
            'contextid' => $file2->get_contextid(),
            'filepath' => '/',
            'filename' => $file2->get_filename()
        ];

        // Copy file2 into file1's draft area.
        file_copy_file_to_file_area($filerecord, $file2->get_filename(), $file1->get_itemid());
        $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $file1->get_itemid(), 'itemid', 0);
        $this->assertCount(2, $draftfiles);
        $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $file2->get_itemid(), 'itemid', 0);
        $this->assertCount(1, $draftfiles);
    }

    /**
     * Test file_is_draft_areas_limit_reached
     */
    public function test_file_is_draft_areas_limit_reached(): void {
        global $CFG;
        $this->resetAfterTest(true);

        $capacity = $CFG->draft_area_bucket_capacity = 5;
        $leak = $CFG->draft_area_bucket_leak = 0.2; // Leaks every 5 seconds.

        $generator = $this->getDataGenerator();
        $user = $generator->create_user();

        $this->setUser($user);

        $itemids = [];
        for ($i = 0; $i < $capacity; $i++) {
            $itemids[$i] = file_get_unused_draft_itemid();
        }

        // This test highly depends on time. We try to make sure that the test starts at the early moments on the second.
        // This was not needed if MDL-37327 was implemented.
        $after = time();
        while (time() === $after) {
            usleep(100000);
        }

        // Burst up to the capacity and make sure that the bucket allows it.
        $burststart = microtime();
        for ($i = 0; $i < $capacity; $i++) {
            if ($i) {
                sleep(1); // A little delay so we have different timemodified value for files.
            }
            $this->assertFalse(file_is_draft_areas_limit_reached($user->id));
            self::create_draft_file([
                'filename' => 'file1.png',
                'itemid' => $itemids[$i],
            ]);
        }

        // The bucket should be full after bursting.
        $this->assertTrue(file_is_draft_areas_limit_reached($user->id));

        // Calculate the time taken to burst up the bucket capacity.
        $timetaken = microtime_diff($burststart, microtime());

        // The bucket leaks so it shouldn't be full after a certain time.
        // Items are added into the bucket at the rate of 1 item per second.
        // One item leaks from the bucket every 1/$leak seconds.
        // So it takes 1/$leak - ($capacity-1) seconds for the bucket to leak one item and not be full anymore.
        $milliseconds = ceil(1000000 * ((1 / $leak) - ($capacity - 1)) - ($timetaken  * 1000));
        usleep($milliseconds);

        $this->assertFalse(file_is_draft_areas_limit_reached($user->id));

        // Only one item was leaked from the bucket. So the bucket should become full again if we add a single item to it.
        self::create_draft_file([
            'filename' => 'file2.png',
            'itemid' => $itemids[0],
        ]);
        $this->assertTrue(file_is_draft_areas_limit_reached($user->id));

        // The bucket leaks at a constant rate. It doesn't matter if it is filled as the result of bursting or not.
        sleep(ceil(1 / $leak));
        $this->assertFalse(file_is_draft_areas_limit_reached($user->id));
    }

    /**
     * Test text cleaning when preparing text editor data.
     *
     * @covers ::file_prepare_standard_editor
     */
    public function test_file_prepare_standard_editor_clean_text(): void {
        $text = "lala <object>xx</object>";

        $syscontext = \context_system::instance();

        $object = new \stdClass();
        $object->some = $text;
        $object->someformat = FORMAT_PLAIN;

        $result = file_prepare_standard_editor(clone($object), 'some',
            ['noclean' => false]);
        $this->assertSame($text, $result->some);
        $result = file_prepare_standard_editor(clone($object), 'some',
            ['noclean' => true]);
        $this->assertSame($text, $result->some);
        $result = file_prepare_standard_editor(clone($object), 'some',
            ['noclean' => false, 'context' => $syscontext], $syscontext, 'core', 'some', 1);
        $this->assertSame($text, $result->some);
        $result = file_prepare_standard_editor(clone($object), 'some',
            ['noclean' => true, 'context' => $syscontext], $syscontext, 'core', 'some', 1);
        $this->assertSame($text, $result->some);

        $object = new \stdClass();
        $object->some = $text;
        $object->someformat = FORMAT_MARKDOWN;

        $result = file_prepare_standard_editor(clone($object), 'some',
            ['noclean' => false]);
        $this->assertSame($text, $result->some);
        $result = file_prepare_standard_editor(clone($object), 'some',
            ['noclean' => true]);
        $this->assertSame($text, $result->some);
        $result = file_prepare_standard_editor(clone($object), 'some',
            ['noclean' => false, 'context' => $syscontext], $syscontext, 'core', 'some', 1);
        $this->assertSame($text, $result->some);
        $result = file_prepare_standard_editor(clone($object), 'some',
            ['noclean' => true, 'context' => $syscontext], $syscontext, 'core', 'some', 1);
        $this->assertSame($text, $result->some);

        $object = new \stdClass();
        $object->some = $text;
        $object->someformat = FORMAT_MOODLE;

        $result = file_prepare_standard_editor(clone($object), 'some',
            ['noclean' => false]);
        $this->assertSame('lala xx', $result->some);
        $result = file_prepare_standard_editor(clone($object), 'some',
            ['noclean' => true]);
        $this->assertSame($text, $result->some);
        $result = file_prepare_standard_editor(clone($object), 'some',
            ['noclean' => false, 'context' => $syscontext], $syscontext, 'core', 'some', 1);
        $this->assertSame('lala xx', $result->some);
        $result = file_prepare_standard_editor(clone($object), 'some',
            ['noclean' => true, 'context' => $syscontext], $syscontext, 'core', 'some', 1);
        $this->assertSame($text, $result->some);

        $object = new \stdClass();
        $object->some = $text;
        $object->someformat = FORMAT_HTML;

        $result = file_prepare_standard_editor(clone($object), 'some',
            ['noclean' => false]);
        $this->assertSame('lala xx', $result->some);
        $result = file_prepare_standard_editor(clone($object), 'some',
            ['noclean' => true]);
        $this->assertSame($text, $result->some);
        $result = file_prepare_standard_editor(clone($object), 'some',
            ['noclean' => false, 'context' => $syscontext], $syscontext, 'core', 'some', 1);
        $this->assertSame('lala xx', $result->some);
        $result = file_prepare_standard_editor(clone($object), 'some',
            ['noclean' => true, 'context' => $syscontext], $syscontext, 'core', 'some', 1);
        $this->assertSame($text, $result->some);
    }

    /**
     * Tests for file_get_typegroup to check that both arrays, and string values are accepted.
     *
     * @dataProvider file_get_typegroup_provider
     * @param string|array $group
     * @param string $expected
     */
    public function test_file_get_typegroup(
        string|array $group,
        string $expected,
    ): void {
        $result = file_get_typegroup('type', $group);
        $this->assertContains($expected, $result);
    }

    public static function file_get_typegroup_provider(): array {
        return [
            'Array of values' => [
                ['.html', '.htm'],
                'text/html',
            ],
            'String of comma-separated values' => [
                '.html, .htm',
                'text/html',
            ],
            'String of colon-separated values' => [
                '.html : .htm',
                'text/html',
            ],
            'String of semi-colon-separated values' => [
                '.html ; .htm',
                'text/html',
            ],
        ];
    }
}

/**
 * Test-specific class to allow easier testing of curl functions.
 *
 * @copyright 2015 Dave Cooper
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class testable_curl extends curl {
    /**
     * Accessor for private options array using reflection.
     *
     * @return array
     */
    public function get_options() {
        // Access to private property.
        $rp = new \ReflectionProperty('curl', 'options');
        return $rp->getValue($this);
    }

    /**
     * Setter for private options array using reflection.
     *
     * @param array $options
     */
    public function set_options($options) {
        // Access to private property.
        $rp = new \ReflectionProperty('curl', 'options');
        $rp->setValue($this, $options);
    }

    /**
     * Setter for individual option.
     * @param string $option
     * @param string $value
     */
    public function set_option($option, $value) {
        $options = $this->get_options();
        $options[$option] = $value;
        $this->set_options($options);
    }

    /**
     * Unsets an option on the curl object
     * @param string $option
     */
    public function unset_option($option) {
        $options = $this->get_options();
        unset($options[$option]);
        $this->set_options($options);
    }

    /**
     * Wrapper to access the private \curl::apply_opt() method using reflection.
     *
     * @param array $options
     * @return resource The curl handle
     */
    public function call_apply_opt($options = null) {
        // Access to private method.
        $rm = new \ReflectionMethod('curl', 'apply_opt');
        $ch = curl_init();
        return $rm->invoke($this, $ch, $options);
    }
}

Filemanager

Name Type Size Permission Actions
analytics Folder 0777
behat Folder 0777
classes Folder 0777
content Folder 0777
context Folder 0777
db Folder 0777
event Folder 0777
external Folder 0777
fixtures Folder 0777
hook Folder 0777
hub Folder 0777
lock Folder 0777
moodlenet Folder 0777
navigation Folder 0777
oauth2 Folder 0777
other Folder 0777
output Folder 0777
performance Folder 0777
plugininfo Folder 0777
privacy Folder 0777
route Folder 0777
router Folder 0777
session Folder 0777
task Folder 0777
accesslib_has_capability_test.php File 29.76 KB 0777
accesslib_test.php File 245.63 KB 0777
adminlib_test.php File 7.42 KB 0777
admintree_test.php File 18.08 KB 0777
ajaxlib_test.php File 4.45 KB 0777
analysers_test.php File 12.71 KB 0777
antivirus_test.php File 11.98 KB 0777
attribute_helper_test.php File 8.41 KB 0777
authlib_test.php File 22.97 KB 0777
behat_lib_test.php File 3.3 KB 0777
blocklib_test.php File 36.31 KB 0777
check_test.php File 2.31 KB 0777
client_test.php File 4.32 KB 0777
collator_test.php File 12.1 KB 0777
completionlib_test.php File 92.46 KB 0777
component_test.php File 49.28 KB 0777
componentlib_test.php File 6.93 KB 0777
configonlylib_test.php File 8.95 KB 0777
content_test.php File 4.79 KB 0777
context_block_test.php File 4.17 KB 0777
context_helper_test.php File 22.28 KB 0777
context_test.php File 3.42 KB 0777
core_media_player_native_test.php File 6.44 KB 0777
core_renderer_template_exploit_test.php File 16.54 KB 0777
core_renderer_test.php File 7.57 KB 0777
core_userfeedback_test.php File 2.3 KB 0777
coverage.php File 3.27 KB 0777
cron_test.php File 6.82 KB 0777
csvclass_test.php File 5.66 KB 0777
curl_security_helper_test.php File 14.88 KB 0777
customcontext_test.php File 4.67 KB 0777
dataformat_test.php File 4.18 KB 0777
datalib_test.php File 48.97 KB 0777
datalib_update_with_unique_index_test.php File 6.12 KB 0777
date_legacy_test.php File 13.67 KB 0777
date_test.php File 30.4 KB 0777
deprecation_test.php File 15.78 KB 0777
di_test.php File 5.33 KB 0777
editorlib_test.php File 1.96 KB 0777
emoticon_manager_test.php File 4.2 KB 0777
encryption_test.php File 9.48 KB 0777
environment_test.php File 9.12 KB 0777
exporter_test.php File 16.83 KB 0777
externallib_test.php File 2.03 KB 0777
filelib_test.php File 83.89 KB 0777
filestorage_zip_archive_test.php File 2.54 KB 0777
filetypes_test.php File 10.09 KB 0777
filter_manager_test.php File 3.33 KB 0777
filterlib_test.php File 37.09 KB 0777
formatting_test.php File 26.09 KB 0777
formslib_test.php File 40.1 KB 0777
gdlib_test.php File 5.73 KB 0777
googlelib_test.php File 1.62 KB 0777
gradelib_test.php File 12.01 KB 0777
grades_external_test.php File 11.22 KB 0777
grading_external_test.php File 26.55 KB 0777
graphlib_test.php File 7.14 KB 0777
grouplib_test.php File 110.79 KB 0777
h5p_clean_orphaned_records_task_test.php File 3.17 KB 0777
html2text_test.php File 8.82 KB 0777
htmlpurifier_test.php File 23.11 KB 0777
http_client_test.php File 14.67 KB 0777
ip_utils_test.php File 19.55 KB 0777
jquery_test.php File 1.59 KB 0777
ldaplib_test.php File 17.77 KB 0777
licenselib_test.php File 11.84 KB 0777
locale_test.php File 4.96 KB 0777
lock_config_test.php File 3.48 KB 0777
lock_test.php File 5.34 KB 0777
markdown_test.php File 2.27 KB 0777
mathslib_test.php File 13.51 KB 0777
medialib_test.php File 19.68 KB 0777
message_test.php File 16.43 KB 0777
messagelib_test.php File 56.44 KB 0777
minify_test.php File 3.15 KB 0777
modinfolib_test.php File 99.71 KB 0777
moodle_page_test.php File 34.62 KB 0777
moodlelib_current_language_test.php File 7.68 KB 0777
moodlelib_partial_test.php File 4.48 KB 0777
moodlelib_test.php File 239.56 KB 0777
myprofilelib_test.php File 12.2 KB 0777
navigationlib_test.php File 32.56 KB 0777
notification_test.php File 4.37 KB 0777
oauth2_test.php File 23.16 KB 0777
outputcomponents_test.php File 34.31 KB 0777
outputfactories_test.php File 6.83 KB 0777
outputrenderers_test.php File 1.63 KB 0777
outputrequirementslib_test.php File 15.72 KB 0777
param_test.php File 4.11 KB 0777
pdflib_test.php File 3.21 KB 0777
persistent_test.php File 30.68 KB 0777
phpxmlrpc_test.php File 2.05 KB 0777
plugin_manager_test.php File 31.47 KB 0777
portfoliolib_test.php File 8.41 KB 0777
progress_display_test.php File 3.76 KB 0777
progress_test.php File 14.53 KB 0777
qrcode_test.php File 1.69 KB 0777
questionlib_test.php File 108.63 KB 0777
regex_test.php File 1.56 KB 0777
report_helper_test.php File 7 KB 0777
requirejs_test.php File 2.23 KB 0777
router_test.php File 3.85 KB 0777
rsslib_test.php File 7.41 KB 0777
rtlcss_test.php File 57.07 KB 0777
sample_questions.ser File 141.76 KB 0777
sample_questions.xml File 102.62 KB 0777
sample_questions_with_old_image_tag.ser File 4.85 KB 0777
sample_questions_with_old_image_tag.xml File 4.08 KB 0777
sample_questions_wrong.xml File 102.57 KB 0777
scss_test.php File 4.31 KB 0777
session_redis_cluster_test.php File 4.17 KB 0777
sessionlib_test.php File 12.37 KB 0777
setuplib_test.php File 20.21 KB 0777
statslib_test.php File 26.82 KB 0777
stored_progress_bar_test.php File 7.17 KB 0777
string_manager_standard_test.php File 10.23 KB 0777
system_clock_test.php File 2.42 KB 0777
text_test.php File 26.68 KB 0777
theme_config_test.php File 7.25 KB 0777
update_api_test.php File 6.65 KB 0777
update_checker_test.php File 10.91 KB 0777
update_code_manager_test.php File 9.12 KB 0777
update_validator_test.php File 18.32 KB 0777
upgrade_util_test.php File 5.36 KB 0777
upgradelib_test.php File 75.43 KB 0777
url_test.php File 25.48 KB 0777
user_menu_test.php File 3.83 KB 0777
user_test.php File 42.3 KB 0777
useragent_test.php File 67.06 KB 0777
weblib_format_text_test.php File 14.21 KB 0777
weblib_test.php File 42.12 KB 0777
xhprof_test.php File 10.05 KB 0777
xmlize_test.php File 2.57 KB 0777
xsendfilelib_test.php File 5 KB 0777
Filemanager