Merge branch 'MDL-58409-master_needs_clean_in_autocomplete_element' of https://github...
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Tue, 14 Aug 2018 15:29:48 +0000 (17:29 +0200)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Tue, 14 Aug 2018 15:29:48 +0000 (17:29 +0200)
118 files changed:
Gruntfile.js
admin/modules.php
admin/registration/index.php
admin/tool/dataprivacy/classes/data_registry.php
admin/tool/dataprivacy/classes/metadata_registry.php
admin/tool/dataprivacy/db/upgrade.php [new file with mode: 0644]
admin/tool/dataprivacy/version.php
backup/backup.php
backup/util/ui/base_moodleform.class.php
backup/util/ui/renderer.php
backup/util/ui/yui/build/moodle-backup-confirmcancel/moodle-backup-confirmcancel-debug.js
backup/util/ui/yui/build/moodle-backup-confirmcancel/moodle-backup-confirmcancel-min.js
backup/util/ui/yui/build/moodle-backup-confirmcancel/moodle-backup-confirmcancel.js
backup/util/ui/yui/src/confirmcancel/js/confirmcancel.js
blocks/myoverview/amd/build/event_list.min.js
blocks/myoverview/amd/src/event_list.js
blocks/myoverview/templates/timeline-view-courses.mustache
blog/classes/privacy/provider.php
calendar/upgrade.txt
config-dist.php
course/classes/search/mycourse.php
course/classes/search/section.php
course/format/upgrade.txt
course/renderer.php
course/tests/search_test.php
course/upgrade.txt
files/classes/privacy/provider.php
lang/en/backup.php
lang/en/deprecated.txt
lang/en/files.php
lang/en/hub.php
lang/en/message.php
lang/en/moodle.php
lib/accesslib.php
lib/classes/event/message_sent.php
lib/classes/hub/api.php
lib/classes/hub/registration.php
lib/classes/hub/site_unregistration_form.php
lib/classes/message/manager.php
lib/deprecatedlib.php
lib/filebrowser/file_info_context_course.php
lib/filelib.php
lib/form/htmleditor.php
lib/messagelib.php
lib/moodlelib.php
lib/outputcomponents.php
lib/outputrenderers.php
lib/tests/behat/behat_transformations.php
lib/tests/csslib_test.php
lib/tests/filelib_test.php
lib/tests/moodle_url_test.php [new file with mode: 0644]
lib/tests/weblib_test.php
lib/upgrade.txt
lib/weblib.php
lib/xhprof/xhprof_moodle.php
message/classes/search/base_message.php
message/tests/events_test.php
message/tests/search_received_test.php
message/tests/search_sent_test.php
message/upgrade.txt
mod/assign/backup/moodle2/backup_assign_stepslib.php
mod/assign/backup/moodle2/restore_assign_stepslib.php
mod/assign/lib.php
mod/assign/submission/onlinetext/locallib.php
mod/assign/tests/lib_test.php
mod/data/field/radiobutton/field.class.php
mod/feedback/show_nonrespondents.php
mod/forum/classes/output/email/renderer.php
mod/forum/classes/output/emaildigestbasic/renderer_textemail.php
mod/forum/classes/output/emaildigestfull/renderer_textemail.php
mod/forum/classes/output/forum_post.php
mod/forum/tests/mail_test.php
mod/glossary/lib.php
mod/lesson/index.php
mod/lesson/locallib.php
mod/lesson/tests/locallib_test.php
mod/lti/service/memberships/classes/local/resources/linkmemberships.php
mod/lti/service/memberships/classes/local/service/memberships.php
mod/upgrade.txt
mod/wiki/editors/html.php [deleted file]
mod/wiki/editors/wiki_editor.php
mod/wiki/instancecomments.php
pluginfile.php
privacy/classes/local/request/contextlist.php
privacy/tests/contextlist_test.php
question/type/ddwtos/tests/edit_form_test.php [new file with mode: 0644]
question/type/gapselect/edit_form_base.php
question/type/gapselect/edit_gapselect_form.php
question/type/gapselect/tests/edit_form_test.php
report/upgrade.txt
repository/coursefiles/pix/icon.svg [new file with mode: 0644]
repository/local/pix/icon.svg [new file with mode: 0644]
repository/recent/pix/icon.svg [new file with mode: 0644]
repository/user/pix/icon.svg [new file with mode: 0644]
search/classes/base.php
search/classes/base_block.php
search/classes/base_mod.php
search/classes/document.php
search/classes/document_icon.php [new file with mode: 0644]
search/classes/engine.php
search/templates/result.mustache
search/tests/base_activity_test.php
search/tests/base_block_test.php
search/tests/base_test.php
search/tests/document_icon_test.php [new file with mode: 0644]
search/tests/document_test.php
search/tests/engine_test.php
theme/boost/scss/moodle/forms.scss
theme/boost/scss/moodle/undo.scss
theme/boost/style/moodle.css
theme/boost/templates/core_form/element-radio.mustache
theme/upgrade.txt
tokenpluginfile.php [new file with mode: 0644]
user/classes/search/user.php
user/message.html [deleted file]
user/messageselect.php [deleted file]
user/tests/search_test.php
version.php

index 4501fc6..78583d0 100644 (file)
@@ -37,7 +37,7 @@ module.exports = function(grunt) {
     var expected = semver.validRange(grunt.file.readJSON('package.json').engines.node);
     var actual = semver.valid(process.version);
     if (!semver.satisfies(actual, expected)) {
-        grunt.fail.fatal('Node version too old. Require ' + expected + ', version installed: ' + actual);
+        grunt.fail.fatal('Node version not satisfied. Require ' + expected + ', version installed: ' + actual);
     }
 
     // Windows users can't run grunt in a subdirectory, so allow them to set
index fb112e7..01f67a1 100644 (file)
     $table->set_attribute('class', 'admintable generaltable');
     $table->setup();
 
+    $pluginmanager = core_plugin_manager::instance();
+
     foreach ($modules as $module) {
+        $plugininfo = $pluginmanager->get_plugin_info('mod_'.$module->name);
+        $status = $plugininfo->get_status();
 
-        if (!file_exists("$CFG->dirroot/mod/$module->name/lib.php")) {
+        if ($status === core_plugin_manager::PLUGIN_STATUS_MISSING) {
             $strmodulename = '<span class="notifyproblem">'.$module->name.' ('.get_string('missingfromdisk').')</span>';
             $missing = true;
         } else {
index 57d65a3..62f251a 100644 (file)
@@ -40,8 +40,9 @@ if ($unregistration && \core\hub\registration::is_registered()) {
     if ($siteunregistrationform->is_cancelled()) {
         redirect(new moodle_url('/admin/registration/index.php'));
     } else if ($data = $siteunregistrationform->get_data()) {
-        if (\core\hub\registration::unregister($data->unpublishalladvertisedcourses,
-            $data->unpublishalluploadedcourses)) {
+        \core\hub\registration::unregister($data->unpublishalladvertisedcourses,
+            $data->unpublishalluploadedcourses);
+        if (!\core\hub\registration::is_registered()) {
             redirect(new moodle_url('/admin/registration/index.php'));
         }
     }
index 1435fcd..dbc8f32 100644 (file)
@@ -147,8 +147,8 @@ class data_registry {
 
         if ($contextcourse = $context->get_course_context(false)) {
             // Below course level we look at module or block level roles + course-assigned roles.
-            $courseroles = get_roles_with_assignment_on_context($contextcourse);
-            $roles = $courseroles + get_roles_with_assignment_on_context($context);
+            $courseroles = get_roles_used_in_context($contextcourse, false);
+            $roles = $courseroles + get_roles_used_in_context($context, false);
         } else {
             // We list category + system for others (we don't work with user instances so no need to work about them).
             $roles = get_roles_used_in_context($context);
index 6282cc4..ca50157 100644 (file)
@@ -70,7 +70,8 @@ class metadata_registry {
                     $internaldata['compliant'] = false;
                 }
                 // Check to see if we are an external plugin.
-                $componentshortname = explode('_', $component);
+                // Plugin names can contain _ characters, limit to 2 to just remove initial plugintype.
+                $componentshortname = explode('_', $component, 2);
                 $shortname = array_pop($componentshortname);
                 if (isset($contributedplugins[$plugintype][$shortname])) {
                     $internaldata['external'] = true;
diff --git a/admin/tool/dataprivacy/db/upgrade.php b/admin/tool/dataprivacy/db/upgrade.php
new file mode 100644 (file)
index 0000000..8c0b4fc
--- /dev/null
@@ -0,0 +1,149 @@
+<?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/>.
+
+/**
+ * tool_dataprivacy plugin upgrade code
+ *
+ * @package    tool_dataprivacy
+ * @copyright  2018 Jun Pataleta
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Function to upgrade tool_dataprivacy.
+ *
+ * @param int $oldversion the version we are upgrading from
+ * @return bool result
+ */
+function xmldb_tool_dataprivacy_upgrade($oldversion) {
+    global $CFG, $DB;
+
+    $dbman = $DB->get_manager();
+
+    if ($oldversion < 2018051405) {
+        // Define table tool_dataprivacy_ctxexpired to be created.
+        $table = new xmldb_table('tool_dataprivacy_ctxexpired');
+
+        // Adding fields to table tool_dataprivacy_ctxexpired.
+        $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+        $table->add_field('contextid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('status', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0');
+        $table->add_field('usermodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+
+        // Adding keys to table tool_dataprivacy_ctxexpired.
+        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+        $table->add_key('contextid', XMLDB_KEY_FOREIGN_UNIQUE, array('contextid'), 'context', array('id'));
+
+        // Conditionally launch create table for tool_dataprivacy_ctxexpired.
+        if (!$dbman->table_exists($table)) {
+            $dbman->create_table($table);
+        }
+
+        // Define table tool_dataprivacy_contextlist to be created.
+        $table = new xmldb_table('tool_dataprivacy_contextlist');
+
+        // Adding fields to table tool_dataprivacy_contextlist.
+        $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+        $table->add_field('component', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
+        $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
+
+        // Adding keys to table tool_dataprivacy_contextlist.
+        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+        // Conditionally launch create table for tool_dataprivacy_contextlist.
+        if (!$dbman->table_exists($table)) {
+            $dbman->create_table($table);
+        }
+
+        // Define table tool_dataprivacy_ctxlst_ctx to be created.
+        $table = new xmldb_table('tool_dataprivacy_ctxlst_ctx');
+
+        // Adding fields to table tool_dataprivacy_ctxlst_ctx.
+        $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+        $table->add_field('contextid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('contextlistid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('status', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0');
+        $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
+        $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
+
+        // Adding keys to table tool_dataprivacy_ctxlst_ctx.
+        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+        $table->add_key('contextlistid', XMLDB_KEY_FOREIGN, array('contextlistid'), 'tool_dataprivacy_contextlist', array('id'));
+
+        // Conditionally launch create table for tool_dataprivacy_ctxlst_ctx.
+        if (!$dbman->table_exists($table)) {
+            $dbman->create_table($table);
+        }
+
+        // Define table tool_dataprivacy_rqst_ctxlst to be created.
+        $table = new xmldb_table('tool_dataprivacy_rqst_ctxlst');
+
+        // Adding fields to table tool_dataprivacy_rqst_ctxlst.
+        $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+        $table->add_field('requestid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('contextlistid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+
+        // Adding keys to table tool_dataprivacy_rqst_ctxlst.
+        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+        $table->add_key('requestid', XMLDB_KEY_FOREIGN, array('requestid'), 'tool_dataprivacy_request', array('id'));
+        $table->add_key('contextlistid', XMLDB_KEY_FOREIGN, array('contextlistid'), 'tool_dataprivacy_contextlist', array('id'));
+        $table->add_key('request_contextlist', XMLDB_KEY_UNIQUE, array('requestid', 'contextlistid'));
+
+        // Conditionally launch create table for tool_dataprivacy_rqst_ctxlst.
+        if (!$dbman->table_exists($table)) {
+            $dbman->create_table($table);
+        }
+
+        // Define field lawfulbases to be added to tool_dataprivacy_purpose.
+        $table = new xmldb_table('tool_dataprivacy_purpose');
+
+        // It is a required field. We initially define and add it as null and later update it to XMLDB_NOTNULL.
+        $field = new xmldb_field('lawfulbases', XMLDB_TYPE_TEXT, null, null, null, null, null, 'descriptionformat');
+
+        // Conditionally launch add field lawfulbases.
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+
+            // Set a kind-of-random value to lawfulbasis field.
+            $DB->set_field('tool_dataprivacy_purpose', 'lawfulbases', 'gdpr_art_6_1_a');
+
+            // We redefine it now as not null.
+            $field = new xmldb_field('lawfulbases', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null, 'descriptionformat');
+
+            // Launch change of nullability for field lawfulbases.
+            $dbman->change_field_notnull($table, $field);
+        }
+
+        // Define field sensitivedatareasons to be added to tool_dataprivacy_purpose.
+        $table = new xmldb_table('tool_dataprivacy_purpose');
+        $field = new xmldb_field('sensitivedatareasons', XMLDB_TYPE_TEXT, null, null, null, null, null, 'lawfulbases');
+
+        // Conditionally launch add field sensitivedatareasons.
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+
+        // Dataprivacy savepoint reached.
+        upgrade_plugin_savepoint(true, 2018051405, 'tool', 'dataprivacy');
+    }
+
+    return true;
+}
index f2cf6d1..f5c7977 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die;
 
-$plugin->version   = 2018051403;
+$plugin->version   = 2018051405;
 $plugin->requires  = 2018050800;        // Moodle 3.5dev (Build 2018031600) and upwards.
 $plugin->component = 'tool_dataprivacy';
index 3df327a..c1b1f64 100644 (file)
@@ -34,6 +34,7 @@ require_once($CFG->dirroot . '/backup/moodle2/backup_plan_builder.class.php');
 $courseid = required_param('id', PARAM_INT);
 $sectionid = optional_param('section', null, PARAM_INT);
 $cmid = optional_param('cm', null, PARAM_INT);
+$cancel      = optional_param('cancel', '', PARAM_ALPHA);
 /**
  * Part of the forms in stages after initial, is POST never GET
  */
@@ -104,7 +105,10 @@ $PAGE->set_title($heading);
 $PAGE->set_heading($heading);
 
 $renderer = $PAGE->get_renderer('core','backup');
-echo $OUTPUT->header();
+if (empty($cancel)) {
+    // Do not print the header if user cancelled the process, as we are going to redirect the user.
+    echo $OUTPUT->header();
+}
 
 // Prepare a progress bar which can display optionally during long-running
 // operations while setting up the UI.
index 32ac8fb..5208258 100644 (file)
@@ -391,7 +391,7 @@ abstract class base_moodleform extends moodleform {
             $config->title = get_string('confirmcancel', 'backup');
         }
         $config->question = get_string('confirmcancelquestion', 'backup');
-        $config->yesLabel = get_string('confirmcancelyes', 'backup');
+        $config->yesLabel = $config->title;
         $config->noLabel = get_string('confirmcancelno', 'backup');
         $config->closeButtonTitle = get_string('close', 'editor');
         $PAGE->requires->yui_module(
index f656577..2bea83c 100644 (file)
@@ -632,7 +632,7 @@ class core_backup_renderer extends plugin_renderer_base {
         $url = $component->get_url();
 
         $output = html_writer::start_tag('div', array('class' => 'restore-course-search form-inline m-b-1'));
-        $output .= html_writer::start_tag('div', array('class' => 'rcs-results'));
+        $output .= html_writer::start_tag('div', array('class' => 'rcs-results w-75'));
 
         $table = new html_table();
         $table->head = array('', get_string('shortnamecourse'), get_string('fullnamecourse'));
@@ -792,7 +792,7 @@ class core_backup_renderer extends plugin_renderer_base {
         $url = $component->get_url();
 
         $output = html_writer::start_tag('div', array('class' => 'restore-course-search form-inline m-b-1'));
-        $output .= html_writer::start_tag('div', array('class' => 'rcs-results w-100'));
+        $output .= html_writer::start_tag('div', array('class' => 'rcs-results w-75'));
 
         $table = new html_table();
         $table->head = array('', get_string('name'), get_string('description'));
index cdbd206..713ac2c 100644 (file)
Binary files a/backup/util/ui/yui/build/moodle-backup-confirmcancel/moodle-backup-confirmcancel-debug.js and b/backup/util/ui/yui/build/moodle-backup-confirmcancel/moodle-backup-confirmcancel-debug.js differ
index db30391..20654af 100644 (file)
Binary files a/backup/util/ui/yui/build/moodle-backup-confirmcancel/moodle-backup-confirmcancel-min.js and b/backup/util/ui/yui/build/moodle-backup-confirmcancel/moodle-backup-confirmcancel-min.js differ
index cdbd206..713ac2c 100644 (file)
Binary files a/backup/util/ui/yui/build/moodle-backup-confirmcancel/moodle-backup-confirmcancel.js and b/backup/util/ui/yui/build/moodle-backup-confirmcancel/moodle-backup-confirmcancel.js differ
index 488fe3a..6d30056 100644 (file)
@@ -66,8 +66,17 @@ M.core_backup.confirmcancel = {
             // Detach the listeners for the confirm box so they don't fire again.
             new Y.EventHandle(M.core_backup.confirmcancel.listeners).detach();
 
+            // The currentTarget is a div surrounding the form elements. Simulating a click on the div is
+            // not going to submit a form so we need to find the form element to click.
+            var element = e.currentTarget.one('input, select, button');
+
             // Simulate the original cancel button click.
-            e.currentTarget.simulate('click');
+            if (element) {
+                element.simulate('click');
+            } else {
+                // Backwards compatibility only.
+                e.currentTarget.simulate('click');
+            }
         }, this);
 
 
index 0d04b59..343b46e 100644 (file)
Binary files a/blocks/myoverview/amd/build/event_list.min.js and b/blocks/myoverview/amd/build/event_list.min.js differ
index 9d96fc5..8a1bd91 100644 (file)
@@ -101,6 +101,8 @@ define(['jquery', 'core/notification', 'core/templates',
         if (!hasLoadedAll(root)) {
             // Only enable the button if we've got more events to load.
             viewMoreButton.prop('disabled', false);
+        } else {
+            viewMoreButton.addClass('hidden');
         }
     };
 
index a569405..6f35022 100644 (file)
@@ -86,7 +86,7 @@
             // If there was only one hidden block then we have no more to show now
             // so we can disable the button.
             if (blocks && blocks.length == 1) {
-                button.prop('disabled', true);
+                button.addClass('hidden');
             }
 
             if (data) {
index 7fb7853..e3acab9 100644 (file)
@@ -135,7 +135,7 @@ class provider implements
         if ($DB->record_exists('blog_external', ['userid' => $userid])) {
             $sql = "
                 SELECT ctx.id
-                  FROM {context}
+                  FROM {context} ctx
                  WHERE ctx.contextlevel = :ctxlevel
                    AND ctx.instanceid = :ctxuserid";
             $params = [
index 6d3489e..342ad35 100644 (file)
@@ -6,6 +6,8 @@ information provided here is intended especially for developers.
 * calendar_set_filters() function now has optional $user parameter.
 * The core_calendar\local\event\container class now provides two new helper methods for getting and setting the requesting user:
   set_requesting_user() and get_requesting_user().
+* The following functions have been finally deprecated and can not be used anymore:
+  * calendar_preferences_button()
 
 === 3.5 ===
 * core_calendar_external::get_calendar_events now returns the categoryid for category events.
index afb9c1c..fa43382 100644 (file)
@@ -380,6 +380,12 @@ $CFG->admin = 'admin';
 //   profilingallowme, profilingallowall, profilinglifetime
 //       $CFG->earlyprofilingenabled = true;
 //
+// Disable database storage for profile data.
+// When using an exernal plugin to store profiling data it is often
+// desirable to not store the data in the database.
+//
+//      $CFG->disableprofilingtodatabase = true;
+//
 // Force displayed usernames
 //   A little hack to anonymise user names for all students.  If you set these
 //   then all non-teachers will always see these for every person.
index 9c3378c..9cbdc58 100644 (file)
@@ -174,4 +174,14 @@ class mycourse extends \core_search\base {
     public function get_component_name() {
         return 'course';
     }
+
+    /**
+     * Returns an icon instance for the document.
+     *
+     * @param \core_search\document $doc
+     * @return \core_search\document_icon
+     */
+    public function get_doc_icon(\core_search\document $doc) : \core_search\document_icon {
+        return new \core_search\document_icon('i/course');
+    }
 }
index ce28664..42badb4 100644 (file)
@@ -195,4 +195,14 @@ class section extends \core_search\base {
     public function get_component_name() {
         return 'course';
     }
+
+    /**
+     * Returns an icon instance for the document.
+     *
+     * @param \core_search\document $doc
+     * @return \core_search\document_icon
+     */
+    public function get_doc_icon(\core_search\document $doc) : \core_search\document_icon {
+        return new \core_search\document_icon('i/section');
+    }
 }
index 16a2c66..68d7194 100644 (file)
@@ -8,6 +8,8 @@ Overview of this plugin type at http://docs.moodle.org/dev/Course_formats
   Note that validate_format_options() is now always called when somebody creates or edits course or section and also
   during restore and course upload. Default implementation validates against the definition of the form elements for
   format options.
+* The final deprecation of xxx_delete_course callback means that this function will no longer be called.
+  Please use the observer for event \core\event\course_content_deleted instead.
 
 === 3.5 ===
 * Course formats should overwrite get_config_for_external function to return the course format settings viewable by the
index 01677a5..60aa87a 100644 (file)
@@ -69,62 +69,10 @@ class core_course_renderer extends plugin_renderer_base {
     }
 
     /**
-     * Adds the item in course settings navigation to toggle modchooser
-     *
-     * Theme can overwrite as an empty function to exclude it (for example if theme does not
-     * use modchooser at all)
-     *
      * @deprecated since 3.2
      */
     protected function add_modchoosertoggle() {
-        debugging('core_course_renderer::add_modchoosertoggle() is deprecated.', DEBUG_DEVELOPER);
-
-        global $CFG;
-
-        // Only needs to be done once per page.
-        if (!$this->page->requires->should_create_one_time_item_now('core_course_modchoosertoggle')) {
-            return;
-        }
-
-        if ($this->page->state > moodle_page::STATE_PRINTING_HEADER ||
-                $this->page->course->id == SITEID ||
-                !$this->page->user_is_editing() ||
-                !($context = context_course::instance($this->page->course->id)) ||
-                !has_capability('moodle/course:manageactivities', $context) ||
-                !course_ajax_enabled($this->page->course) ||
-                !($coursenode = $this->page->settingsnav->find('courseadmin', navigation_node::TYPE_COURSE)) ||
-                !($turneditingnode = $coursenode->get('turneditingonoff'))) {
-            // Too late, or we are on site page, or we could not find the
-            // adjacent nodes in course settings menu, or we are not allowed to edit.
-            return;
-        }
-
-        if ($this->page->url->compare(new moodle_url('/course/view.php'), URL_MATCH_BASE)) {
-            // We are on the course page, retain the current page params e.g. section.
-            $modchoosertoggleurl = clone($this->page->url);
-        } else {
-            // Edit on the main course page.
-            $modchoosertoggleurl = new moodle_url('/course/view.php', array('id' => $this->page->course->id,
-                'return' => $this->page->url->out_as_local_url(false)));
-        }
-        $modchoosertoggleurl->param('sesskey', sesskey());
-        if ($usemodchooser = get_user_preferences('usemodchooser', $CFG->modchooserdefault)) {
-            $modchoosertogglestring = get_string('modchooserdisable', 'moodle');
-            $modchoosertoggleurl->param('modchooser', 'off');
-        } else {
-            $modchoosertogglestring = get_string('modchooserenable', 'moodle');
-            $modchoosertoggleurl->param('modchooser', 'on');
-        }
-        $modchoosertoggle = navigation_node::create($modchoosertogglestring, $modchoosertoggleurl, navigation_node::TYPE_SETTING, null, 'modchoosertoggle');
-
-        // Insert the modchoosertoggle after the settings node 'turneditingonoff' (navigation_node only has function to insert before, so we insert before and then swap).
-        $coursenode->add_node($modchoosertoggle, 'turneditingonoff');
-        $turneditingnode->remove();
-        $coursenode->add_node($turneditingnode, 'modchoosertoggle');
-
-        $modchoosertoggle->add_class('modchoosertoggle');
-        $modchoosertoggle->add_class('visibleifjs');
-        user_preference_allow_ajax_update('usemodchooser', PARAM_BOOL);
+        throw new coding_exception('core_course_renderer::add_modchoosertoggle() can not be used anymore.');
     }
 
     /**
index 9751f1a..ac9d44d 100644 (file)
@@ -446,4 +446,36 @@ class course_search_testcase extends advanced_testcase {
         $this->assertEquals(\core_search\manager::ACCESS_DELETED,
                 $searcharea->check_access($documents[1]->get('itemid')));
     }
+
+    /**
+     * Test document icon for mycourse area.
+     */
+    public function test_get_doc_icon_for_mycourse_area() {
+        $searcharea = \core_search\manager::get_search_area($this->mycoursesareaid);
+
+        $document = $this->getMockBuilder('\core_search\document')
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $result = $searcharea->get_doc_icon($document);
+
+        $this->assertEquals('i/course', $result->get_name());
+        $this->assertEquals('moodle', $result->get_component());
+    }
+
+    /**
+     * Test document icon for section area.
+     */
+    public function test_get_doc_icon_for_section_area() {
+        $searcharea = \core_search\manager::get_search_area($this->sectionareaid);
+
+        $document = $this->getMockBuilder('\core_search\document')
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $result = $searcharea->get_doc_icon($document);
+
+        $this->assertEquals('i/section', $result->get_name());
+        $this->assertEquals('moodle', $result->get_component());
+    }
 }
index a181c44..62a9d36 100644 (file)
@@ -2,9 +2,9 @@ This files describes API changes in /course/*,
 information provided here is intended especially for developers.
 
 === 3.5 ===
- * There is a new capability 'moodle/course:forcelanguage' to control which users can force the course
+ * There is a new capability 'moodle/course:setforcedlanguage' to control which users can force the course
    language; create_course and update_course functions delegate access control to the caller code; if you
-   are calling those functions you may be interested in checking if the logged in user has 'moodle/course:forcelanguage' capability.
+   are calling those functions you may be interested in checking if the logged in user has 'moodle/course:setforcedlanguage' capability.
 
 === 3.3 ===
 
index 2bf2dbb..865d75f 100644 (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);
+    }
 }
index abaf88e..39feee5 100644 (file)
@@ -124,8 +124,8 @@ $string['confirmcancelrestore'] = 'Cancel restore';
 $string['confirmcancelimport'] = 'Cancel import';
 $string['confirmcancelquestion'] = 'Are you sure you wish to cancel?
 Any information you have entered will be lost.';
-$string['confirmcancelyes'] = 'Cancel';
-$string['confirmcancelno'] = 'Stay';
+$string['confirmcancelyes'] = 'Cancel backup';
+$string['confirmcancelno'] = 'Do not cancel';
 $string['confirmnewcoursecontinue'] = 'New course warning';
 $string['confirmnewcoursecontinuequestion'] = 'A temporary (hidden) course will be created by the course restoration process. To abort restoration click cancel. Do not close the browser while restoring.';
 $string['coursecategory'] = 'Category the course will be restored into';
index d3b31d1..5a3e194 100644 (file)
@@ -120,3 +120,18 @@ selectnotestate,core_notes
 extendenrol,core
 groupextendenrol,core
 virusfounduser,core_antivirus
+formattexttype,core
+currentlyselectedusers,core
+emailuserhasnone,core
+emaildisplayhidden,core
+sitemessage,core
+coursemessage,core
+addedrecip,core
+addedrecips,core
+messagingdisabled,core_message
+messagedselectedcountusersfailed,core
+backtoparticipants,core
+keepsearching,core
+allfieldsrequired,core
+previewhtml,core
+messagedselecteduserfailed,core
index 6cb55ac..b2e305b 100644 (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.';
index 4a9937d..2e2bcf7 100644 (file)
@@ -80,6 +80,7 @@ $string['errorotherhubsnotsupported'] = 'This page can no longer be used for reg
 $string['errorregistration'] = 'An error occurred during registration, please try again later. ({$a})';
 $string['errorunpublishcourses'] = 'Due to an unexpected error, the courses could not be deleted from Moodle.net. Try again later (recommended) or contact Moodle.net administrator.';
 $string['errorws'] = '{$a}';
+$string['errorwstokenreset'] = '{$a}. Registration token on this site has been reset. You can now register your site again.';
 $string['existingscreenshotnumber'] = '{$a} existing screenshots. You will be able to see these screenshots on this page, only once the Moodle.net administrator enables your course.';
 $string['errorregistrationupdate'] = 'An error occurred during registration update ({$a})';
 $string['existingscreenshots'] = 'Existing screenshots';
@@ -210,6 +211,7 @@ $string['update'] = 'Update';
 $string['updatesite'] = 'Update registration on {$a}';
 $string['updatestatus'] = 'Check it now.';
 $string['usedifferentemail'] = 'Use different email';
+$string['unregisterexplained'] = 'If the site with URL {$a} is registered on Moodle.net its registration will be removed.';
 $string['urlalreadyregistered'] = 'Your site seems to be already registered on Moodle.net, which means something has gone wrong. Please contact the Moodle.net administrator to reset your registration so you can try again.';
 $string['usersnumber'] = 'Number of users ({$a})';
 $string['wrongtoken'] = 'The registration failed for some unknown reason (network?). Please try again.';
index 917d352..5428f68 100644 (file)
@@ -72,7 +72,6 @@ $string['message'] = 'Message';
 $string['messagepreferences'] = 'Message preferences';
 $string['messages'] = 'Messages';
 $string['messagingdatahasnotbeenmigrated'] = 'Your messages are temporarily unavailable due to upgrades in the messaging infrastructure. Please wait for them to be migrated.';
-$string['messagingdisabled'] = 'Messaging is disabled on this site, emails will be sent instead';
 $string['newonlymsg'] = 'Show only new';
 $string['newmessage'] = 'New message';
 $string['newmessagesearch'] = 'Select or search for a contact to send a new message.';
@@ -179,3 +178,6 @@ $string['viewnotificationresource'] = 'Go to: {$a}';
 $string['viewunreadmessageswith'] = 'View unread messages with {$a}';
 $string['writeamessage'] = 'Write a message...';
 $string['you'] = 'You:';
+
+// Deprecated since Moodle 3.6.
+$string['messagingdisabled'] = 'Messaging is disabled on this site, emails will be sent instead';
index 84b7020..df32932 100644 (file)
@@ -51,8 +51,6 @@ $string['addcountertousername'] = 'Create user by adding number to username';
 $string['addcreator'] = 'Add course creator';
 $string['adddots'] = 'Add...';
 $string['added'] = 'Added {$a}';
-$string['addedrecip'] = 'Added {$a} new recipient';
-$string['addedrecips'] = 'Added {$a} new recipients';
 $string['addedtogroup'] = 'Added to group "{$a}"';
 $string['addedtogroupnot'] = 'Not added to group "{$a}"';
 $string['addedtogroupnotenrolled'] = 'Not added to group "{$a}", because not enrolled in course';
@@ -127,7 +125,6 @@ $string['allactions'] = 'All actions';
 $string['allactivities'] = 'All activities';
 $string['allcategories'] = 'All categories';
 $string['alldays'] = 'All days';
-$string['allfieldsrequired'] = 'All fields are required';
 $string['allfiles'] = 'All files';
 $string['allgroups'] = 'All groups';
 $string['allchanges'] = 'All changes';
@@ -173,7 +170,6 @@ $string['backto'] = 'Back to {$a}';
 $string['backtocourselisting'] = 'Back to course listing';
 $string['backtohome'] = 'Back to the site home';
 $string['backtopageyouwereon'] = 'Back to the page you were on';
-$string['backtoparticipants'] = 'Back to participants list';
 $string['backup'] = 'Backup';
 $string['backupactivehelp'] = 'Choose whether or not to do automated backups.';
 $string['backupcancelled'] = 'Backup cancelled';
@@ -347,7 +343,6 @@ $string['courseoverviewfilesext'] = 'Course summary files extensions';
 $string['courseoverviewfileslimit'] = 'Course summary files limit';
 $string['courseoverviewfiles_help'] = 'Course summary files, such as images, are displayed in the list of courses together with the summary.';
 $string['courseinfo'] = 'Course info';
-$string['coursemessage'] = 'Message course users';
 $string['coursenotaccessible'] = 'This course does not allow public access';
 $string['courselegacyfiles'] = 'Legacy course files';
 $string['courselegacyfiles_help'] = 'The course files area provides some backward compatibility with Moodle 1.9 and earlier.  All files in this area are always accessible to all participants in the course (whether you link to them or not) and there is no way to know where any of these files are being used in Moodle.
@@ -437,7 +432,6 @@ $string['currentcourseadding'] = 'Current course, adding data to it';
 $string['currentcoursedeleting'] = 'Current course, deleting it first';
 $string['currentlanguage'] = 'Current language';
 $string['currentlocaltime'] = 'your current local time';
-$string['currentlyselectedusers'] = 'Currently selected users';
 $string['currentpicture'] = 'Current picture';
 $string['currentrelease'] = 'Current release information';
 $string['currentversion'] = 'Current version';
@@ -632,7 +626,6 @@ $string['emaildisableclick'] = 'Click here to disable all email from being sent
 $string['emaildisplay'] = 'Email display';
 $string['emaildisplay_help'] = 'Privileged users (such as teachers and managers) will always be able to see your email address.';
 $string['emaildisplaycourse'] = 'Allow only other course members to see my email address';
-$string['emaildisplayhidden'] = 'Email hidden';
 $string['emaildisplayno'] = 'Hide my email address from non-privileged users';
 $string['emaildisplayyes'] = 'Allow everyone to see my email address';
 $string['emailenable'] = 'This email address is enabled';
@@ -726,7 +719,6 @@ $string['emailresetconfirmsent'] = 'An email has been sent to your address at <b
 If you continue to have difficulty, contact the site administrator.';
 $string['emailtoprivatefiles'] = 'You can also e-mail files as attachments straight to your private files space. Simply attach your files to an e-mail and send it to {$a}';
 $string['emailtoprivatefilesdenied'] = 'Your administrator has disabled the option to upload your own private files.';
-$string['emailuserhasnone'] = 'There is no email address for the user.';
 $string['emailvia'] = '{$a->name} (via {$a->siteshortname})';
 $string['emptydragdropregion'] = 'empty region';
 $string['enable'] = 'Enable';
@@ -861,7 +853,6 @@ $string['formathtml'] = 'HTML format';
 $string['formatmarkdown'] = 'Markdown format';
 $string['formatplain'] = 'Plain text format';
 $string['formattext'] = 'Moodle auto-format';
-$string['formattexttype'] = 'Formatting';
 $string['forumpreferences'] = 'Forum preferences';
 $string['framesetinfo'] = 'This frameset document contains:';
 $string['from'] = 'From';
@@ -1090,7 +1081,6 @@ $string['ip_address'] = 'IP address';
 $string['jump'] = 'Jump';
 $string['jumpto'] = 'Jump to...';
 $string['keep'] = 'Keep';
-$string['keepsearching'] = 'Keep searching';
 $string['langltr'] = 'Language direction left-to-right';
 $string['langrtl'] = 'Language direction right-to-left';
 $string['language'] = 'Language';
@@ -1190,9 +1180,7 @@ $string['maxsizeandattachmentsandareasize'] = 'Maximum size for new files: {$a->
 $string['memberincourse'] = 'People in the course';
 $string['messagebody'] = 'Message body';
 $string['messagedselectedusers'] = 'Selected users have been messaged and the recipient list has been reset.';
-$string['messagedselecteduserfailed'] = 'The message was not sent to user {$a->fullname}.';
 $string['messagedselectedusersfailed'] = 'Something went wrong while messaging selected users.  Some may have received the email.';
-$string['messagedselectedcountusersfailed'] = 'A problem occurred and {$a} messages have not been sent.';
 $string['messageprovider:availableupdate'] = 'Available update notifications';
 $string['messageprovider:backup'] = 'Backup notifications';
 $string['messageprovider:badgecreatornotice'] = 'Badge creator notifications';
@@ -1555,7 +1543,6 @@ $string['preferredtheme'] = 'Preferred theme';
 $string['preprocessingbackupfile'] = 'Preprocessing backup file';
 $string['prev'] = 'Prev';
 $string['preview'] = 'Preview';
-$string['previewhtml'] = 'HTML format preview';
 $string['previeworchoose'] = 'Preview or choose a theme';
 $string['previous'] = 'Previous';
 $string['previouslyselectedusers'] = 'Previously selected users not matching \'{$a}\'';
@@ -1856,7 +1843,6 @@ $string['sitefilesused'] = 'Site files used in this course';
 $string['sitehome'] = 'Site home';
 $string['sitelegacyfiles'] = 'Legacy site files';
 $string['sitelogs'] = 'Site logs';
-$string['sitemessage'] = 'Message users';
 $string['sitenews'] = 'Site announcements';
 $string['sitepages'] = 'Site pages';
 $string['sitepartlist'] = 'You do not have the required permissions to view the participants list';
@@ -2087,7 +2073,6 @@ $string['uploadthisfile'] = 'Upload this file';
 $string['url'] = 'URL';
 $string['used'] = 'Used';
 $string['usedinnplaces'] = 'Used in {$a} places';
-$string['usemessageform'] = 'or use the form below to send a message to the selected students';
 $string['user'] = 'User';
 $string['useraccount'] = 'User account';
 $string['userconfirmed'] = 'Confirmed {$a}';
@@ -2213,3 +2198,19 @@ $string['sectionusedefaultname'] = 'Use default section name';
 $string['publish'] = 'Publish';
 $string['extendenrol'] = 'Extend enrolment (individual)';
 $string['groupextendenrol'] = 'Extend enrolment (common)';
+
+// Deprecated since Moodle 3.6.
+$string['addedrecip'] = 'Added {$a} new recipient';
+$string['addedrecips'] = 'Added {$a} new recipients';
+$string['allfieldsrequired'] = 'All fields are required';
+$string['backtoparticipants'] = 'Back to participants list';
+$string['currentlyselectedusers'] = 'Currently selected users';
+$string['coursemessage'] = 'Message course users';
+$string['emaildisplayhidden'] = 'Email hidden';
+$string['emailuserhasnone'] = 'There is no email address for the user.';
+$string['formattexttype'] = 'Formatting';
+$string['keepsearching'] = 'Keep searching';
+$string['messagedselectedcountusersfailed'] = 'A problem occurred and {$a} messages have not been sent.';
+$string['messagedselecteduserfailed'] = 'The message was not sent to user {$a->fullname}.';
+$string['previewhtml'] = 'HTML format preview';
+$string['sitemessage'] = 'Message users';
index 536504c..5172ef4 100644 (file)
@@ -2548,12 +2548,17 @@ function get_profile_roles(context $context) {
  * Gets the list of roles assigned to this context and up (parents)
  *
  * @param context $context
+ * @param boolean $includeparents, false means without parents.
  * @return array
  */
-function get_roles_used_in_context(context $context) {
+function get_roles_used_in_context(context $context, $includeparents = true) {
     global $DB;
 
-    list($contextlist, $params) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'cl');
+    if ($includeparents === true) {
+        list($contextlist, $params) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'cl');
+    } else {
+        list($contextlist, $params) = $DB->get_in_or_equal($context->id, SQL_PARAMS_NAMED, 'cl');
+    }
 
     if ($coursecontext = $context->get_course_context(false)) {
         $params['coursecontext'] = $coursecontext->id;
@@ -3988,22 +3993,6 @@ function get_user_capability_course($capability, $userid = null, $doanything = t
     return empty($courses) ? false : $courses;
 }
 
-/**
- * This function finds the roles assigned directly to this context only
- * i.e. no roles in parent contexts
- *
- * @param context $context
- * @return array
- */
-function get_roles_on_exact_context(context $context) {
-    global $DB;
-
-    return $DB->get_records_sql("SELECT r.*
-                                   FROM {role_assignments} ra, {role} r
-                                  WHERE ra.roleid = r.id AND ra.contextid = ?",
-                                array($context->id));
-}
-
 /**
  * Switches the current user to another role for the current session and only
  * in the given context.
@@ -4091,22 +4080,6 @@ function get_capabilities_from_role_on_context($role, context $context) {
                                 array($context->id, $role->id));
 }
 
-/**
- * Find out which roles has assignment on this context
- *
- * @param context $context
- * @return array
- *
- */
-function get_roles_with_assignment_on_context(context $context) {
-    global $DB;
-
-    return $DB->get_records_sql("SELECT r.*
-                                   FROM {role_assignments} ra, {role} r
-                                  WHERE ra.roleid = r.id AND ra.contextid = ?",
-                                array($context->id));
-}
-
 /**
  * Find all user assignment of users for this role, on this context
  *
index 9b3a8c5..0bfe22c 100644 (file)
@@ -43,14 +43,13 @@ defined('MOODLE_INTERNAL') || die();
 class message_sent extends base {
     /**
      * Create event using ids.
-     * @todo MDL-55449 Make $courseid mandatory in Moodle 3.6
      * @param int $userfromid
      * @param int $usertoid
      * @param int $messageid
-     * @param int|null $courseid course id the event is related with. Use SITEID if no relation exists.
+     * @param int $courseid course id the event is related with.
      * @return message_sent
      */
-    public static function create_from_ids($userfromid, $usertoid, $messageid, $courseid = null) {
+    public static function create_from_ids(int $userfromid, int $usertoid, int $messageid, int $courseid) {
         // We may be sending a message from the 'noreply' address, which means we are not actually sending a
         // message from a valid user. In this case, we will set the userid to 0.
         // Check if the userid is valid.
@@ -58,15 +57,6 @@ class message_sent extends base {
             $userfromid = 0;
         }
 
-        // TODO: MDL-55449 Make $courseid mandatory in Moodle 3.6.
-        if (is_null($courseid)) {
-            // Arrived here with not defined $courseid to associate the event with.
-            // Let's default to SITEID and perform debugging so devs are aware. MDL-47162.
-            $courseid = SITEID;
-            debugging('message_sent::create_from_ids() needs a $courseid to be passed, nothing was detected. Please, change ' .
-                    'the call to include it, using SITEID if the message is unrelated to any real course.', DEBUG_DEVELOPER);
-        }
-
         $event = self::create(array(
             'objectid' => $messageid,
             'userid' => $userfromid,
index 2c66b58..75a0508 100644 (file)
@@ -91,10 +91,11 @@ class api {
         $curloutput = @json_decode($curl->get($serverurl, $params), true);
         $info = $curl->get_info();
         if ($curl->get_errno()) {
+            // Connection error.
             throw new moodle_exception('errorconnect', 'hub', '', $curl->error);
         } else if (isset($curloutput['exception'])) {
-            // Error message returned by web service.
-            throw new moodle_exception('errorws', 'hub', '', $curloutput['message']);
+            // Exception occurred on moodle.net .
+            self::process_curl_exception($token, $curloutput);
         } else if ($info['http_code'] != 200) {
             throw new moodle_exception('errorconnect', 'hub', '', $info['http_code']);
         } else {
@@ -102,6 +103,29 @@ class api {
         }
     }
 
+    /**
+     * Analyses exception received from moodle.net
+     *
+     * @param string $token token used for CURL request
+     * @param array $curloutput output from CURL request
+     * @throws moodle_exception
+     */
+    protected static function process_curl_exception($token, $curloutput) {
+        if (!isset($curloutput['exception'])) {
+            return;
+        }
+        if ($token === registration::get_token()) {
+            // Check if registration token was rejected or there are other problems with registration.
+            if (($curloutput['exception'] === 'moodle_exception' && $curloutput['errorcode'] === 'invalidtoken')
+                    || $curloutput['exception'] === 'registration_exception') {
+                // Force admin to repeat site registration process.
+                registration::reset_token();
+                throw new moodle_exception('errorwstokenreset', 'hub', '', $curloutput['message']);
+            }
+        }
+        throw new moodle_exception('errorws', 'hub', '', $curloutput['message']);
+    }
+
     /**
      * Update site registration on moodle.net
      *
@@ -109,7 +133,7 @@ class api {
      * @throws moodle_exception
      */
     public static function update_registration(array $siteinfo) {
-        $params = array('siteinfo' => $siteinfo);
+        $params = array('siteinfo' => $siteinfo, 'validateurl' => 1);
         self::call('hub_update_site_info', $params);
     }
 
@@ -276,7 +300,8 @@ class api {
      * @throws moodle_exception
      */
     public static function unregister_site() {
-        self::call('hub_unregister_site');
+        global $CFG;
+        self::call('hub_unregister_site', ['url' => [$CFG->wwwroot]]);
     }
 
     /**
index dc09ffb..823e47f 100644 (file)
@@ -275,6 +275,11 @@ class registration {
         try {
             api::update_registration($siteinfo);
         } catch (moodle_exception $e) {
+            if (!self::is_registered()) {
+                // Token was rejected during registration update and site and locally stored token was reset,
+                // proceed to site registration. This method will redirect away.
+                self::register('');
+            }
             \core\notification::add(get_string('errorregistrationupdate', 'hub', $e->getMessage()),
                 \core\output\notification::NOTIFY_ERROR);
             return false;
@@ -428,6 +433,20 @@ class registration {
         return true;
     }
 
+    /**
+     * Resets the registration token without changing site identifier so site can be re-registered
+     *
+     * @return bool
+     */
+    public static function reset_token() {
+        global $DB;
+        if (!$hub = self::get_registration()) {
+            return true;
+        }
+        $DB->delete_records('registration_hubs', array('id' => $hub->id));
+        self::$registration = null;
+    }
+
     /**
      * Generate a new token for the site that is not registered
      *
index ed4116a..70fc070 100644 (file)
@@ -42,6 +42,7 @@ class site_unregistration_form extends \moodleform {
      * Form definition
      */
     public function definition() {
+        global $CFG;
         $mform = & $this->_form;
         $mform->addElement('header', 'site', get_string('unregister', 'hub'));
 
@@ -56,6 +57,8 @@ class site_unregistration_form extends \moodleform {
         $mform->addElement('hidden', 'unregistration', 1);
         $mform->setType('unregistration', PARAM_INT);
 
+        $mform->addElement('static', 'explanation', '', get_string('unregisterexplained', 'hub', $CFG->wwwroot));
+
         $this->add_action_buttons(true, $unregisterlabel);
     }
 }
index c05e14d..17192f3 100644 (file)
@@ -50,30 +50,14 @@ class manager {
      *
      * NOTE: to be used from message_send() only.
      *
-     * @todo MDL-55449 Drop support for stdClass in Moodle 3.6
      * @param \core\message\message $eventdata fully prepared event data for processors
      * @param \stdClass $savemessage the message saved in 'message' table
      * @param array $processorlist list of processors for target user
      * @return int $messageid the id from 'messages' (false is not returned)
      */
-    public static function send_message($eventdata, \stdClass $savemessage, array $processorlist) {
+    public static function send_message(message $eventdata, \stdClass $savemessage, array $processorlist) {
         global $CFG;
 
-        // TODO MDL-55449 Drop support for stdClass in Moodle 3.6.
-        if (!($eventdata instanceof \stdClass) && !($eventdata instanceof message)) {
-            // Not a valid object.
-            throw new \coding_exception('Message should be of type stdClass or \core\message\message');
-        }
-
-        // TODO MDL-55449 Drop support for stdClass in Moodle 3.6.
-        if ($eventdata instanceof \stdClass) {
-            if (!isset($eventdata->courseid)) {
-                $eventdata->courseid = null;
-            }
-
-            debugging('eventdata as \stdClass is deprecated. Please use \core\message\message instead.', DEBUG_DEVELOPER);
-        }
-
         require_once($CFG->dirroot.'/message/lib.php'); // This is most probably already included from messagelib.php file.
 
         if (empty($processorlist)) {
index 38c5f08..1298165 100644 (file)
@@ -768,7 +768,10 @@ function print_side_block() {
 /**
  * Prints a basic textarea field.
  *
- * @deprecated since Moodle 2.0
+ * This was 'deprecated' in 2.0, but not properly (there was no alternative) so the
+ * debugging message was commented out.
+ *
+ * @deprecated since Moodle 3.6
  *
  * When using this function, you should
  *
@@ -790,13 +793,12 @@ function print_textarea($unused, $rows, $cols, $width, $height, $name, $value=''
     /// However, you can set them to zero to override the mincols and minrows values below.
 
     // Disabling because there is not yet a viable $OUTPUT option for cases when mforms can't be used
-    // debugging('print_textarea() has been deprecated. You should be using mforms and the editor element.');
+    debugging('print_textarea() is deprecated. Please use $OUTPUT->print_textarea() instead.', DEBUG_DEVELOPER);
 
-    global $CFG;
+    global $OUTPUT;
 
     $mincols = 65;
     $minrows = 10;
-    $str = '';
 
     if ($id === '') {
         $id = 'edit-'.$name;
@@ -809,19 +811,12 @@ function print_textarea($unused, $rows, $cols, $width, $height, $name, $value=''
         $cols = $mincols;
     }
 
-    editors_head_setup();
-    $editor = editors_get_preferred_editor(FORMAT_HTML);
-    $editor->set_text($value);
-    $editor->use_editor($id, array('legacy'=>true));
-
-    $str .= "\n".'<textarea class="form-textarea" id="'. $id .'" name="'. $name .'" rows="'. $rows .'" cols="'. $cols .'" spellcheck="true">'."\n";
-    $str .= htmlspecialchars($value); // needed for editing of cleaned text!
-    $str .= '</textarea>'."\n";
-
+    $textarea = $OUTPUT->print_textarea($name, $id, $value, $rows, $cols);
     if ($return) {
-        return $str;
+        return $textarea;
     }
-    echo $str;
+
+    echo $textarea;
 }
 
 /**
@@ -904,30 +899,12 @@ function print_checkbox() {
 }
 
 /**
- * Prints the 'update this xxx' button that appears on module pages.
- *
  * @deprecated since Moodle 3.2
- *
- * @param string $cmid the course_module id.
- * @param string $ignored not used any more. (Used to be courseid.)
- * @param string $string the module name - get_string('modulename', 'xxx')
- * @return string the HTML for the button, if this user has permission to edit it, else an empty string.
  */
-function update_module_button($cmid, $ignored, $string) {
-    global $CFG, $OUTPUT;
-
-    debugging('update_module_button() has been deprecated and should not be used anymore. Activity modules should not add the ' .
-        'edit module button, the link is already available in the Administration block. Themes can choose to display the link ' .
-        'in the buttons row consistently for all module types.', DEBUG_DEVELOPER);
-
-    if (has_capability('moodle/course:manageactivities', context_module::instance($cmid))) {
-        $string = get_string('updatethis', '', $string);
-
-        $url = new moodle_url("$CFG->wwwroot/course/mod.php", array('update' => $cmid, 'return' => true, 'sesskey' => sesskey()));
-        return $OUTPUT->single_button($url, $string);
-    } else {
-        return '';
-    }
+function update_module_button() {
+    throw new coding_exception('update_module_button() can not be used anymore. Activity modules should ' .
+        'not add the edit module button, the link is already available in the Administration block. Themes ' .
+        'can choose to display the link in the buttons row consistently for all module types.');
 }
 
 /**
@@ -2276,1667 +2253,252 @@ function external_function_info() {
 }
 
 /**
- * Retrieves an array of records from a CSV file and places
- * them into a given table structure
- * This function is deprecated. Please use csv_import_reader() instead.
- *
- * @deprecated since Moodle 3.2 MDL-55126
- * @todo MDL-55195 for final deprecation in Moodle 3.6
+ * @deprecated since Moodle 3.2
  * @see csv_import_reader::load_csv_content()
- * @global stdClass $CFG
- * @global moodle_database $DB
- * @param string $file The path to a CSV file
- * @param string $table The table to retrieve columns from
- * @return bool|array Returns an array of CSV records or false
  */
-function get_records_csv($file, $table) {
-    global $CFG, $DB;
-
-    debugging('get_records_csv() is deprecated. Please use lib/csvlib.class.php csv_import_reader() instead.');
-
-    if (!$metacolumns = $DB->get_columns($table)) {
-        return false;
-    }
-
-    if(!($handle = @fopen($file, 'r'))) {
-        print_error('get_records_csv failed to open '.$file);
-    }
-
-    $fieldnames = fgetcsv($handle, 4096);
-    if(empty($fieldnames)) {
-        fclose($handle);
-        return false;
-    }
-
-    $columns = array();
-
-    foreach($metacolumns as $metacolumn) {
-        $ord = array_search($metacolumn->name, $fieldnames);
-        if(is_int($ord)) {
-            $columns[$metacolumn->name] = $ord;
-        }
-    }
-
-    $rows = array();
-
-    while (($data = fgetcsv($handle, 4096)) !== false) {
-        $item = new stdClass;
-        foreach($columns as $name => $ord) {
-            $item->$name = $data[$ord];
-        }
-        $rows[] = $item;
-    }
-
-    fclose($handle);
-    return $rows;
-}
-
-/**
- * Create a file with CSV contents
- * This function is deprecated. Please use download_as_dataformat() instead.
- *
- * @deprecated since Moodle 3.2 MDL-55126
- * @todo MDL-55195 for final deprecation in Moodle 3.6
- * @see download_as_dataformat (lib/dataformatlib.php)
- * @global stdClass $CFG
- * @global moodle_database $DB
- * @param string $file The file to put the CSV content into
- * @param array $records An array of records to write to a CSV file
- * @param string $table The table to get columns from
- * @return bool success
- */
-function put_records_csv($file, $records, $table = NULL) {
-    global $CFG, $DB;
-
-    debugging('put_records_csv() is deprecated. Please use lib/dataformatlib.php download_as_dataformat()');
-
-    if (empty($records)) {
-        return true;
-    }
-
-    $metacolumns = NULL;
-    if ($table !== NULL && !$metacolumns = $DB->get_columns($table)) {
-        return false;
-    }
-
-    echo "x";
-
-    if(!($fp = @fopen($CFG->tempdir.'/'.$file, 'w'))) {
-        print_error('put_records_csv failed to open '.$file);
-    }
-
-    $proto = reset($records);
-    if(is_object($proto)) {
-        $fields_records = array_keys(get_object_vars($proto));
-    }
-    else if(is_array($proto)) {
-        $fields_records = array_keys($proto);
-    }
-    else {
-        return false;
-    }
-    echo "x";
-
-    if(!empty($metacolumns)) {
-        $fields_table = array_map(create_function('$a', 'return $a->name;'), $metacolumns);
-        $fields = array_intersect($fields_records, $fields_table);
-    }
-    else {
-        $fields = $fields_records;
-    }
-
-    fwrite($fp, implode(',', $fields));
-    fwrite($fp, "\r\n");
-
-    foreach($records as $record) {
-        $array  = (array)$record;
-        $values = array();
-        foreach($fields as $field) {
-            if(strpos($array[$field], ',')) {
-                $values[] = '"'.str_replace('"', '\"', $array[$field]).'"';
-            }
-            else {
-                $values[] = $array[$field];
-            }
-        }
-        fwrite($fp, implode(',', $values)."\r\n");
-    }
-
-    fclose($fp);
-    @chmod($CFG->tempdir.'/'.$file, $CFG->filepermissions);
-    return true;
+function get_records_csv() {
+    throw new coding_exception('get_records_csv() can not be used anymore. Please use ' .
+        'lib/csvlib.class.php csv_import_reader() instead.');
 }
 
 /**
- * Determines if the given value is a valid CSS colour.
- *
- * A CSS colour can be one of the following:
- *    - Hex colour:  #AA66BB
- *    - RGB colour:  rgb(0-255, 0-255, 0-255)
- *    - RGBA colour: rgba(0-255, 0-255, 0-255, 0-1)
- *    - HSL colour:  hsl(0-360, 0-100%, 0-100%)
- *    - HSLA colour: hsla(0-360, 0-100%, 0-100%, 0-1)
- *
- * Or a recognised browser colour mapping {@link css_optimiser::$htmlcolours}
- *
  * @deprecated since Moodle 3.2
- * @todo MDL-56173 for final deprecation in Moodle 3.6
- * @param string $value The colour value to check
- * @return bool
+ * @see download_as_dataformat (lib/dataformatlib.php)
  */
-function css_is_colour($value) {
-    debugging('css_is_colour() is deprecated without a replacement. Please copy the implementation '.
-        'into your plugin if you need this functionality.', DEBUG_DEVELOPER);
-
-    $value = trim($value);
-
-    $hex  = '/^#([a-fA-F0-9]{1,3}|[a-fA-F0-9]{6})$/';
-    $rgb  = '#^rgb\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$#i';
-    $rgba = '#^rgba\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1}(\.\d+)?)\s*\)$#i';
-    $hsl  = '#^hsl\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\%\s*,\s*(\d{1,3})\%\s*\)$#i';
-    $hsla = '#^hsla\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\%\s*,\s*(\d{1,3})\%\s*,\s*(\d{1}(\.\d+)?)\s*\)$#i';
-
-    if (in_array(strtolower($value), array('inherit'))) {
-        return true;
-    } else if (preg_match($hex, $value)) {
-        return true;
-    } else if (in_array(strtolower($value), array_keys(css_optimiser::$htmlcolours))) {
-        return true;
-    } else if (preg_match($rgb, $value, $m) && $m[1] < 256 && $m[2] < 256 && $m[3] < 256) {
-        // It is an RGB colour.
-        return true;
-    } else if (preg_match($rgba, $value, $m) && $m[1] < 256 && $m[2] < 256 && $m[3] < 256) {
-        // It is an RGBA colour.
-        return true;
-    } else if (preg_match($hsl, $value, $m) && $m[1] <= 360 && $m[2] <= 100 && $m[3] <= 100) {
-        // It is an HSL colour.
-        return true;
-    } else if (preg_match($hsla, $value, $m) && $m[1] <= 360 && $m[2] <= 100 && $m[3] <= 100) {
-        // It is an HSLA colour.
-        return true;
-    }
-    // Doesn't look like a colour.
-    return false;
+function put_records_csv() {
+    throw new coding_exception('put_records_csv() can not be used anymore. Please use ' .
+        'lib/dataformatlib.php download_as_dataformat() instead.');
 }
 
 /**
- * Returns true is the passed value looks like a CSS width.
- * In order to pass this test the value must be purely numerical or end with a
- * valid CSS unit term.
- *
- * @param string|int $value
- * @return boolean
  * @deprecated since Moodle 3.2
- * @todo MDL-56173 for final deprecation in Moodle 3.6
  */
-function css_is_width($value) {
-    debugging('css_is_width() is deprecated without a replacement. Please copy the implementation '.
-        'into your plugin if you need this functionality.', DEBUG_DEVELOPER);
-
-    $value = trim($value);
-    if (in_array(strtolower($value), array('auto', 'inherit'))) {
-        return true;
-    }
-    if ((string)$value === '0' || preg_match('#^(\-\s*)?(\d*\.)?(\d+)\s*(em|px|pt|\%|in|cm|mm|ex|pc)$#i', $value)) {
-        return true;
-    }
-    return false;
+function css_is_colour() {
+    throw new coding_exception('css_is_colour() can not be used anymore.');
 }
 
 /**
- * A simple sorting function to sort two array values on the number of items they contain
- *
- * @param array $a
- * @param array $b
- * @return int
  * @deprecated since Moodle 3.2
- * @todo MDL-56173 for final deprecation in Moodle 3.6
  */
-function css_sort_by_count(array $a, array $b) {
-    debugging('css_sort_by_count() is deprecated without a replacement. Please copy the implementation '.
-        'into your plugin if you need this functionality.', DEBUG_DEVELOPER);
-
-    $a = count($a);
-    $b = count($b);
-    if ($a == $b) {
-        return 0;
-    }
-    return ($a > $b) ? -1 : 1;
+function css_is_width() {
+    throw new coding_exception('css_is_width() can not be used anymore.');
 }
 
 /**
- * A basic CSS optimiser that strips out unwanted things and then processes CSS organising and cleaning styles.
  * @deprecated since Moodle 3.2
- * @todo MDL-56173 for final deprecation in Moodle 3.6
- */
-class css_optimiser {
-    /**
-     * An array of the common HTML colours that are supported by most browsers.
-     *
-     * This reference table is used to allow us to unify colours, and will aid
-     * us in identifying buggy CSS using unsupported colours.
-     *
-     * @var string[]
-     * @deprecated since Moodle 3.2
-     * @todo MDL-56173 for final deprecation in Moodle 3.6
-     */
-    public static $htmlcolours = array(
-        'aliceblue' => '#F0F8FF',
-        'antiquewhite' => '#FAEBD7',
-        'aqua' => '#00FFFF',
-        'aquamarine' => '#7FFFD4',
-        'azure' => '#F0FFFF',
-        'beige' => '#F5F5DC',
-        'bisque' => '#FFE4C4',
-        'black' => '#000000',
-        'blanchedalmond' => '#FFEBCD',
-        'blue' => '#0000FF',
-        'blueviolet' => '#8A2BE2',
-        'brown' => '#A52A2A',
-        'burlywood' => '#DEB887',
-        'cadetblue' => '#5F9EA0',
-        'chartreuse' => '#7FFF00',
-        'chocolate' => '#D2691E',
-        'coral' => '#FF7F50',
-        'cornflowerblue' => '#6495ED',
-        'cornsilk' => '#FFF8DC',
-        'crimson' => '#DC143C',
-        'cyan' => '#00FFFF',
-        'darkblue' => '#00008B',
-        'darkcyan' => '#008B8B',
-        'darkgoldenrod' => '#B8860B',
-        'darkgray' => '#A9A9A9',
-        'darkgrey' => '#A9A9A9',
-        'darkgreen' => '#006400',
-        'darkKhaki' => '#BDB76B',
-        'darkmagenta' => '#8B008B',
-        'darkolivegreen' => '#556B2F',
-        'arkorange' => '#FF8C00',
-        'darkorchid' => '#9932CC',
-        'darkred' => '#8B0000',
-        'darksalmon' => '#E9967A',
-        'darkseagreen' => '#8FBC8F',
-        'darkslateblue' => '#483D8B',
-        'darkslategray' => '#2F4F4F',
-        'darkslategrey' => '#2F4F4F',
-        'darkturquoise' => '#00CED1',
-        'darkviolet' => '#9400D3',
-        'deeppink' => '#FF1493',
-        'deepskyblue' => '#00BFFF',
-        'dimgray' => '#696969',
-        'dimgrey' => '#696969',
-        'dodgerblue' => '#1E90FF',
-        'firebrick' => '#B22222',
-        'floralwhite' => '#FFFAF0',
-        'forestgreen' => '#228B22',
-        'fuchsia' => '#FF00FF',
-        'gainsboro' => '#DCDCDC',
-        'ghostwhite' => '#F8F8FF',
-        'gold' => '#FFD700',
-        'goldenrod' => '#DAA520',
-        'gray' => '#808080',
-        'grey' => '#808080',
-        'green' => '#008000',
-        'greenyellow' => '#ADFF2F',
-        'honeydew' => '#F0FFF0',
-        'hotpink' => '#FF69B4',
-        'indianred ' => '#CD5C5C',
-        'indigo ' => '#4B0082',
-        'ivory' => '#FFFFF0',
-        'khaki' => '#F0E68C',
-        'lavender' => '#E6E6FA',
-        'lavenderblush' => '#FFF0F5',
-        'lawngreen' => '#7CFC00',
-        'lemonchiffon' => '#FFFACD',
-        'lightblue' => '#ADD8E6',
-        'lightcoral' => '#F08080',
-        'lightcyan' => '#E0FFFF',
-        'lightgoldenrodyellow' => '#FAFAD2',
-        'lightgray' => '#D3D3D3',
-        'lightgrey' => '#D3D3D3',
-        'lightgreen' => '#90EE90',
-        'lightpink' => '#FFB6C1',
-        'lightsalmon' => '#FFA07A',
-        'lightseagreen' => '#20B2AA',
-        'lightskyblue' => '#87CEFA',
-        'lightslategray' => '#778899',
-        'lightslategrey' => '#778899',
-        'lightsteelblue' => '#B0C4DE',
-        'lightyellow' => '#FFFFE0',
-        'lime' => '#00FF00',
-        'limegreen' => '#32CD32',
-        'linen' => '#FAF0E6',
-        'magenta' => '#FF00FF',
-        'maroon' => '#800000',
-        'mediumaquamarine' => '#66CDAA',
-        'mediumblue' => '#0000CD',
-        'mediumorchid' => '#BA55D3',
-        'mediumpurple' => '#9370D8',
-        'mediumseagreen' => '#3CB371',
-        'mediumslateblue' => '#7B68EE',
-        'mediumspringgreen' => '#00FA9A',
-        'mediumturquoise' => '#48D1CC',
-        'mediumvioletred' => '#C71585',
-        'midnightblue' => '#191970',
-        'mintcream' => '#F5FFFA',
-        'mistyrose' => '#FFE4E1',
-        'moccasin' => '#FFE4B5',
-        'navajowhite' => '#FFDEAD',
-        'navy' => '#000080',
-        'oldlace' => '#FDF5E6',
-        'olive' => '#808000',
-        'olivedrab' => '#6B8E23',
-        'orange' => '#FFA500',
-        'orangered' => '#FF4500',
-        'orchid' => '#DA70D6',
-        'palegoldenrod' => '#EEE8AA',
-        'palegreen' => '#98FB98',
-        'paleturquoise' => '#AFEEEE',
-        'palevioletred' => '#D87093',
-        'papayawhip' => '#FFEFD5',
-        'peachpuff' => '#FFDAB9',
-        'peru' => '#CD853F',
-        'pink' => '#FFC0CB',
-        'plum' => '#DDA0DD',
-        'powderblue' => '#B0E0E6',
-        'purple' => '#800080',
-        'red' => '#FF0000',
-        'rosybrown' => '#BC8F8F',
-        'royalblue' => '#4169E1',
-        'saddlebrown' => '#8B4513',
-        'salmon' => '#FA8072',
-        'sandybrown' => '#F4A460',
-        'seagreen' => '#2E8B57',
-        'seashell' => '#FFF5EE',
-        'sienna' => '#A0522D',
-        'silver' => '#C0C0C0',
-        'skyblue' => '#87CEEB',
-        'slateblue' => '#6A5ACD',
-        'slategray' => '#708090',
-        'slategrey' => '#708090',
-        'snow' => '#FFFAFA',
-        'springgreen' => '#00FF7F',
-        'steelblue' => '#4682B4',
-        'tan' => '#D2B48C',
-        'teal' => '#008080',
-        'thistle' => '#D8BFD8',
-        'tomato' => '#FF6347',
-        'transparent' => 'transparent',
-        'turquoise' => '#40E0D0',
-        'violet' => '#EE82EE',
-        'wheat' => '#F5DEB3',
-        'white' => '#FFFFFF',
-        'whitesmoke' => '#F5F5F5',
-        'yellow' => '#FFFF00',
-        'yellowgreen' => '#9ACD32'
-    );
-
-    /**
-     * Used to orocesses incoming CSS optimising it and then returning it. Now just returns
-     * what is sent to it. Do not use.
-     *
-     * @param string $css The raw CSS to optimise
-     * @return string The optimised CSS
-     * @deprecated since Moodle 3.2
-     * @todo MDL-56173 for final deprecation in Moodle 3.6
-     */
-    public function process($css) {
-        debugging('class css_optimiser is deprecated and no longer does anything, '.
-            'please consider using stylelint to optimise your css.', DEBUG_DEVELOPER);
-
-        return $css;
-    }
+ */
+function css_sort_by_count() {
+    throw new coding_exception('css_sort_by_count() can not be used anymore.');
 }
 
 /**
- * Load the course contexts for all of the users courses
- *
  * @deprecated since Moodle 3.2
- * @param array $courses array of course objects. The courses the user is enrolled in.
- * @return array of course contexts
  */
-function message_get_course_contexts($courses) {
-    debugging('message_get_course_contexts() is deprecated and is no longer used.', DEBUG_DEVELOPER);
-
-    $coursecontexts = array();
-
-    foreach($courses as $course) {
-        $coursecontexts[$course->id] = context_course::instance($course->id);
-    }
-
-    return $coursecontexts;
+function message_get_course_contexts() {
+    throw new coding_exception('message_get_course_contexts() can not be used anymore.');
 }
 
 /**
- * strip off action parameters like 'removecontact'
- *
  * @deprecated since Moodle 3.2
- * @param moodle_url/string $moodleurl a URL. Typically the current page URL.
- * @return string the URL minus parameters that perform actions (like adding/removing/blocking a contact).
  */
-function message_remove_url_params($moodleurl) {
-    debugging('message_remove_url_params() is deprecated and is no longer used.', DEBUG_DEVELOPER);
-
-    $newurl = new moodle_url($moodleurl);
-    $newurl->remove_params('addcontact','removecontact','blockcontact','unblockcontact');
-    return $newurl->out();
+function message_remove_url_params() {
+    throw new coding_exception('message_remove_url_params() can not be used anymore.');
 }
 
 /**
- * Count the number of messages with a field having a specified value.
- * if $field is empty then return count of the whole array
- * if $field is non-existent then return 0
- *
  * @deprecated since Moodle 3.2
- * @param array $messagearray array of message objects
- * @param string $field the field to inspect on the message objects
- * @param string $value the value to test the field against
  */
-function message_count_messages($messagearray, $field='', $value='') {
-    debugging('message_count_messages() is deprecated and is no longer used.', DEBUG_DEVELOPER);
-
-    if (!is_array($messagearray)) return 0;
-    if ($field == '' or empty($messagearray)) return count($messagearray);
-
-    $count = 0;
-    foreach ($messagearray as $message) {
-        $count += ($message->$field == $value) ? 1 : 0;
-    }
-    return $count;
+function message_count_messages() {
+    throw new coding_exception('message_count_messages() can not be used anymore.');
 }
 
 /**
- * Count the number of users blocked by $user1
- *
  * @deprecated since Moodle 3.2
- * @param object $user1 user object
- * @return int the number of blocked users
  */
-function message_count_blocked_users($user1=null) {
-    debugging('message_count_blocked_users() is deprecated, please use \core_message\api::count_blocked_users() instead.',
-        DEBUG_DEVELOPER);
-
-    return \core_message\api::count_blocked_users($user1);
+function message_count_blocked_users() {
+    throw new coding_exception('message_count_blocked_users() can not be used anymore. Please use ' .
+        '\core_message\api::count_blocked_users() instead.');
 }
 
 /**
- * Print a message contact link
- *
  * @deprecated since Moodle 3.2
- * @param int $userid the ID of the user to apply to action to
- * @param string $linktype can be add, remove, block or unblock
- * @param bool $return if true return the link as a string. If false echo the link.
- * @param string $script the URL to send the user to when the link is clicked. If null, the current page.
- * @param bool $text include text next to the icons?
- * @param bool $icon include a graphical icon?
- * @return string  if $return is true otherwise bool
  */
-function message_contact_link($userid, $linktype='add', $return=false, $script=null, $text=false, $icon=true) {
-    debugging('message_contact_link() is deprecated and is no longer used.', DEBUG_DEVELOPER);
-
-    global $OUTPUT, $PAGE;
-
-    //hold onto the strings as we're probably creating a bunch of links
-    static $str;
-
-    if (empty($script)) {
-        //strip off previous action params like 'removecontact'
-        $script = message_remove_url_params($PAGE->url);
-    }
-
-    if (empty($str->blockcontact)) {
-        $str = new stdClass();
-        $str->blockcontact   =  get_string('blockcontact', 'message');
-        $str->unblockcontact =  get_string('unblockcontact', 'message');
-        $str->removecontact  =  get_string('removecontact', 'message');
-        $str->addcontact     =  get_string('addcontact', 'message');
-    }
-
-    $command = $linktype.'contact';
-    $string  = $str->{$command};
-
-    $safealttext = s($string);
-
-    $safestring = '';
-    if (!empty($text)) {
-        $safestring = $safealttext;
-    }
-
-    $img = '';
-    if ($icon) {
-        $iconpath = null;
-        switch ($linktype) {
-            case 'block':
-                $iconpath = 't/block';
-                break;
-            case 'unblock':
-                $iconpath = 't/unblock';
-                break;
-            case 'remove':
-                $iconpath = 't/removecontact';
-                break;
-            case 'add':
-            default:
-                $iconpath = 't/addcontact';
-        }
-
-        $img = $OUTPUT->pix_icon($iconpath, $safealttext);
-    }
-
-    $output = '<span class="'.$linktype.'contact">'.
-        '<a href="'.$script.'&amp;'.$command.'='.$userid.
-        '&amp;sesskey='.sesskey().'" title="'.$safealttext.'">'.
-        $img.
-        $safestring.'</a></span>';
-
-    if ($return) {
-        return $output;
-    } else {
-        echo $output;
-        return true;
-    }
+function message_contact_link() {
+    throw new coding_exception('message_contact_link() can not be used anymore.');
 }
 
 /**
  * @deprecated since Moodle 3.2
  */
-function message_get_recent_notifications($user, $limitfrom=0, $limitto=100) {
-    throw new coding_exception('message_get_recent_notifications() can not be used any more.', DEBUG_DEVELOPER);
+function message_get_recent_notifications() {
+    throw new coding_exception('message_get_recent_notifications() can not be used anymore.');
 }
 
 /**
- * echo or return a link to take the user to the full message history between themselves and another user
- *
  * @deprecated since Moodle 3.2
- * @param int $userid1 the ID of the user displayed on the left (usually the current user)
- * @param int $userid2 the ID of the other user
- * @param bool $return true to return the link as a string. False to echo the link.
- * @param string $keywords any keywords to highlight in the message history
- * @param string $position anchor name to jump to within the message history
- * @param string $linktext optionally specify the link text
- * @return string|bool. Returns a string if $return is true. Otherwise returns a boolean.
- */
-function message_history_link($userid1, $userid2, $return=false, $keywords='', $position='', $linktext='') {
-    debugging('message_history_link() is deprecated and is no longer used.', DEBUG_DEVELOPER);
-
-    global $OUTPUT, $PAGE;
-    static $strmessagehistory;
-
-    if (empty($strmessagehistory)) {
-        $strmessagehistory = get_string('messagehistory', 'message');
-    }
-
-    if ($position) {
-        $position = "#$position";
-    }
-    if ($keywords) {
-        $keywords = "&search=".urlencode($keywords);
-    }
-
-    if ($linktext == 'icon') {  // Icon only
-        $fulllink = $OUTPUT->pix_icon('t/messages', $strmessagehistory);
-    } else if ($linktext == 'both') {  // Icon and standard name
-        $fulllink = $OUTPUT->pix_icon('t/messages', '');
-        $fulllink .= '&nbsp;'.$strmessagehistory;
-    } else if ($linktext) {    // Custom name
-        $fulllink = $linktext;
-    } else {                   // Standard name only
-        $fulllink = $strmessagehistory;
-    }
-
-    $popupoptions = array(
-        'height' => 500,
-        'width' => 500,
-        'menubar' => false,
-        'location' => false,
-        'status' => true,
-        'scrollbars' => true,
-        'resizable' => true);
-
-    $link = new moodle_url('/message/index.php?history='.MESSAGE_HISTORY_ALL."&user1=$userid1&user2=$userid2$keywords$position");
-    if ($PAGE->url && $PAGE->url->get_param('viewing')) {
-        $link->param('viewing', $PAGE->url->get_param('viewing'));
-    }
-    $action = null;
-    $str = $OUTPUT->action_link($link, $fulllink, $action, array('title' => $strmessagehistory));
-
-    $str = '<span class="history">'.$str.'</span>';
-
-    if ($return) {
-        return $str;
-    } else {
-        echo $str;
-        return true;
-    }
+ */
+function message_history_link() {
+    throw new coding_exception('message_history_link() can not be used anymore.');
 }
 
 /**
  * @deprecated since Moodle 3.2
  */
-function message_search($searchterms, $fromme=true, $tome=true, $courseid='none', $userid=0) {
-    throw new coding_exception('message_search() can not be used any more.', DEBUG_DEVELOPER);
+function message_search() {
+    throw new coding_exception('message_search() can not be used anymore.');
 }
 
 /**
- * Given a message object that we already know has a long message
- * this function truncates the message nicely to the first
- * sane place between $CFG->forum_longpost and $CFG->forum_shortpost
- *
  * @deprecated since Moodle 3.2
- * @param string $message the message
- * @param int $minlength the minimum length to trim the message to
- * @return string the shortened message
  */
-function message_shorten_message($message, $minlength = 0) {
-    debugging('message_shorten_message() is deprecated and is no longer used.', DEBUG_DEVELOPER);
-
-    $i = 0;
-    $tag = false;
-    $length = strlen($message);
-    $count = 0;
-    $stopzone = false;
-    $truncate = 0;
-    if ($minlength == 0) $minlength = MESSAGE_SHORTLENGTH;
-
-
-    for ($i=0; $i<$length; $i++) {
-        $char = $message[$i];
-
-        switch ($char) {
-            case "<":
-                $tag = true;
-                break;
-            case ">":
-                $tag = false;
-                break;
-            default:
-                if (!$tag) {
-                    if ($stopzone) {
-                        if ($char == '.' or $char == ' ') {
-                            $truncate = $i+1;
-                            break 2;
-                        }
-                    }
-                    $count++;
-                }
-                break;
-        }
-        if (!$stopzone) {
-            if ($count > $minlength) {
-                $stopzone = true;
-            }
-        }
-    }
-
-    if (!$truncate) {
-        $truncate = $i;
-    }
-
-    return substr($message, 0, $truncate);
+function message_shorten_message() {
+    throw new coding_exception('message_shorten_message() can not be used anymore.');
 }
 
 /**
- * Given a string and an array of keywords, this function looks
- * for the first keyword in the string, and then chops out a
- * small section from the text that shows that word in context.
- *
  * @deprecated since Moodle 3.2
- * @param string $message the text to search
- * @param array $keywords array of keywords to find
  */
-function message_get_fragment($message, $keywords) {
-    debugging('message_get_fragment() is deprecated and is no longer used.', DEBUG_DEVELOPER);
-
-    $fullsize = 160;
-    $halfsize = (int)($fullsize/2);
-
-    $message = strip_tags($message);
-
-    foreach ($keywords as $keyword) {  // Just get the first one
-        if ($keyword !== '') {
-            break;
-        }
-    }
-    if (empty($keyword)) {   // None found, so just return start of message
-        return message_shorten_message($message, 30);
-    }
-
-    $leadin = $leadout = '';
-
-/// Find the start of the fragment
-    $start = 0;
-    $length = strlen($message);
-
-    $pos = strpos($message, $keyword);
-    if ($pos > $halfsize) {
-        $start = $pos - $halfsize;
-        $leadin = '...';
-    }
-/// Find the end of the fragment
-    $end = $start + $fullsize;
-    if ($end > $length) {
-        $end = $length;
-    } else {
-        $leadout = '...';
-    }
-
-/// Pull out the fragment and format it
-
-    $fragment = substr($message, $start, $end - $start);
-    $fragment = $leadin.highlight(implode(' ',$keywords), $fragment).$leadout;
-    return $fragment;
+function message_get_fragment() {
+    throw new coding_exception('message_get_fragment() can not be used anymore.');
 }
 
 /**
  * @deprecated since Moodle 3.2
  */
-function message_get_history($user1, $user2, $limitnum=0, $viewingnewmessages=false) {
-    throw new coding_exception('message_get_history() can not be used any more.', DEBUG_DEVELOPER);
+function message_get_history() {
+    throw new coding_exception('message_get_history() can not be used anymore.');
 }
 
 /**
- * Constructs the add/remove contact link to display next to other users
- *
  * @deprecated since Moodle 3.2
- * @param bool $incontactlist is the user a contact
- * @param bool $isblocked is the user blocked
- * @param stdClass $contact contact object
- * @param string $script the URL to send the user to when the link is clicked. If null, the current page.
- * @param bool $text include text next to the icons?
- * @param bool $icon include a graphical icon?
- * @return string
- */
-function message_get_contact_add_remove_link($incontactlist, $isblocked, $contact, $script=null, $text=false, $icon=true) {
-    debugging('message_get_contact_add_remove_link() is deprecated and is no longer used.', DEBUG_DEVELOPER);
-
-    $strcontact = '';
-
-    if($incontactlist){
-        $strcontact = message_contact_link($contact->id, 'remove', true, $script, $text, $icon);
-    } else if ($isblocked) {
-        $strcontact = message_contact_link($contact->id, 'add', true, $script, $text, $icon);
-    } else{
-        $strcontact = message_contact_link($contact->id, 'add', true, $script, $text, $icon);
-    }
-
-    return $strcontact;
+ */
+function message_get_contact_add_remove_link() {
+    throw new coding_exception('message_get_contact_add_remove_link() can not be used anymore.');
 }
 
 /**
- * Constructs the block contact link to display next to other users
- *
  * @deprecated since Moodle 3.2
- * @param bool $incontactlist is the user a contact?
- * @param bool $isblocked is the user blocked?
- * @param stdClass $contact contact object
- * @param string $script the URL to send the user to when the link is clicked. If null, the current page.
- * @param bool $text include text next to the icons?
- * @param bool $icon include a graphical icon?
- * @return string
- */
-function message_get_contact_block_link($incontactlist, $isblocked, $contact, $script=null, $text=false, $icon=true) {
-    debugging('message_get_contact_block_link() is deprecated and is no longer used.', DEBUG_DEVELOPER);
-
-    $strblock   = '';
-
-    //commented out to allow the user to block a contact without having to remove them first
-    /*if ($incontactlist) {
-        //$strblock = '';
-    } else*/
-    if ($isblocked) {
-        $strblock   = message_contact_link($contact->id, 'unblock', true, $script, $text, $icon);
-    } else{
-        $strblock   = message_contact_link($contact->id, 'block', true, $script, $text, $icon);
-    }
-
-    return $strblock;
+ */
+function message_get_contact_block_link() {
+    throw new coding_exception('message_get_contact_block_link() can not be used anymore.');
 }
 
 /**
- * marks ALL messages being sent from $fromuserid to $touserid as read
- *
  * @deprecated since Moodle 3.2
- * @param int $touserid the id of the message recipient
- * @param int $fromuserid the id of the message sender
- * @return void
  */
-function message_mark_messages_read($touserid, $fromuserid) {
-    debugging('message_mark_messages_read() is deprecated and is no longer used, please use
-        \core_message\api::mark_all_messages_as_read() instead.', DEBUG_DEVELOPER);
-
-    \core_message\api::mark_all_messages_as_read($touserid, $fromuserid);
+function message_mark_messages_read() {
+    throw new coding_exception('message_mark_messages_read() can not be used anymore. Please use ' .
+        '\core_message\api::mark_all_messages_as_read() instead.');
 }
 
 /**
- * Return a list of page types
- *
  * @deprecated since Moodle 3.2
- * @param string $pagetype current page type
- * @param stdClass $parentcontext Block's parent context
- * @param stdClass $currentcontext Current context of block
  */
-function message_page_type_list($pagetype, $parentcontext, $currentcontext) {
-    debugging('message_page_type_list() is deprecated and is no longer used.', DEBUG_DEVELOPER);
-
-    return array('messages-*'=>get_string('page-message-x', 'message'));
+function message_page_type_list() {
+    throw new coding_exception('message_page_type_list() can not be used anymore.');
 }
 
 /**
- * Determines if a user is permitted to send another user a private message.
- * If no sender is provided then it defaults to the logged in user.
- *
  * @deprecated since Moodle 3.2
- * @param object $recipient User object.
- * @param object $sender User object.
- * @return bool true if user is permitted, false otherwise.
  */
-function message_can_post_message($recipient, $sender = null) {
-    debugging('message_can_post_message() is deprecated and is no longer used, please use
-        \core_message\api::can_post_message() instead.', DEBUG_DEVELOPER);
-
-    return \core_message\api::can_post_message($recipient, $sender);
+function message_can_post_message() {
+    throw new coding_exception('message_can_post_message() can not be used anymore. Please use ' .
+        '\core_message\api::can_post_message() instead.');
 }
 
 /**
- * Checks if the recipient is allowing messages from users that aren't a
- * contact. If not then it checks to make sure the sender is in the
- * recipient's contacts.
- *
  * @deprecated since Moodle 3.2
- * @param object $recipient User object.
- * @param object $sender User object.
- * @return bool true if $sender is blocked, false otherwise.
  */
-function message_is_user_non_contact_blocked($recipient, $sender = null) {
-    debugging('message_is_user_non_contact_blocked() is deprecated and is no longer used, please use
-        \core_message\api::is_user_non_contact_blocked() instead.', DEBUG_DEVELOPER);
-
-    return \core_message\api::is_user_non_contact_blocked($recipient, $sender);
+function message_is_user_non_contact_blocked() {
+    throw new coding_exception('message_is_user_non_contact_blocked() can not be used anymore. Please use ' .
+        '\core_message\api::is_user_non_contact_blocked() instead.');
 }
 
 /**
- * Checks if the recipient has specifically blocked the sending user.
- *
- * Note: This function will always return false if the sender has the
- * readallmessages capability at the system context level.
- *
  * @deprecated since Moodle 3.2
- * @param object $recipient User object.
- * @param object $sender User object.
- * @return bool true if $sender is blocked, false otherwise.
  */
-function message_is_user_blocked($recipient, $sender = null) {
-    debugging('message_is_user_blocked() is deprecated and is no longer used, please use
-        \core_message\api::is_user_blocked() instead.', DEBUG_DEVELOPER);
-
-    $senderid = null;
-    if ($sender !== null && isset($sender->id)) {
-        $senderid = $sender->id;
-    }
-    return \core_message\api::is_user_blocked($recipient->id, $senderid);
+function message_is_user_blocked() {
+    throw new coding_exception('message_is_user_blocked() can not be used anymore. Please use ' .
+        '\core_message\api::is_user_blocked() instead.');
 }
 
 /**
- * Display logs.
- *
- * @deprecated since 3.2
+ * @deprecated since Moodle 3.2
  */
-function print_log($course, $user=0, $date=0, $order="l.time ASC", $page=0, $perpage=100,
-                   $url="", $modname="", $modid=0, $modaction="", $groupid=0) {
-    debugging(__FUNCTION__ . '() is deprecated. Please use the report_log framework instead.', DEBUG_DEVELOPER);
-
-    global $CFG, $DB, $OUTPUT;
-
-    if (!$logs = build_logs_array($course, $user, $date, $order, $page*$perpage, $perpage,
-                       $modname, $modid, $modaction, $groupid)) {
-        echo $OUTPUT->notification("No logs found!");
-        echo $OUTPUT->footer();
-        exit;
-    }
-
-    $courses = array();
-
-    if ($course->id == SITEID) {
-        $courses[0] = '';
-        if ($ccc = get_courses('all', 'c.id ASC', 'c.id,c.shortname')) {
-            foreach ($ccc as $cc) {
-                $courses[$cc->id] = $cc->shortname;
-            }
-        }
-    } else {
-        $courses[$course->id] = $course->shortname;
-    }
-
-    $totalcount = $logs['totalcount'];
-    $ldcache = array();
-
-    $strftimedatetime = get_string("strftimedatetime");
-
-    echo "<div class=\"info\">\n";
-    print_string("displayingrecords", "", $totalcount);
-    echo "</div>\n";
-
-    echo $OUTPUT->paging_bar($totalcount, $page, $perpage, "$url&perpage=$perpage");
-
-    $table = new html_table();
-    $table->classes = array('logtable','generaltable');
-    $table->align = array('right', 'left', 'left');
-    $table->head = array(
-        get_string('time'),
-        get_string('ip_address'),
-        get_string('fullnameuser'),
-        get_string('action'),
-        get_string('info')
-    );
-    $table->data = array();
-
-    if ($course->id == SITEID) {
-        array_unshift($table->align, 'left');
-        array_unshift($table->head, get_string('course'));
-    }
-
-    // Make sure that the logs array is an array, even it is empty, to avoid warnings from the foreach.
-    if (empty($logs['logs'])) {
-        $logs['logs'] = array();
-    }
-
-    foreach ($logs['logs'] as $log) {
-
-        if (isset($ldcache[$log->module][$log->action])) {
-            $ld = $ldcache[$log->module][$log->action];
-        } else {
-            $ld = $DB->get_record('log_display', array('module'=>$log->module, 'action'=>$log->action));
-            $ldcache[$log->module][$log->action] = $ld;
-        }
-        if ($ld && is_numeric($log->info)) {
-            // ugly hack to make sure fullname is shown correctly
-            if ($ld->mtable == 'user' && $ld->field == $DB->sql_concat('firstname', "' '" , 'lastname')) {
-                $log->info = fullname($DB->get_record($ld->mtable, array('id'=>$log->info)), true);
-            } else {
-                $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$log->info));
-            }
-        }
-
-        //Filter log->info
-        $log->info = format_string($log->info);
-
-        // If $log->url has been trimmed short by the db size restriction
-        // code in add_to_log, keep a note so we don't add a link to a broken url
-        $brokenurl=(core_text::strlen($log->url)==100 && core_text::substr($log->url,97)=='...');
-
-        $row = array();
-        if ($course->id == SITEID) {
-            if (empty($log->course)) {
-                $row[] = get_string('site');
-            } else {
-                $row[] = "<a href=\"{$CFG->wwwroot}/course/view.php?id={$log->course}\">". format_string($courses[$log->course])."</a>";
-            }
-        }
-
-        $row[] = userdate($log->time, '%a').' '.userdate($log->time, $strftimedatetime);
-
-        $link = new moodle_url("/iplookup/index.php?ip=$log->ip&user=$log->userid");
-        $row[] = $OUTPUT->action_link($link, $log->ip, new popup_action('click', $link, 'iplookup', array('height' => 440, 'width' => 700)));
-
-        $row[] = html_writer::link(new moodle_url("/user/view.php?id={$log->userid}&course={$log->course}"), fullname($log, has_capability('moodle/site:viewfullnames', context_course::instance($course->id))));
-
-        $displayaction="$log->module $log->action";
-        if ($brokenurl) {
-            $row[] = $displayaction;
-        } else {
-            $link = make_log_url($log->module,$log->url);
-            $row[] = $OUTPUT->action_link($link, $displayaction, new popup_action('click', $link, 'fromloglive'), array('height' => 440, 'width' => 700));
-        }
-        $row[] = $log->info;
-        $table->data[] = $row;
-    }
-
-    echo html_writer::table($table);
-    echo $OUTPUT->paging_bar($totalcount, $page, $perpage, "$url&perpage=$perpage");
+function print_log() {
+    throw new coding_exception('print_log() can not be used anymore. Please use the ' .
+        'report_log framework instead.');
 }
 
 /**
- * Display MNET logs.
- *
- * @deprecated since 3.2
+ * @deprecated since Moodle 3.2
  */
-function print_mnet_log($hostid, $course, $user=0, $date=0, $order="l.time ASC", $page=0, $perpage=100,
-                   $url="", $modname="", $modid=0, $modaction="", $groupid=0) {
-    debugging(__FUNCTION__ . '() is deprecated. Please use the report_log framework instead.', DEBUG_DEVELOPER);
-
-    global $CFG, $DB, $OUTPUT;
-
-    if (!$logs = build_mnet_logs_array($hostid, $course, $user, $date, $order, $page*$perpage, $perpage,
-                       $modname, $modid, $modaction, $groupid)) {
-        echo $OUTPUT->notification("No logs found!");
-        echo $OUTPUT->footer();
-        exit;
-    }
-
-    if ($course->id == SITEID) {
-        $courses[0] = '';
-        if ($ccc = get_courses('all', 'c.id ASC', 'c.id,c.shortname,c.visible')) {
-            foreach ($ccc as $cc) {
-                $courses[$cc->id] = $cc->shortname;
-            }
-        }
-    }
-
-    $totalcount = $logs['totalcount'];
-    $ldcache = array();
-
-    $strftimedatetime = get_string("strftimedatetime");
-
-    echo "<div class=\"info\">\n";
-    print_string("displayingrecords", "", $totalcount);
-    echo "</div>\n";
-
-    echo $OUTPUT->paging_bar($totalcount, $page, $perpage, "$url&perpage=$perpage");
-
-    echo "<table class=\"logtable\" cellpadding=\"3\" cellspacing=\"0\">\n";
-    echo "<tr>";
-    if ($course->id == SITEID) {
-        echo "<th class=\"c0 header\">".get_string('course')."</th>\n";
-    }
-    echo "<th class=\"c1 header\">".get_string('time')."</th>\n";
-    echo "<th class=\"c2 header\">".get_string('ip_address')."</th>\n";
-    echo "<th class=\"c3 header\">".get_string('fullnameuser')."</th>\n";
-    echo "<th class=\"c4 header\">".get_string('action')."</th>\n";
-    echo "<th class=\"c5 header\">".get_string('info')."</th>\n";
-    echo "</tr>\n";
-
-    if (empty($logs['logs'])) {
-        echo "</table>\n";
-        return;
-    }
-
-    $row = 1;
-    foreach ($logs['logs'] as $log) {
-
-        $log->info = $log->coursename;
-        $row = ($row + 1) % 2;
-
-        if (isset($ldcache[$log->module][$log->action])) {
-            $ld = $ldcache[$log->module][$log->action];
-        } else {
-            $ld = $DB->get_record('log_display', array('module'=>$log->module, 'action'=>$log->action));
-            $ldcache[$log->module][$log->action] = $ld;
-        }
-        if (0 && $ld && !empty($log->info)) {
-            // ugly hack to make sure fullname is shown correctly
-            if (($ld->mtable == 'user') and ($ld->field == $DB->sql_concat('firstname', "' '" , 'lastname'))) {
-                $log->info = fullname($DB->get_record($ld->mtable, array('id'=>$log->info)), true);
-            } else {
-                $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$log->info));
-            }
-        }
-
-        //Filter log->info
-        $log->info = format_string($log->info);
-
-        echo '<tr class="r'.$row.'">';
-        if ($course->id == SITEID) {
-            $courseshortname = format_string($courses[$log->course], true, array('context' => context_course::instance(SITEID)));
-            echo "<td class=\"r$row c0\" >\n";
-            echo "    <a href=\"{$CFG->wwwroot}/course/view.php?id={$log->course}\">".$courseshortname."</a>\n";
-            echo "</td>\n";
-        }
-        echo "<td class=\"r$row c1\" align=\"right\">".userdate($log->time, '%a').
-             ' '.userdate($log->time, $strftimedatetime)."</td>\n";
-        echo "<td class=\"r$row c2\" >\n";
-        $link = new moodle_url("/iplookup/index.php?ip=$log->ip&user=$log->userid");
-        echo $OUTPUT->action_link($link, $log->ip, new popup_action('click', $link, 'iplookup', array('height' => 400, 'width' => 700)));
-        echo "</td>\n";
-        $fullname = fullname($log, has_capability('moodle/site:viewfullnames', context_course::instance($course->id)));
-        echo "<td class=\"r$row c3\" >\n";
-        echo "    <a href=\"$CFG->wwwroot/user/view.php?id={$log->userid}\">$fullname</a>\n";
-        echo "</td>\n";
-        echo "<td class=\"r$row c4\">\n";
-        echo $log->action .': '.$log->module;
-        echo "</td>\n";
-        echo "<td class=\"r$row c5\">{$log->info}</td>\n";
-        echo "</tr>\n";
-    }
-    echo "</table>\n";
-
-    echo $OUTPUT->paging_bar($totalcount, $page, $perpage, "$url&perpage=$perpage");
+function print_mnet_log() {
+    throw new coding_exception('print_mnet_log() can not be used anymore. Please use the ' .
+        'report_log framework instead.');
 }
 
 /**
- * Display logs in CSV format.
- *
- * @deprecated since 3.2
+ * @deprecated since Moodle 3.2
  */
-function print_log_csv($course, $user, $date, $order='l.time DESC', $modname,
-                        $modid, $modaction, $groupid) {
-    debugging(__FUNCTION__ . '() is deprecated. Please use the report_log framework instead.', DEBUG_DEVELOPER);
-
-    global $DB, $CFG;
-
-    require_once($CFG->libdir . '/csvlib.class.php');
-
-    $csvexporter = new csv_export_writer('tab');
-
-    $header = array();
-    $header[] = get_string('course');
-    $header[] = get_string('time');
-    $header[] = get_string('ip_address');
-    $header[] = get_string('fullnameuser');
-    $header[] = get_string('action');
-    $header[] = get_string('info');
-
-    if (!$logs = build_logs_array($course, $user, $date, $order, '', '',
-                       $modname, $modid, $modaction, $groupid)) {
-        return false;
-    }
-
-    $courses = array();
-
-    if ($course->id == SITEID) {
-        $courses[0] = '';
-        if ($ccc = get_courses('all', 'c.id ASC', 'c.id,c.shortname')) {
-            foreach ($ccc as $cc) {
-                $courses[$cc->id] = $cc->shortname;
-            }
-        }
-    } else {
-        $courses[$course->id] = $course->shortname;
-    }
-
-    $count=0;
-    $ldcache = array();
-    $tt = getdate(time());
-    $today = mktime (0, 0, 0, $tt["mon"], $tt["mday"], $tt["year"]);
-
-    $strftimedatetime = get_string("strftimedatetime");
-
-    $csvexporter->set_filename('logs', '.txt');
-    $title = array(get_string('savedat').userdate(time(), $strftimedatetime));
-    $csvexporter->add_data($title);
-    $csvexporter->add_data($header);
-
-    if (empty($logs['logs'])) {
-        return true;
-    }
-
-    foreach ($logs['logs'] as $log) {
-        if (isset($ldcache[$log->module][$log->action])) {
-            $ld = $ldcache[$log->module][$log->action];
-        } else {
-            $ld = $DB->get_record('log_display', array('module'=>$log->module, 'action'=>$log->action));
-            $ldcache[$log->module][$log->action] = $ld;
-        }
-        if ($ld && is_numeric($log->info)) {
-            // ugly hack to make sure fullname is shown correctly
-            if (($ld->mtable == 'user') and ($ld->field ==  $DB->sql_concat('firstname', "' '" , 'lastname'))) {
-                $log->info = fullname($DB->get_record($ld->mtable, array('id'=>$log->info)), true);
-            } else {
-                $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$log->info));
-            }
-        }
-
-        //Filter log->info
-        $log->info = format_string($log->info);
-        $log->info = strip_tags(urldecode($log->info));    // Some XSS protection
-
-        $coursecontext = context_course::instance($course->id);
-        $firstField = format_string($courses[$log->course], true, array('context' => $coursecontext));
-        $fullname = fullname($log, has_capability('moodle/site:viewfullnames', $coursecontext));
-        $actionurl = $CFG->wwwroot. make_log_url($log->module,$log->url);
-        $row = array($firstField, userdate($log->time, $strftimedatetime), $log->ip, $fullname, $log->module.' '.$log->action.' ('.$actionurl.')', $log->info);
-        $csvexporter->add_data($row);
-    }
-    $csvexporter->download_file();
-    return true;
+function print_log_csv() {
+    throw new coding_exception('print_log_csv() can not be used anymore. Please use the ' .
+        'report_log framework instead.');
 }
 
 /**
- * Display logs in XLS format.
- *
- * @deprecated since 3.2
+ * @deprecated since Moodle 3.2
  */
-function print_log_xls($course, $user, $date, $order='l.time DESC', $modname,
-                        $modid, $modaction, $groupid) {
-    debugging(__FUNCTION__ . '() is deprecated. Please use the report_log framework instead.', DEBUG_DEVELOPER);
-
-    global $CFG, $DB;
-
-    require_once("$CFG->libdir/excellib.class.php");
-
-    if (!$logs = build_logs_array($course, $user, $date, $order, '', '',
-                       $modname, $modid, $modaction, $groupid)) {
-        return false;
-    }
-
-    $courses = array();
-
-    if ($course->id == SITEID) {
-        $courses[0] = '';
-        if ($ccc = get_courses('all', 'c.id ASC', 'c.id,c.shortname')) {
-            foreach ($ccc as $cc) {
-                $courses[$cc->id] = $cc->shortname;
-            }
-        }
-    } else {
-        $courses[$course->id] = $course->shortname;
-    }
-
-    $count=0;
-    $ldcache = array();
-    $tt = getdate(time());
-    $today = mktime (0, 0, 0, $tt["mon"], $tt["mday"], $tt["year"]);
-
-    $strftimedatetime = get_string("strftimedatetime");
-
-    $nroPages = ceil(count($logs)/(EXCELROWS-FIRSTUSEDEXCELROW+1));
-    $filename = 'logs_'.userdate(time(),get_string('backupnameformat', 'langconfig'),99,false);
-    $filename .= '.xls';
-
-    $workbook = new MoodleExcelWorkbook('-');
-    $workbook->send($filename);
-
-    $worksheet = array();
-    $headers = array(get_string('course'), get_string('time'), get_string('ip_address'),
-                        get_string('fullnameuser'),    get_string('action'), get_string('info'));
-
-    // Creating worksheets
-    for ($wsnumber = 1; $wsnumber <= $nroPages; $wsnumber++) {
-        $sheettitle = get_string('logs').' '.$wsnumber.'-'.$nroPages;
-        $worksheet[$wsnumber] = $workbook->add_worksheet($sheettitle);
-        $worksheet[$wsnumber]->set_column(1, 1, 30);
-        $worksheet[$wsnumber]->write_string(0, 0, get_string('savedat').
-                                    userdate(time(), $strftimedatetime));
-        $col = 0;
-        foreach ($headers as $item) {
-            $worksheet[$wsnumber]->write(FIRSTUSEDEXCELROW-1,$col,$item,'');
-            $col++;
-        }
-    }
-
-    if (empty($logs['logs'])) {
-        $workbook->close();
-        return true;
-    }
-
-    $formatDate =& $workbook->add_format();
-    $formatDate->set_num_format(get_string('log_excel_date_format'));
-
-    $row = FIRSTUSEDEXCELROW;
-    $wsnumber = 1;
-    $myxls =& $worksheet[$wsnumber];
-    foreach ($logs['logs'] as $log) {
-        if (isset($ldcache[$log->module][$log->action])) {
-            $ld = $ldcache[$log->module][$log->action];
-        } else {
-            $ld = $DB->get_record('log_display', array('module'=>$log->module, 'action'=>$log->action));
-            $ldcache[$log->module][$log->action] = $ld;
-        }
-        if ($ld && is_numeric($log->info)) {
-            // ugly hack to make sure fullname is shown correctly
-            if (($ld->mtable == 'user') and ($ld->field == $DB->sql_concat('firstname', "' '" , 'lastname'))) {
-                $log->info = fullname($DB->get_record($ld->mtable, array('id'=>$log->info)), true);
-            } else {
-                $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$log->info));
-            }
-        }
-
-        // Filter log->info
-        $log->info = format_string($log->info);
-        $log->info = strip_tags(urldecode($log->info));  // Some XSS protection
-
-        if ($nroPages>1) {
-            if ($row > EXCELROWS) {
-                $wsnumber++;
-                $myxls =& $worksheet[$wsnumber];
-                $row = FIRSTUSEDEXCELROW;
-            }
-        }
-
-        $coursecontext = context_course::instance($course->id);
-
-        $myxls->write($row, 0, format_string($courses[$log->course], true, array('context' => $coursecontext)), '');
-        $myxls->write_date($row, 1, $log->time, $formatDate); // write_date() does conversion/timezone support. MDL-14934
-        $myxls->write($row, 2, $log->ip, '');
-        $fullname = fullname($log, has_capability('moodle/site:viewfullnames', $coursecontext));
-        $myxls->write($row, 3, $fullname, '');
-        $actionurl = $CFG->wwwroot. make_log_url($log->module,$log->url);
-        $myxls->write($row, 4, $log->module.' '.$log->action.' ('.$actionurl.')', '');
-        $myxls->write($row, 5, $log->info, '');
-
-        $row++;
-    }
-
-    $workbook->close();
-    return true;
+function print_log_xls() {
+    throw new coding_exception('print_log_xls() can not be used anymore. Please use the ' .
+        'report_log framework instead.');
 }
 
 /**
- * Display logs in ODS format.
- *
- * @deprecated since 3.2
+ * @deprecated since Moodle 3.2
  */
-function print_log_ods($course, $user, $date, $order='l.time DESC', $modname,
-                        $modid, $modaction, $groupid) {
-    debugging(__FUNCTION__ . '() is deprecated. Please use the report_log framework instead.', DEBUG_DEVELOPER);
-
-    global $CFG, $DB;
-
-    require_once("$CFG->libdir/odslib.class.php");
-
-    if (!$logs = build_logs_array($course, $user, $date, $order, '', '',
-                       $modname, $modid, $modaction, $groupid)) {
-        return false;
-    }
-
-    $courses = array();
-
-    if ($course->id == SITEID) {
-        $courses[0] = '';
-        if ($ccc = get_courses('all', 'c.id ASC', 'c.id,c.shortname')) {
-            foreach ($ccc as $cc) {
-                $courses[$cc->id] = $cc->shortname;
-            }
-        }
-    } else {
-        $courses[$course->id] = $course->shortname;
-    }
-
-    $ldcache = array();
-
-    $strftimedatetime = get_string("strftimedatetime");
-
-    $nroPages = ceil(count($logs)/(EXCELROWS-FIRSTUSEDEXCELROW+1));
-    $filename = 'logs_'.userdate(time(),get_string('backupnameformat', 'langconfig'),99,false);
-    $filename .= '.ods';
-
-    $workbook = new MoodleODSWorkbook('-');
-    $workbook->send($filename);
-
-    $worksheet = array();
-    $headers = array(get_string('course'), get_string('time'), get_string('ip_address'),
-                        get_string('fullnameuser'),    get_string('action'), get_string('info'));
-
-    // Creating worksheets
-    for ($wsnumber = 1; $wsnumber <= $nroPages; $wsnumber++) {
-        $sheettitle = get_string('logs').' '.$wsnumber.'-'.$nroPages;
-        $worksheet[$wsnumber] = $workbook->add_worksheet($sheettitle);
-        $worksheet[$wsnumber]->set_column(1, 1, 30);
-        $worksheet[$wsnumber]->write_string(0, 0, get_string('savedat').
-                                    userdate(time(), $strftimedatetime));
-        $col = 0;
-        foreach ($headers as $item) {
-            $worksheet[$wsnumber]->write(FIRSTUSEDEXCELROW-1,$col,$item,'');
-            $col++;
-        }
-    }
-
-    if (empty($logs['logs'])) {
-        $workbook->close();
-        return true;
-    }
-
-    $formatDate =& $workbook->add_format();
-    $formatDate->set_num_format(get_string('log_excel_date_format'));
-
-    $row = FIRSTUSEDEXCELROW;
-    $wsnumber = 1;
-    $myxls =& $worksheet[$wsnumber];
-    foreach ($logs['logs'] as $log) {
-        if (isset($ldcache[$log->module][$log->action])) {
-            $ld = $ldcache[$log->module][$log->action];
-        } else {
-            $ld = $DB->get_record('log_display', array('module'=>$log->module, 'action'=>$log->action));
-            $ldcache[$log->module][$log->action] = $ld;
-        }
-        if ($ld && is_numeric($log->info)) {
-            // ugly hack to make sure fullname is shown correctly
-            if (($ld->mtable == 'user') and ($ld->field == $DB->sql_concat('firstname', "' '" , 'lastname'))) {
-                $log->info = fullname($DB->get_record($ld->mtable, array('id'=>$log->info)), true);
-            } else {
-                $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$log->info));
-            }
-        }
-
-        // Filter log->info
-        $log->info = format_string($log->info);
-        $log->info = strip_tags(urldecode($log->info));  // Some XSS protection
-
-        if ($nroPages>1) {
-            if ($row > EXCELROWS) {
-                $wsnumber++;
-                $myxls =& $worksheet[$wsnumber];
-                $row = FIRSTUSEDEXCELROW;
-            }
-        }
-
-        $coursecontext = context_course::instance($course->id);
-
-        $myxls->write_string($row, 0, format_string($courses[$log->course], true, array('context' => $coursecontext)));
-        $myxls->write_date($row, 1, $log->time);
-        $myxls->write_string($row, 2, $log->ip);
-        $fullname = fullname($log, has_capability('moodle/site:viewfullnames', $coursecontext));
-        $myxls->write_string($row, 3, $fullname);
-        $actionurl = $CFG->wwwroot. make_log_url($log->module,$log->url);
-        $myxls->write_string($row, 4, $log->module.' '.$log->action.' ('.$actionurl.')');
-        $myxls->write_string($row, 5, $log->info);
-
-        $row++;
-    }
-
-    $workbook->close();
-    return true;
+function print_log_ods() {
+    throw new coding_exception('print_log_ods() can not be used anymore. Please use the ' .
+        'report_log framework instead.');
 }
 
 /**
- * Build an array of logs.
- *
- * @deprecated since 3.2
+ * @deprecated since Moodle 3.2
  */
-function build_logs_array($course, $user=0, $date=0, $order="l.time ASC", $limitfrom='', $limitnum='',
-                   $modname="", $modid=0, $modaction="", $groupid=0) {
-    global $DB, $SESSION, $USER;
-
-    debugging(__FUNCTION__ . '() is deprecated. Please use the report_log framework instead.', DEBUG_DEVELOPER);
-    // It is assumed that $date is the GMT time of midnight for that day,
-    // and so the next 86400 seconds worth of logs are printed.
-
-    // Setup for group handling.
-
-    // If the group mode is separate, and this user does not have editing privileges,
-    // then only the user's group can be viewed.
-    if ($course->groupmode == SEPARATEGROUPS and !has_capability('moodle/course:managegroups', context_course::instance($course->id))) {
-        if (isset($SESSION->currentgroup[$course->id])) {
-            $groupid =  $SESSION->currentgroup[$course->id];
-        } else {
-            $groupid = groups_get_all_groups($course->id, $USER->id);
-            if (is_array($groupid)) {
-                $groupid = array_shift(array_keys($groupid));
-                $SESSION->currentgroup[$course->id] = $groupid;
-            } else {
-                $groupid = 0;
-            }
-        }
-    }
-    // If this course doesn't have groups, no groupid can be specified.
-    else if (!$course->groupmode) {
-        $groupid = 0;
-    }
-
-    $joins = array();
-    $params = array();
-
-    if ($course->id != SITEID || $modid != 0) {
-        $joins[] = "l.course = :courseid";
-        $params['courseid'] = $course->id;
-    }
-
-    if ($modname) {
-        $joins[] = "l.module = :modname";
-        $params['modname'] = $modname;
-    }
-
-    if ('site_errors' === $modid) {
-        $joins[] = "( l.action='error' OR l.action='infected' )";
-    } else if ($modid) {
-        $joins[] = "l.cmid = :modid";
-        $params['modid'] = $modid;
-    }
-
-    if ($modaction) {
-        $firstletter = substr($modaction, 0, 1);
-        if ($firstletter == '-') {
-            $joins[] = $DB->sql_like('l.action', ':modaction', false, true, true);
-            $params['modaction'] = '%'.substr($modaction, 1).'%';
-        } else {
-            $joins[] = $DB->sql_like('l.action', ':modaction', false);
-            $params['modaction'] = '%'.$modaction.'%';
-        }
-    }
-
-
-    /// Getting all members of a group.
-    if ($groupid and !$user) {
-        if ($gusers = groups_get_members($groupid)) {
-            $gusers = array_keys($gusers);
-            $joins[] = 'l.userid IN (' . implode(',', $gusers) . ')';
-        } else {
-            $joins[] = 'l.userid = 0'; // No users in groups, so we want something that will always be false.
-        }
-    }
-    else if ($user) {
-        $joins[] = "l.userid = :userid";
-        $params['userid'] = $user;
-    }
-
-    if ($date) {
-        $enddate = $date + 86400;
-        $joins[] = "l.time > :date AND l.time < :enddate";
-        $params['date'] = $date;
-        $params['enddate'] = $enddate;
-    }
-
-    $selector = implode(' AND ', $joins);
-
-    $totalcount = 0;  // Initialise
-    $result = array();
-    $result['logs'] = get_logs($selector, $params, $order, $limitfrom, $limitnum, $totalcount);
-    $result['totalcount'] = $totalcount;
-    return $result;
+function build_logs_array() {
+    throw new coding_exception('build_logs_array() can not be used anymore. Please use the ' .
+        'report_log framework instead.');
 }
 
 /**
- * Select all log records for a given course and user.
- *
- * @deprecated since 3.2
- * @param int $userid The id of the user as found in the 'user' table.
- * @param int $courseid The id of the course as found in the 'course' table.
- * @param string $coursestart unix timestamp representing course start date and time.
- * @return array
+ * @deprecated since Moodle 3.2
  */
-function get_logs_usercourse($userid, $courseid, $coursestart) {
-    global $DB;
-
-    debugging(__FUNCTION__ . '() is deprecated. Please use the report_log framework instead.', DEBUG_DEVELOPER);
-
-    $params = array();
-
-    $courseselect = '';
-    if ($courseid) {
-        $courseselect = "AND course = :courseid";
-        $params['courseid'] = $courseid;
-    }
-    $params['userid'] = $userid;
-    // We have to sanitize this param ourselves here instead of relying on DB.
-    // Postgres complains if you use name parameter or column alias in GROUP BY.
-    // See MDL-27696 and 51c3e85 for details.
-    $coursestart = (int)$coursestart;
-
-    return $DB->get_records_sql("SELECT FLOOR((time - $coursestart)/". DAYSECS .") AS day, COUNT(*) AS num
-                                   FROM {log}
-                                  WHERE userid = :userid
-                                        AND time > $coursestart $courseselect
-                               GROUP BY FLOOR((time - $coursestart)/". DAYSECS .")", $params);
+function get_logs_usercourse() {
+    throw new coding_exception('get_logs_usercourse() can not be used anymore. Please use the ' .
+        'report_log framework instead.');
 }
 
 /**
- * Select all log records for a given course, user, and day.
- *
- * @deprecated since 3.2
- * @param int $userid The id of the user as found in the 'user' table.
- * @param int $courseid The id of the course as found in the 'course' table.
- * @param string $daystart unix timestamp of the start of the day for which the logs needs to be retrived
- * @return array
+ * @deprecated since Moodle 3.2
  */
-function get_logs_userday($userid, $courseid, $daystart) {
-    global $DB;
-
-    debugging(__FUNCTION__ . '() is deprecated. Please use the report_log framework instead.', DEBUG_DEVELOPER);
-
-    $params = array('userid'=>$userid);
-
-    $courseselect = '';
-    if ($courseid) {
-        $courseselect = "AND course = :courseid";
-        $params['courseid'] = $courseid;
-    }
-    // Note: unfortunately pg complains if you use name parameter or column alias in GROUP BY.
-    $daystart = (int) $daystart;
-
-    return $DB->get_records_sql("SELECT FLOOR((time - $daystart)/". HOURSECS .") AS hour, COUNT(*) AS num
-                                   FROM {log}
-                                  WHERE userid = :userid
-                                        AND time > $daystart $courseselect
-                               GROUP BY FLOOR((time - $daystart)/". HOURSECS .") ", $params);
+function get_logs_userday() {
+    throw new coding_exception('get_logs_userday() can not be used anymore. Please use the ' .
+        'report_log framework instead.');
 }
 
 /**
- * Select all log records based on SQL criteria.
- *
- * @deprecated since 3.2
- * @param string $select SQL select criteria
- * @param array $params named sql type params
- * @param string $order SQL order by clause to sort the records returned
- * @param string $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set)
- * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set)
- * @param int $totalcount Passed in by reference.
- * @return array
+ * @deprecated since Moodle 3.2
  */
-function get_logs($select, array $params=null, $order='l.time DESC', $limitfrom='', $limitnum='', &$totalcount) {
-    global $DB;
-
-    debugging(__FUNCTION__ . '() is deprecated. Please use the report_log framework instead.', DEBUG_DEVELOPER);
-
-    if ($order) {
-        $order = "ORDER BY $order";
-    }
-
-    if ($select) {
-        $select = "WHERE $select";
-    }
-
-    $sql = "SELECT COUNT(*)
-              FROM {log} l
-           $select";
-
-    $totalcount = $DB->count_records_sql($sql, $params);
-    $allnames = get_all_user_name_fields(true, 'u');
-    $sql = "SELECT l.*, $allnames, u.picture
-              FROM {log} l
-              LEFT JOIN {user} u ON l.userid = u.id
-           $select
-            $order";
-
-    return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
+function get_logs() {
+    throw new coding_exception('get_logs() can not be used anymore. Please use the ' .
+        'report_log framework instead.');
 }
 
 /**
- * Renders a hidden password field so that browsers won't incorrectly autofill password fields with the user's password.
- *
- * @deprecated since Moodle 3.2 MDL-53048
+ * @deprecated since Moodle 3.2
  */
 function prevent_form_autofill_password() {
-    debugging('prevent_form_autofill_password has been deprecated and is no longer in use.', DEBUG_DEVELOPER);
-    return '';
+    throw new coding_exception('prevent_form_autofill_password() can not be used anymore.');
 }
 
 /**
@@ -3948,23 +2510,11 @@ function message_get_recent_conversations($userorid, $limitfrom = 0, $limitto =
 }
 
 /**
- * Display calendar preference button.
- *
- * @param stdClass $course course object
  * @deprecated since Moodle 3.2
- * @return string return preference button in html
  */
-function calendar_preferences_button(stdClass $course) {
-    debugging('This should no longer be used, the calendar preferences are now linked to the user preferences page.');
-
-    global $OUTPUT;
-
-    // Guests have no preferences.
-    if (!isloggedin() || isguestuser()) {
-        return '';
-    }
-
-    return $OUTPUT->single_button(new moodle_url('/user/calendar.php'), get_string("preferences", "calendar"));
+function calendar_preferences_button() {
+    throw new coding_exception('calendar_preferences_button() can not be used anymore. The calendar ' .
+        'preferences are now linked to the user preferences page.');
 }
 
 /**
@@ -4865,3 +3415,34 @@ function events_get_handlers($eventname) {
 
     return $handlers[$eventname];
 }
+
+/**
+ * This function finds the roles assigned directly to this context only
+ * i.e. no roles in parent contexts
+ *
+ * @deprecated since Moodle 3.6. Please use the get_roles_used_in_context().
+ * @todo final deprecation. To be removed in Moodle 4.0
+ * @param context $context
+ * @return array
+ */
+function get_roles_on_exact_context(context $context) {
+    debugging('get_roles_on_exact_context() is deprecated, please use get_roles_used_in_context() instead.',
+        DEBUG_DEVELOPER);
+
+    return get_roles_used_in_context($context, false);
+}
+
+/**
+ * Find out which roles has assignment on this context
+ *
+ * @deprecated since Moodle 3.6. Please use the get_roles_used_in_context().
+ * @todo final deprecation. To be removed in Moodle 4.0
+ * @param context $context
+ * @return array
+ */
+function get_roles_with_assignment_on_context(context $context) {
+    debugging('get_roles_with_assignment_on_context() is deprecated, please use get_roles_used_in_context() instead.',
+        DEBUG_DEVELOPER);
+
+    return get_roles_used_in_context($context, false);
+}
index 7afca6c..4c8b797 100644 (file)
@@ -229,7 +229,9 @@ class file_info_context_course extends file_info {
             }
         }
         $urlbase = $CFG->wwwroot.'/pluginfile.php';
-        return new file_info_stored($this->browser, $this->context, $storedfile, $urlbase, $section->section, true, true, true, false);
+        require_once($CFG->dirroot.'/course/lib.php');
+        $sectionname = get_section_name($this->course, $section);
+        return new file_info_stored($this->browser, $this->context, $storedfile, $urlbase, $sectionname, true, true, true, false);
     }
 
     /**
index 2d189a3..ded335b 100644 (file)
@@ -455,30 +455,42 @@ 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;
+        $baseurl = "{$CFG->wwwroot}/{$file}";
+
+        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/";
@@ -870,6 +882,131 @@ function file_remove_editor_orphaned_files($editor) {
     }
 }
 
+/**
+ * Finds all draft areas used in a textarea and copies the files into the primary textarea. If a user copies and pastes
+ * content from another draft area it's possible for a single textarea to reference multiple draft areas.
+ *
+ * @category files
+ * @param int $draftitemid the id of the primary draft area.
+ * @param int $usercontextid the user's context id.
+ * @param string $text some html content that needs to have files copied to the correct draft area.
+ * @param bool $forcehttps force https urls.
+ *
+ * @return string $text html content modified with new draft links
+ */
+function file_merge_draft_areas($draftitemid, $usercontextid, $text, $forcehttps = false) {
+    if (is_null($text)) {
+        return null;
+    }
+
+    $urls = extract_draft_file_urls_from_text($text, $forcehttps, $usercontextid, 'user', 'draft');
+
+    // No draft areas to rewrite.
+    if (empty($urls)) {
+        return $text;
+    }
+
+    foreach ($urls as $url) {
+        // Do not process the "home" draft area.
+        if ($url['itemid'] == $draftitemid) {
+            continue;
+        }
+
+        // Decode the filename.
+        $filename = urldecode($url['filename']);
+
+        // Copy the file.
+        file_copy_file_to_file_area($url, $filename, $draftitemid);
+
+        // Rewrite draft area.
+        $text = file_replace_file_area_in_text($url, $draftitemid, $text, $forcehttps);
+    }
+    return $text;
+}
+
+/**
+ * Rewrites a file area in arbitrary text.
+ *
+ * @param array $file General information about the file.
+ * @param int $newid The new file area itemid.
+ * @param string $text The text to rewrite.
+ * @param bool $forcehttps force https urls.
+ * @return string The rewritten text.
+ */
+function file_replace_file_area_in_text($file, $newid, $text, $forcehttps = false) {
+    global $CFG;
+
+    $wwwroot = $CFG->wwwroot;
+    if ($forcehttps) {
+        $wwwroot = str_replace('http://', 'https://', $wwwroot);
+    }
+
+    $search = [
+        $wwwroot,
+        $file['urlbase'],
+        $file['contextid'],
+        $file['component'],
+        $file['filearea'],
+        $file['itemid'],
+        $file['filename']
+    ];
+    $replace = [
+        $wwwroot,
+        $file['urlbase'],
+        $file['contextid'],
+        $file['component'],
+        $file['filearea'],
+        $newid,
+        $file['filename']
+    ];
+
+    $text = str_ireplace( implode('/', $search), implode('/', $replace), $text);
+    return $text;
+}
+
+/**
+ * Copies a file from one file area to another.
+ *
+ * @param array $file Information about the file to be copied.
+ * @param string $filename The filename.
+ * @param int $itemid The new file area.
+ */
+function file_copy_file_to_file_area($file, $filename, $itemid) {
+    $fs = get_file_storage();
+
+    // Load the current file in the old draft area.
+    $fileinfo = array(
+        'component' => $file['component'],
+        'filearea' => $file['filearea'],
+        'itemid' => $file['itemid'],
+        'contextid' => $file['contextid'],
+        'filepath' => '/',
+        'filename' => $filename
+    );
+    $oldfile = $fs->get_file($fileinfo['contextid'], $fileinfo['component'], $fileinfo['filearea'],
+        $fileinfo['itemid'], $fileinfo['filepath'], $fileinfo['filename']);
+    $newfileinfo = array(
+        'component' => $file['component'],
+        'filearea' => $file['filearea'],
+        'itemid' => $itemid,
+        'contextid' => $file['contextid'],
+        'filepath' => '/',
+        'filename' => $filename
+    );
+
+    $newcontextid = $newfileinfo['contextid'];
+    $newcomponent = $newfileinfo['component'];
+    $newfilearea = $newfileinfo['filearea'];
+    $newitemid = $newfileinfo['itemid'];
+    $newfilepath = $newfileinfo['filepath'];
+    $newfilename = $newfileinfo['filename'];
+
+    // Check if the file exists.
+    if (!$fs->file_exists($newcontextid, $newcomponent, $newfilearea, $newitemid, $newfilepath, $newfilename)) {
+        $fs->create_file_from_storedfile($newfileinfo, $oldfile);
+    }
+}
+
 /**
  * Saves files from a draft file area to a real one (merging the list of files).
  * Can rewrite URLs in some content at the same time if desired.
@@ -915,6 +1052,10 @@ function file_save_draft_area_files($draftitemid, $contextid, $component, $filea
         $allowreferences = false;
     }
 
+    // Check if the user has copy-pasted from other draft areas. Those files will be located in different draft
+    // areas and need to be copied into the current draft area.
+    $text = file_merge_draft_areas($draftitemid, $usercontext->id, $text, $forcehttps);
+
     // Check if the draft area has exceeded the authorised limit. This should never happen as validation
     // should have taken place before, unless the user is doing something nauthly. If so, let's just not save
     // anything at all in the next area.
index 32353d6..1d6d4b1 100644 (file)
@@ -20,6 +20,7 @@
  *
  * Contains HTML class for htmleditor type element
  *
+ * @deprecated since 3.6
  * @package   core_form
  * @copyright 2006 Jamie Pratt <me@jamiep.org>
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -55,6 +56,8 @@ class MoodleQuickForm_htmleditor extends MoodleQuickForm_textarea{
      *              or an associative array
      */
     public function __construct($elementName=null, $elementLabel=null, $options=array(), $attributes=null){
+        debugging("The form element 'htmleditor' has been deprecated. Please use the 'editor' element instead.", DEBUG_DEVELOPER);
+
         parent::__construct($elementName, $elementLabel, $attributes);
         // set the options, do not bother setting bogus ones
         if (is_array($options)) {
@@ -88,21 +91,17 @@ class MoodleQuickForm_htmleditor extends MoodleQuickForm_textarea{
      *
      * @return string
      */
-    function toHtml(){
+    public function toHtml() {
+        global $OUTPUT;
+
         if ($this->_flagFrozen) {
             return $this->getFrozenHtml();
         } else {
+            $value = preg_replace("/(\r\n|\n|\r)/", '&#010;', $this->getValue());
+
             return $this->_getTabs() .
-                    print_textarea(true,
-                                    $this->_options['rows'],
-                                    $this->_options['cols'],
-                                    $this->_options['width'],
-                                    $this->_options['height'],
-                                    $this->getName(),
-                                    preg_replace("/(\r\n|\n|\r)/", '&#010;',$this->getValue()),
-                                    0, // unused anymore
-                                    true,
-                                    $this->getAttribute('id'));
+                $OUTPUT->print_textarea($this->getName(), $this->getAttribute('id'), $value, $this->_options['rows'],
+                    $this->_options['cols']);
         }
     }
 
index 0b5d300..060e508 100644 (file)
@@ -50,23 +50,13 @@ require_once(__DIR__ . '/../message/lib.php');
  * Note: processor failure is is not reported as false return value,
  *       earlier versions did not do it consistently either.
  *
- * @todo MDL-55449 Drop support for stdClass in Moodle 3.6
  * @category message
  * @param \core\message\message $eventdata information about the message (component, userfrom, userto, ...)
  * @return mixed the integer ID of the new message or false if there was a problem with submitted data
  */
-function message_send($eventdata) {
+function message_send(\core\message\message $eventdata) {
     global $CFG, $DB;
 
-    // TODO MDL-55449 Drop support for stdClass in Moodle 3.6.
-    if ($eventdata instanceof \stdClass) {
-        if (!isset($eventdata->courseid)) {
-            $eventdata->courseid = null;
-        }
-
-        debugging('eventdata as \stdClass is deprecated. Please use core\message\message instead.', DEBUG_DEVELOPER);
-    }
-
     //new message ID to return
     $messageid = false;
 
index dc52ee5..4962aa4 100644 (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);
 
@@ -5009,7 +5012,6 @@ function remove_course_contents($courseid, $showfeedback = true, array $options
 
             include_once("$moddir/lib.php");                 // Shows php warning only if plugin defective.
             $moddelete = $modname .'_delete_instance';       // Delete everything connected to an instance.
-            $moddeletecourse = $modname .'_delete_course';   // Delete other stray stuff (uncommon).
 
             if ($instances) {
                 foreach ($instances as $cm) {
@@ -5035,12 +5037,6 @@ function remove_course_contents($courseid, $showfeedback = true, array $options
                     }
                 }
             }
-            if (function_exists($moddeletecourse)) {
-                // Execute optional course cleanup callback. Deprecated since Moodle 3.2. TODO MDL-53297 remove in 3.6.
-                debugging("Callback delete_course is deprecated. Function $moddeletecourse should be converted " .
-                    'to observer of event \core\event\course_content_deleted', DEBUG_DEVELOPER);
-                $moddeletecourse($course, $showfeedback);
-            }
             if ($instances and $showfeedback) {
                 echo $OUTPUT->notification($strdeleted.get_string('pluginname', $modname), 'notifysuccess');
             }
@@ -5080,22 +5076,6 @@ function remove_course_contents($courseid, $showfeedback = true, array $options
         echo $OUTPUT->notification($strdeleted.get_string('type_mod_plural', 'plugin'), 'notifysuccess');
     }
 
-    // Cleanup the rest of plugins. Deprecated since Moodle 3.2. TODO MDL-53297 remove in 3.6.
-    $cleanuplugintypes = array('report', 'coursereport', 'format');
-    $callbacks = get_plugins_with_function('delete_course', 'lib.php');
-    foreach ($cleanuplugintypes as $type) {
-        if (!empty($callbacks[$type])) {
-            foreach ($callbacks[$type] as $pluginfunction) {
-                debugging("Callback delete_course is deprecated. Function $pluginfunction should be converted " .
-                    'to observer of event \core\event\course_content_deleted', DEBUG_DEVELOPER);
-                $pluginfunction($course->id, $showfeedback);
-            }
-            if ($showfeedback) {
-                echo $OUTPUT->notification($strdeleted.get_string('type_'.$type.'_plural', 'plugin'), 'notifysuccess');
-            }
-        }
-    }
-
     // Delete questions and question categories.
     question_delete_course($course, $showfeedback);
     if ($showfeedback) {
@@ -6990,6 +6970,7 @@ function get_string_manager($forcereload=false) {
                  $translist = array();
             } else {
                 $translist = explode(',', $CFG->langlist);
+                $translist = array_map('trim', $translist);
             }
 
             if (!empty($CFG->config_php_settings['customstringmanager'])) {
index db773a6..dc90aea 100644 (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;
         }
index 1736379..10b589d 100644 (file)
@@ -1518,6 +1518,35 @@ class core_renderer extends renderer_base {
         return $this->render($menu);
     }
 
+    /**
+     * Returns the HTML for a basic textarea field.
+     *
+     * @param string $name Name to use for the textarea element
+     * @param string $id The id to use fort he textarea element
+     * @param string $value Initial content to display in the textarea
+     * @param int $rows Number of rows to display
+     * @param int $cols Number of columns to display
+     * @return string the HTML to display
+     */
+    public function print_textarea($name, $id, $value, $rows, $cols) {
+        global $OUTPUT;
+
+        editors_head_setup();
+        $editor = editors_get_preferred_editor(FORMAT_HTML);
+        $editor->set_text($value);
+        $editor->use_editor($id, []);
+
+        $context = [
+            'id' => $id,
+            'name' => $name,
+            'value' => $value,
+            'rows' => $rows,
+            'cols' => $cols
+        ];
+
+        return $OUTPUT->render_from_template('core_form/editor_textarea', $context);
+    }
+
     /**
      * Renders an action menu component.
      *
@@ -2474,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) {
index 21ef278..7e4fd49 100644 (file)
@@ -45,20 +45,11 @@ use Behat\Gherkin\Node\TableNode;
 class behat_transformations extends behat_base {
 
     /**
-     * Transformations for TableNode arguments.
-     *
-     * Transformations applicable to TableNode arguments should also
-     * be applied, adding them in a different method for Behat API restrictions.
-     *
-     * @deprecated since Moodle 3.2 MDL-56335 - please do not use this function any more.
-     * @param TableNode $tablenode
-     * @return TableNode The transformed table
+     * @deprecated since Moodle 3.2
      */
-    public function prefixed_tablenode_transformations(TableNode $tablenode) {
-        debugging('prefixed_tablenode_transformations() is deprecated. Please use tablenode_transformations() instead.',
-            DEBUG_DEVELOPER);
-
-        return $this->tablenode_transformations($tablenode);
+    public function prefixed_tablenode_transformations() {
+        throw new coding_exception('prefixed_tablenode_transformations() can not be used anymore. ' .
+            'Please use tablenode_transformations() instead.');
     }
 
     /**
index 5058bc8..b121e26 100644 (file)
@@ -39,165 +39,21 @@ require_once($CFG->libdir . '/csslib.php');
  */
 class core_csslib_testcase extends advanced_testcase {
 
-    public function test_background() {
-        $optimiser = new css_optimiser();
-
-        $cssin = '.test {background-color: #123456;}';
-        $this->assertSame($cssin, $optimiser->process($cssin));
-        $this->assertDebuggingCalled('class css_optimiser is deprecated and no longer does anything, '
-            . 'please consider using stylelint to optimise your css.');
-    }
-
-
     /**
-     * Test CSS colour matching.
+     * Test that css_is_colour function throws an exception.
      */
     public function test_css_is_colour() {
-        $debugstr = 'css_is_colour() is deprecated without a replacement. Please copy the implementation '
-            . 'into your plugin if you need this functionality.';
-        // First lets test hex colours.
-        $this->assertTrue(css_is_colour('#123456'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertTrue(css_is_colour('#123'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertTrue(css_is_colour('#ABCDEF'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertTrue(css_is_colour('#ABC'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertTrue(css_is_colour('#abcdef'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertTrue(css_is_colour('#abc'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertTrue(css_is_colour('#aBcDeF'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertTrue(css_is_colour('#aBc'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertTrue(css_is_colour('#1a2Bc3'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertTrue(css_is_colour('#1Ac'));
-        $this->assertDebuggingCalled($debugstr);
-
-        // Note the following two colour's aren't really colours but browsers process
-        // them still.
-        $this->assertTrue(css_is_colour('#A'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertTrue(css_is_colour('#12'));
-        $this->assertDebuggingCalled($debugstr);
-        // Having four or five characters however are not valid colours and
-        // browsers don't parse them. They need to fail so that broken CSS
-        // stays broken after optimisation.
-        $this->assertFalse(css_is_colour('#1234'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertFalse(css_is_colour('#12345'));
-        $this->assertDebuggingCalled($debugstr);
-
-        $this->assertFalse(css_is_colour('#BCDEFG'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertFalse(css_is_colour('#'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertFalse(css_is_colour('#0000000'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertFalse(css_is_colour('#132-245'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertFalse(css_is_colour('#13 23 43'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertFalse(css_is_colour('123456'));
-        $this->assertDebuggingCalled($debugstr);
-
-        // Next lets test real browser mapped colours.
-        $this->assertTrue(css_is_colour('black'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertTrue(css_is_colour('blue'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertTrue(css_is_colour('BLACK'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertTrue(css_is_colour('Black'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertTrue(css_is_colour('bLACK'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertTrue(css_is_colour('mediumaquamarine'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertTrue(css_is_colour('mediumAquamarine'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertFalse(css_is_colour('monkey'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertFalse(css_is_colour(''));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertFalse(css_is_colour('not a colour'));
-        $this->assertDebuggingCalled($debugstr);
-
-        // Next lets test rgb(a) colours.
-        $this->assertTrue(css_is_colour('rgb(255,255,255)'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertTrue(css_is_colour('rgb(0, 0, 0)'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertTrue(css_is_colour('RGB (255, 255   ,    255)'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertTrue(css_is_colour('rgba(0,0,0,0)'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertTrue(css_is_colour('RGBA(255,255,255,1)'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertTrue(css_is_colour('rgbA(255,255,255,0.5)'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertFalse(css_is_colour('rgb(-255,-255,-255)'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertFalse(css_is_colour('rgb(256,-256,256)'));
-        $this->assertDebuggingCalled($debugstr);
-
-        // Now lets test HSL colours.
-        $this->assertTrue(css_is_colour('hsl(0,0%,100%)'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertTrue(css_is_colour('hsl(180, 0%, 10%)'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertTrue(css_is_colour('hsl (360, 100%   ,    95%)'));
-        $this->assertDebuggingCalled($debugstr);
-
-        // Finally test the special values.
-        $this->assertTrue(css_is_colour('inherit'));
-        $this->assertDebuggingCalled($debugstr);
+        $this->expectException('coding_exception');
+        $this->expectExceptionMessage('css_is_colour() can not be used anymore.');
+        css_is_colour();
     }
 
     /**
-     * Test the css_is_width function.
+     * Test that css_is_width function throws an exception.
      */
     public function test_css_is_width() {
-        $debugstr = 'css_is_width() is deprecated without a replacement. Please copy the implementation '
-            . 'into your plugin if you need this functionality.';
-        $this->assertTrue(css_is_width('0'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertTrue(css_is_width('0px'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertTrue(css_is_width('0em'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertTrue(css_is_width('199px'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertTrue(css_is_width('199em'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertTrue(css_is_width('199%'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertTrue(css_is_width('-1px'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertTrue(css_is_width('auto'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertTrue(css_is_width('inherit'));
-        $this->assertDebuggingCalled($debugstr);
-
-        // Valid widths but missing their unit specifier.
-        $this->assertFalse(css_is_width('0.75'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertFalse(css_is_width('3'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertFalse(css_is_width('-1'));
-        $this->assertDebuggingCalled($debugstr);
-
-        // Totally invalid widths.
-        $this->assertFalse(css_is_width('-'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertFalse(css_is_width('bananas'));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertFalse(css_is_width(''));
-        $this->assertDebuggingCalled($debugstr);
-        $this->assertFalse(css_is_width('top'));
-        $this->assertDebuggingCalled($debugstr);
+        $this->expectException('coding_exception');
+        $this->expectExceptionMessage('css_is_width() can not be used anymore.');
+        css_is_width();
     }
 }
index a6ff45f..d417ad3 100644 (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
      *
@@ -1473,6 +1546,43 @@ EOF;
         $this->assertEquals($fourthrecord['filename'], $allfiles[3]->filename);
         $this->assertEquals($fifthrecord['filename'], $allfiles[4]->filename);
     }
+
+    public function test_file_copy_file_to_file_area() {
+        // 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);
+    }
 }
 
 /**
diff --git a/lib/tests/moodle_url_test.php b/lib/tests/moodle_url_test.php
new file mode 100644 (file)
index 0000000..c5a9be4
--- /dev/null
@@ -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]*@",
+            ],
+        ];
+    }
+}
index 5c3d4d4..4446672 100644 (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));
@@ -887,4 +655,52 @@ EXPECTED;
             $_GET = $currentget;
         }
     }
+
+    /**
+     * Tests for extract_draft_file_urls_from_text() function.
+     */
+    public function test_extract_draft_file_urls_from_text() {
+        global $CFG;
+
+        $url1 = "{$CFG->wwwroot}/draftfile.php/5/user/draft/99999999/test1.jpg";
+        $url2 = "{$CFG->wwwroot}/draftfile.php/5/user/draft/99999998/test2.jpg";
+
+        $html = "<p>This is a test.</p><p><img src=\"${url1}\" alt=\"\" role=\"presentation\"></p>
+                <br>Test content.<p></p><p><img src=\"{$url2}\" alt=\"\" width=\"2048\" height=\"1536\"
+                role=\"presentation\" class=\"img-responsive atto_image_button_text-bottom\"><br></p>";
+        $draftareas = array(
+            array(
+                'urlbase' => 'draftfile.php',
+                'contextid' => '5',
+                'component' => 'user',
+                'filearea' => 'draft',
+                'itemid' => '99999999',
+                'filename' => 'test1.jpg',
+                0 => "{$CFG->wwwroot}/draftfile.php/5/user/draft/99999999/test1.jpg",
+                1 => 'draftfile.php',
+                2 => '5',
+                3 => 'user',
+                4 => 'draft',
+                5 => '99999999',
+                6 => 'test1.jpg'
+            ),
+            array(
+                'urlbase' => 'draftfile.php',
+                'contextid' => '5',
+                'component' => 'user',
+                'filearea' => 'draft',
+                'itemid' => '99999998',
+                'filename' => 'test2.jpg',
+                0 => "{$CFG->wwwroot}/draftfile.php/5/user/draft/99999998/test2.jpg",
+                1 => 'draftfile.php',
+                2 => '5',
+                3 => 'user',
+                4 => 'draft',
+                5 => '99999998',
+                6 => 'test2.jpg'
+            )
+        );
+        $extracteddraftareas = extract_draft_file_urls_from_text($html, false, 5, 'user', 'draft');
+        $this->assertEquals($draftareas, $extracteddraftareas);
+    }
 }
index fb3dee5..e9a5c7e 100644 (file)
@@ -3,9 +3,19 @@ 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.
+* The form element 'htmleditor' has been deprecated. Please use the 'editor' element instead.
+* The print_textarea() function has been deprecated. Please use $OUTPUT->print_textarea() instead.
 * The following functions have been finally deprecated and can not be used any more:
     - external_function_info()
     - core_renderer::update_module_button()
@@ -25,7 +35,19 @@ information provided here is intended especially for developers.
     - site_scale_used()
     - clam_message_admins()
     - get_clam_error_code()
-* The following classes have been finally deprecated and can not be used any more:
+    - get_records_csv()
+    - put_records_csv()
+    - print_log()
+    - print_mnet_log()
+    - print_log_csv()
+    - print_log_xls()
+    - print_log_ods()
+    - build_logs_array()
+    - get_logs_usercourse()
+    - get_logs_userday()
+    - get_logs()
+    - prevent_form_autofill_password()
+    - prefixed_tablenode_transformations()
     - core_media_renderer
     - core_media
 * Following api's have been removed in behat_config_manager, please use behat_config_util instead.
@@ -55,6 +77,18 @@ information provided here is intended especially for developers.
   policy and respect the privacy setting made by site administrators. The list of user identifiers should never be
   hard-coded. Instead, the setting $CFG->showuseridentity should be always respected, which has always been the default
   behaviour (MDL-59847).
+* The function message_send() in messagelib.php will now only take the object \core\message\message as a parameter.
+* The method message_sent::create_from_ids() parameter courseid is now required. A debugging
+  message was previously displayed, and the SITEID was used, when not provided.
+* The method \core\message\manager::send_message() now only takes the object \core\message\message as the first parameter.
+* Following functions have been deprecated, please use get_roles_used_in_context.
+    - get_roles_on_exact_context()
+    - get_roles_with_assignment_on_context()
+* New functions to support the merging of user draft areas from the interface; see MDL-45170 for details:
+  - file_copy_file_to_file_area()
+  - file_merge_draft_areas()
+  - file_replace_file_area_in_text()
+  - extract_draft_file_urls_from_text()
 
 === 3.5 ===
 
index ece1f68..6b6c00a 100644 (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;
     }
 
     /**
@@ -2029,6 +2053,63 @@ function content_to_text($content, $contentformat) {
     return trim($content, "\r\n ");
 }
 
+/**
+ * Factory method for extracting draft file links from arbitrary text using regular expressions. Only text
+ * is required; other file fields may be passed to filter.
+ *
+ * @param string $text Some html content.
+ * @param bool $forcehttps force https urls.
+ * @param int $contextid This parameter and the next three identify the file area to save to.
+ * @param string $component The component name.
+ * @param string $filearea The filearea.
+ * @param int $itemid The item id for the filearea.
+ * @param string $filename The specific filename of the file.
+ * @return array
+ */
+function extract_draft_file_urls_from_text($text, $forcehttps = false, $contextid = null, $component = null,
+                                           $filearea = null, $itemid = null, $filename = null) {
+    global $CFG;
+
+    $wwwroot = $CFG->wwwroot;
+    if ($forcehttps) {
+        $wwwroot = str_replace('http://', 'https://', $wwwroot);
+    }
+    $urlstring = '/' . preg_quote($wwwroot, '/');
+
+    $urlbase = preg_quote('draftfile.php');
+    $urlstring .= "\/(?<urlbase>{$urlbase})";
+
+    if (is_null($contextid)) {
+        $contextid = '[0-9]+';
+    }
+    $urlstring .= "\/(?<contextid>{$contextid})";
+
+    if (is_null($component)) {
+        $component = '[a-z_]+';
+    }
+    $urlstring .= "\/(?<component>{$component})";
+
+    if (is_null($filearea)) {
+        $filearea = '[a-z_]+';
+    }
+    $urlstring .= "\/(?<filearea>{$filearea})";
+
+    if (is_null($itemid)) {
+        $itemid = '[0-9]+';
+    }
+    $urlstring .= "\/(?<itemid>{$itemid})";
+
+    // Filename matching magic based on file_rewrite_urls_to_pluginfile().
+    if (is_null($filename)) {
+        $filename = '[^\'\",&<>|`\s:\\\\]+';
+    }
+    $urlstring .= "\/(?<filename>{$filename})/";
+
+    // Regular expression which matches URLs and returns their components.
+    preg_match_all($urlstring, $text, $urls, PREG_SET_ORDER);
+    return $urls;
+}
+
 /**
  * This function will highlight search words in a given string
  *
@@ -2411,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;
@@ -2429,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)) {
@@ -2462,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);
@@ -2485,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;
 }
index c0d82a8..82a1d1a 100644 (file)
@@ -882,14 +882,29 @@ class moodle_xhprofrun implements iXHProfRuns {
         $rec = new stdClass();
         $rec->runid = $this->runid;
         $rec->url = $this->url;
-        $rec->data = base64_encode(gzcompress(serialize($xhprof_data), 9));
         $rec->totalexecutiontime = $this->totalexecutiontime;
         $rec->totalcputime = $this->totalcputime;
         $rec->totalcalls = $this->totalcalls;
         $rec->totalmemory = $this->totalmemory;
         $rec->timecreated = $this->timecreated;
 
-        $DB->insert_record('profiling', $rec);
+        // Send to database with compressed and endoded data.
+        if (empty($CFG->disableprofilingtodatabase)) {
+            $rec->data = base64_encode(gzcompress(serialize($xhprof_data), 9));
+            $DB->insert_record('profiling', $rec);
+        }
+
+        // Send raw data to plugins.
+        $rec->data = $xhprof_data;
+
+        // Allow a plugin to take the trace data and process it.
+        if ($pluginsfunction = get_plugins_with_function('store_profiling_data')) {
+            foreach ($pluginsfunction as $plugintype => $plugins) {
+                foreach ($plugins as $pluginfunction) {
+                    $pluginfunction($rec);
+                }
+            }
+        }
 
         if (PHPUNIT_TEST) {
             // Calculate export variables.
index 58479f2..5a3912f 100644 (file)
@@ -196,4 +196,15 @@ abstract class base_message extends \core_search\base {
               ORDER BY m.timecreated ASC";
         return $DB->get_recordset_sql($sql, $params);
     }
+
+    /**
+     * Returns an icon instance for the document.
+     *
+     * @param \core_search\document $doc
+     *
+     * @return \core_search\document_icon
+     */
+    public function get_doc_icon(\core_search\document $doc) : \core_search\document_icon {
+        return new \core_search\document_icon('t/message');
+    }
 }
index 9c9c415..3f63ed0 100644 (file)
@@ -272,22 +272,6 @@ class core_message_events_testcase extends core_message_messagelib_testcase {
         $this->assertEquals(4, $event->other['courseid']);
     }
 
-    public function test_mesage_sent_via_create_from_ids_without_other_courseid() {
-
-        // Creating a message_sent event without courseid leads to debugging + SITEID.
-        // TODO: MDL-55449 Ensure this leads to exception instead of debugging in Moodle 3.6.
-        $event = \core\event\message_sent::create_from_ids(1, 2, 3);
-
-        // Trigger and capturing the event.
-        $sink = $this->redirectEvents();
-        $event->trigger();
-        $events = $sink->get_events();
-        $event = reset($events);
-
-        $this->assertDebuggingCalled();
-        $this->assertEquals(SITEID, $event->other['courseid']);
-    }
-
     /**
      * Test the message viewed event.
      */
index fcc2921..79b7137 100644 (file)
@@ -336,4 +336,20 @@ class message_received_search_testcase extends advanced_testcase {
 
         $this->assertFalse($doc);
     }
+
+    /**
+     * Test document icon.
+     */
+    public function test_get_doc_icon() {
+        $searcharea = \core_search\manager::get_search_area($this->messagereceivedareaid);
+
+        $document = $this->getMockBuilder('\core_search\document')
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $result = $searcharea->get_doc_icon($document);
+
+        $this->assertEquals('t/message', $result->get_name());
+        $this->assertEquals('moodle', $result->get_component());
+    }
 }
index 69338d1..7edee7d 100644 (file)
@@ -351,4 +351,20 @@ class message_sent_search_testcase extends advanced_testcase {
         $this->assertFalse($doc);
 
     }
+
+    /**
+     * Test document icon.
+     */
+    public function test_get_doc_icon() {
+        $searcharea = \core_search\manager::get_search_area($this->messagesentareaid);
+
+        $document = $this->getMockBuilder('\core_search\document')
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $result = $searcharea->get_doc_icon($document);
+
+        $this->assertEquals('t/message', $result->get_name());
+        $this->assertEquals('moodle', $result->get_component());
+    }
 }
index dd1dd08..4f40560 100644 (file)
@@ -1,6 +1,25 @@
 This files describes API changes in /message/ messaging system,
 information provided here is intended especially for developers.
 
+=== 3.6 ===
+
+* The following functions have been finally deprecated and can not be used anymore:
+  * message_get_course_contexts()
+  * message_remove_url_params()
+  * message_count_messages()
+  * message_count_blocked_users()
+  * message_contact_link()
+  * message_history_link()
+  * message_shorten_message()
+  * message_get_fragment()
+  * message_get_contact_add_remove_link()
+  * message_get_contact_block_link()
+  * message_mark_messages_read()
+  * message_page_type_list()
+  * message_can_post_message()
+  * message_is_user_non_contact_blocked()
+  * message_is_user_blocked()
+
 === 3.5 ===
 
 * Changed the database structure so there are no longer two tables for messages, with the only
index 4facb4f..0f018ca 100644 (file)
@@ -61,6 +61,7 @@ class backup_assign_activity_structure_step extends backup_activity_structure_st
 
         // To know if we are including userinfo.
         $userinfo = $this->get_setting_value('userinfo');
+        $groupinfo = $this->get_setting_value('groups');
 
         // Define each element separated.
         $assign = new backup_nested_element('assign', array('id'),
@@ -159,8 +160,12 @@ class backup_assign_activity_structure_step extends backup_activity_structure_st
             $userflag->set_source_table('assign_user_flags',
                                      array('assignment' => backup::VAR_PARENTID));
 
-            $submission->set_source_table('assign_submission',
-                                     array('assignment' => backup::VAR_PARENTID));
+            $submissionparams = array('assignment' => backup::VAR_PARENTID);
+            if (!$groupinfo) {
+                // Without group info, skip group submissions.
+                $submissionparams['groupid'] = backup_helper::is_sqlparam(0);
+            }
+            $submission->set_source_table('assign_submission', $submissionparams);
 
             $grade->set_source_table('assign_grades',
                                      array('assignment' => backup::VAR_PARENTID));
@@ -172,6 +177,10 @@ class backup_assign_activity_structure_step extends backup_activity_structure_st
             $overrideparams['userid'] = backup_helper::is_sqlparam(null); // Without userinfo, skip user overrides.
         }
 
+        if (!$groupinfo) {
+            // Without group info, skip group overrides.
+            $overrideparams['groupid'] = backup_helper::is_sqlparam(0);
+        }
         $override->set_source_table('assign_overrides', $overrideparams);
 
         // Define id annotations.
index 0987c53..d4ba0a5 100644 (file)
@@ -387,6 +387,12 @@ class restore_assign_activity_structure_step extends restore_activity_structure_
             return;
         }
 
+        // Skip group overrides if we are not restoring groupinfo.
+        $groupinfo = $this->get_setting_value('groups');
+        if (!$groupinfo && !is_null($data->groupid)) {
+            return;
+        }
+
         $data->assignid = $this->get_new_parentid('assign');
 
         if (!is_null($data->userid)) {
index 002404d..ee78784 100644 (file)
@@ -258,8 +258,11 @@ function assign_update_events($assign, $override = null) {
         // Only load events for this override.
         if (isset($override->userid)) {
             $conds['userid'] = $override->userid;
-        } else {
+        } else if (isset($override->groupid)) {
             $conds['groupid'] = $override->groupid;
+        } else {
+            // This is not a valid override, it may have been left from a bad import or restore.
+            $conds['groupid'] = $conds['userid'] = 0;
         }
     }
     $oldevents = $DB->get_records('event', $conds, 'id ASC');
index 85ea4dc..b2365b3 100644 (file)
@@ -395,7 +395,9 @@ class assign_submission_onlinetext extends assign_submission_plugin {
         $files = array();
         $onlinetextsubmission = $this->get_onlinetext_submission($submission->id);
 
-        if ($onlinetextsubmission) {
+        // Note that this check is the same logic as the result from the is_empty function but we do
+        // not call it directly because we already have the submission record.
+        if ($onlinetextsubmission && !empty($onlinetextsubmission->onlinetext)) {
             $finaltext = $this->assignment->download_rewrite_pluginfile_urls($onlinetextsubmission->onlinetext, $user, $this);
             $formattedtext = format_text($finaltext,
                                          $onlinetextsubmission->onlineformat,
@@ -575,8 +577,13 @@ class assign_submission_onlinetext extends assign_submission_plugin {
      */
     public function is_empty(stdClass $submission) {
         $onlinetextsubmission = $this->get_onlinetext_submission($submission->id);
+        $wordcount = 0;
+
+        if (isset($onlinetextsubmission->onlinetext)) {
+            $wordcount = count_words(trim($onlinetextsubmission->onlinetext));
+        }
 
-        return empty($onlinetextsubmission->onlinetext);
+        return $wordcount == 0;
     }
 
     /**
@@ -592,7 +599,13 @@ class assign_submission_onlinetext extends assign_submission_plugin {
         if (!isset($data->onlinetext_editor)) {
             return true;
         }
-        return !strlen((string)$data->onlinetext_editor['text']);
+        $wordcount = 0;
+
+        if (isset($data->onlinetext_editor['text'])) {
+            $wordcount = count_words(trim((string)$data->onlinetext_editor['text']));
+        }
+
+        return $wordcount == 0;
     }
 
     /**
index 4d47f85..54054b3 100644 (file)
@@ -362,14 +362,23 @@ class mod_assign_lib_testcase extends advanced_testcase {
             ]);
 
         $instance = $assign->get_instance();
-        $eventparams = ['modulename' => 'assign', 'instance' => $instance->id];
+        $eventparams = [
+            'modulename' => 'assign',
+            'instance' => $instance->id,
+            'eventtype' => ASSIGN_EVENT_TYPE_DUE,
+            'groupid' => 0
+        ];
 
         // Make sure the calendar event for assignment 1 matches the initial due date.
         $eventtime = $DB->get_field('event', 'timestart', $eventparams, MUST_EXIST);
         $this->assertEquals($eventtime, $duedate);
 
         // Manually update assignment 1's due date.
-        $DB->update_record('assign', (object) ['id' => $instance->id, 'duedate' => $newduedate]);
+        $DB->update_record('assign', (object) [
+            'id' => $instance->id,
+            'duedate' => $newduedate,
+            'course' => $course->id
+        ]);
 
         // Then refresh the assignment events of assignment 1's course.
         $this->assertTrue(assign_refresh_events($course->id));
@@ -380,15 +389,25 @@ class mod_assign_lib_testcase extends advanced_testcase {
 
         // Create a second course and assignment.
         $othercourse = $this->getDataGenerator()->create_course();;
-        $otherassign = $this->create_instance($othercourse, ['duedate' => $duedate, 'course' => $othercourse->id]);
+        $otherassign = $this->create_instance($othercourse, [
+            'duedate' => $duedate,
+        ]);
         $otherinstance = $otherassign->get_instance();
 
         // Manually update assignment 1 and 2's due dates.
         $newduedate += DAYSECS;
-        $DB->update_record('assign', (object)['id' => $instance->id, 'duedate' => $newduedate]);
-        $DB->update_record('assign', (object)['id' => $otherinstance->id, 'duedate' => $newduedate]);
+        $DB->update_record('assign', (object)[
+            'id' => $instance->id,
+            'duedate' => $newduedate,
+            'course' => $course->id
+        ]);
+        $DB->update_record('assign', (object)[
+            'id' => $otherinstance->id,
+            'duedate' => $newduedate,
+            'course' => $othercourse->id
+        ]);
 
-        // Refresh events of all courses.
+        // Refresh events of all courses and check the calendar events matches the new date.
         $this->assertTrue(assign_refresh_events());
 
         // Check the due date calendar event for assignment 1.
index 2cf8827..416ccc6 100644 (file)
@@ -103,7 +103,8 @@ class data_field_radiobutton extends data_field_base {
         }
         $return = html_writer::label(get_string('fieldtypelabel', "datafield_" . $this->type),
             'menuf_' . $this->field->id, false, array('class' => 'accesshide'));
-        $return .= html_writer::select($options, 'f_'.$this->field->id, $value, null, array('class' => 'custom-select'));
+        $return .= html_writer::select($options, 'f_'.$this->field->id, $value,
+            array('' => 'choosedots'), array('class' => 'custom-select'));
         return $return;
     }
 
index d629736..8be8e65 100644 (file)
@@ -31,7 +31,7 @@ require_once($CFG->libdir.'/tablelib.php');
 ////////////////////////////////////////////////////////
 $id = required_param('id', PARAM_INT);
 $subject = optional_param('subject', '', PARAM_CLEANHTML);
-$message = optional_param('message', '', PARAM_CLEANHTML);
+$message = optional_param_array('message', '', PARAM_CLEANHTML);
 $format = optional_param('format', FORMAT_MOODLE, PARAM_INT);
 $messageuser = optional_param_array('messageuser', false, PARAM_INT);
 $action = optional_param('action', '', PARAM_ALPHA);
@@ -44,6 +44,10 @@ $current_tab = 'nonrespondents';
 //get the objects
 ////////////////////////////////////////////////////////
 
+if ($message) {
+    $message = $message['text'];
+}
+
 list ($course, $cm) = get_course_and_cm_from_cmid($id, 'feedback');
 if (! $feedback = $DB->get_record("feedback", array("id"=>$cm->instance))) {
     print_error('invalidcoursemodule');
@@ -275,7 +279,7 @@ if (empty($students)) {
         echo '<label for="feedback_subject">'.get_string('subject', 'feedback').'&nbsp;</label>';
         echo '<input type="text" id="feedback_subject" size="50" maxlength="255" name="subject" value="'.s($subject).'" />';
         echo '</div>';
-        print_textarea(true, 15, 25, 30, 10, "message", $message);
+        echo $OUTPUT->print_textarea('message', 'edit-message', $message, 15, 25);
         print_string('formathtml');
         echo '<input type="hidden" name="format" value="'.FORMAT_HTML.'" />';
         echo '<br /><div class="buttons">';
index f38b76c..fa5f012 100644 (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;
index 1756031..3f4adfc 100644 (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);
     }
 }
index 119c4dc..cdf8579 100644 (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);
     }
 }
index 14f9026..a31d3ea 100644 (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);
         }
     }
 }
index e15ceaa..e96ba94 100644 (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');
index 98756d7..84055d4 100644 (file)
@@ -3820,10 +3820,10 @@ function glossary_get_entries_by_search($glossary, $context, $query, $fullsearch
                                         $options = array()) {
     global $DB, $USER;
 
-    // Remove too little terms.
+    // Clean terms.
     $terms = explode(' ', $query);
     foreach ($terms as $key => $term) {
-        if (strlen(trim($term, '+-')) < 2) {
+        if (strlen(trim($term, '+-')) < 1) {
             unset($terms[$key]);
         }
     }
index a6b05d1..e2c3a63 100644 (file)
@@ -85,7 +85,8 @@ if ($usesections) {
     $table->head  = array ($strname, $strgrade, $strdeadline);
     $table->align = array ("left", "center", "center");
 }
-
+// Get all deadlines.
+$deadlines = lesson_get_user_deadline($course->id);
 foreach ($lessons as $lesson) {
     if (!$lesson->visible) {
         //Show dimmed if the mod is hidden
@@ -97,12 +98,13 @@ foreach ($lessons as $lesson) {
     $cm = get_coursemodule_from_instance('lesson', $lesson->id);
     $context = context_module::instance($cm->id);
 
-    if ($lesson->deadline == 0) {
+    $deadline = $deadlines[$lesson->id]->userdeadline;
+    if ($deadline == 0) {
         $due = $strnodeadline;
-    } else if ($lesson->deadline > $timenow) {
-        $due = userdate($lesson->deadline);
+    } else if ($deadline > $timenow) {
+        $due = userdate($deadline);
     } else {
-        $due = "<font color=\"red\">".userdate($lesson->deadline)."</font>";
+        $due = "<font color=\"red\">" . userdate($deadline) . "</font>";
     }
 
     if ($usesections) {
index 0d8d407..d775cc2 100644 (file)
@@ -1183,6 +1183,44 @@ function lesson_get_user_detailed_report_data(lesson $lesson, $userid, $attempt)
     return array($answerpages, $userstats);
 }
 
+/**
+ * Return user's deadline for all lessons in a course, hereby taking into account group and user overrides.
+ *
+ * @param int $courseid the course id.
+ * @return object An object with of all lessonsids and close unixdates in this course,
+ * taking into account the most lenient overrides, if existing and 0 if no close date is set.
+ */
+function lesson_get_user_deadline($courseid) {
+    global $DB, $USER;
+
+    // For teacher and manager/admins return lesson's deadline.
+    if (has_capability('moodle/course:update', context_course::instance($courseid))) {
+        $sql = "SELECT lesson.id, lesson.deadline AS userdeadline
+                  FROM {lesson} lesson
+                 WHERE lesson.course = :courseid";
+
+        $results = $DB->get_records_sql($sql, array('courseid' => $courseid));
+        return $results;
+    }
+
+    $sql = "SELECT a.id,
+                   COALESCE(v.userclose, v.groupclose, a.deadline, 0) AS userdeadline
+              FROM (
+                      SELECT lesson.id as lessonid,
+                             MAX(leo.deadline) AS userclose, MAX(qgo.deadline) AS groupclose
+                        FROM {lesson} lesson
+                   LEFT JOIN {lesson_overrides} leo on lesson.id = leo.lessonid AND leo.userid = :userid
+                   LEFT JOIN {groups_members} gm ON gm.userid = :useringroupid
+                   LEFT JOIN {lesson_overrides} qgo on lesson.id = qgo.lessonid AND qgo.groupid = gm.groupid
+                       WHERE lesson.course = :courseid
+                    GROUP BY lesson.id
+                   ) v
+              JOIN {lesson} a ON a.id = v.lessonid";
+
+    $results = $DB->get_records_sql($sql, array('userid' => $USER->id, 'useringroupid' => $USER->id, 'courseid' => $courseid));
+    return $results;
+
+}
 
 /**
  * Abstract class that page type's MUST inherit from.
index dedbb03..e12e6e8 100644 (file)
@@ -76,4 +76,144 @@ class mod_lesson_locallib_testcase extends advanced_testcase {
             }
         }
     }
+
+    /**
+     * Test test_lesson_get_user_deadline().
+     */
+    public function test_lesson_get_user_deadline() {
+        global $DB;
+
+        $this->resetAfterTest();
+        $this->setAdminUser();
+
+        $basetimestamp = time(); // The timestamp we will base the enddates on.
+
+        // Create generator, course and lessons.
+        $student1 = $this->getDataGenerator()->create_user();
+        $student2 = $this->getDataGenerator()->create_user();
+        $student3 = $this->getDataGenerator()->create_user();
+        $teacher = $this->getDataGenerator()->create_user();
+        $course = $this->getDataGenerator()->create_course();
+        $lessongenerator = $this->getDataGenerator()->get_plugin_generator('mod_lesson');
+
+        // Both lessons close in two hours.
+        $lesson1 = $lessongenerator->create_instance(array('course' => $course->id, 'deadline' => $basetimestamp + 7200));
+        $lesson2 = $lessongenerator->create_instance(array('course' => $course->id, 'deadline' => $basetimestamp + 7200));
+        $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
+        $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
+
+        $student1id = $student1->id;
+        $student2id = $student2->id;
+        $student3id = $student3->id;
+        $teacherid = $teacher->id;
+
+        // Users enrolments.
+        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
+        $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
+        $this->getDataGenerator()->enrol_user($student1id, $course->id, $studentrole->id, 'manual');
+        $this->getDataGenerator()->enrol_user($student2id, $course->id, $studentrole->id, 'manual');
+        $this->getDataGenerator()->enrol_user($student3id, $course->id, $studentrole->id, 'manual');
+        $this->getDataGenerator()->enrol_user($teacherid, $course->id, $teacherrole->id, 'manual');
+
+        // Create groups.
+        $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
+        $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
+        $group1id = $group1->id;
+        $group2id = $group2->id;
+        $this->getDataGenerator()->create_group_member(array('userid' => $student1id, 'groupid' => $group1id));
+        $this->getDataGenerator()->create_group_member(array('userid' => $student2id, 'groupid' => $group2id));
+
+        // Group 1 gets an group override for lesson 1 to close in three hours.
+        $record1 = (object) [
+            'lessonid' => $lesson1->id,
+            'groupid' => $group1id,
+            'deadline' => $basetimestamp + 10800 // In three hours.
+        ];
+        $DB->insert_record('lesson_overrides', $record1);
+
+        // Let's test lesson 1 closes in three hours for user student 1 since member of group 1.
+        // lesson 2 closes in two hours.
+        $this->setUser($student1id);
+        $params = new stdClass();
+
+        $comparearray = array();
+        $object = new stdClass();
+        $object->id = $lesson1->id;
+        $object->userdeadline = $basetimestamp + 10800; // The overriden deadline for lesson 1.
+
+        $comparearray[$lesson1->id] = $object;
+
+        $object = new stdClass();
+        $object->id = $lesson2->id;
+        $object->userdeadline = $basetimestamp + 7200; // The unchanged deadline for lesson 2.
+
+        $comparearray[$lesson2->id] = $object;
+
+        $this->assertEquals($comparearray, lesson_get_user_deadline($course->id));
+
+        // Let's test lesson 1 closes in two hours (the original value) for user student 3 since member of no group.
+        $this->setUser($student3id);
+        $params = new stdClass();
+
+        $comparearray = array();
+        $object = new stdClass();
+        $object->id = $lesson1->id;
+        $object->userdeadline = $basetimestamp + 7200; // The original deadline for lesson 1.
+
+        $comparearray[$lesson1->id] = $object;
+
+        $object = new stdClass();
+        $object->id = $lesson2->id;
+        $object->userdeadline = $basetimestamp + 7200; // The original deadline for lesson 2.
+
+        $comparearray[$lesson2->id] = $object;
+
+        $this->assertEquals($comparearray, lesson_get_user_deadline($course->id));
+
+        // User 2 gets an user override for lesson 1 to close in four hours.
+        $record2 = (object) [
+            'lessonid' => $lesson1->id,
+            'userid' => $student2id,
+            'deadline' => $basetimestamp + 14400 // In four hours.
+        ];
+        $DB->insert_record('lesson_overrides', $record2);
+
+        // Let's test lesson 1 closes in four hours for user student 2 since personally overriden.
+        // lesson 2 closes in two hours.
+        $this->setUser($student2id);
+
+        $comparearray = array();
+        $object = new stdClass();
+        $object->id = $lesson1->id;
+        $object->userdeadline = $basetimestamp + 14400; // The overriden deadline for lesson 1.
+
+        $comparearray[$lesson1->id] = $object;
+
+        $object = new stdClass();
+        $object->id = $lesson2->id;
+        $object->userdeadline = $basetimestamp + 7200; // The unchanged deadline for lesson 2.
+
+        $comparearray[$lesson2->id] = $object;
+
+        $this->assertEquals($comparearray, lesson_get_user_deadline($course->id));
+
+        // Let's test a teacher sees the original times.
+        // lesson 1 and lesson 2 close in two hours.
+        $this->setUser($teacherid);
+
+        $comparearray = array();
+        $object = new stdClass();
+        $object->id = $lesson1->id;
+        $object->userdeadline = $basetimestamp + 7200; // The unchanged deadline for lesson 1.
+
+        $comparearray[$lesson1->id] = $object;
+
+        $object = new stdClass();
+        $object->id = $lesson2->id;
+        $object->userdeadline = $basetimestamp + 7200; // The unchanged deadline for lesson 2.
+
+        $comparearray[$lesson2->id] = $object;
+
+        $this->assertEquals($comparearray, lesson_get_user_deadline($course->id));
+    }
 }
index 621a3dd..bcbb01a 100644 (file)
@@ -146,7 +146,7 @@ class linkmemberships extends resource_base {
      */
     public function parse_value($value) {
 
-        if (strpos($value, '$ToolProxyBinding.memberships.url') !== false) {
+        if (strpos($value, '$LtiLink.memberships.url') !== false) {
             $id = optional_param('id', 0, PARAM_INT); // Course Module ID.
             if (!empty($id)) {
                 $cm = get_coursemodule_from_id('lti', $id, 0, false, MUST_EXIST);
index ed6a071..052ef3a 100644 (file)
@@ -229,8 +229,10 @@ class memberships extends \mod_lti\local\ltiservice\service_base {
                                                                                      $user->id,
                                                                                      $lti->servicesalt,
                                                                                      $lti->typeid));
+                    // Not per specification but added to comply with earlier version of the service.
+                    $member->resultSourcedId = $message->lis_result_sourcedid;
                 }
-                $membership->message = $message;
+                $membership->message = [$message];
             }
 
             foreach ($includedcapabilities as $capabilityname => $capability) {
index 7a0e029..b8dddb0 100644 (file)
@@ -8,6 +8,10 @@ information provided here is intended especially for developers.
 * lti_get_shortcuts has been deprecated. Please use get_shortcuts() instead to add items to the activity chooser.
 * Now, when mod_<modname>_core_calendar_is_event_visible or mod_<modname>_core_calendar_provide_event_action callback functions
   are called, the userid of the requesting user is also passed to them.
+* The following functions have been finally deprecated and can not be used anymore:
+    - update_module_button()
+* The final deprecation of xxx_delete_course callback means that this function will no longer be called.
+  Please use the observer for event \core\event\course_content_deleted instead.
 
 === 3.5 ===
 
diff --git a/mod/wiki/editors/html.php b/mod/wiki/editors/html.php
deleted file mode 100644 (file)
index 96b4e80..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-<?php
-/**
- * This file defines a simple editor
- *
- * @author Jordi Piguillem
- * @author Josep Arus
- *
- * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
- * @package mod_wiki
- *
- */
-
-/**
- * @TODO: Doc this function
- */
-function wiki_print_editor_html($pageid, $content, $version = -1, $section = null, $upload = false, $deleteuploads = array()) {
-    global $CFG, $OUTPUT;
-
-    $OUTPUT->heading(strtoupper(get_string('formathtml', 'wiki')), 3);
-
-    $action = $CFG->wwwroot.'/mod/wiki/edit.php?pageid='.$pageid;
-
-    if (!empty($section)) {
-        $action .= "&section=".urlencode($section);
-    }
-
-    echo $OUTPUT->container_start('container');
-    echo '<form method="post" action="'.$action.'">';
-    $textarea = print_textarea(true, 20, 100, 0, 0, "newcontent", $content, 0, true, '', 'form-textarea-advanced');
-    echo $OUTPUT->container($textarea, 'wiki_editor');
-    wiki_print_edit_form_default_fields('html', $pageid, $version, $upload, $deleteuploads);
-    echo '</form>';
-    echo $OUTPUT->container_end();
-}
index 07166e4..d68d9ac 100644 (file)
@@ -75,7 +75,8 @@ function wiki_print_editor_wiki($pageid, $content, $editor, $version = -1, $sect
 
     echo $OUTPUT->container_start();
     echo '<form method="post" id="mform1" action="' . $action . '">';
-    echo $OUTPUT->container(print_textarea(false, 20, 60, 0, 0, "newcontent", $content, 0, true), false, 'wiki_editor');
+    $textarea = $OUTPUT->print_textarea('newcontent', 'edit-newcontent', $content, 20, 60);
+    echo $OUTPUT->container($textarea, false, 'wiki_editor');
     echo $OUTPUT->container_start();
     wiki_print_edit_form_default_fields($editor, $pageid, $version, $upload, $deleteuploads);
     echo $OUTPUT->container_end();
index 85366c9..b2466b6 100644 (file)
@@ -41,9 +41,13 @@ $pageid = required_param('pageid', PARAM_TEXT);
 $action = optional_param('action', '', PARAM_ALPHANUMEXT);
 $id = optional_param('id', 0, PARAM_INT);
 $commentid = optional_param('commentid', 0, PARAM_INT);
-$newcontent = optional_param('newcontent', '', PARAM_CLEANHTML);
+$newcontent = optional_param_array('newcontent', '', PARAM_CLEANHTML);
 $confirm = optional_param('confirm', 0, PARAM_BOOL);
 
+if ($newcontent) {
+    $newcontent = $newcontent['text'];
+}
+
 if (!$page = wiki_get_page($pageid)) {
     print_error('incorrectpageid', 'wiki');
 }
index 3d6d542..24e6cda 100644 (file)
  */
 
 // 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.
index c5b9cb1..b9b2cd0 100644 (file)
@@ -47,7 +47,27 @@ class contextlist extends contextlist_base {
         global $DB;
 
         $fields = \context_helper::get_preload_record_columns_sql('ctx');
-        $wrapper = "SELECT {$fields} FROM {context} ctx WHERE id IN ({$sql})";
+        if ($fieldname = $this->guess_id_field_from_sql($sql)) {
+            if (is_numeric($fieldname)) {
+                $wrapper = "
+                  SELECT {$fields}
+                    FROM {context} ctx
+                   WHERE ctx.id = :fieldvalue";
+                $params = ['fieldvalue' => $fieldname];
+            } else {
+                // Able to guess a field name.
+                $wrapper = "
+                  SELECT {$fields}
+                    FROM {context} ctx
+                    JOIN ({$sql}) target ON ctx.id = target.{$fieldname}";
+            }
+        } else {
+            // No field name available. Fall back on a potentially slower version.
+            $wrapper = "
+              SELECT {$fields}
+                FROM {context} ctx
+               WHERE ctx.id IN ({$sql})";
+        }
         $contexts = $DB->get_recordset_sql($wrapper, $params);
 
         $contextids = [];
@@ -110,4 +130,61 @@ class contextlist extends contextlist_base {
     public function set_component($component) {
         parent::set_component($component);
     }
+
+    /**
+     * Guess the name of the contextid field from the supplied SQL.
+     *
+     * @param   string  $sql The SQL to guess from
+     * @return  string  The field name or a numeric value representing the context id
+     */
+    protected function guess_id_field_from_sql(string $sql) : string {
+        // We are not interested in any subquery/view/conditions for the purpose of this method, so
+        // let's reduce the query to the interesting parts by recursively cleaning all
+        // contents within parenthesis. If there are problems (null), we keep the text unmodified.
+        // So just top-level sql will remain after the reduction.
+        $recursiveregexp = '/\((([^()]*|(?R))*)\)/';
+        $sql = (preg_replace($recursiveregexp, '', $sql) ?: $sql);
+        // Get the list of relevant words from the SQL Query.
+        // We explode the SQL by the space character, then trim any extra whitespace (e.g. newlines), before we filter
+        // empty value, and finally we re-index the array.
+        $sql = rtrim($sql, ';');
+        $words = array_map('trim', preg_split('/\s+/', $sql));
+        $words = array_filter($words, function($word) {
+            return $word !== '';
+        });
+        $words = array_values($words);
+        $uwords = array_map('strtoupper', $words); // Uppercase all them.
+
+        // If the query has boolean operators (UNION, it is the only one we support cross-db)
+        // then we cannot guarantee whats coming after the first query, it can be anything.
+        if (array_search('UNION', $uwords)) {
+            return '';
+        }
+
+        if ($firstfrom = array_search('FROM', $uwords)) {
+            // Found a FROM keyword.
+            // Select the previous word.
+            $fieldname = $words[$firstfrom - 1];
+            if (is_numeric($fieldname)) {
+                return $fieldname;
+            }
+
+            if ($hasdot = strpos($fieldname, '.')) {
+                // This field is against a table alias. Take off the alias.
+                $fieldname = substr($fieldname, $hasdot + 1);
+            }
+
+            return $fieldname;
+
+        } else if ((count($words) == 1) && (is_numeric($words[0]))) {
+            // Not a real SQL, just a single numerical value - such as one returned by {@link self::add_system_context()}.
+            return $words[0];
+
+        } else if ((count($words) == 2) && (strtoupper($words[0]) === 'SELECT') && (is_numeric($words[1]))) {
+            // SQL returning a constant numerical value.
+            return $words[1];
+        }
+
+        return '';
+    }
 }
index 736e523..43d8b74 100644 (file)
@@ -105,4 +105,106 @@ class contextlist_test extends advanced_testcase {
         $this->assertContains(\context_user::instance($user1->id)->id, $contexts);
         $this->assertContains(\context_user::instance($user2->id)->id, $contexts);
     }
+
+    /**
+     * Test {@link \core_privacy\local\request\contextlist::test_guess_id_field_from_sql()} implementation.
+     *
+     * @dataProvider data_guess_id_field_from_sql
+     * @param string $sql Input SQL we try to extract the context id field name from.
+     * @param string $expected Expected detected value.
+     */
+    public function test_guess_id_field_from_sql($sql, $expected) {
+
+        $rc = new \ReflectionClass(contextlist::class);
+        $rcm = $rc->getMethod('guess_id_field_from_sql');
+        $rcm->setAccessible(true);
+        $actual = $rcm->invoke(new contextlist(), $sql);
+
+        $this->assertEquals($expected, $actual, 'Unable to guess context id field in: '.$sql);
+    }
+
+    /**
+     * Provides data sets for {@link self::test_guess_id_field_from_sql()}.
+     *
+     * @return array
+     */
+    public function data_guess_id_field_from_sql() {
+        return [
+            'easy' => [
+                'SELECT contextid FROM {foo}',
+                'contextid',
+            ],
+            'with_distinct' => [
+                'SELECT DISTINCT contextid FROM {foo}',
+                'contextid',
+            ],
+            'with_dot' => [
+                'SELECT cx.id FROM {foo} JOIN {context} cx ON blahblahblah',
+                'id',
+            ],
+            'letter_case_does_not_matter' => [
+                'Select ctxid From {foo} Where bar = ?',
+                'ctxid',
+            ],
+            'alias' => [
+                'SELECT foo.contextid AS ctx FROM {bar} JOIN {foo} ON bar.id = foo.barid',
+                'ctx',
+            ],
+            'tabs' => [
+                "SELECT\tctxid\t\tFROM foo f",
+                'ctxid',
+            ],
+            'whitespace' => [
+                "SELECT
+                    ctxid\t
+                   \tFROM foo f",
+                'ctxid',
+            ],
+            'just_number' => [
+                '1',
+                '1',
+            ],
+            'select_number' => [
+                'SELECT 2',
+                '2',
+            ],
+            'select_number_with_semicolon' => [
+                'SELECT 3;',
+                '3',
+            ],
+            'select_number_from_table' => [
+                'SELECT 4 FROM users',
+                '4',
+            ],
+            'select_with_complex_subqueries' => [
+                'SELECT id FROM table WHERE id IN (
+                     SELECT x FROM xtable
+                     UNION
+                     SELECT y FROM (
+                         SELECT y FROM ytable
+                         JOIN ztable ON (z = y)))',
+                'id'
+            ],
+            'invalid_union_with_first_being_column_name' => [
+                'SELECT id FROM table UNION SELECT 1 FROM table',
+                ''
+            ],
+            'invalid_union_with_first_being_numeric' => [
+                'SELECT 1 FROM table UNION SELECT id FROM table',
+                ''
+            ],
+            'invalid_union_without_from' => [
+                'SELECT 1 UNION SELECT id FROM table',
+                ''
+            ],
+            'invalid_1' => [
+                'SELECT 1+1',
+                '',
+            ],
+            'invalid_2' => [
+                'muhehe',
+                '',
+            ],
+        ];
+    }
 }
diff --git a/question/type/ddwtos/tests/edit_form_test.php b/question/type/ddwtos/tests/edit_form_test.php
new file mode 100644 (file)
index 0000000..0e462aa
--- /dev/null
@@ -0,0 +1,81 @@
+<?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 the drag-and-drop words into sentences edit form.
+ *
+ * @package   qtype_ddwtos
+ * @copyright 2018 The Open University
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+global $CFG;
+
+require_once($CFG->dirroot . '/question/engine/tests/helpers.php');
+require_once($CFG->dirroot . '/question/type/edit_question_form.php');
+require_once($CFG->dirroot . '/question/type/ddwtos/edit_ddwtos_form.php');
+
+/**
+ * Unit tests for the drag-and-drop words into sentences edit form.
+ *
+ * @copyright  2012 The Open University
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qtype_ddwtos_edit_form_test extends advanced_testcase {
+    /**
+     * Helper method.
+     *
+     * @param string $classname the question form class to instantiate.
+     *
+     * @return question_edit_form great a question form instance that can be tested.
+     */
+    protected function get_form($classname) {
+        $this->setAdminUser();
+        $this->resetAfterTest();
+
+        $syscontext = context_system::instance();
+        $category = question_make_default_categories(array($syscontext));
+        $fakequestion = new stdClass();
+        $fakequestion->qtype = 'stack';
+        $fakequestion->contextid = $syscontext->id;
+        $fakequestion->createdby = 2;
+        $fakequestion->category = $category->id;
+        $fakequestion->questiontext = 'Test [[1]] question [[2]]';
+        $fakequestion->options = new stdClass();
+        $fakequestion->options->answers = array();
+        $fakequestion->formoptions = new stdClass();
+        $fakequestion->formoptions->movecontext = null;
+        $fakequestion->formoptions->repeatelements = true;
+        $fakequestion->inputs = null;
+        return new $classname(new moodle_url('/'), $fakequestion, $category,
+                new question_edit_contexts($syscontext));
+    }
+
+    /**
+     * Test the form shows the right number of groups of choices.
+     */
+    public function test_number_of_choice_groups() {
+        $form = $this->get_form('qtype_ddwtos_edit_form');
+        // Use reflection to get the protected property we need.
+        $property = new ReflectionProperty('qtype_ddwtos_edit_form', '_form');
+        $property->setAccessible(true);
+        $mform = $property->getValue($form);
+        $choices = $mform->getElement('choices[0]');
+        $groupoptions = $choices->_elements[1];
+        $this->assertCount(8, $groupoptions->_options);
+    }
+}
index ed5e516..bd26c77 100644 (file)
@@ -32,10 +32,6 @@ defined('MOODLE_INTERNAL') || die();
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class qtype_gapselect_edit_form_base extends question_edit_form {
-    /**
-     * Maximum number of different groups of drag items there can be in a question.
-     */
-    const MAX_GROUPS = 8;
 
     /** @var array of HTML tags allowed in choices / drag boxes. */
     protected $allowedhtmltags = array(
@@ -166,15 +162,25 @@ class qtype_gapselect_edit_form_base extends question_edit_form {
                 get_string('addmorechoiceblanks', 'qtype_gapselect'), true);
     }
 
+    /**
+     * Return how many different groups of choices there should be.
+     *
+     * @return int the maximum group number.
+     */
+    function get_maximum_choice_group_number() {
+        return 8;
+    }
+
     /**
      * Creates an array with elements for a choice group.
      *
      * @param object $mform The Moodle form we are working with
+     * @param int $maxgroup The number of max group generate element select.
      * @return array Array for form elements
      */
     protected function choice_group($mform) {
         $options = array();
-        for ($i = 1; $i <= self::MAX_GROUPS; $i += 1) {
+        for ($i = 1; $i <= $this->get_maximum_choice_group_number(); $i += 1) {
             $options[$i] = $i;
         }
         $grouparray = array();
index c159a3f..c39c8cc 100644 (file)
@@ -41,4 +41,8 @@ class qtype_gapselect_edit_form extends qtype_gapselect_edit_form_base {
     public function qtype() {
         return 'gapselect';
     }
+
+    function get_maximum_choice_group_number() {
+        return 20;
+    }
 }
index 7c6dc38..6c42735 100644 (file)
@@ -15,7 +15,7 @@
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Unit tests for the select missing words question definition class.
+ * Unit tests for the select missing words question edit form.
  *
  * @package   qtype_gapselect
  * @copyright 2012 The Open University
@@ -28,7 +28,7 @@ global $CFG;
 
 require_once($CFG->dirroot . '/question/engine/tests/helpers.php');
 require_once($CFG->dirroot . '/question/type/edit_question_form.php');
-require_once($CFG->dirroot . '/question/type/gapselect/edit_form_base.php');
+require_once($CFG->dirroot . '/question/type/gapselect/edit_gapselect_form.php');
 
 
 /**
@@ -38,25 +38,6 @@ require_once($CFG->dirroot . '/question/type/gapselect/edit_form_base.php');
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class qtype_gapselect_edit_form_base_testable extends qtype_gapselect_edit_form_base {
-    public function __construct() {
-        $syscontext = context_system::instance();
-        $category = question_make_default_categories(array($syscontext));
-        $fakequestion = new stdClass();
-        $fakequestion->qtype = 'stack';
-        $fakequestion->contextid = $syscontext->id;
-        $fakequestion->createdby = 2;
-        $fakequestion->category = $category->id;
-        $fakequestion->questiontext = 'Test [[1]] question [[2]]';
-        $fakequestion->options = new stdClass();
-        $fakequestion->options->answers = array();
-        $fakequestion->formoptions = new stdClass();
-        $fakequestion->formoptions->movecontext = null;
-        $fakequestion->formoptions->repeatelements = true;
-        $fakequestion->inputs = null;
-        parent::__construct(new moodle_url('/'), $fakequestion, $category,
-                new question_edit_contexts($syscontext));
-    }
-
     public function get_illegal_tag_error($text) {
         return parent::get_illegal_tag_error($text);
     }
@@ -72,7 +53,7 @@ class qtype_gapselect_edit_form_base_testable extends qtype_gapselect_edit_form_
 
 
 /**
- * Unit tests for Stack question editing form.
+ * Unit tests for select missing words question edit form.
  *
  * @copyright  2012 The Open University
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -81,17 +62,35 @@ class qtype_gapselect_edit_form_test extends advanced_testcase {
 
     /**
      * Helper method.
-     * @return qtype_gapselect_edit_form_base_testable a new form instance that can be tested.
+     *
+     * @param string $classname the question form class to instantiate.
+     *
+     * @return question_edit_form great a question form instance that can be tested.
      */
-    protected function get_form() {
+    protected function get_form($classname) {
         $this->setAdminUser();
         $this->resetAfterTest();
 
-        return new qtype_gapselect_edit_form_base_testable();
+        $syscontext = context_system::instance();
+        $category = question_make_default_categories(array($syscontext));
+        $fakequestion = new stdClass();
+        $fakequestion->qtype = 'stack';
+        $fakequestion->contextid = $syscontext->id;
+        $fakequestion->createdby = 2;
+        $fakequestion->category = $category->id;
+        $fakequestion->questiontext = 'Test [[1]] question [[2]]';
+        $fakequestion->options = new stdClass();
+        $fakequestion->options->answers = array();
+        $fakequestion->formoptions = new stdClass();
+        $fakequestion->formoptions->movecontext = null;
+        $fakequestion->formoptions->repeatelements = true;
+        $fakequestion->inputs = null;
+        return new $classname(new moodle_url('/'), $fakequestion, $category,
+                new question_edit_contexts($syscontext));
     }
 
     public function test_get_illegal_tag_error() {
-        $form = $this->get_form();
+        $form = $this->get_form('qtype_gapselect_edit_form_base_testable');
 
         $this->assertEquals('', $form->get_illegal_tag_error('frog'));
         $this->assertEquals('', $form->get_illegal_tag_error('<i>toad</i>'));
@@ -127,4 +126,18 @@ class qtype_gapselect_edit_form_test extends advanced_testcase {
         $this->assertEquals(get_string('tagsnotallowedatall', 'qtype_gapselect', $a),
                 $form->get_illegal_tag_error('<i><br /></i>'));
     }
+
+    /**
+     * Test the form shows the right number of groups of choices.
+     */
+    public function test_number_of_choice_groups() {
+        $form = $this->get_form('qtype_gapselect_edit_form');
+        // Use reflection to get the protected property we need.
+        $property = new ReflectionProperty('qtype_gapselect_edit_form', '_form');
+        $property->setAccessible(true);
+        $mform = $property->getValue($form);
+        $choices = $mform->getElement('choices[0]');
+        $groupoptions = $choices->_elements[1];
+        $this->assertCount(20, $groupoptions->_options);
+    }
 }
index f410e0b..9656574 100644 (file)
@@ -1,6 +1,11 @@
 This files describes API changes in /report/* - plugins,
 information provided here is intended especially for developers.
 
+=== 3.6 ===
+
+* The final deprecation of xxx_delete_course callback means that this function will no longer be called.
+  Please use the observer for event \core\event\course_content_deleted instead.
+
 === 3.2 ===
 * Callback delete_course is deprecated and should be replaced with observer for event \core\event\course_content_deleted
 * The report_log_print_graph signature and behaviour has changed to generate charts using
diff --git a/repository/coursefiles/pix/icon.svg b/repository/coursefiles/pix/icon.svg
new file mode 100644 (file)
index 0000000..7ef26ad
--- /dev/null
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In  -->\r
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
+       <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
+]>\r
+<svg version="1.1"\r
+        xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"\r
+        x="0px" y="0px" width="74px" height="51px" viewBox="-0.535 -0.774 74 51"\r
+        style="overflow:visible;enable-background:new -0.535 -0.774 74 51;" xml:space="preserve" preserveAspectRatio="xMinYMid meet">\r
+<defs>\r
+</defs>\r
+<radialGradient id="SVGID_1_" cx="137.084" cy="32.2324" r="123.3346" gradientUnits="userSpaceOnUse">\r
+       <stop  offset="0" style="stop-color:#FAAF40"/>\r
+       <stop  offset="0.0432" style="stop-color:#F9A538"/>\r
+       <stop  offset="0.1116" style="stop-color:#F89D31"/>\r
+       <stop  offset="0.2269" style="stop-color:#F89A2F"/>\r
+       <stop  offset="0.5276" style="stop-color:#F7922D"/>\r
+       <stop  offset="1" style="stop-color:#F37B28"/>\r
+       <a:midPointStop  offset="0" style="stop-color:#FAAF40"/>\r
+       <a:midPointStop  offset="0.1982" style="stop-color:#FAAF40"/>\r
+       <a:midPointStop  offset="0.2269" style="stop-color:#F89A2F"/>\r
+       <a:midPointStop  offset="0.6064" style="stop-color:#F89A2F"/>\r
+       <a:midPointStop  offset="1" style="stop-color:#F37B28"/>\r
+</radialGradient>\r
+<path style="fill:url(#SVGID_1_);" d="M61.192,49.375V26.558c0-4.771-1.972-7.156-5.911-7.156c-3.942,0-5.912,2.386-5.912,7.156\r
+       v22.817H37.754V26.558c0-4.771-1.938-7.156-5.811-7.156c-3.94,0-5.91,2.386-5.91,7.156v22.817H14.417V25.211\r
+       c0-4.979,1.728-8.747,5.185-11.304c3.043-2.282,7.158-3.425,12.343-3.425c5.255,0,9.127,1.349,11.617,4.045\r
+       c2.142-2.696,6.049-4.045,11.72-4.045c5.186,0,9.298,1.143,12.341,3.425c3.457,2.557,5.186,6.325,5.186,11.304v24.164H61.192z"/>\r
+<path style="fill:#58595B;" d="M15.499,16.321c-0.394,1.999-0.788,3.999-1.181,5.997c10.983,3.72,21.4,0.111,26.883-9.489\r
+       C33.184,7.282,25.601,12.914,15.499,16.321"/>\r
+<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="14.4287" y1="16.0166" x2="40.6895" y2="16.0166">\r
+       <stop  offset="0" style="stop-color:#929497"/>\r
+       <stop  offset="0.1245" style="stop-color:#757578"/>\r
+       <stop  offset="0.2792" style="stop-color:#575658"/>\r
+       <stop  offset="0.4403" style="stop-color:#403E3F"/>\r
+       <stop  offset="0.6085" style="stop-color:#302D2E"/>\r
+       <stop  offset="0.7884" style="stop-color:#262223"/>\r
+       <stop  offset="1" style="stop-color:#231F20"/>\r
+       <a:midPointStop  offset="0" style="stop-color:#929497"/>\r
+       <a:midPointStop  offset="0.2606" style="stop-color:#929497"/>\r
+       <a:midPointStop  offset="1" style="stop-color:#231F20"/>\r
+</linearGradient>\r
+<path style="fill:url(#SVGID_2_);" d="M15.499,14.889c-0.356,2.259-0.714,4.518-1.07,6.776c10.527,3.567,20.84,0.503,26.261-8.944\r
+       C33.708,4.677,25.925,11.373,15.499,14.889"/>\r
+<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="19.457" y1="7.248" x2="30.3611" y2="22.8207">\r
+       <stop  offset="0" style="stop-color:#231F20"/>\r
+       <stop  offset="1" style="stop-color:#231F20;stop-opacity:0"/>\r
+       <a:midPointStop  offset="0" style="stop-color:#231F20"/>\r
+       <a:midPointStop  offset="0.5" style="stop-color:#231F20"/>\r
+       <a:midPointStop  offset="1" style="stop-color:#231F20;stop-opacity:0"/>\r
+</linearGradient>\r
+<path style="fill:url(#SVGID_3_);" d="M27.993,17.575c-4.764-0.997-10.036,1.485-13.564,4.09\r
+       C12.103,4.879,22.536,5.222,36.098,9.415c-0.857,4.143-2.387,9.598-5.017,12.903C31.081,20.182,30.052,18.6,27.993,17.575"/>\r
+<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="0.0078" y1="7.7275" x2="48.0068" y2="7.7275">\r
+       <stop  offset="0" style="stop-color:#929497"/>\r
+       <stop  offset="0.1245" style="stop-color:#757578"/>\r
+       <stop  offset="0.2792" style="stop-color:#575658"/>\r
+       <stop  offset="0.4403" style="stop-color:#403E3F"/>\r
+       <stop  offset="0.6085" style="stop-color:#302D2E"/>\r
+       <stop  offset="0.7884" style="stop-color:#262223"/>\r
+       <stop  offset="1" style="stop-color:#231F20"/>\r
+       <a:midPointStop  offset="0" style="stop-color:#929497"/>\r
+       <a:midPointStop  offset="0.2606" style="stop-color:#929497"/>\r
+       <a:midPointStop  offset="1" style="stop-color:#231F20"/>\r
+</linearGradient>\r
+<path style="fill:url(#SVGID_4_);" d="M0.008,14.889C17.625,4.318,27.468,2.282,48.007,0.202\r
+       C24.279,18.919,23.64,14.889,0.008,14.889"/>\r
+<line style="fill:#383738;stroke:#4A4A4C;stroke-width:0.5;" x1="48.007" y1="0.202" x2="29.186" y2="13.948"/>\r
+<path style="opacity:0.23;fill:#231F20;" d="M25.506,7.567C25.733,9.723,25.286,5.423,25.506,7.567"/>\r
+<line style="fill:#FFFFFF;stroke:#A8ABAD;stroke-width:0.5;" x1="0.008" y1="14.889" x2="29.186" y2="13.948"/>\r
+<path style="fill:none;stroke:#F16922;stroke-width:0.5;" d="M23.79,7.733c-4.996,1.381-21.387,5.041-21.64,7.086\r
+       c-0.483,3.917-0.123,10.143-0.123,10.143"/>\r
+<path style="fill:#F16922;" d="M3.532,39.84C1.697,35.389-0.443,30.363,2.1,24.184C3.794,29.957,3.544,34.362,3.532,39.84"/>\r
+<ellipse transform="matrix(0.942 -0.3356 0.3356 0.942 -1.1544 8.1596)" style="fill:#6D6E70;" cx="23.032" cy="7.42" rx="0.792" ry="0.411"/>\r
+</svg>\r
diff --git a/repository/local/pix/icon.svg b/repository/local/pix/icon.svg
new file mode 100644 (file)
index 0000000..7ef26ad
--- /dev/null
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In  -->\r
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
+       <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
+]>\r
+<svg version="1.1"\r
+        xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"\r
+        x="0px" y="0px" width="74px" height="51px" viewBox="-0.535 -0.774 74 51"\r
+        style="overflow:visible;enable-background:new -0.535 -0.774 74 51;" xml:space="preserve" preserveAspectRatio="xMinYMid meet">\r
+<defs>\r
+</defs>\r
+<radialGradient id="SVGID_1_" cx="137.084" cy="32.2324" r="123.3346" gradientUnits="userSpaceOnUse">\r
+       <stop  offset="0" style="stop-color:#FAAF40"/>\r
+       <stop  offset="0.0432" style="stop-color:#F9A538"/>\r
+       <stop  offset="0.1116" style="stop-color:#F89D31"/>\r
+       <stop  offset="0.2269" style="stop-color:#F89A2F"/>\r
+       <stop  offset="0.5276" style="stop-color:#F7922D"/>\r
+       <stop  offset="1" style="stop-color:#F37B28"/>\r
+       <a:midPointStop  offset="0" style="stop-color:#FAAF40"/>\r
+       <a:midPointStop  offset="0.1982" style="stop-color:#FAAF40"/>\r
+       <a:midPointStop  offset="0.2269" style="stop-color:#F89A2F"/>\r
+       <a:midPointStop  offset="0.6064" style="stop-color:#F89A2F"/>\r
+       <a:midPointStop  offset="1" style="stop-color:#F37B28"/>\r
+</radialGradient>\r
+<path style="fill:url(#SVGID_1_);" d="M61.192,49.375V26.558c0-4.771-1.972-7.156-5.911-7.156c-3.942,0-5.912,2.386-5.912,7.156\r
+       v22.817H37.754V26.558c0-4.771-1.938-7.156-5.811-7.156c-3.94,0-5.91,2.386-5.91,7.156v22.817H14.417V25.211\r
+       c0-4.979,1.728-8.747,5.185-11.304c3.043-2.282,7.158-3.425,12.343-3.425c5.255,0,9.127,1.349,11.617,4.045\r
+       c2.142-2.696,6.049-4.045,11.72-4.045c5.186,0,9.298,1.143,12.341,3.425c3.457,2.557,5.186,6.325,5.186,11.304v24.164H61.192z"/>\r
+<path style="fill:#58595B;" d="M15.499,16.321c-0.394,1.999-0.788,3.999-1.181,5.997c10.983,3.72,21.4,0.111,26.883-9.489\r
+       C33.184,7.282,25.601,12.914,15.499,16.321"/>\r
+<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="14.4287" y1="16.0166" x2="40.6895" y2="16.0166">\r
+       <stop  offset="0" style="stop-color:#929497"/>\r
+       <stop  offset="0.1245" style="stop-color:#757578"/>\r
+       <stop  offset="0.2792" style="stop-color:#575658"/>\r
+       <stop  offset="0.4403" style="stop-color:#403E3F"/>\r
+       <stop  offset="0.6085" style="stop-color:#302D2E"/>\r
+       <stop  offset="0.7884" style="stop-color:#262223"/>\r
+       <stop  offset="1" style="stop-color:#231F20"/>\r
+       <a:midPointStop  offset="0" style="stop-color:#929497"/>\r
+       <a:midPointStop  offset="0.2606" style="stop-color:#929497"/>\r
+       <a:midPointStop  offset="1" style="stop-color:#231F20"/>\r
+</linearGradient>\r
+<path style="fill:url(#SVGID_2_);" d="M15.499,14.889c-0.356,2.259-0.714,4.518-1.07,6.776c10.527,3.567,20.84,0.503,26.261-8.944\r
+       C33.708,4.677,25.925,11.373,15.499,14.889"/>\r
+<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="19.457" y1="7.248" x2="30.3611" y2="22.8207">\r
+       <stop  offset="0" style="stop-color:#231F20"/>\r
+       <stop  offset="1" style="stop-color:#231F20;stop-opacity:0"/>\r
+       <a:midPointStop  offset="0" style="stop-color:#231F20"/>\r
+       <a:midPointStop  offset="0.5" style="stop-color:#231F20"/>\r
+       <a:midPointStop  offset="1" style="stop-color:#231F20;stop-opacity:0"/>\r
+</linearGradient>\r
+<path style="fill:url(#SVGID_3_);" d="M27.993,17.575c-4.764-0.997-10.036,1.485-13.564,4.09\r
+       C12.103,4.879,22.536,5.222,36.098,9.415c-0.857,4.143-2.387,9.598-5.017,12.903C31.081,20.182,30.052,18.6,27.993,17.575"/>\r
+<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="0.0078" y1="7.7275" x2="48.0068" y2="7.7275">\r
+       <stop  offset="0" style="stop-color:#929497"/>\r
+       <stop  offset="0.1245" style="stop-color:#757578"/>\r
+       <stop  offset="0.2792" style="stop-color:#575658"/>\r
+       <stop  offset="0.4403" style="stop-color:#403E3F"/>\r
+       <stop  offset="0.6085" style="stop-color:#302D2E"/>\r
+       <stop  offset="0.7884" style="stop-color:#262223"/>\r
+       <stop  offset="1" style="stop-color:#231F20"/>\r
+       <a:midPointStop  offset="0" style="stop-color:#929497"/>\r
+       <a:midPointStop  offset="0.2606" style="stop-color:#929497"/>\r
+       <a:midPointStop  offset="1" style="stop-color:#231F20"/>\r
+</linearGradient>\r
+<path style="fill:url(#SVGID_4_);" d="M0.008,14.889C17.625,4.318,27.468,2.282,48.007,0.202\r
+       C24.279,18.919,23.64,14.889,0.008,14.889"/>\r
+<line style="fill:#383738;stroke:#4A4A4C;stroke-width:0.5;" x1="48.007" y1="0.202" x2="29.186" y2="13.948"/>\r
+<path style="opacity:0.23;fill:#231F20;" d="M25.506,7.567C25.733,9.723,25.286,5.423,25.506,7.567"/>\r
+<line style="fill:#FFFFFF;stroke:#A8ABAD;stroke-width:0.5;" x1="0.008" y1="14.889" x2="29.186" y2="13.948"/>\r
+<path style="fill:none;stroke:#F16922;stroke-width:0.5;" d="M23.79,7.733c-4.996,1.381-21.387,5.041-21.64,7.086\r
+       c-0.483,3.917-0.123,10.143-0.123,10.143"/>\r
+<path style="fill:#F16922;" d="M3.532,39.84C1.697,35.389-0.443,30.363,2.1,24.184C3.794,29.957,3.544,34.362,3.532,39.84"/>\r
+<ellipse transform="matrix(0.942 -0.3356 0.3356 0.942 -1.1544 8.1596)" style="fill:#6D6E70;" cx="23.032" cy="7.42" rx="0.792" ry="0.411"/>\r
+</svg>\r
diff --git a/repository/recent/pix/icon.svg b/repository/recent/pix/icon.svg
new file mode 100644 (file)
index 0000000..7ef26ad
--- /dev/null
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In  -->\r
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
+       <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
+]>\r
+<svg version="1.1"\r
+        xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"\r
+        x="0px" y="0px" width="74px" height="51px" viewBox="-0.535 -0.774 74 51"\r
+        style="overflow:visible;enable-background:new -0.535 -0.774 74 51;" xml:space="preserve" preserveAspectRatio="xMinYMid meet">\r
+<defs>\r
+</defs>\r
+<radialGradient id="SVGID_1_" cx="137.084" cy="32.2324" r="123.3346" gradientUnits="userSpaceOnUse">\r
+       <stop  offset="0" style="stop-color:#FAAF40"/>\r
+       <stop  offset="0.0432" style="stop-color:#F9A538"/>\r
+       <stop  offset="0.1116" style="stop-color:#F89D31"/>\r
+       <stop  offset="0.2269" style="stop-color:#F89A2F"/>\r
+       <stop  offset="0.5276" style="stop-color:#F7922D"/>\r
+       <stop  offset="1" style="stop-color:#F37B28"/>\r
+       <a:midPointStop  offset="0" style="stop-color:#FAAF40"/>\r
+       <a:midPointStop  offset="0.1982" style="stop-color:#FAAF40"/>\r
+       <a:midPointStop  offset="0.2269" style="stop-color:#F89A2F"/>\r
+       <a:midPointStop  offset="0.6064" style="stop-color:#F89A2F"/>\r
+       <a:midPointStop  offset="1" style="stop-color:#F37B28"/>\r
+</radialGradient>\r
+<path style="fill:url(#SVGID_1_);" d="M61.192,49.375V26.558c0-4.771-1.972-7.156-5.911-7.156c-3.942,0-5.912,2.386-5.912,7.156\r
+       v22.817H37.754V26.558c0-4.771-1.938-7.156-5.811-7.156c-3.94,0-5.91,2.386-5.91,7.156v22.817H14.417V25.211\r
+       c0-4.979,1.728-8.747,5.185-11.304c3.043-2.282,7.158-3.425,12.343-3.425c5.255,0,9.127,1.349,11.617,4.045\r
+       c2.142-2.696,6.049-4.045,11.72-4.045c5.186,0,9.298,1.143,12.341,3.425c3.457,2.557,5.186,6.325,5.186,11.304v24.164H61.192z"/>\r
+<path style="fill:#58595B;" d="M15.499,16.321c-0.394,1.999-0.788,3.999-1.181,5.997c10.983,3.72,21.4,0.111,26.883-9.489\r
+       C33.184,7.282,25.601,12.914,15.499,16.321"/>\r
+<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="14.4287" y1="16.0166" x2="40.6895" y2="16.0166">\r
+       <stop  offset="0" style="stop-color:#929497"/>\r
+       <stop  offset="0.1245" style="stop-color:#757578"/>\r
+       <stop  offset="0.2792" style="stop-color:#575658"/>\r
+       <stop  offset="0.4403" style="stop-color:#403E3F"/>\r
+       <stop  offset="0.6085" style="stop-color:#302D2E"/>\r
+       <stop  offset="0.7884" style="stop-color:#262223"/>\r
+       <stop  offset="1" style="stop-color:#231F20"/>\r
+       <a:midPointStop  offset="0" style="stop-color:#929497"/>\r
+       <a:midPointStop  offset="0.2606" style="stop-color:#929497"/>\r
+       <a:midPointStop  offset="1" style="stop-color:#231F20"/>\r
+</linearGradient>\r
+<path style="fill:url(#SVGID_2_);" d="M15.499,14.889c-0.356,2.259-0.714,4.518-1.07,6.776c10.527,3.567,20.84,0.503,26.261-8.944\r
+       C33.708,4.677,25.925,11.373,15.499,14.889"/>\r
+<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="19.457" y1="7.248" x2="30.3611" y2="22.8207">\r
+       <stop  offset="0" style="stop-color:#231F20"/>\r
+       <stop  offset="1" style="stop-color:#231F20;stop-opacity:0"/>\r
+       <a:midPointStop  offset="0" style="stop-color:#231F20"/>\r
+       <a:midPointStop  offset="0.5" style="stop-color:#231F20"/>\r
+       <a:midPointStop  offset="1" style="stop-color:#231F20;stop-opacity:0"/>\r
+</linearGradient>\r
+<path style="fill:url(#SVGID_3_);" d="M27.993,17.575c-4.764-0.997-10.036,1.485-13.564,4.09\r
+       C12.103,4.879,22.536,5.222,36.098,9.415c-0.857,4.143-2.387,9.598-5.017,12.903C31.081,20.182,30.052,18.6,27.993,17.575"/>\r
+<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="0.0078" y1="7.7275" x2="48.0068" y2="7.7275">\r
+       <stop  offset="0" style="stop-color:#929497"/>\r
+       <stop  offset="0.1245" style="stop-color:#757578"/>\r
+       <stop  offset="0.2792" style="stop-color:#575658"/>\r
+       <stop  offset="0.4403" style="stop-color:#403E3F"/>\r
+       <stop  offset="0.6085" style="stop-color:#302D2E"/>\r
+       <stop  offset="0.7884" style="stop-color:#262223"/>\r
+       <stop  offset="1" style="stop-color:#231F20"/>\r
+       <a:midPointStop  offset="0" style="stop-color:#929497"/>\r
+       <a:midPointStop  offset="0.2606" style="stop-color:#929497"/>\r
+       <a:midPointStop  offset="1" style="stop-color:#231F20"/>\r
+</linearGradient>\r
+<path style="fill:url(#SVGID_4_);" d="M0.008,14.889C17.625,4.318,27.468,2.282,48.007,0.202\r
+       C24.279,18.919,23.64,14.889,0.008,14.889"/>\r
+<line style="fill:#383738;stroke:#4A4A4C;stroke-width:0.5;" x1="48.007" y1="0.202" x2="29.186" y2="13.948"/>\r
+<path style="opacity:0.23;fill:#231F20;" d="M25.506,7.567C25.733,9.723,25.286,5.423,25.506,7.567"/>\r
+<line style="fill:#FFFFFF;stroke:#A8ABAD;stroke-width:0.5;" x1="0.008" y1="14.889" x2="29.186" y2="13.948"/>\r
+<path style="fill:none;stroke:#F16922;stroke-width:0.5;" d="M23.79,7.733c-4.996,1.381-21.387,5.041-21.64,7.086\r
+       c-0.483,3.917-0.123,10.143-0.123,10.143"/>\r
+<path style="fill:#F16922;" d="M3.532,39.84C1.697,35.389-0.443,30.363,2.1,24.184C3.794,29.957,3.544,34.362,3.532,39.84"/>\r
+<ellipse transform="matrix(0.942 -0.3356 0.3356 0.942 -1.1544 8.1596)" style="fill:#6D6E70;" cx="23.032" cy="7.42" rx="0.792" ry="0.411"/>\r
+</svg>\r
diff --git a/repository/user/pix/icon.svg b/repository/user/pix/icon.svg
new file mode 100644 (file)
index 0000000..7ef26ad
--- /dev/null
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In  -->\r
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
+       <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
+]>\r
+<svg version="1.1"\r
+        xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"\r
+        x="0px" y="0px" width="74px" height="51px" viewBox="-0.535 -0.774 74 51"\r
+        style="overflow:visible;enable-background:new -0.535 -0.774 74 51;" xml:space="preserve" preserveAspectRatio="xMinYMid meet">\r
+<defs>\r
+</defs>\r
+<radialGradient id="SVGID_1_" cx="137.084" cy="32.2324" r="123.3346" gradientUnits="userSpaceOnUse">\r
+       <stop  offset="0" style="stop-color:#FAAF40"/>\r
+       <stop  offset="0.0432" style="stop-color:#F9A538"/>\r
+       <stop  offset="0.1116" style="stop-color:#F89D31"/>\r
+       <stop  offset="0.2269" style="stop-color:#F89A2F"/>\r
+       <stop  offset="0.5276" style="stop-color:#F7922D"/>\r
+       <stop  offset="1" style="stop-color:#F37B28"/>\r
+       <a:midPointStop  offset="0" style="stop-color:#FAAF40"/>\r
+       <a:midPointStop  offset="0.1982" style="stop-color:#FAAF40"/>\r
+       <a:midPointStop  offset="0.2269" style="stop-color:#F89A2F"/>\r
+       <a:midPointStop  offset="0.6064" style="stop-color:#F89A2F"/>\r
+       <a:midPointStop  offset="1" style="stop-color:#F37B28"/>\r
+</radialGradient>\r
+<path style="fill:url(#SVGID_1_);" d="M61.192,49.375V26.558c0-4.771-1.972-7.156-5.911-7.156c-3.942,0-5.912,2.386-5.912,7.156\r
+       v22.817H37.754V26.558c0-4.771-1.938-7.156-5.811-7.156c-3.94,0-5.91,2.386-5.91,7.156v22.817H14.417V25.211\r
+       c0-4.979,1.728-8.747,5.185-11.304c3.043-2.282,7.158-3.425,12.343-3.425c5.255,0,9.127,1.349,11.617,4.045\r
+       c2.142-2.696,6.049-4.045,11.72-4.045c5.186,0,9.298,1.143,12.341,3.425c3.457,2.557,5.186,6.325,5.186,11.304v24.164H61.192z"/>\r
+<path style="fill:#58595B;" d="M15.499,16.321c-0.394,1.999-0.788,3.999-1.181,5.997c10.983,3.72,21.4,0.111,26.883-9.489\r
+       C33.184,7.282,25.601,12.914,15.499,16.321"/>\r
+<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="14.4287" y1="16.0166" x2="40.6895" y2="16.0166">\r
+       <stop  offset="0" style="stop-color:#929497"/>\r
+       <stop  offset="0.1245" style="stop-color:#757578"/>\r
+       <stop  offset="0.2792" style="stop-color:#575658"/>\r
+       <stop  offset="0.4403" style="stop-color:#403E3F"/>\r
+       <stop  offset="0.6085" style="stop-color:#302D2E"/>\r
+       <stop  offset="0.7884" style="stop-color:#262223"/>\r
+       <stop  offset="1" style="stop-color:#231F20"/>\r
+       <a:midPointStop  offset="0" style="stop-color:#929497"/>\r
+       <a:midPointStop  offset="0.2606" style="stop-color:#929497"/>\r
+       <a:midPointStop  offset="1" style="stop-color:#231F20"/>\r
+</linearGradient>\r
+<path style="fill:url(#SVGID_2_);" d="M15.499,14.889c-0.356,2.259-0.714,4.518-1.07,6.776c10.527,3.567,20.84,0.503,26.261-8.944\r
+       C33.708,4.677,25.925,11.373,15.499,14.889"/>\r
+<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="19.457" y1="7.248" x2="30.3611" y2="22.8207">\r
+       <stop  offset="0" style="stop-color:#231F20"/>\r
+       <stop  offset="1" style="stop-color:#231F20;stop-opacity:0"/>\r
+       <a:midPointStop  offset="0" style="stop-color:#231F20"/>\r
+       <a:midPointStop  offset="0.5" style="stop-color:#231F20"/>\r
+       <a:midPointStop  offset="1" style="stop-color:#231F20;stop-opacity:0"/>\r
+</linearGradient>\r
+<path style="fill:url(#SVGID_3_);" d="M27.993,17.575c-4.764-0.997-10.036,1.485-13.564,4.09\r
+       C12.103,4.879,22.536,5.222,36.098,9.415c-0.857,4.143-2.387,9.598-5.017,12.903C31.081,20.182,30.052,18.6,27.993,17.575"/>\r
+<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="0.0078" y1="7.7275" x2="48.0068" y2="7.7275">\r
+       <stop  offset="0" style="stop-color:#929497"/>\r
+       <stop  offset="0.1245" style="stop-color:#757578"/>\r
+       <stop  offset="0.2792" style="stop-color:#575658"/>\r
+       <stop  offset="0.4403" style="stop-color:#403E3F"/>\r
+       <stop  offset="0.6085" style="stop-color:#302D2E"/>\r
+       <stop  offset="0.7884" style="stop-color:#262223"/>\r
+       <stop  offset="1" style="stop-color:#231F20"/>\r
+       <a:midPointStop  offset="0" style="stop-color:#929497"/>\r
+       <a:midPointStop  offset="0.2606" style="stop-color:#929497"/>\r
+       <a:midPointStop  offset="1" style="stop-color:#231F20"/>\r
+</linearGradient>\r
+<path style="fill:url(#SVGID_4_);" d="M0.008