Merge branch 'MDL-36754-master' of git://github.com/andrewnicols/moodle

This commit is contained in:
David Monllao 2018-08-14 08:49:45 +02:00
commit 3ff9233a18
18 changed files with 698 additions and 285 deletions

View File

@ -27,6 +27,8 @@ namespace core_files\privacy;
defined('MOODLE_INTERNAL') || die();
use core_privacy\local\metadata\collection;
use core_privacy\local\request\contextlist;
use core_privacy\local\request\approved_contextlist;
/**
* Data provider class.
@ -41,7 +43,10 @@ use core_privacy\local\metadata\collection;
*/
class provider implements
\core_privacy\local\metadata\provider,
\core_privacy\local\request\subsystem\plugin_provider {
\core_privacy\local\request\subsystem\plugin_provider,
// We store a userkey for token-based file access.
\core_privacy\local\request\subsystem\provider {
/**
* Returns metadata.
@ -65,7 +70,95 @@ class provider implements
'timemodified' => 'privacy:metadata:files:timemodified',
], 'privacy:metadata:files');
$collection->add_subsystem_link('core_userkey', [], 'privacy:metadata:core_userkey');
return $collection;
}
/**
* Get the list of contexts that contain user information for the specified user.
*
* This is currently just the user context.
*
* @param int $userid The user to search.
* @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
*/
public static function get_contexts_for_userid(int $userid) : contextlist {
$sql = "SELECT ctx.id
FROM {user_private_key} k
JOIN {user} u ON k.userid = u.id
JOIN {context} ctx ON ctx.instanceid = u.id AND ctx.contextlevel = :contextlevel
WHERE k.userid = :userid AND k.script = :script";
$params = [
'userid' => $userid,
'contextlevel' => CONTEXT_USER,
'script' => 'core_files',
];
$contextlist = new contextlist();
$contextlist->add_from_sql($sql, $params);
return $contextlist;
}
/**
* Export all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts to export information for.
*/
public static function export_user_data(approved_contextlist $contextlist) {
// If the user has data, then only the CONTEXT_USER should be present so get the first context.
$contexts = $contextlist->get_contexts();
if (count($contexts) == 0) {
return;
}
// Sanity check that context is at the user context level, then get the userid.
$context = reset($contexts);
if ($context->contextlevel !== CONTEXT_USER) {
return;
}
// Export associated userkeys.
$subcontext = [
get_string('files'),
];
\core_userkey\privacy\provider::export_userkeys($context, $subcontext, 'core_files');
}
/**
* Delete all use data which matches the specified deletion_criteria.
*
* @param context $context A user context.
*/
public static function delete_data_for_all_users_in_context(\context $context) {
// Sanity check that context is at the user context level, then get the userid.
if ($context->contextlevel !== CONTEXT_USER) {
return;
}
// Delete all the userkeys.
\core_userkey\privacy\provider::delete_userkeys('core_files', $context->instanceid);
}
/**
* Delete all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
*/
public static function delete_data_for_user(approved_contextlist $contextlist) {
// If the user has data, then only the user context should be present so get the first context.
$contexts = $contextlist->get_contexts();
if (count($contexts) == 0) {
return;
}
// Sanity check that context is at the user context level, then get the userid.
$context = reset($contexts);
if ($context->contextlevel !== CONTEXT_USER) {
return;
}
// Delete all the userkeys for core_files..
\core_userkey\privacy\provider::delete_userkeys('core_files', $context->instanceid);
}
}

View File

@ -37,3 +37,4 @@ $string['privacy:metadata:files:source'] = 'The source of the file';
$string['privacy:metadata:files:timecreated'] = 'The time when the file was created';
$string['privacy:metadata:files:timemodified'] = 'The time when the file was last modified';
$string['privacy:metadata:files:userid'] = 'The user who created the file';
$string['privacy:metadata:core_userkey'] = 'A private token is generated and stored. This token can be used to access Moodle files without requiring you to log in.';

View File

@ -455,30 +455,41 @@ function file_prepare_draft_area(&$draftitemid, $contextid, $component, $fileare
* Passing a new option reverse = true in the $options var will make the function to convert actual URLs in $text to encoded URLs
* in the @@PLUGINFILE@@ form.
*
* @category files
* @global stdClass $CFG
* @param string $text The content that may contain ULRs in need of rewriting.
* @param string $file The script that should be used to serve these files. pluginfile.php, draftfile.php, etc.
* @param int $contextid This parameter and the next two identify the file area to use.
* @param string $component
* @param string $filearea helps identify the file area.
* @param int $itemid helps identify the file area.
* @param array $options text and file options ('forcehttps'=>false), use reverse = true to reverse the behaviour of the function.
* @return string the processed text.
* @param string $text The content that may contain ULRs in need of rewriting.
* @param string $file The script that should be used to serve these files. pluginfile.php, draftfile.php, etc.
* @param int $contextid This parameter and the next two identify the file area to use.
* @param string $component
* @param string $filearea helps identify the file area.
* @param int $itemid helps identify the file area.
* @param array $options
* bool $options.forcehttps Force the user of https
* bool $options.reverse Reverse the behaviour of the function
* bool $options.includetoken Use a token for authentication
* string The processed text.
*/
function file_rewrite_pluginfile_urls($text, $file, $contextid, $component, $filearea, $itemid, array $options=null) {
global $CFG;
global $CFG, $USER;
$options = (array)$options;
if (!isset($options['forcehttps'])) {
$options['forcehttps'] = false;
}
if (!$CFG->slasharguments) {
$file = $file . '?file=';
$baseurl = "{$CFG->wwwroot}/{$file}";
if (!empty($options['includetoken'])) {
$token = get_user_key('core_files', $USER->id);
$finalfile = basename($file);
$tokenfile = "token{$finalfile}";
$file = substr($file, 0, strlen($file) - strlen($finalfile)) . $tokenfile;
if (!$CFG->slasharguments) {
$baseurl .= "?token={$token}&file=";
} else {
$baseurl .= "/{$token}";
}
}
$baseurl = "$CFG->wwwroot/$file/$contextid/$component/$filearea/";
$baseurl .= "/{$contextid}/{$component}/{$filearea}/";
if ($itemid !== null) {
$baseurl .= "$itemid/";

View File

@ -3112,9 +3112,10 @@ function validate_user_key($keyvalue, $script, $instance) {
* @uses PARAM_ALPHANUM
* @param string $script unique script identifier
* @param int $instance optional instance id
* @param string $keyvalue The key. If not supplied, this will be fetched from the current session.
* @return int Instance ID
*/
function require_user_key_login($script, $instance=null) {
function require_user_key_login($script, $instance = null, $keyvalue = null) {
global $DB;
if (!NO_MOODLE_COOKIES) {
@ -3124,7 +3125,9 @@ function require_user_key_login($script, $instance=null) {
// Extra safety.
\core\session\manager::write_close();
$keyvalue = required_param('key', PARAM_ALPHANUM);
if (null === $keyvalue) {
$keyvalue = required_param('key', PARAM_ALPHANUM);
}
$key = validate_user_key($keyvalue, $script, $instance);

View File

@ -206,6 +206,11 @@ class user_picture implements renderable {
*/
public $includefullname = false;
/**
* @var bool Include user authentication token.
*/
public $includetoken = false;
/**
* User picture constructor.
*
@ -403,7 +408,8 @@ class user_picture implements renderable {
$path .= $page->theme->name.'/';
}
// Set the image URL to the URL for the uploaded file and return.
$url = moodle_url::make_pluginfile_url($contextid, 'user', 'icon', NULL, $path, $filename);
$url = moodle_url::make_pluginfile_url(
$contextid, 'user', 'icon', null, $path, $filename, false, $this->includetoken);
$url->param('rev', $this->user->picture);
return $url;
}

View File

@ -2503,6 +2503,7 @@ class core_renderer extends renderer_base {
* - class = image class attribute (default 'userpicture')
* - visibletoscreenreaders=true (whether to be visible to screen readers)
* - includefullname=false (whether to include the user's full name together with the user picture)
* - includetoken = false
* @return string HTML fragment
*/
public function user_picture(stdClass $user, array $options = null) {

View File

@ -1052,6 +1052,79 @@ EOF;
$this->assertEquals($originaltext, $finaltext);
}
/**
* Test file_rewrite_pluginfile_urls with includetoken.
*/
public function test_file_rewrite_pluginfile_urls_includetoken() {
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);
}
/**
* Test file_rewrite_pluginfile_urls with includetoken with slasharguments disabled..
*/
public function test_file_rewrite_pluginfile_urls_includetoken_no_slashargs() {
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
*

View File

@ -0,0 +1,347 @@
<?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/>.
/**
* Tests for moodle_url.
*
* @package core
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Tests for moodle_url.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_moodle_url_testcase extends advanced_testcase {
/**
* Test basic moodle_url construction.
*/
public function test_moodle_url_constructor() {
global $CFG;
$url = new moodle_url('/index.php');
$this->assertSame($CFG->wwwroot.'/index.php', $url->out());
$url = new moodle_url('/index.php', array());
$this->assertSame($CFG->wwwroot.'/index.php', $url->out());
$url = new moodle_url('/index.php', array('id' => 2));
$this->assertSame($CFG->wwwroot.'/index.php?id=2', $url->out());
$url = new moodle_url('/index.php', array('id' => 'two'));
$this->assertSame($CFG->wwwroot.'/index.php?id=two', $url->out());
$url = new moodle_url('/index.php', array('id' => 1, 'cid' => '2'));
$this->assertSame($CFG->wwwroot.'/index.php?id=1&amp;cid=2', $url->out());
$this->assertSame($CFG->wwwroot.'/index.php?id=1&cid=2', $url->out(false));
$url = new moodle_url('/index.php', null, 'test');
$this->assertSame($CFG->wwwroot.'/index.php#test', $url->out());
$url = new moodle_url('/index.php', array('id' => 2), 'test');
$this->assertSame($CFG->wwwroot.'/index.php?id=2#test', $url->out());
}
/**
* Tests moodle_url::get_path().
*/
public function test_moodle_url_get_path() {
$url = new moodle_url('http://www.example.org:447/my/file/is/here.txt?really=1');
$this->assertSame('/my/file/is/here.txt', $url->get_path());
$url = new moodle_url('http://www.example.org/');
$this->assertSame('/', $url->get_path());
$url = new moodle_url('http://www.example.org/pluginfile.php/slash/arguments');
$this->assertSame('/pluginfile.php/slash/arguments', $url->get_path());
$this->assertSame('/pluginfile.php', $url->get_path(false));
}
public function test_moodle_url_round_trip() {
$strurl = 'http://moodle.org/course/view.php?id=5';
$url = new moodle_url($strurl);
$this->assertSame($strurl, $url->out(false));
$strurl = 'http://moodle.org/user/index.php?contextid=53&sifirst=M&silast=D';
$url = new moodle_url($strurl);
$this->assertSame($strurl, $url->out(false));
}
/**
* Test Moodle URL objects created with a param with empty value.
*/
public function test_moodle_url_empty_param_values() {
$strurl = 'http://moodle.org/course/view.php?id=0';
$url = new moodle_url($strurl, array('id' => 0));
$this->assertSame($strurl, $url->out(false));
$strurl = 'http://moodle.org/course/view.php?id';
$url = new moodle_url($strurl, array('id' => false));
$this->assertSame($strurl, $url->out(false));
$strurl = 'http://moodle.org/course/view.php?id';
$url = new moodle_url($strurl, array('id' => null));
$this->assertSame($strurl, $url->out(false));
$strurl = 'http://moodle.org/course/view.php?id';
$url = new moodle_url($strurl, array('id' => ''));
$this->assertSame($strurl, $url->out(false));
$strurl = 'http://moodle.org/course/view.php?id';
$url = new moodle_url($strurl);
$this->assertSame($strurl, $url->out(false));
}
/**
* Test set good scheme on Moodle URL objects.
*/
public function test_moodle_url_set_good_scheme() {
$url = new moodle_url('http://moodle.org/foo/bar');
$url->set_scheme('myscheme');
$this->assertSame('myscheme://moodle.org/foo/bar', $url->out());
}
/**
* Test set bad scheme on Moodle URL objects.
*
* @expectedException coding_exception
*/
public function test_moodle_url_set_bad_scheme() {
$url = new moodle_url('http://moodle.org/foo/bar');
$url->set_scheme('not a valid $ scheme');
}
public function test_moodle_url_round_trip_array_params() {
$strurl = 'http://example.com/?a%5B1%5D=1&a%5B2%5D=2';
$url = new moodle_url($strurl);
$this->assertSame($strurl, $url->out(false));
$url = new moodle_url('http://example.com/?a[1]=1&a[2]=2');
$this->assertSame($strurl, $url->out(false));
// For un-keyed array params, we expect 0..n keys to be returned.
$strurl = 'http://example.com/?a%5B0%5D=0&a%5B1%5D=1';
$url = new moodle_url('http://example.com/?a[]=0&a[]=1');
$this->assertSame($strurl, $url->out(false));
}
public function test_compare_url() {
$url1 = new moodle_url('index.php', array('var1' => 1, 'var2' => 2));
$url2 = new moodle_url('index2.php', array('var1' => 1, 'var2' => 2, 'var3' => 3));
$this->assertFalse($url1->compare($url2, URL_MATCH_BASE));
$this->assertFalse($url1->compare($url2, URL_MATCH_PARAMS));
$this->assertFalse($url1->compare($url2, URL_MATCH_EXACT));
$url2 = new moodle_url('index.php', array('var1' => 1, 'var3' => 3));
$this->assertTrue($url1->compare($url2, URL_MATCH_BASE));
$this->assertFalse($url1->compare($url2, URL_MATCH_PARAMS));
$this->assertFalse($url1->compare($url2, URL_MATCH_EXACT));
$url2 = new moodle_url('index.php', array('var1' => 1, 'var2' => 2, 'var3' => 3));
$this->assertTrue($url1->compare($url2, URL_MATCH_BASE));
$this->assertTrue($url1->compare($url2, URL_MATCH_PARAMS));
$this->assertFalse($url1->compare($url2, URL_MATCH_EXACT));
$url2 = new moodle_url('index.php', array('var2' => 2, 'var1' => 1));
$this->assertTrue($url1->compare($url2, URL_MATCH_BASE));
$this->assertTrue($url1->compare($url2, URL_MATCH_PARAMS));
$this->assertTrue($url1->compare($url2, URL_MATCH_EXACT));
$url1->set_anchor('test');
$this->assertTrue($url1->compare($url2, URL_MATCH_BASE));
$this->assertTrue($url1->compare($url2, URL_MATCH_PARAMS));
$this->assertFalse($url1->compare($url2, URL_MATCH_EXACT));
$url2->set_anchor('test');
$this->assertTrue($url1->compare($url2, URL_MATCH_BASE));
$this->assertTrue($url1->compare($url2, URL_MATCH_PARAMS));
$this->assertTrue($url1->compare($url2, URL_MATCH_EXACT));
}
public function test_out_as_local_url() {
global $CFG;
// Test http url.
$url1 = new moodle_url('/lib/tests/weblib_test.php');
$this->assertSame('/lib/tests/weblib_test.php', $url1->out_as_local_url());
// Test https url.
$httpswwwroot = str_replace("http://", "https://", $CFG->wwwroot);
$url2 = new moodle_url($httpswwwroot.'/login/profile.php');
$this->assertSame('/login/profile.php', $url2->out_as_local_url());
// Test http url matching wwwroot.
$url3 = new moodle_url($CFG->wwwroot);
$this->assertSame('', $url3->out_as_local_url());
// Test http url matching wwwroot ending with slash (/).
$url3 = new moodle_url($CFG->wwwroot.'/');
$this->assertSame('/', $url3->out_as_local_url());
}
/**
* @expectedException coding_exception
* @return void
*/
public function test_out_as_local_url_error() {
$url2 = new moodle_url('http://www.google.com/lib/tests/weblib_test.php');
$url2->out_as_local_url();
}
/**
* You should get error with modified url
*
* @expectedException coding_exception
* @return void
*/
public function test_modified_url_out_as_local_url_error() {
global $CFG;
$modifiedurl = $CFG->wwwroot.'1';
$url3 = new moodle_url($modifiedurl.'/login/profile.php');
$url3->out_as_local_url();
}
/**
* Try get local url from external https url and you should get error
*
* @expectedException coding_exception
*/
public function test_https_out_as_local_url_error() {
$url4 = new moodle_url('https://www.google.com/lib/tests/weblib_test.php');
$url4->out_as_local_url();
}
public function test_moodle_url_get_scheme() {
// Should return the scheme only.
$url = new moodle_url('http://www.example.org:447/my/file/is/here.txt?really=1');
$this->assertSame('http', $url->get_scheme());
// Should work for secure URLs.
$url = new moodle_url('https://www.example.org:447/my/file/is/here.txt?really=1');
$this->assertSame('https', $url->get_scheme());
// Should return an empty string if no scheme is specified.
$url = new moodle_url('www.example.org:447/my/file/is/here.txt?really=1');
$this->assertSame('', $url->get_scheme());
}
public function test_moodle_url_get_host() {
// Should return the host part only.
$url = new moodle_url('http://www.example.org:447/my/file/is/here.txt?really=1');
$this->assertSame('www.example.org', $url->get_host());
}
public function test_moodle_url_get_port() {
// Should return the port if one provided.
$url = new moodle_url('http://www.example.org:447/my/file/is/here.txt?really=1');
$this->assertSame(447, $url->get_port());
// Should return an empty string if port not specified.
$url = new moodle_url('http://www.example.org/some/path/here.php');
$this->assertSame('', $url->get_port());
}
/**
* Test the make_pluginfile_url function.
*
* @dataProvider make_pluginfile_url_provider
* @param bool $slashargs
* @param array $args Args to be provided to make_pluginfile_url
* @param string $expected The expected result
*/
public function test_make_pluginfile_url($slashargs, $args, $expected) {
global $CFG;
$this->resetAfterTest();
$CFG->slasharguments = $slashargs;
$url = call_user_func_array('moodle_url::make_pluginfile_url', $args);
$this->assertRegexp($expected, $url->out(true));
}
/**
* Data provider for make_pluginfile_url tests.
*
* @return array[]
*/
public function make_pluginfile_url_provider() {
$baseurl = "https://www.example.com/moodle/pluginfile.php";
$tokenbaseurl = "https://www.example.com/moodle/tokenpluginfile.php";
return [
'Standard with slashargs' => [
'slashargs' => true,
'args' => [
1,
'mod_forum',
'posts',
422,
'/my/location/',
'file.png',
],
'expected' => "@{$baseurl}/1/mod_forum/posts/422/my/location/file.png@",
],
'Standard without slashargs' => [
'slashargs' => false,
'args' => [
1,
'mod_forum',
'posts',
422,
'/my/location/',
'file.png',
],
'expected' => "@{$baseurl}\?file=%2F1%2Fmod_forum%2Fposts%2F422%2Fmy%2Flocation%2Ffile.png@",
],
'Token included with slashargs' => [
'slashargs' => true,
'args' => [
1,
'mod_forum',
'posts',
422,
'/my/location/',
'file.png',
false,
true,
],
'expected' => "@{$tokenbaseurl}/[^/]*/1/mod_forum/posts/422/my/location/file.png@",
],
'Token included without slashargs' => [
'slashargs' => false,
'args' => [
1,
'mod_forum',
'posts',
422,
'/my/location/',
'file.png',
false,
true,
],
'expected' => "@{$tokenbaseurl}\?file=%2F1%2Fmod_forum%2Fposts%2F422%2Fmy%2Flocation%2Ffile.png&amp;token=[a-z0-9]*@",
],
];
}
}

View File

@ -244,238 +244,6 @@ class core_weblib_testcase extends advanced_testcase {
$this->assertSame('this is a link [ http://someaddress.com/query ]', wikify_links('this is a <a href="http://someaddress.com/query">link</a>'));
}
/**
* Test basic moodle_url construction.
*/
public function test_moodle_url_constructor() {
global $CFG;
$url = new moodle_url('/index.php');
$this->assertSame($CFG->wwwroot.'/index.php', $url->out());
$url = new moodle_url('/index.php', array());
$this->assertSame($CFG->wwwroot.'/index.php', $url->out());
$url = new moodle_url('/index.php', array('id' => 2));
$this->assertSame($CFG->wwwroot.'/index.php?id=2', $url->out());
$url = new moodle_url('/index.php', array('id' => 'two'));
$this->assertSame($CFG->wwwroot.'/index.php?id=two', $url->out());
$url = new moodle_url('/index.php', array('id' => 1, 'cid' => '2'));
$this->assertSame($CFG->wwwroot.'/index.php?id=1&amp;cid=2', $url->out());
$this->assertSame($CFG->wwwroot.'/index.php?id=1&cid=2', $url->out(false));
$url = new moodle_url('/index.php', null, 'test');
$this->assertSame($CFG->wwwroot.'/index.php#test', $url->out());
$url = new moodle_url('/index.php', array('id' => 2), 'test');
$this->assertSame($CFG->wwwroot.'/index.php?id=2#test', $url->out());
}
/**
* Tests moodle_url::get_path().
*/
public function test_moodle_url_get_path() {
$url = new moodle_url('http://www.example.org:447/my/file/is/here.txt?really=1');
$this->assertSame('/my/file/is/here.txt', $url->get_path());
$url = new moodle_url('http://www.example.org/');
$this->assertSame('/', $url->get_path());
$url = new moodle_url('http://www.example.org/pluginfile.php/slash/arguments');
$this->assertSame('/pluginfile.php/slash/arguments', $url->get_path());
$this->assertSame('/pluginfile.php', $url->get_path(false));
}
public function test_moodle_url_round_trip() {
$strurl = 'http://moodle.org/course/view.php?id=5';
$url = new moodle_url($strurl);
$this->assertSame($strurl, $url->out(false));
$strurl = 'http://moodle.org/user/index.php?contextid=53&sifirst=M&silast=D';
$url = new moodle_url($strurl);
$this->assertSame($strurl, $url->out(false));
}
/**
* Test Moodle URL objects created with a param with empty value.
*/
public function test_moodle_url_empty_param_values() {
$strurl = 'http://moodle.org/course/view.php?id=0';
$url = new moodle_url($strurl, array('id' => 0));
$this->assertSame($strurl, $url->out(false));
$strurl = 'http://moodle.org/course/view.php?id';
$url = new moodle_url($strurl, array('id' => false));
$this->assertSame($strurl, $url->out(false));
$strurl = 'http://moodle.org/course/view.php?id';
$url = new moodle_url($strurl, array('id' => null));
$this->assertSame($strurl, $url->out(false));
$strurl = 'http://moodle.org/course/view.php?id';
$url = new moodle_url($strurl, array('id' => ''));
$this->assertSame($strurl, $url->out(false));
$strurl = 'http://moodle.org/course/view.php?id';
$url = new moodle_url($strurl);
$this->assertSame($strurl, $url->out(false));
}
/**
* Test set good scheme on Moodle URL objects.
*/
public function test_moodle_url_set_good_scheme() {
$url = new moodle_url('http://moodle.org/foo/bar');
$url->set_scheme('myscheme');
$this->assertSame('myscheme://moodle.org/foo/bar', $url->out());
}
/**
* Test set bad scheme on Moodle URL objects.
*
* @expectedException coding_exception
*/
public function test_moodle_url_set_bad_scheme() {
$url = new moodle_url('http://moodle.org/foo/bar');
$url->set_scheme('not a valid $ scheme');
}
public function test_moodle_url_round_trip_array_params() {
$strurl = 'http://example.com/?a%5B1%5D=1&a%5B2%5D=2';
$url = new moodle_url($strurl);
$this->assertSame($strurl, $url->out(false));
$url = new moodle_url('http://example.com/?a[1]=1&a[2]=2');
$this->assertSame($strurl, $url->out(false));
// For un-keyed array params, we expect 0..n keys to be returned.
$strurl = 'http://example.com/?a%5B0%5D=0&a%5B1%5D=1';
$url = new moodle_url('http://example.com/?a[]=0&a[]=1');
$this->assertSame($strurl, $url->out(false));
}
public function test_compare_url() {
$url1 = new moodle_url('index.php', array('var1' => 1, 'var2' => 2));
$url2 = new moodle_url('index2.php', array('var1' => 1, 'var2' => 2, 'var3' => 3));
$this->assertFalse($url1->compare($url2, URL_MATCH_BASE));
$this->assertFalse($url1->compare($url2, URL_MATCH_PARAMS));
$this->assertFalse($url1->compare($url2, URL_MATCH_EXACT));
$url2 = new moodle_url('index.php', array('var1' => 1, 'var3' => 3));
$this->assertTrue($url1->compare($url2, URL_MATCH_BASE));
$this->assertFalse($url1->compare($url2, URL_MATCH_PARAMS));
$this->assertFalse($url1->compare($url2, URL_MATCH_EXACT));
$url2 = new moodle_url('index.php', array('var1' => 1, 'var2' => 2, 'var3' => 3));
$this->assertTrue($url1->compare($url2, URL_MATCH_BASE));
$this->assertTrue($url1->compare($url2, URL_MATCH_PARAMS));
$this->assertFalse($url1->compare($url2, URL_MATCH_EXACT));
$url2 = new moodle_url('index.php', array('var2' => 2, 'var1' => 1));
$this->assertTrue($url1->compare($url2, URL_MATCH_BASE));
$this->assertTrue($url1->compare($url2, URL_MATCH_PARAMS));
$this->assertTrue($url1->compare($url2, URL_MATCH_EXACT));
$url1->set_anchor('test');
$this->assertTrue($url1->compare($url2, URL_MATCH_BASE));
$this->assertTrue($url1->compare($url2, URL_MATCH_PARAMS));
$this->assertFalse($url1->compare($url2, URL_MATCH_EXACT));
$url2->set_anchor('test');
$this->assertTrue($url1->compare($url2, URL_MATCH_BASE));
$this->assertTrue($url1->compare($url2, URL_MATCH_PARAMS));
$this->assertTrue($url1->compare($url2, URL_MATCH_EXACT));
}
public function test_out_as_local_url() {
global $CFG;
// Test http url.
$url1 = new moodle_url('/lib/tests/weblib_test.php');
$this->assertSame('/lib/tests/weblib_test.php', $url1->out_as_local_url());
// Test https url.
$httpswwwroot = str_replace("http://", "https://", $CFG->wwwroot);
$url2 = new moodle_url($httpswwwroot.'/login/profile.php');
$this->assertSame('/login/profile.php', $url2->out_as_local_url());
// Test http url matching wwwroot.
$url3 = new moodle_url($CFG->wwwroot);
$this->assertSame('', $url3->out_as_local_url());
// Test http url matching wwwroot ending with slash (/).
$url3 = new moodle_url($CFG->wwwroot.'/');
$this->assertSame('/', $url3->out_as_local_url());
}
/**
* @expectedException coding_exception
* @return void
*/
public function test_out_as_local_url_error() {
$url2 = new moodle_url('http://www.google.com/lib/tests/weblib_test.php');
$url2->out_as_local_url();
}
/**
* You should get error with modified url
*
* @expectedException coding_exception
* @return void
*/
public function test_modified_url_out_as_local_url_error() {
global $CFG;
$modifiedurl = $CFG->wwwroot.'1';
$url3 = new moodle_url($modifiedurl.'/login/profile.php');
$url3->out_as_local_url();
}
/**
* Try get local url from external https url and you should get error
*
* @expectedException coding_exception
*/
public function test_https_out_as_local_url_error() {
$url4 = new moodle_url('https://www.google.com/lib/tests/weblib_test.php');
$url4->out_as_local_url();
}
public function test_moodle_url_get_scheme() {
// Should return the scheme only.
$url = new moodle_url('http://www.example.org:447/my/file/is/here.txt?really=1');
$this->assertSame('http', $url->get_scheme());
// Should work for secure URLs.
$url = new moodle_url('https://www.example.org:447/my/file/is/here.txt?really=1');
$this->assertSame('https', $url->get_scheme());
// Should return an empty string if no scheme is specified.
$url = new moodle_url('www.example.org:447/my/file/is/here.txt?really=1');
$this->assertSame('', $url->get_scheme());
}
public function test_moodle_url_get_host() {
// Should return the host part only.
$url = new moodle_url('http://www.example.org:447/my/file/is/here.txt?really=1');
$this->assertSame('www.example.org', $url->get_host());
}
public function test_moodle_url_get_port() {
// Should return the port if one provided.
$url = new moodle_url('http://www.example.org:447/my/file/is/here.txt?really=1');
$this->assertSame(447, $url->get_port());
// Should return an empty string if port not specified.
$url = new moodle_url('http://www.example.org/some/path/here.php');
$this->assertSame('', $url->get_port());
}
public function test_clean_text() {
$text = "lala <applet>xx</applet>";
$this->assertSame($text, clean_text($text, FORMAT_PLAIN));

View File

@ -3,6 +3,14 @@ information provided here is intended especially for developers.
=== 3.6 ===
* A new token-based version of pluginfile.php has been added which can be used for out-of-session file serving by
setting the `$includetoken` parameter to true on the `moodle_url::make_pluginfile_url()`, and
`moodle_url::make_file_url()` functions.
* The following picture functions have been updated to support use of the new token-based file serving:
- print_group_picture
- get_group_picture_url
* The `user_picture` class has a new public `$includetoken` property which can be set to make use of the new token-based
file serving.
* Custom AJAX handlers for the form autocomplete fields can now optionally return string in their processResults()
callback. If a string is returned, it is displayed instead of the list of suggested items. This can be used, for
example, to inform the user that there are too many items matching the current search criteria.

View File

@ -773,17 +773,41 @@ class moodle_url {
* @param string $pathname
* @param string $filename
* @param bool $forcedownload
* @param boolean $includetoken Whether to use a user token when displaying this group image.
* If the group picture is included in an e-mail or some other location where the audience is a specific
* user who will not be logged in when viewing, then we use a token to authenticate the user.
* @return moodle_url
*/
public static function make_pluginfile_url($contextid, $component, $area, $itemid, $pathname, $filename,
$forcedownload = false) {
global $CFG;
$urlbase = "$CFG->wwwroot/pluginfile.php";
if ($itemid === null) {
return self::make_file_url($urlbase, "/$contextid/$component/$area".$pathname.$filename, $forcedownload);
$forcedownload = false, $includetoken = false) {
global $CFG, $USER;
$path = [];
if ($includetoken) {
$urlbase = "$CFG->wwwroot/tokenpluginfile.php";
$token = get_user_key('core_files', $USER->id);
if ($CFG->slasharguments) {
$path[] = $token;
}
} else {
return self::make_file_url($urlbase, "/$contextid/$component/$area/$itemid".$pathname.$filename, $forcedownload);
$urlbase = "$CFG->wwwroot/pluginfile.php";
}
$path[] = $contextid;
$path[] = $component;
$path[] = $area;
if ($itemid !== null) {
$path[] = $itemid;
}
$path = "/" . implode('/', $path) . "{$pathname}{$filename}";
$url = self::make_file_url($urlbase, $path, $forcedownload, $includetoken);
if ($includetoken && empty($CFG->slasharguments)) {
$url->param('token', $token);
}
return $url;
}
/**
@ -2468,15 +2492,18 @@ function print_collapsible_region_end($return = false) {
* @param boolean $large Default small picture, or large.
* @param boolean $return If false print picture, otherwise return the output as string
* @param boolean $link Enclose image in a link to view specified course?
* @param boolean $includetoken Whether to use a user token when displaying this group image.
* If the group picture is included in an e-mail or some other location where the audience is a specific
* user who will not be logged in when viewing, then we use a token to authenticate the user.
* @return string|void Depending on the setting of $return
*/
function print_group_picture($group, $courseid, $large=false, $return=false, $link=true) {
function print_group_picture($group, $courseid, $large = false, $return = false, $link = true, $includetoken = false) {
global $CFG;
if (is_array($group)) {
$output = '';
foreach ($group as $g) {
$output .= print_group_picture($g, $courseid, $large, true, $link);
$output .= print_group_picture($g, $courseid, $large, true, $link, $includetoken);
}
if ($return) {
return $output;
@ -2486,7 +2513,7 @@ function print_group_picture($group, $courseid, $large=false, $return=false, $li
}
}
$pictureurl = get_group_picture_url($group, $courseid, $large);
$pictureurl = get_group_picture_url($group, $courseid, $large, $includetoken);
// If there is no picture, do nothing.
if (!isset($pictureurl)) {
@ -2519,9 +2546,12 @@ function print_group_picture($group, $courseid, $large=false, $return=false, $li
* @param stdClass $group A group object.
* @param int $courseid The course ID for the group.
* @param bool $large A large or small group picture? Default is small.
* @param boolean $includetoken Whether to use a user token when displaying this group image.
* If the group picture is included in an e-mail or some other location where the audience is a specific
* user who will not be logged in when viewing, then we use a token to authenticate the user.
* @return moodle_url Returns the url for the group picture.
*/
function get_group_picture_url($group, $courseid, $large = false) {
function get_group_picture_url($group, $courseid, $large = false, $includetoken = false) {
global $CFG;
$context = context_course::instance($courseid);
@ -2542,7 +2572,8 @@ function get_group_picture_url($group, $courseid, $large = false) {
$file = 'f2';
}
$grouppictureurl = moodle_url::make_pluginfile_url($context->id, 'group', 'icon', $group->id, '/', $file);
$grouppictureurl = moodle_url::make_pluginfile_url(
$context->id, 'group', 'icon', $group->id, '/', $file, false, $includetoken);
$grouppictureurl->param('rev', $group->picture);
return $grouppictureurl;
}

View File

@ -56,9 +56,16 @@ class renderer extends \mod_forum_renderer {
*/
public function format_message_text($cm, $post) {
$context = \context_module::instance($cm->id);
$message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php',
$message = file_rewrite_pluginfile_urls(
$post->message,
'pluginfile.php',
$context->id,
'mod_forum', 'post', $post->id);
'mod_forum',
'post',
$post->id,
[
'includetoken' => true,
]);
$options = new \stdClass();
$options->para = true;
$options->context = $context;

View File

@ -53,9 +53,17 @@ class renderer_textemail extends \mod_forum\output\email\renderer_textemail {
* @return string
*/
public function format_message_text($cm, $post) {
$message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php',
\context_module::instance($cm->id)->id,
'mod_forum', 'post', $post->id);
$context = \context_module::instance($cm->id);
$message = file_rewrite_pluginfile_urls(
$post->message,
'pluginfile.php',
$context->id,
'mod_forum',
'post',
$post->id,
[
'includetoken' => true,
]);
return format_text_email($message, $post->messageformat);
}
}

View File

@ -53,9 +53,17 @@ class renderer_textemail extends \mod_forum\output\email\renderer_textemail {
* @return string
*/
public function format_message_text($cm, $post) {
$message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php',
\context_module::instance($cm->id)->id,
'mod_forum', 'post', $post->id);
$context = \context_module::instance($cm->id);
$message = file_rewrite_pluginfile_urls(
$post->message,
'pluginfile.php',
$context->id,
'mod_forum',
'post',
$post->id,
[
'includetoken' => true,
]);
return format_text_email($message, $post->messageformat);
}
}

View File

@ -135,7 +135,7 @@ class forum_post implements \renderable, \templatable {
*
* @param \mod_forum_renderer $renderer The render to be used for formatting the message and attachments
* @param bool $plaintext Whethe the target is a plaintext target
* @return stdClass Data ready for use in a mustache template
* @return array Data ready for use in a mustache template
*/
public function export_for_template(\renderer_base $renderer, $plaintext = false) {
if ($plaintext) {
@ -149,7 +149,7 @@ class forum_post implements \renderable, \templatable {
* Export this data so it can be used as the context for a mustache template.
*
* @param \mod_forum_renderer $renderer The render to be used for formatting the message and attachments
* @return stdClass Data ready for use in a mustache template
* @return array Data ready for use in a mustache template
*/
protected function export_for_template_text(\mod_forum_renderer $renderer) {
return array(
@ -180,9 +180,9 @@ class forum_post implements \renderable, \templatable {
'discussionlink' => $this->get_discussionlink(),
'authorlink' => $this->get_authorlink(),
'authorpicture' => $this->get_author_picture(),
'authorpicture' => $this->get_author_picture($renderer),
'grouppicture' => $this->get_group_picture(),
'grouppicture' => $this->get_group_picture($renderer),
);
}
@ -190,7 +190,7 @@ class forum_post implements \renderable, \templatable {
* Export this data so it can be used as the context for a mustache template.
*
* @param \mod_forum_renderer $renderer The render to be used for formatting the message and attachments
* @return stdClass Data ready for use in a mustache template
* @return array Data ready for use in a mustache template
*/
protected function export_for_template_html(\mod_forum_renderer $renderer) {
return array(
@ -221,9 +221,9 @@ class forum_post implements \renderable, \templatable {
'discussionlink' => $this->get_discussionlink(),
'authorlink' => $this->get_authorlink(),
'authorpicture' => $this->get_author_picture(),
'authorpicture' => $this->get_author_picture($renderer),
'grouppicture' => $this->get_group_picture(),
'grouppicture' => $this->get_group_picture($renderer),
);
}
@ -543,20 +543,20 @@ class forum_post implements \renderable, \templatable {
/**
* The HTML for the author's user picture.
*
* @param \renderer_base $renderer
* @return string
*/
public function get_author_picture() {
global $OUTPUT;
return $OUTPUT->user_picture($this->author, array('courseid' => $this->course->id));
public function get_author_picture(\renderer_base $renderer) {
return $renderer->user_picture($this->author, array('courseid' => $this->course->id));
}
/**
* The HTML for a group picture.
*
* @param \renderer_base $renderer
* @return string
*/
public function get_group_picture() {
public function get_group_picture(\renderer_base $renderer) {
if (isset($this->userfrom->groups)) {
$groups = $this->userfrom->groups[$this->forum->id];
} else {
@ -564,7 +564,7 @@ class forum_post implements \renderable, \templatable {
}
if ($this->get_is_firstpost()) {
return print_group_picture($groups, $this->course->id, false, true, true);
return print_group_picture($groups, $this->course->id, false, true, true, true);
}
}
}

View File

@ -1042,7 +1042,7 @@ class mod_forum_mail_testcase extends advanced_testcase {
'<div class="attachments">( *\n *)?<a href',
'<div class="subject">\n.*HTML text and image', '>Moodle Forum',
'<p>Welcome to Moodle, '
.'<img src="https://www.example.com/moodle/pluginfile.php/\d+/mod_forum/post/\d+/'
.'<img src="https://www.example.com/moodle/tokenpluginfile.php/[^/]*/\d+/mod_forum/post/\d+/'
.'Screen%20Shot%202016-03-22%20at%205\.54\.36%20AM%20%281%29\.png"'
.' alt="" width="200" height="393" class="img-responsive" />!</p>',
'>Love Moodle', '>1\d1');

View File

@ -25,12 +25,16 @@
*/
// Disable moodle specific debug messages and any errors in output.
define('NO_DEBUG_DISPLAY', true);
if (!defined('NO_DEBUG_DISPLAY')) {
define('NO_DEBUG_DISPLAY', true);
}
require_once('config.php');
require_once('lib/filelib.php');
$relativepath = get_file_argument();
if (empty($relativepath)) {
$relativepath = get_file_argument();
}
$forcedownload = optional_param('forcedownload', 0, PARAM_BOOL);
$preview = optional_param('preview', null, PARAM_ALPHANUM);
// Offline means download the file from the repository and serve it, even if it was an external link.

44
tokenpluginfile.php Normal file
View File

@ -0,0 +1,44 @@
<?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/>.
/**
* Entry point for token-based access to pluginfile.php.
*
* @package core
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
// Disable the use of sessions/cookies - we recreate $USER for every call.
define('NO_MOODLE_COOKIES', true);
// Disable debugging for this script.
// It is typically used to display images.
define('NO_DEBUG_DISPLAY', true);
require_once('config.php');
$relativepath = get_file_argument();
$token = optional_param('token', '', PARAM_ALPHANUM);
if (0 == strpos($relativepath, '/token/')) {
$relativepath = ltrim($relativepath, '/');
$pathparts = explode('/', $relativepath, 2);
$token = $pathparts[0];
$relativepath = "/{$pathparts[1]}";
}
require_user_key_login('core_files', null, $token);
require_once('pluginfile.php');