Merge branch 'MDL-62948-master' of git://github.com/andrewnicols/moodle
authorJun Pataleta <jun@moodle.com>
Mon, 23 Jul 2018 07:20:47 +0000 (15:20 +0800)
committerJun Pataleta <jun@moodle.com>
Mon, 23 Jul 2018 07:20:47 +0000 (15:20 +0800)
80 files changed:
admin/tool/dataprivacy/tests/api_test.php
admin/tool/log/store/legacy/classes/log/store.php
admin/tool/log/upgrade.txt [new file with mode: 0644]
auth/shibboleth/classes/helper.php [new file with mode: 0644]
auth/shibboleth/logout.php
auth/shibboleth/upgrade.txt
backup/util/dbops/restore_dbops.class.php
blocks/myprofile/lang/en/block_myprofile.php
blocks/myprofile/lang/en/deprecated.txt
calendar/tests/behat/calendar.feature
course/externallib.php
course/format/singleactivity/lib.php
course/lib.php
group/assign.php
install/lang/hu/install.php
install/lang/ja/install.php
lang/en/admin.php
lang/en/calendar.php
lang/en/competency.php
lang/en/deprecated.txt
lang/en/media.php
lang/en/message.php
lang/en/moodle.php
lib/classes/session/manager.php
lib/db/caches.php
lib/deprecatedlib.php
lib/editor/atto/tests/behat/disablecontrol.feature [new file with mode: 0644]
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor.js
lib/editor/atto/yui/src/editor/js/editor.js [changed mode: 0644->0755]
lib/editor/tests/fixtures/disable_control_example.php [new file with mode: 0644]
lib/editor/tests/fixtures/editor_form.php [new file with mode: 0644]
lib/editor/textarea/lib.php [changed mode: 0644->0755]
lib/editor/textarea/tests/behat/disablecontrol.feature [new file with mode: 0644]
lib/editor/tinymce/module.js [changed mode: 0644->0755]
lib/editor/tinymce/tests/behat/disablecontrol.feature [new file with mode: 0644]
lib/filelib.php
lib/form/filemanager.php
lib/form/form.js [changed mode: 0644->0755]
lib/moodlelib.php
lib/tests/filelib_test.php
lib/tests/string_manager_standard_test.php
lib/upgrade.txt
message/output/popup/amd/build/notification_popover_controller.min.js
message/output/popup/amd/src/notification_popover_controller.js
message/output/popup/mark_notification_read.php
mod/assign/lang/en/assign.php
mod/assign/lang/en/deprecated.txt
mod/assign/submission/file/locallib.php
mod/assign/submission/file/tests/locallib_test.php
mod/data/lang/en/data.php
mod/data/lang/en/deprecated.txt
mod/feedback/lang/en/deprecated.txt
mod/feedback/lang/en/feedback.php
mod/feedback/lib.php
mod/feedback/upgrade.txt
mod/forum/lib.php
mod/lesson/renderer.php
mod/lti/lib.php
mod/quiz/report/statistics/report.php
mod/scorm/datamodels/scorm_13.js
mod/upgrade.txt
question/classes/bank/action_column_base.php
question/classes/bank/view.php
question/format/gift/format.php
question/format/gift/tests/giftformat_test.php
question/tests/bank_view_test.php [new file with mode: 0644]
question/tests/privacy_provider_test.php
theme/boost/scss/moodle/modules.scss
theme/boost/style/moodle.css
user/amd/build/name_page_filter.min.js [deleted file]
user/amd/src/name_page_filter.js [deleted file]
user/index.php
user/lib.php
user/profile/lib.php
user/tests/behat/custom_profile_fields.feature [new file with mode: 0644]
user/tests/behat/filter_participants.feature
user/tests/behat/filter_participants_showall.feature [new file with mode: 0644]
version.php

index f4a7a66..e5b6b16 100644 (file)
@@ -417,27 +417,20 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
      * @return array
      */
     public function get_data_requests_provider() {
-        $generator = new testing_data_generator();
-        $user1 = $generator->create_user();
-        $user2 = $generator->create_user();
-        $user3 = $generator->create_user();
-        $user4 = $generator->create_user();
-        $user5 = $generator->create_user();
-        $users = [$user1, $user2, $user3, $user4, $user5];
         $completeonly = [api::DATAREQUEST_STATUS_COMPLETE];
         $completeandcancelled = [api::DATAREQUEST_STATUS_COMPLETE, api::DATAREQUEST_STATUS_CANCELLED];
 
         return [
             // Own data requests.
-            [$users, $user1, false, $completeonly],
+            ['user', false, $completeonly],
             // Non-DPO fetching all requets.
-            [$users, $user2, true, $completeonly],
+            ['user', true, $completeonly],
             // Admin fetching all completed and cancelled requests.
-            [$users, get_admin(), true, $completeandcancelled],
+            ['dpo', true, $completeandcancelled],
             // Admin fetching all completed requests.
-            [$users, get_admin(), true, $completeonly],
+            ['dpo', true, $completeonly],
             // Guest fetching all requests.
-            [$users, guest_user(), true, $completeonly],
+            ['guest', true, $completeonly],
         ];
     }
 
@@ -445,12 +438,31 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
      * Test for api::get_data_requests()
      *
      * @dataProvider get_data_requests_provider
-     * @param stdClass[] $users Array of users to create data requests for.
-     * @param stdClass $loggeduser The user logging in.
+     * @param string $usertype The type of the user logging in.
      * @param boolean $fetchall Whether to fetch all records.
      * @param int[] $statuses Status filters.
      */
-    public function test_get_data_requests($users, $loggeduser, $fetchall, $statuses) {
+    public function test_get_data_requests($usertype, $fetchall, $statuses) {
+        $generator = new testing_data_generator();
+        $user1 = $generator->create_user();
+        $user2 = $generator->create_user();
+        $user3 = $generator->create_user();
+        $user4 = $generator->create_user();
+        $user5 = $generator->create_user();
+        $users = [$user1, $user2, $user3, $user4, $user5];
+
+        switch ($usertype) {
+            case 'user':
+                $loggeduser = $user1;
+                break;
+            case 'dpo':
+                $loggeduser = get_admin();
+                break;
+            case 'guest':
+                $loggeduser = guest_user();
+                break;
+        }
+
         $comment = 'Data %s request comment by user %d';
         $exportstring = helper::get_shortened_request_type_string(api::DATAREQUEST_TYPE_EXPORT);
         $deletionstring = helper::get_shortened_request_type_string(api::DATAREQUEST_TYPE_DELETE);
index d40eec0..bcb1d54 100644 (file)
@@ -16,6 +16,8 @@
 
 /**
  * Legacy log reader.
+ * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead.
+ * @todo  MDL-52805 This is to be removed in Moodle 4.0
  *
  * @package    logstore_legacy
  * @copyright  2013 Petr Skoda {@link http://skodak.org}
@@ -30,6 +32,12 @@ class store implements \tool_log\log\store, \core\log\sql_reader {
     use \tool_log\helper\store,
         \tool_log\helper\reader;
 
+    /**
+     * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead.
+     * @todo  MDL-52805 This is to be removed in Moodle 4.0
+     *
+     * @param \tool_log\log\manager $manager
+     */
     public function __construct(\tool_log\log\manager $manager) {
         $this->helper_setup($manager);
     }
@@ -83,6 +91,17 @@ class store implements \tool_log\log\store, \core\log\sql_reader {
         return array($selectwhere, $params, $sort);
     }
 
+    /**
+     * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead.
+     * @todo MDL-52805 This will be removed in Moodle 4.0
+     *
+     * @param  string $selectwhere
+     * @param  array  $params
+     * @param  string $sort
+     * @param  int    $limitfrom
+     * @param  int    $limitnum
+     * @return array
+     */
     public function get_events_select($selectwhere, array $params, $sort, $limitfrom, $limitnum) {
         global $DB;
 
@@ -114,6 +133,8 @@ class store implements \tool_log\log\store, \core\log\sql_reader {
 
     /**
      * Fetch records using given criteria returning a Traversable object.
+     * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead.
+     * @todo MDL-52805 This will be removed in Moodle 4.0
      *
      * Note that the traversable object contains a moodle_recordset, so
      * remember that is important that you call close() once you finish
@@ -146,6 +167,8 @@ class store implements \tool_log\log\store, \core\log\sql_reader {
 
     /**
      * Returns an event from the log data.
+     * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead.
+     * @todo MDL-52805 This will be removed in Moodle 4.0
      *
      * @param stdClass $data Log data
      * @return \core\event\base
@@ -154,6 +177,14 @@ class store implements \tool_log\log\store, \core\log\sql_reader {
         return \logstore_legacy\event\legacy_logged::restore_legacy($data);
     }
 
+    /**
+     * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead.
+     * @todo MDL-52805 This will be removed in Moodle 4.0
+     *
+     * @param  string $selectwhere
+     * @param  array  $params
+     * @return int
+     */
     public function get_events_select_count($selectwhere, array $params) {
         global $DB;
 
@@ -170,6 +201,8 @@ class store implements \tool_log\log\store, \core\log\sql_reader {
 
     /**
      * Are the new events appearing in the reader?
+     * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead.
+     * @todo MDL-52805 This will be removed in Moodle 4.0
      *
      * @return bool true means new log events are being added, false means no new data will be added
      */
@@ -177,6 +210,10 @@ class store implements \tool_log\log\store, \core\log\sql_reader {
         return (bool)$this->get_config('loglegacy', true);
     }
 
+    /**
+     * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead.
+     * @todo MDL-52805 This will be removed in Moodle 4.0
+     */
     public function dispose() {
     }
 
diff --git a/admin/tool/log/upgrade.txt b/admin/tool/log/upgrade.txt
new file mode 100644 (file)
index 0000000..e1b65e3
--- /dev/null
@@ -0,0 +1,8 @@
+This files describes API changes in /admin/tool/log - plugins,
+information provided here is intended especially for developers.
+
+
+=== 3.6 ===
+
+* The legacy log store is in its first stage of deprecation and is due for removal in Moodle 4.0. Please use one of
+  the other log stores such as "standard" and "database".
\ No newline at end of file
diff --git a/auth/shibboleth/classes/helper.php b/auth/shibboleth/classes/helper.php
new file mode 100644 (file)
index 0000000..c1d5705
--- /dev/null
@@ -0,0 +1,123 @@
+<?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/>.
+
+/**
+ * Contains a helper class for the Shibboleth authentication plugin.
+ *
+ * @package    auth_shibboleth
+ * @copyright  2018 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace auth_shibboleth;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * The helper class for the Shibboleth authentication plugin.
+ *
+ * @package    auth_shibboleth
+ * @copyright  2018 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class helper {
+
+    /**
+     * Delete session of user using file sessions.
+     *
+     * @param string $spsessionid SP-provided Shibboleth Session ID
+     * @return \SoapFault or void if everything was fine
+     */
+    public static function logout_file_session($spsessionid) {
+        global $CFG;
+
+        if (!empty($CFG->session_file_save_path)) {
+            $dir = $CFG->session_file_save_path;
+        } else {
+            $dir = $CFG->dataroot . '/sessions';
+        }
+
+        if (is_dir($dir)) {
+            if ($dh = opendir($dir)) {
+                // Read all session files.
+                while (($file = readdir($dh)) !== false) {
+                    // Check if it is a file.
+                    if (is_file($dir.'/'.$file)) {
+                        // Read session file data.
+                        $data = file($dir.'/'.$file);
+                        if (isset($data[0])) {
+                            $usersession = self::unserializesession($data[0]);
+                            // Check if we have found session that shall be deleted.
+                            if (isset($usersession['SESSION']) && isset($usersession['SESSION']->shibboleth_session_id)) {
+                                // If there is a match, delete file.
+                                if ($usersession['SESSION']->shibboleth_session_id == $spsessionid) {
+                                    // Delete session file.
+                                    if (!unlink($dir.'/'.$file)) {
+                                        return new SoapFault('LogoutError', 'Could not delete Moodle session file.');
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+                closedir($dh);
+            }
+        }
+    }
+
+    /**
+     * Delete session of user using DB sessions.
+     *
+     * @param string $spsessionid SP-provided Shibboleth Session ID
+     */
+    public static function logout_db_session($spsessionid) {
+        global $CFG, $DB;
+
+        $sessions = $DB->get_records_sql(
+            'SELECT userid, sessdata FROM {sessions} WHERE timemodified > ?',
+            array(time() - $CFG->sessiontimeout)
+        );
+
+        foreach ($sessions as $session) {
+            // Get user session from DB.
+            if (session_decode(base64_decode($session->sessdata))) {
+                if (isset($_SESSION['SESSION']) && isset($_SESSION['SESSION']->shibboleth_session_id)) {
+                    // If there is a match, kill the session.
+                    if ($_SESSION['SESSION']->shibboleth_session_id == trim($spsessionid)) {
+                        // Delete this user's sessions.
+                        \core\session\manager::kill_user_sessions($session->userid);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Unserialize a session string.
+     *
+     * @param string $serializedstring
+     * @return array
+     */
+    private static function unserializesession($serializedstring) {
+        $variables = array();
+        $a = preg_split("/(\w+)\|/", $serializedstring, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
+        $counta = count($a);
+        for ($i = 0; $i < $counta; $i = $i + 2) {
+            $variables[$a[$i]] = unserialize($a[$i + 1]);
+        }
+        return $variables;
+    }
+}
index 83f9234..f514d4e 100644 (file)
@@ -127,75 +127,14 @@ WSDL;
  * @return SoapFault or void if everything was fine
  */
 function LogoutNotification($spsessionid) {
-
-    global $CFG, $SESSION, $DB;
-
-    // Delete session of user using $spsessionid.
-    if(empty($CFG->dbsessions)) {
-
-        // File session
-        $dir = $CFG->dataroot .'/sessions';
-        if (is_dir($dir)) {
-            if ($dh = opendir($dir)) {
-                // Read all session files
-                while (($file = readdir($dh)) !== false) {
-                    // Check if it is a file
-                    if (is_file($dir.'/'.$file)){
-                        $session_key = preg_replace('/sess_/', '', $file);
-
-                        // Read session file data
-                        $data = file($dir.'/'.$file);
-                        if (isset($data[0])){
-                            $usersession = unserializesession($data[0]);
-
-                            // Check if we have found session that shall be deleted
-                            if (isset($usersession['SESSION']) && isset($usersession['SESSION']->shibboleth_session_id)) {
-
-                                // If there is a match, delete file
-                                if ($usersession['SESSION']->shibboleth_session_id == $spsessionid) {
-                                    // Delete session file
-                                    if (!unlink($dir.'/'.$file)){
-                                        return new SoapFault('LogoutError', 'Could not delete Moodle session file.');
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-                closedir($dh);
-            }
-        }
-    } else {
-        // DB Sessions.
-        $sessions = $DB->get_records_sql(
-            'SELECT userid, sessdata FROM {sessions} WHERE timemodified > ?',
-            array(time() - $CFG->sessiontimeout)
-        );
-        foreach ($sessions as $session) {
-            // Get user session from DB.
-            if (session_decode(base64_decode($session->sessdata))) {
-                if (isset($_SESSION['SESSION']) && isset($_SESSION['SESSION']->shibboleth_session_id)) {
-                    // If there is a match, kill the session.
-                    if ($_SESSION['SESSION']->shibboleth_session_id == trim($spsessionid)) {
-                        // Delete this user's sessions.
-                        \core\session\manager::kill_user_sessions($session->userid);
-                    }
-                }
-            }
-        }
+    $sessionclass = \core\session\manager::get_handler_class();
+    switch ($sessionclass) {
+        case '\core\session\file':
+            return \auth_shibboleth\helper::logout_file_session($spsessionid);
+        case '\core\session\database':
+            return \auth_shibboleth\helper::logout_db_session($spsessionid);
+        default:
+            throw new moodle_exception("Shibboleth logout not implemented for '$sessionclass'");
     }
     // If no SoapFault was thrown, the function will return OK as the SP assumes.
 }
-
-/*****************************************************************************/
-
-// Same function as in adodb, but cannot be used for file session for some reason...
-function unserializesession($serialized_string) {
-    $variables = array();
-    $a = preg_split("/(\w+)\|/", $serialized_string, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
-    $counta = count($a);
-    for ($i = 0; $i < $counta; $i = $i+2) {
-            $variables[$a[$i]] = unserialize($a[$i+1]);
-    }
-    return $variables;
-}
index dbd7b4b..9083025 100644 (file)
@@ -1,6 +1,11 @@
 This files describes API changes in /auth/shibboleth/*,
 information provided here is intended especially for developers.
 
+=== 3.5.2 ===
+
+* Moved the public function unserializesession in auth/shibboleth/logout.php to auth/shibboleth/classes/helper.php and
+  made it private. This function should not have been used outside of this file.
+
 === 3.3 ===
 
 * The config.html file was migrated to use the admin settings API.
index ec45776..9992713 100644 (file)
@@ -604,7 +604,8 @@ abstract class restore_dbops {
         //             6b) User cannot, check if we are in some contextlevel with fallback
         //                 7a) There is fallback, move ALL the qcats to fallback, warn. End qcat loop
         //                 7b) No fallback, error. End qcat loop
-        //         5b) Match, mark q to be mapped
+        //         5b) Random question, must always create new.
+        //         5c) Match, mark q to be mapped
         // 8) Check if backup is from Moodle >= 3.5 and error if more than one top-level category in the context.
 
         // Get all the contexts (question banks) in restore for the given contextlevel
@@ -708,7 +709,11 @@ abstract class restore_dbops {
                                 break 2; // out from qcat loop (both 7a and 7b), we have decided about ALL categories in context (bank)
                             }
 
-                        // 5b) Match, mark q to be mapped
+                        // 5b) Random questions must always be newly created.
+                        } else if ($question->qtype == 'random') {
+                            // Nothing to mark, newitemid means create
+
+                        // 5c) Match, mark q to be mapped.
                         } else {
                             self::set_backup_ids_record($restoreid, 'question', $question->id, $matchqid);
                         }
index b7de027..5d886c2 100644 (file)
@@ -46,6 +46,3 @@ $string['myprofile:myaddinstance'] = 'Add a new logged in user block to Dashboar
 $string['myprofile_settings'] = 'Visible user information';
 $string['pluginname'] = 'Logged in user';
 $string['privacy:metadata'] = 'The Logged in user block only shows information about the logged in user and does not store data itself.';
-
-// Deprecated since Moodle 3.2.
-$string['display_un'] = 'Display name';
index 5603daa..5483edb 100644 (file)
@@ -170,8 +170,8 @@ Feature: Perform basic calendar functionality
     And I click on "New event" "button"
     When I click on "Save" "button"
     Then I should see "Required"
-    And I am on site homepage
-    And I follow "Calendar"
+    And I am on homepage
+    And I follow "This month"
     And I click on "New event" "button"
     And I set the field "Type of event" to "Course"
     When I click on "Save" "button"
index 07f2d9f..455a528 100644 (file)
@@ -941,7 +941,11 @@ class core_course_external extends external_api {
 
                 // Make sure maxbytes are less then CFG->maxbytes.
                 if (array_key_exists('maxbytes', $course)) {
-                    $course['maxbytes'] = get_max_upload_file_size($CFG->maxbytes, $course['maxbytes']);
+                    // We allow updates back to 0 max bytes, a special value denoting the course uses the site limit.
+                    // Otherwise, either use the size specified, or cap at the max size for the course.
+                    if ($course['maxbytes'] != 0) {
+                        $course['maxbytes'] = get_max_upload_file_size($CFG->maxbytes, $course['maxbytes']);
+                    }
                 }
 
                 if (!empty($course['courseformatoptions'])) {
index e910366..50fa91a 100644 (file)
@@ -342,8 +342,7 @@ class format_singleactivity extends format_base {
 
     /**
      * Checks if the activity type has multiple items in the activity chooser.
-     * This may happen as a result of defining callback modulename_get_shortcuts()
-     * or [deprecated] modulename_get_types() - TODO MDL-53697 remove this line.
+     * This may happen as a result of defining callback modulename_get_shortcuts().
      *
      * @return bool|null (null if the check is not possible)
      */
index 2062044..69cbc89 100644 (file)
@@ -669,9 +669,6 @@ function get_module_metadata($course, $modnames, $sectionreturn = null) {
         }
         $defaultmodule->archetype = plugin_supports('mod', $modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
 
-        // Legacy support for callback get_types() - do not use any more, use get_shortcuts() instead!
-        $typescallbackexists = component_callback_exists($modname, 'get_types');
-
         // Each module can implement callback modulename_get_shortcuts() in its lib.php and return the list
         // of elements to be added to activity chooser.
         $items = component_callback($modname, 'get_shortcuts', array($defaultmodule), null);
@@ -699,58 +696,14 @@ function get_module_metadata($course, $modnames, $sectionreturn = null) {
                 $modlist[$course->id][$modname][$item->name] = $item;
             }
             $return += $modlist[$course->id][$modname];
-            if ($typescallbackexists) {
-                debugging('Both callbacks get_shortcuts() and get_types() are found in module ' . $modname .
-                    '. Callback get_types() will be completely ignored', DEBUG_DEVELOPER);
-            }
             // If get_shortcuts() callback is defined, the default module action is not added.
             // It is a responsibility of the callback to add it to the return value unless it is not needed.
             continue;
         }
 
-        if ($typescallbackexists) {
-            debugging('Callback get_types() is found in module ' . $modname . ', this functionality is deprecated, ' .
-                'please use callback get_shortcuts() instead', DEBUG_DEVELOPER);
-        }
-        $types = component_callback($modname, 'get_types', array(), MOD_SUBTYPE_NO_CHILDREN);
-        if ($types !== MOD_SUBTYPE_NO_CHILDREN) {
-            // Legacy support for deprecated callback get_types(). To be removed in Moodle 3.5. TODO MDL-53697.
-            if (is_array($types) && count($types) > 0) {
-                $grouptitle = $modnamestr;
-                $icon = $OUTPUT->pix_icon('icon', '', $modname, array('class' => 'icon'));
-                foreach($types as $type) {
-                    if ($type->typestr === '--') {
-                        continue;
-                    }
-                    if (strpos($type->typestr, '--') === 0) {
-                        $grouptitle = str_replace('--', '', $type->typestr);
-                        continue;
-                    }
-                    // Set the Sub Type metadata.
-                    $subtype = new stdClass();
-                    $subtype->title = get_string('activitytypetitle', '',
-                        (object)['activity' => $grouptitle, 'type' => $type->typestr]);
-                    $subtype->type = str_replace('&amp;', '&', $type->type);
-                    $typename = preg_replace('/.*type=/', '', $subtype->type);
-                    $subtype->archetype = $type->modclass;
-
-                    if (!empty($type->help)) {
-                        $subtype->help = $type->help;
-                    } else if (get_string_manager()->string_exists('help' . $subtype->name, $modname)) {
-                        $subtype->help = get_string('help' . $subtype->name, $modname);
-                    }
-                    $subtype->link = new moodle_url($urlbase, array('add' => $modname, 'type' => $typename));
-                    $subtype->name = $modname . ':' . $subtype->link;
-                    $subtype->icon = $icon;
-                    $modlist[$course->id][$modname][$subtype->name] = $subtype;
-                }
-                $return += $modlist[$course->id][$modname];
-            }
-        } else {
-            // Neither get_shortcuts() nor get_types() callbacks found, use the default item for the activity chooser.
-            $modlist[$course->id][$modname][$modname] = $defaultmodule;
-            $return[$modname] = $defaultmodule;
-        }
+        // The callback get_shortcuts() was not found, use the default item for the activity chooser.
+        $modlist[$course->id][$modname][$modname] = $defaultmodule;
+        $return[$modname] = $defaultmodule;
     }
 
     core_collator::asort_objects_by_property($return, 'title');
index eca8ae4..806646f 100644 (file)
@@ -59,6 +59,8 @@ if ($frm = data_submitted() and confirm_sesskey()) {
         // Invalidate the course groups cache seeing as we've changed it.
         cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($courseid));
 
+        // Invalidate the user_group_groupings cache, too.
+        cache_helper::purge_by_definition('core', 'user_group_groupings');
     } else if (isset($frm->remove) and !empty($frm->removeselect)) {
         foreach ($frm->removeselect as $groupid) {
             // Ask this method not to purge the cache, we'll do it ourselves afterwards.
@@ -66,6 +68,9 @@ if ($frm = data_submitted() and confirm_sesskey()) {
         }
         // Invalidate the course groups cache seeing as we've changed it.
         cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($courseid));
+
+        // Invalidate the user_group_groupings cache, too.
+        cache_helper::purge_by_definition('core', 'user_group_groupings');
     }
 }
 
index b966608..964592c 100644 (file)
@@ -30,7 +30,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$string['admindirname'] = 'Rendszergazdakönyvtár';
+$string['admindirname'] = 'Rendszergazda-könyvtár';
 $string['availablelangs'] = 'Elérhető nyelvek listája';
 $string['chooselanguagehead'] = 'Nyelv kiválasztása';
 $string['chooselanguagesub'] = 'Válasszon nyelvet a telepítéshez! Ez lesz a portál alapbeállítás szerinti nyelve, de később módosíthatja.';
index 7cd886a..578ef82 100644 (file)
@@ -46,7 +46,7 @@ $string['dbprefix'] = 'テーブル接頭辞';
 $string['dirroot'] = 'Moodleディレクトリ';
 $string['environmenthead'] = 'あなたの環境を確認しています ...';
 $string['environmentsub2'] = 'それぞれのMoodleリリースにはPHPバージョンの最小必要条件および多くの必須PHP拡張モジュールがあります。完全な環境チェックはインストールおよびアップグレードごとに実行されます。新しいPHPバージョンのインストールまたはPHP拡張モジュールの有効化に関して分からない場合、あなたのサーバ管理者にご連絡ください。';
-$string['errorsinenvironment'] = 'ç\92°å¢\83ã\83\81ã\82§ã\83\83ã\82¯ã\81\8c失敗しました!';
+$string['errorsinenvironment'] = 'ç\92°å¢\83ã\83\81ã\82§ã\83\83ã\82¯ã\81«失敗しました!';
 $string['installation'] = 'インストレーション';
 $string['langdownloaderror'] = '残念ですが、言語「 {$a} 」をダウンロードできませんでした。インストール処理は英語で継続されます。';
 $string['memorylimithelp'] = '<p>現在、サーバのPHPメモリ制限が {$a} に設定されています。</p>
index cf656d3..6c38f65 100644 (file)
@@ -1301,8 +1301,7 @@ $string['cachesession'] = 'Session cache';
 $string['cachesessionhelp'] = 'User specific cache that expires when the user\'s session ends. Designed to alleviate session bloat/strain.';
 $string['cacheapplication'] = 'Application cache';
 $string['cacheapplicationhelp'] = 'Cached items are shared among all users and expire by a determined time to live (ttl).';
-// Deprecated since Moodle 3.2.
-$string['mobile'] = 'Mobile';
+
 // Deprecated since Moodle 3.3.
 $string['loginpasswordautocomplete'] = 'Prevent password autocompletion on login form';
 $string['loginpasswordautocomplete_help'] = 'If enabled, users are not allowed to save their account password in their browser.';
index d93fb9c..6003668 100644 (file)
@@ -266,9 +266,6 @@ $string['when'] = 'When';
 $string['yesterday'] = 'Yesterday';
 $string['youcandeleteallrepeats'] = 'This event is part of a repeating event series. You can delete this event only, or all {$a} events in the series at once.';
 
-// Deprecated since Moodle 3.2.
-$string['for'] = 'for';
-
 // Deprecated since Moodle 3.4.
 $string['quickdownloadcalendar'] = 'Quick download / subscribe to calendar';
 $string['ical'] = 'iCal';
index 606cac2..ca114fc 100644 (file)
@@ -200,6 +200,3 @@ $string['usercompetencystatus_idle'] = 'Idle';
 $string['usercompetencystatus_inreview'] = 'In review';
 $string['usercompetencystatus_waitingforreview'] = 'Waiting for review';
 $string['userplans'] = 'Learning plans';
-
-// Deprecated since Moodle 3.2.
-$string['invalidpersistent'] = 'Invalid persistent';
index db1cf82..2297072 100644 (file)
@@ -5,34 +5,7 @@ myfilesmanage,core
 mypreferences,core_grades
 myprofile,core
 viewallmyentries,core_blog
-modchooserenable,core
-modchooserdisable,core
-invalidpersistent,core_competency
 revealpassword,core_form
-mediasettings,core_media
-legacyheading,core_media
-legacyheading_desc,core_media
-mobile,core_admin
-for,core_calendar
-context,core_message
-discussion,core_message
-emptysearchstring,core_message
-formorethan,core_message
-keywords,core_message
-messagehistory,core_message
-newsearch,core_message
-nosearchresults,core_message
-onlymycourses,core_message
-pagerefreshes,core_message
-page-message-x,core_message
-recent,core_message
-savemysettings,core_message
-search,core_message
-settingssaved,core_message
-strftimedaydatetime,core_message
-timenosee,core_message
-timesent,core_message
-userssearchresults,core_message
 loginpasswordautocomplete,core_admin
 loginpasswordautocomplete_help,core_admin
 deletecomment,core
index 03fe641..76372d5 100644 (file)
@@ -36,8 +36,3 @@ Where two players support the same format, enabling both increases compatibility
 $string['privacy:metadata'] = 'Media embedding does not store any personal data.';
 $string['supports'] = 'Supports';
 $string['videoextensions'] = 'Video: {$a}';
-
-// Deprecated since Moodle 3.2.
-$string['mediasettings'] = 'Media embedding';
-$string['legacyheading'] = 'Legacy media players';
-$string['legacyheading_desc'] = 'These players are not frequently used on the Web and require browser plugins that are less widely installed.';
index 93ec3a2..917d352 100644 (file)
@@ -76,7 +76,6 @@ $string['messagingdisabled'] = 'Messaging is disabled on this site, emails will
 $string['newonlymsg'] = 'Show only new';
 $string['newmessage'] = 'New message';
 $string['newmessagesearch'] = 'Select or search for a contact to send a new message.';
-$string['newsearch'] = 'New search';
 $string['noframesjs'] = 'Use more accessible interface';
 $string['nocontacts'] = 'No contacts';
 $string['nomessages'] = 'No messages';
@@ -180,24 +179,3 @@ $string['viewnotificationresource'] = 'Go to: {$a}';
 $string['viewunreadmessageswith'] = 'View unread messages with {$a}';
 $string['writeamessage'] = 'Write a message...';
 $string['you'] = 'You:';
-
-// Deprecated since Moodle 3.2.
-$string['context'] = 'context';
-$string['discussion'] = 'Discussion';
-$string['emptysearchstring'] = 'You must search for something';
-$string['formorethan'] = 'For more than';
-$string['keywords'] = 'Keywords';
-$string['messagehistory'] = 'Message history';
-$string['newsearch'] = 'New search';
-$string['nosearchresults'] = 'There were no results from your search';
-$string['onlymycourses'] = 'Only in my courses';
-$string['pagerefreshes'] = 'This page refreshes automatically every {$a} seconds';
-$string['page-message-x'] = 'Any message pages';
-$string['recent'] = 'Recent';
-$string['savemysettings'] = 'Save my settings';
-$string['search'] = 'Search';
-$string['settingssaved'] = 'Your settings have been saved';
-$string['strftimedaydatetime'] = '%A, %d %B %Y, %I:%M %p';
-$string['timenosee'] = 'Minutes since I was last seen online';
-$string['timesent'] = 'Time sent';
-$string['userssearchresults'] = 'Users found: {$a}';
index e3521f0..84b7020 100644 (file)
@@ -2205,10 +2205,6 @@ $string['yourwordforx'] = 'Your word for \'{$a}\'';
 $string['zippingbackup'] = 'Zipping backup';
 $string['deprecatedeventname'] = '{$a} (no longer in use)';
 
-// Deprecated since Moodle 3.2.
-$string['modchooserenable'] = 'Activity chooser on';
-$string['modchooserdisable'] = 'Activity chooser off';
-
 // Deprecated since Moodle 3.3.
 $string['deletecomment'] = 'Delete this comment';
 $string['sectionusedefaultname'] = 'Use default section name';
index 2960b77..f617cc2 100644 (file)
@@ -123,28 +123,34 @@ class manager {
     }
 
     /**
-     * Create handler instance.
+     * Get fully qualified name of session handler class.
+     *
+     * @return string The name of the handler class
      */
-    protected static function load_handler() {
+    public static function get_handler_class() {
         global $CFG, $DB;
 
-        if (self::$handler) {
-            return;
-        }
-
-        // Find out which handler to use.
         if (PHPUNIT_TEST) {
-            $class = '\core\session\file';
-
+            return '\core\session\file';
         } else if (!empty($CFG->session_handler_class)) {
-            $class = $CFG->session_handler_class;
-
+            return $CFG->session_handler_class;
         } else if (!empty($CFG->dbsessions) and $DB->session_lock_supported()) {
-            $class = '\core\session\database';
+            return '\core\session\database';
+        }
 
-        } else {
-            $class = '\core\session\file';
+        return '\core\session\file';
+    }
+
+    /**
+     * Create handler instance.
+     */
+    protected static function load_handler() {
+        if (self::$handler) {
+            return;
         }
+
+        // Find out which handler to use.
+        $class = self::get_handler_class();
         self::$handler = new $class();
     }
 
index 84ccd70..cd69bbc 100644 (file)
@@ -383,5 +383,8 @@ $definitions = array(
         'simplekeys' => true,
         'simpledata' => true,
         'ttl' => 1800,
+        'invalidationevents' => array(
+            'createduser',
+        )
     ),
 );
index d479b71..9e9b519 100644 (file)
@@ -4131,19 +4131,11 @@ function site_scale_used($scaleid, &$courses) {
 }
 
 /**
- * Returns detailed function information
- *
- * @deprecated since Moodle 3.1
- * @param string|object $function name of external function or record from external_function
- * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
- *                        MUST_EXIST means throw exception if no record or multiple records found
- * @return stdClass description or false if not found or exception thrown
- * @since Moodle 2.0
+ * @deprecated since Moodle 3.1. Use external_api::external_function_info().
  */
 function external_function_info($function, $strictness=MUST_EXIST) {
-    debugging('external_function_info() is deprecated. Please use external_api::external_function_info() instead.',
-              DEBUG_DEVELOPER);
-    return external_api::external_function_info($function, $strictness);
+    throw new coding_exception('external_function_info() can not be used any'.
+        'more. Please use external_api::external_function_info() instead.');
 }
 
 /**
diff --git a/lib/editor/atto/tests/behat/disablecontrol.feature b/lib/editor/atto/tests/behat/disablecontrol.feature
new file mode 100644 (file)
index 0000000..491e6e1
--- /dev/null
@@ -0,0 +1,45 @@
+@editor @editor_atto @atto @editor_moodleform
+Feature: Atto with enable/disable function.
+  In order to test enable/disable function
+  I create a sample page to test this feature.
+  As a user
+  I need to enable/disable all buttons/plugins and content of editor if "enable/disable" feature enabled.
+
+  Background:
+    Given the following "courses" exist:
+      | fullname | shortname | format |
+      | Course 1 | C1        | topics |
+    And the following "activities" exist:
+      | activity | name | intro                                                                                              | course | idnumber |
+      | label    | L1   | <a href="../lib/editor/tests/fixtures/disable_control_example.php">Control Enable/Disable Atto</a> | C1     | label1   |
+    And I log in as "admin"
+    And I am on "Course 1" course homepage
+    And I follow "Control Enable/Disable Atto"
+
+  @javascript
+  Scenario: Check disable Atto editor.
+    When I set the field "mycontrol" to "Disable"
+    Then the "disabled" attribute of "button.atto_collapse_button" "css_element" should contain "disabled"
+    And the "disabled" attribute of "button.atto_title_button" "css_element" should contain "disabled"
+    And the "disabled" attribute of "button.atto_bold_button_bold" "css_element" should contain "disabled"
+    And the "disabled" attribute of "button.atto_italic_button_italic" "css_element" should contain "disabled"
+    And the "disabled" attribute of "button.atto_unorderedlist_button_insertUnorderedList" "css_element" should contain "disabled"
+    And the "disabled" attribute of "button.atto_orderedlist_button_insertOrderedList" "css_element" should contain "disabled"
+    And the "disabled" attribute of "button.atto_link_button" "css_element" should contain "disabled"
+    And the "disabled" attribute of "button.atto_link_button_unlink" "css_element" should contain "disabled"
+    And the "disabled" attribute of "button.atto_image_button" "css_element" should contain "disabled"
+    And the "contenteditable" attribute of "div#id_myeditoreditable" "css_element" should contain "false"
+
+  @javascript
+  Scenario: Check enable Atto editor.
+    When I set the field "mycontrol" to "Enable"
+    Then "button.atto_collapse_button[disabled]" "css_element" should not exist
+    And "button.atto_title_button[disabled]" "css_element" should not exist
+    And "button.atto_bold_button_bold[disabled]" "css_element" should not exist
+    And "button.atto_italic_button_italic[disabled]" "css_element" should not exist
+    And "button.atto_unorderedlist_button_insertUnorderedList[disabled]" "css_element" should not exist
+    And "button.atto_orderedlist_button_insertOrderedList[disabled]" "css_element" should not exist
+    And "button.atto_link_button[disabled]" "css_element" should not exist
+    And "button.atto_link_button_unlink[disabled]" "css_element" should not exist
+    And "button.atto_image_button[disabled]" "css_element" should not exist
+    And the "contenteditable" attribute of "div#id_myeditoreditable" "css_element" should contain "true"
index 90e0996..9fce41e 100644 (file)
Binary files a/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js and b/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js differ
index 22df82b..0bb1434 100644 (file)
Binary files a/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js and b/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js differ
index 1b88fea..cf12f6c 100644 (file)
Binary files a/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor.js and b/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor.js differ
old mode 100644 (file)
new mode 100755 (executable)
index a28e936..b846bcd
@@ -225,6 +225,12 @@ Y.extend(Editor, Y.Base, {
         // Hide the old textarea.
         this.textarea.hide();
 
+        // Set up custom event for editor updated.
+        Y.mix(Y.Node.DOM_EVENTS, {'form:editorUpdated': true});
+        this.textarea.on('form:editorUpdated', function() {
+            this.updateEditorState();
+        }, this);
+
         // Copy the text to the contenteditable div.
         this.updateFromTextArea();
 
@@ -387,6 +393,20 @@ Y.extend(Editor, Y.Base, {
         }
     },
 
+    /**
+     * Update the state of the editor.
+     */
+    updateEditorState: function() {
+        var disabled = this.textarea.hasAttribute('readonly'),
+            editorfield = Y.one('#' + this.get('elementid') + 'editable');
+        // Enable/Disable all plugins.
+        this._setPluginState(!disabled);
+        // Enable/Disable content of editor.
+        if (editorfield) {
+            editorfield.setAttribute('contenteditable', !disabled);
+        }
+    },
+
     /**
      * Register an event handle for disposal in the destructor.
      *
diff --git a/lib/editor/tests/fixtures/disable_control_example.php b/lib/editor/tests/fixtures/disable_control_example.php
new file mode 100644 (file)
index 0000000..752c0bc
--- /dev/null
@@ -0,0 +1,47 @@
+<?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/>.
+
+/**
+ * Demonstrates use of editor with enable/disable function.
+ *
+ * This fixture is only used by the Behat test.
+ *
+ * @package core_editor
+ * @copyright 2018 Jake Hau <phuchau1509@gmail.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require(__DIR__ . '/../../../../config.php');
+require_once('./editor_form.php');
+
+// Behat test fixture only.
+defined('BEHAT_SITE_RUNNING') || die('Only available on Behat test server');
+
+// Require login.
+require_login();
+
+$PAGE->set_url('/lib/editor/tests/fixtures/disable_control_example.php');
+$PAGE->set_context(context_system::instance());
+
+// Create moodle form.
+$mform = new editor_form();
+
+echo $OUTPUT->header();
+
+// Display moodle form.
+$mform->display();
+
+echo $OUTPUT->footer();
diff --git a/lib/editor/tests/fixtures/editor_form.php b/lib/editor/tests/fixtures/editor_form.php
new file mode 100644 (file)
index 0000000..645728a
--- /dev/null
@@ -0,0 +1,62 @@
+<?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/>.
+
+/**
+ * Provides {@link lib/editor/tests/fixtures/editor_form} class.
+ *
+ * @package core_editor
+ * @copyright 2018 Jake Hau <phuchau1509@gmail.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->libdir.'/formslib.php');
+
+/**
+ * Class editor_form
+ *
+ * Demonstrates use of editor with disabledIf function.
+ * This fixture is only used by the Behat test.
+ *
+ * @package core_editor
+ * @copyright 2018 Jake Hau <phuchau1509@gmail.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class editor_form extends moodleform {
+
+    /**
+     * Form definition. Abstract method - always override!
+     */
+    protected function definition() {
+        $mform = $this->_form;
+        $editoroptions = $this->_customdata['editoroptions'];
+
+        // Add header.
+        $mform->addElement('header', 'myheader', 'Editor in Moodle form');
+
+        // Add element control.
+        $mform->addElement('select', 'mycontrol', 'My control', ['Enable', 'Disable']);
+
+        // Add editor.
+        $mform->addElement('editor', 'myeditor', 'My Editor', null, $editoroptions);
+        $mform->setType('myeditor', PARAM_RAW);
+
+        // Add control.
+        $mform->disabledIf('myeditor', 'mycontrol', 'eq', 1);
+    }
+}
old mode 100644 (file)
new mode 100755 (executable)
diff --git a/lib/editor/textarea/tests/behat/disablecontrol.feature b/lib/editor/textarea/tests/behat/disablecontrol.feature
new file mode 100644 (file)
index 0000000..0d82331
--- /dev/null
@@ -0,0 +1,31 @@
+@editor @editor_textarea @texarea @editor_moodleform
+Feature: Text area with enable/disable function.
+  In order to test enable/disable function
+  I set default editor is Text area editor, and I create a sample page to test this feature.
+  As a user
+  I need to enable/disable content of editor if "enable/disable" feature enabled.
+
+  Background:
+    Given the following "courses" exist:
+      | fullname | shortname | format |
+      | Course 1 | C1        | topics |
+    And the following "activities" exist:
+      | activity | name | intro                                                                                                   | course | idnumber |
+      | label    | L1   | <a href="../lib/editor/tests/fixtures/disable_control_example.php">Control Enable/Disable Text area</a> | C1     | label1   |
+    And I log in as "admin"
+    And I follow "Preferences" in the user menu
+    And I follow "Editor preferences"
+    And I set the field "Text editor" to "Plain text area"
+    And I press "Save changes"
+    And I am on "Course 1" course homepage
+    And I follow "Control Enable/Disable Text area"
+
+  @javascript
+  Scenario: Check disable Text area editor.
+    When I set the field "mycontrol" to "Disable"
+    Then the "readonly" attribute of "textarea#id_myeditor" "css_element" should contain "readonly"
+
+  @javascript
+  Scenario: Check enable Text area editor.
+    When I set the field "mycontrol" to "Enable"
+    Then "textarea#id_myeditor[readonly]" "css_element" should not exist
old mode 100644 (file)
new mode 100755 (executable)
index b7b7904..0bcfd47
@@ -96,6 +96,10 @@ M.editor_tinymce.init_editor = function(Y, editorid, options) {
     if (item) {
         item.parentNode.removeChild(item);
     }
+
+    document.getElementById(editorid).addEventListener('form:editorUpdated', function() {
+        M.editor_tinymce.updateEditorState(editorid);
+    });
 };
 
 M.editor_tinymce.init_callback = function() {
@@ -110,6 +114,25 @@ M.editor_tinymce.toggle = function(id) {
     tinyMCE.execCommand('mceToggleEditor', false, id);
 };
 
+/**
+ * Update the state of the editor.
+ * @param {String} id
+ */
+M.editor_tinymce.updateEditorState = function(id) {
+    var instance = window.tinyMCE.get(id),
+        content = instance.getBody(),
+        controls = instance.controlManager.controls,
+        disabled = instance.getElement().readOnly;
+    // Enable/Disable all plugins.
+    for (var key in controls) {
+        if (controls.hasOwnProperty(key)) {
+            controls[key].setDisabled(disabled);
+        }
+    }
+    // Enable/Disable body content.
+    content.setAttribute('contenteditable', !disabled);
+};
+
 M.editor_tinymce.filepicker_callback = function(args) {
 };
 
diff --git a/lib/editor/tinymce/tests/behat/disablecontrol.feature b/lib/editor/tinymce/tests/behat/disablecontrol.feature
new file mode 100644 (file)
index 0000000..d8cc6da
--- /dev/null
@@ -0,0 +1,55 @@
+@editor @editor_tinymce @tinymce @editor_moodleform
+Feature: Tinymce with enable/disable function.
+  In order to test enable/disable function
+  I set default editor is Tinymce editor, and I create a sample page to test this feature.
+  As a user
+  I need to enable/disable all buttons/plugins and content of editor if "enable/disable" feature enabled.
+
+  Background:
+    Given the following "courses" exist:
+      | fullname | shortname | format |
+      | Course 1 | C1        | topics |
+    And the following "activities" exist:
+      | activity | name | intro                                                                                                 | course | idnumber |
+      | label    | L1   | <a href="../lib/editor/tests/fixtures/disable_control_example.php">Control Enable/Disable Tinymce</a> | C1     | label1   |
+    And I log in as "admin"
+    And I follow "Preferences" in the user menu
+    And I follow "Editor preferences"
+    And I set the field "Text editor" to "TinyMCE HTML editor"
+    And I press "Save changes"
+    And I am on "Course 1" course homepage
+    And I follow "Control Enable/Disable Tinymce"
+
+  @javascript
+  Scenario: Check disable Tinymce editor.
+    When I click on "option[value=1]" "css_element" in the "select#id_mycontrol" "css_element"
+    Then the "class" attribute of "a#id_myeditor_pdw_toggle" "css_element" should contain "mceButtonDisabled"
+    And the "class" attribute of "table#id_myeditor_formatselect" "css_element" should contain "mceListBoxDisabled"
+    And the "class" attribute of "a#id_myeditor_bold" "css_element" should contain "mceButtonDisabled"
+    And the "class" attribute of "a#id_myeditor_italic" "css_element" should contain "mceButtonDisabled"
+    And the "class" attribute of "a#id_myeditor_bullist" "css_element" should contain "mceButtonDisabled"
+    And the "class" attribute of "a#id_myeditor_numlist" "css_element" should contain "mceButtonDisabled"
+    And the "class" attribute of "a#id_myeditor_link" "css_element" should contain "mceButtonDisabled"
+    And the "class" attribute of "a#id_myeditor_unlink" "css_element" should contain "mceButtonDisabled"
+    And the "class" attribute of "a#id_myeditor_moodlenolink" "css_element" should contain "mceButtonDisabled"
+    And the "class" attribute of "a#id_myeditor_image" "css_element" should contain "mceButtonDisabled"
+    And the "class" attribute of "a#id_myeditor_moodlemedia" "css_element" should contain "mceButtonDisabled"
+    And I switch to "id_myeditor_ifr" iframe
+    And the "contenteditable" attribute of "body" "css_element" should contain "false"
+
+  @javascript
+  Scenario: Check enable Tinymce editor.
+    When I click on "option[value=0]" "css_element" in the "select#id_mycontrol" "css_element"
+    Then the "class" attribute of "a#id_myeditor_pdw_toggle" "css_element" should contain "mceButtonEnabled"
+    And the "class" attribute of "table#id_myeditor_formatselect" "css_element" should contain "mceListBoxEnabled"
+    And the "class" attribute of "a#id_myeditor_bold" "css_element" should contain "mceButtonEnabled"
+    And the "class" attribute of "a#id_myeditor_italic" "css_element" should contain "mceButtonEnabled"
+    And the "class" attribute of "a#id_myeditor_bullist" "css_element" should contain "mceButtonEnabled"
+    And the "class" attribute of "a#id_myeditor_numlist" "css_element" should contain "mceButtonEnabled"
+    And the "class" attribute of "a#id_myeditor_link" "css_element" should contain "mceButtonDisabled"
+    And the "class" attribute of "a#id_myeditor_unlink" "css_element" should contain "mceButtonDisabled"
+    And the "class" attribute of "a#id_myeditor_moodlenolink" "css_element" should contain "mceButtonDisabled"
+    And the "class" attribute of "a#id_myeditor_image" "css_element" should contain "mceButtonEnabled"
+    And the "class" attribute of "a#id_myeditor_moodlemedia" "css_element" should contain "mceButtonEnabled"
+    And I switch to "id_myeditor_ifr" iframe
+    And the "contenteditable" attribute of "body" "css_element" should contain "true"
index a8c844a..2d189a3 100644 (file)
@@ -757,6 +757,34 @@ function file_get_drafarea_files($draftitemid, $filepath = '/') {
     return $data;
 }
 
+/**
+ * Returns all of the files in the draftarea.
+ *
+ * @param  int $draftitemid The draft item ID
+ * @param  string $filepath path for the uploaded files.
+ * @return array An array of files associated with this draft item id.
+ */
+function file_get_all_files_in_draftarea(int $draftitemid, string $filepath = '/') : array {
+    $files = [];
+    $draftfiles = file_get_drafarea_files($draftitemid, $filepath);
+    file_get_drafarea_folders($draftitemid, $filepath, $draftfiles);
+
+    if (!empty($draftfiles)) {
+        foreach ($draftfiles->list as $draftfile) {
+            if ($draftfile->type == 'file') {
+                $files[] = $draftfile;
+            }
+        }
+
+        if (isset($draftfiles->children)) {
+            foreach ($draftfiles->children as $draftfile) {
+                $files = array_merge($files, file_get_all_files_in_draftarea($draftitemid, $draftfile->filepath));
+            }
+        }
+    }
+    return $files;
+}
+
 /**
  * Returns draft area itemid for a given element.
  *
index 71686d0..26c2271 100644 (file)
@@ -333,7 +333,7 @@ class MoodleQuickForm_filemanager extends HTML_QuickForm_element implements temp
             return;
         }
 
-        $draftfiles = file_get_drafarea_files($value);
+        $draftfiles = file_get_all_files_in_draftarea($value);
         $wrongfiles = array();
 
         if (empty($draftfiles)) {
@@ -341,7 +341,7 @@ class MoodleQuickForm_filemanager extends HTML_QuickForm_element implements temp
             return;
         }
 
-        foreach ($draftfiles->list as $file) {
+        foreach ($draftfiles as $file) {
             if (!$filetypesutil->is_allowed_file_type($file->filename, $whitelist)) {
                 $wrongfiles[] = $file->filename;
             }
old mode 100644 (file)
new mode 100755 (executable)
index 50f5960..1c6dacc
@@ -250,8 +250,10 @@ if (typeof M.form.dependencyManager === 'undefined') {
          * @param {Boolean} disabled True to disable, false to enable.
          */
         _disableElement: function(name, disabled) {
-            var els = this.elementsByName(name);
-            var filepicker = this.isFilePicker(name);
+            var els = this.elementsByName(name),
+                filepicker = this.isFilePicker(name),
+                editors = this.get('form').all('.fitem [data-fieldtype="editor"] textarea[name="' + name + '[text]"]');
+
             els.each(function(node) {
                 if (disabled) {
                     node.setAttribute('disabled', 'disabled');
@@ -271,6 +273,14 @@ if (typeof M.form.dependencyManager === 'undefined') {
                     }
                 }
             });
+            editors.each(function(editor) {
+                if (disabled) {
+                    editor.setAttribute('readonly', 'readonly');
+                } else {
+                    editor.removeAttribute('readonly', 'readonly');
+                }
+                editor.getDOMNode().dispatchEvent(new Event('form:editorUpdated'));
+            });
         },
         /**
          * Hides or shows all form elements with the given name.
index 47cf0b1..928ec9f 100644 (file)
@@ -453,13 +453,6 @@ define('MOD_ARCHETYPE_ASSIGNMENT', 2);
 /** System (not user-addable) module archetype */
 define('MOD_ARCHETYPE_SYSTEM', 3);
 
-/**
- * Return this from modname_get_types callback to use default display in activity chooser.
- * Deprecated, will be removed in 3.5, TODO MDL-53697.
- * @deprecated since Moodle 3.1
- */
-define('MOD_SUBTYPE_NO_CHILDREN', 'modsubtypenochildren');
-
 /**
  * Security token used for allowing access
  * from external application such as web services.
index 01d03bd..a6ff45f 100644 (file)
@@ -1424,6 +1424,55 @@ EOF;
             $this->assertContains($file->get_filename(), $expected);
         }
     }
+
+    /**
+     * Test that all files in the draftarea are returned.
+     */
+    public function test_file_get_all_files_in_draftarea() {
+        $this->resetAfterTest();
+        $this->setAdminUser();
+
+        $filerecord = ['filename' => 'basepic.jpg'];
+        $file = self::create_draft_file($filerecord);
+
+        $secondrecord = [
+            'filename' => 'infolder.jpg',
+            'filepath' => '/assignment/',
+            'itemid' => $file->get_itemid()
+        ];
+        $file = self::create_draft_file($secondrecord);
+
+        $thirdrecord = [
+            'filename' => 'deeperfolder.jpg',
+            'filepath' => '/assignment/pics/',
+            'itemid' => $file->get_itemid()
+        ];
+        $file = self::create_draft_file($thirdrecord);
+
+        $fourthrecord = [
+            'filename' => 'differentimage.jpg',
+            'filepath' => '/secondfolder/',
+            'itemid' => $file->get_itemid()
+        ];
+        $file = self::create_draft_file($fourthrecord);
+
+        // This record has the same name as the last record, but it's in a different folder.
+        // Just checking this is also returned.
+        $fifthrecord = [
+            'filename' => 'differentimage.jpg',
+            'filepath' => '/assignment/pics/',
+            'itemid' => $file->get_itemid()
+        ];
+        $file = self::create_draft_file($fifthrecord);
+
+        $allfiles = file_get_all_files_in_draftarea($file->get_itemid());
+        $this->assertCount(5, $allfiles);
+        $this->assertEquals($filerecord['filename'], $allfiles[0]->filename);
+        $this->assertEquals($secondrecord['filename'], $allfiles[1]->filename);
+        $this->assertEquals($thirdrecord['filename'], $allfiles[2]->filename);
+        $this->assertEquals($fourthrecord['filename'], $allfiles[3]->filename);
+        $this->assertEquals($fifthrecord['filename'], $allfiles[4]->filename);
+    }
 }
 
 /**
index 3f12ad3..9389ffd 100644 (file)
@@ -75,11 +75,11 @@ class core_string_manager_standard_testcase extends advanced_testcase {
         $this->assertFalse($stringman->string_deprecated('hidden', 'grades'));
 
         // Check deprecated string.
-        $this->assertTrue($stringman->string_deprecated('modchooserenable', 'core'));
-        $this->assertTrue($stringman->string_exists('modchooserenable', 'core'));
+        $this->assertTrue($stringman->string_deprecated('groupextendenrol', 'core'));
+        $this->assertTrue($stringman->string_exists('groupextendenrol', 'core'));
         $this->assertDebuggingNotCalled();
-        $this->assertEquals('Activity chooser on', get_string('modchooserenable', 'core'));
-        $this->assertDebuggingCalled('String [modchooserenable,core] is deprecated. '.
+        $this->assertEquals('Extend enrolment (common)', get_string('groupextendenrol', 'core'));
+        $this->assertDebuggingCalled('String [groupextendenrol,core] is deprecated. '.
             'Either you should no longer be using that string, or the string has been incorrectly deprecated, in which case you should report this as a bug. '.
             'Please refer to https://docs.moodle.org/dev/String_deprecation');
     }
index 5d29d4c..be7297d 100644 (file)
@@ -1,6 +1,12 @@
 This files describes API changes in core libraries and APIs,
 information provided here is intended especially for developers.
 
+=== 3.6 ===
+
+* The following functions have been finally deprecated and can not be used any more:
+
+- external_function_info()
+
 === 3.5 ===
 
 * There is a new privacy API that every subsystem and plugin has to implement so that the site can become GDPR
index b241a56..2222a85 100644 (file)
Binary files a/message/output/popup/amd/build/notification_popover_controller.min.js and b/message/output/popup/amd/build/notification_popover_controller.min.js differ
index 9aa7630..944f8a9 100644 (file)
@@ -223,10 +223,13 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/str', 'core/url',
             });
 
             // Link to mark read page before loading the actual link.
-            notification.contexturl = URL.relativeUrl('message/output/popup/mark_notification_read.php', {
-                notificationid: notification.id,
-                redirecturl: notification.contexturl
-            });
+            var notificationurlparams = {
+                notificationid: notification.id
+            };
+            if (notification.contexturl) {
+                notificationurlparams.redirecturl = notification.contexturl;
+            }
+            notification.contexturl = URL.relativeUrl('message/output/popup/mark_notification_read.php', notificationurlparams);
 
             var promise = Templates.render('message_popup/notification_content_item', notification)
             .then(function(html, js) {
index 0d393f4..0458678 100644 (file)
@@ -31,13 +31,18 @@ if (isguestuser()) {
 }
 
 $notificationid = required_param('notificationid', PARAM_INT);
-$redirecturl = optional_param('redirecturl', $CFG->wwwroot, PARAM_LOCALURL);
+$redirecturl = optional_param('redirecturl', '', PARAM_URL);
 $notification = $DB->get_record('notifications', array('id' => $notificationid));
 
+// If the redirect URL after filtering is empty, or it was never passed, then redirect to the notification page.
+if (empty($redirecturl)) {
+    $redirecturl = new moodle_url('/message/output/popup/notifications.php', ['notificationid' => $notificationid]);
+}
+
 // Check notification belongs to this user.
 if ($USER->id != $notification->useridto) {
     redirect($CFG->wwwroot);
 }
 
 \core_message\api::mark_notification_as_read($notification);
-redirect($redirecturl);
+redirect(new moodle_url($redirecturl));
index 557f188..06775e5 100644 (file)
@@ -585,6 +585,3 @@ $string['viewsubmissiongradingtable'] = 'View submission grading table.';
 $string['viewrevealidentitiesconfirm'] = 'View reveal student identities confirmation page.';
 $string['workflowfilter'] = 'Workflow filter';
 $string['xofy'] = '{$a->x} of {$a->y}';
-
-// Deprecated since Moodle 3.2.
-$string['changegradewarning'] = 'This assignment has graded submissions and changing the grade will not automatically re-calculate existing submission grades. You must re-grade all existing submissions, if you wish to change the grade.';
index 76d8f54..e69de29 100644 (file)
@@ -1 +0,0 @@
-changegradewarning,mod_assign
\ No newline at end of file
index 2a02a85..3b1959b 100644 (file)
@@ -513,8 +513,16 @@ class assign_submission_file extends assign_submission_plugin {
      * @return bool
      */
     public function submission_is_empty(stdClass $data) {
-        $files = file_get_drafarea_files($data->files_filemanager);
-        return count($files->list) == 0;
+        global $USER;
+        $fs = get_file_storage();
+        // Get a count of all the draft files, excluding any directories.
+        $files = $fs->get_area_files(context_user::instance($USER->id)->id,
+                                     'user',
+                                     'draft',
+                                     $data->files_filemanager,
+                                     'id',
+                                     false);
+        return count($files) == 0;
     }
 
     /**
index b033946..c055ece 100644 (file)
@@ -72,6 +72,34 @@ class assignsubmission_file_locallib_testcase extends advanced_testcase {
         $this->assertTrue($result === $expected);
     }
 
+    /**
+     * Test that an empty directory is is not detected as a valid submission by submission_is_empty.
+     */
+    public function test_submission_is_empty_directory_only() {
+        $this->resetAfterTest();
+        $course = $this->getDataGenerator()->create_course();
+        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
+        $assign = $this->create_instance($course, [
+                'assignsubmission_file_enabled' => 1,
+                'assignsubmission_file_maxfiles' => 12,
+                'assignsubmission_file_maxsizebytes' => 10,
+            ]);
+        $this->setUser($student->id);
+        $itemid = file_get_unused_draft_itemid();
+        $submission = (object)['files_filemanager' => $itemid];
+        $plugin = $assign->get_submission_plugin_by_type('file');
+        $fs = get_file_storage();
+        $fs->create_directory(
+                context_user::instance($student->id)->id,
+                'user',
+                'draft',
+                $itemid,
+                '/subdirectory/'
+        );
+
+        $this->assertTrue($plugin->submission_is_empty($submission));
+    }
+
     /**
      * Test new_submission_empty
      *
@@ -105,6 +133,34 @@ class assignsubmission_file_locallib_testcase extends advanced_testcase {
         $this->assertTrue($result === $expected);
     }
 
+    /**
+     * Test that an empty directory is is not detected as a valid submission by new_submission_is_empty.
+     */
+    public function test_new_submission_empty_directory_only() {
+        $this->resetAfterTest();
+        $course = $this->getDataGenerator()->create_course();
+        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
+        $assign = $this->create_instance($course, [
+                'assignsubmission_file_enabled' => 1,
+                'assignsubmission_file_maxfiles' => 12,
+                'assignsubmission_file_maxsizebytes' => 10,
+            ]);
+        $this->setUser($student->id);
+        $itemid = file_get_unused_draft_itemid();
+        $submission = (object)['files_filemanager' => $itemid];
+        $plugin = $assign->get_submission_plugin_by_type('file');
+        $fs = get_file_storage();
+        $fs->create_directory(
+                context_user::instance($student->id)->id,
+                'user',
+                'draft',
+                $itemid,
+                '/subdirectory/'
+        );
+
+        $this->assertTrue($assign->new_submission_empty($submission));
+    }
+
     /**
      * Dataprovider for the test_submission_is_empty testcase
      *
@@ -121,6 +177,15 @@ class assignsubmission_file_locallib_testcase extends advanced_testcase {
                 ],
                 false
             ],
+            'With file in directory' => [
+                [
+                    'component' => 'user',
+                    'filearea' => 'draft',
+                    'filepath' => '/subdir/',
+                    'filename' => 'not_a_virus.exe'
+                ],
+                false
+            ],
             'Without file' => [null, true]
         ];
     }
index 6084a7f..28d233e 100644 (file)
@@ -400,17 +400,3 @@ $string['viewfromdate'] = 'Read only from';
 $string['viewtodate'] = 'Read only to';
 $string['viewtodatevalidation'] = 'The read only to date cannot be before the read only from date.';
 $string['wrongdataid'] = 'Wrong data id provided';
-
-// Deprecated since Moodle 3.2.
-$string['namedate'] = 'Date field';
-$string['namefile'] = 'File field';
-$string['namecheckbox'] = 'Checkbox field';
-$string['namelatlong'] = 'Latitude/longitude field';
-$string['namemenu'] = 'Menu field';
-$string['namemultimenu'] = 'Multiple-selection menu field';
-$string['namenumber'] = 'Number field';
-$string['namepicture'] = 'Picture field';
-$string['nameradiobutton'] = 'Radio button field';
-$string['nametext'] = 'Text field';
-$string['nametextarea'] = 'Textarea field';
-$string['nameurl'] = 'URL field';
index cf44132..e69de29 100644 (file)
@@ -1,12 +0,0 @@
-namedate,mod_data
-namefile,mod_data
-namecheckbox,mod_data
-namelatlong,mod_data
-namemenu,mod_data
-namemultimenu,mod_data
-namenumber,mod_data
-namepicture,mod_data
-nameradiobutton,mod_data
-nametext,mod_data
-nametextarea,mod_data
-nameurl,mod_data
\ No newline at end of file
index fe76fa7..e69de29 100644 (file)
@@ -1,2 +0,0 @@
-start,mod_feedback
-stop,mod_feedback
\ No newline at end of file
index f87fb98..23d1ab5 100644 (file)
@@ -284,6 +284,3 @@ $string['use_one_line_for_each_value'] = 'Use one line for each answer!';
 $string['use_this_template'] = 'Use this template';
 $string['using_templates'] = 'Use a template';
 $string['vertical'] = 'Vertical';
-// Deprecated since Moodle 3.2.
-$string['start'] = 'Start';
-$string['stop'] = 'End';
index 724647a..0881cc9 100644 (file)
@@ -944,24 +944,10 @@ function feedback_delete_course_module($id) {
 ////////////////////////////////////////////////
 
 /**
- * returns the context-id related to the given coursemodule-id
- *
  * @deprecated since 3.1
- * @staticvar object $context
- * @param int $cmid the coursemodule-id
- * @return object $context
  */
-function feedback_get_context($cmid) {
-    debugging('Function feedback_get_context() is deprecated because it was not used.',
-            DEBUG_DEVELOPER);
-    static $context;
-
-    if (isset($context)) {
-        return $context;
-    }
-
-    $context = context_module::instance($cmid);
-    return $context;
+function feedback_get_context() {
+    throw new coding_exception('feedback_get_context() can not be used anymore.');
 }
 
 /**
@@ -1591,56 +1577,10 @@ function feedback_get_depend_candidates_for_item($feedback, $item) {
 }
 
 /**
- * creates a new item-record
- *
  * @deprecated since 3.1
- * @param object $data the data from edit_item_form
- * @return int the new itemid
  */
-function feedback_create_item($data) {
-    debugging('Function feedback_create_item() is deprecated because it was not used.',
-            DEBUG_DEVELOPER);
-    global $DB;
-
-    $item = new stdClass();
-    $item->feedback = $data->feedbackid;
-
-    $item->template=0;
-    if (isset($data->templateid)) {
-            $item->template = intval($data->templateid);
-    }
-
-    $itemname = trim($data->itemname);
-    $item->name = ($itemname ? $data->itemname : get_string('no_itemname', 'feedback'));
-
-    if (!empty($data->itemlabel)) {
-        $item->label = trim($data->itemlabel);
-    } else {
-        $item->label = get_string('no_itemlabel', 'feedback');
-    }
-
-    $itemobj = feedback_get_item_class($data->typ);
-    $item->presentation = ''; //the date comes from postupdate() of the itemobj
-
-    $item->hasvalue = $itemobj->get_hasvalue();
-
-    $item->typ = $data->typ;
-    $item->position = $data->position;
-
-    $item->required=0;
-    if (!empty($data->required)) {
-        $item->required = $data->required;
-    }
-
-    $item->id = $DB->insert_record('feedback_item', $item);
-
-    //move all itemdata to the data
-    $data->id = $item->id;
-    $data->feedback = $item->feedback;
-    $data->name = $item->name;
-    $data->label = $item->label;
-    $data->required = $item->required;
-    return $itemobj->postupdate($data);
+function feedback_create_item() {
+    throw new coding_exception('feedback_create_item() can not be used anymore.');
 }
 
 /**
@@ -1907,46 +1847,27 @@ function feedback_move_item($moveitem, $pos) {
 }
 
 /**
- * prints the given item as a preview.
- * each item-class has an own print_item_preview function implemented.
- *
  * @deprecated since Moodle 3.1
- * @global object
- * @param object $item the item what we want to print out
- * @return void
  */
-function feedback_print_item_preview($item) {
-    debugging('Function feedback_print_item_preview() is deprecated and does nothing. '
-            . 'Items must implement complete_form_element()', DEBUG_DEVELOPER);
+function feedback_print_item_preview() {
+    throw new coding_exception('feedback_print_item_preview() can not be used anymore. '
+            . 'Items must implement complete_form_element().');
 }
 
 /**
- * prints the given item in the completion form.
- * each item-class has an own print_item_complete function implemented.
- *
  * @deprecated since Moodle 3.1
- * @param object $item the item what we want to print out
- * @param mixed $value the value
- * @param boolean $highlightrequire if this set true and the value are false on completing so the item will be highlighted
- * @return void
  */
-function feedback_print_item_complete($item, $value = false, $highlightrequire = false) {
-    debugging('Function feedback_print_item_complete() is deprecated and does nothing. '
-            . 'Items must implement complete_form_element()', DEBUG_DEVELOPER);
+function feedback_print_item_complete() {
+    throw new coding_exception('feedback_print_item_complete() can not be used anymore. '
+        . 'Items must implement complete_form_element().');
 }
 
 /**
- * prints the given item in the show entries page.
- * each item-class has an own print_item_show_value function implemented.
- *
  * @deprecated since Moodle 3.1
- * @param object $item the item what we want to print out
- * @param mixed $value
- * @return void
  */
-function feedback_print_item_show_value($item, $value = false) {
-    debugging('Function feedback_print_item_show_value() is deprecated and does nothing. '
-            . 'Items must implement complete_form_element()', DEBUG_DEVELOPER);
+function feedback_print_item_show_value() {
+    throw new coding_exception('feedback_print_item_show_value() can not be used anymore. '
+        . 'Items must implement complete_form_element().');
 }
 
 /**
@@ -2041,21 +1962,11 @@ function feedback_save_tmp_values($feedbackcompletedtmp, $feedbackcompleted) {
 }
 
 /**
- * deletes the given temporary completed and all related temporary values
- *
  * @deprecated since Moodle 3.1
- *
- * @param int $tmpcplid
- * @return void
  */
-function feedback_delete_completedtmp($tmpcplid) {
-    global $DB;
-
-    debugging('Function feedback_delete_completedtmp() is deprecated because it is no longer used',
-            DEBUG_DEVELOPER);
+function feedback_delete_completedtmp() {
+    throw new coding_exception('feedback_delete_completedtmp() can not be used anymore.');
 
-    $DB->delete_records('feedback_valuetmp', array('completed'=>$tmpcplid));
-    $DB->delete_records('feedback_completedtmp', array('id'=>$tmpcplid));
 }
 
 ////////////////////////////////////////////////
@@ -2131,66 +2042,10 @@ function feedback_get_last_break_position($feedbackid) {
 }
 
 /**
- * this returns the position where the user can continue the completing.
- *
  * @deprecated since Moodle 3.1
- * @global object
- * @global object
- * @global object
- * @param int $feedbackid
- * @param int $courseid
- * @param string $guestid this id will be saved temporary and is unique
- * @return int the position to continue
  */
-function feedback_get_page_to_continue($feedbackid, $courseid = false, $guestid = false) {
-    global $CFG, $USER, $DB;
-
-    debugging('Function feedback_get_page_to_continue() is deprecated and since it is '
-            . 'no longer used in mod_feedback', DEBUG_DEVELOPER);
-
-    //is there any break?
-
-    if (!$allbreaks = feedback_get_all_break_positions($feedbackid)) {
-        return false;
-    }
-
-    $params = array();
-    if ($courseid) {
-        $courseselect = "AND fv.course_id = :courseid";
-        $params['courseid'] = $courseid;
-    } else {
-        $courseselect = '';
-    }
-
-    if ($guestid) {
-        $userselect = "AND fc.guestid = :guestid";
-        $usergroup = "GROUP BY fc.guestid";
-        $params['guestid'] = $guestid;
-    } else {
-        $userselect = "AND fc.userid = :userid";
-        $usergroup = "GROUP BY fc.userid";
-        $params['userid'] = $USER->id;
-    }
-
-    $sql =  "SELECT MAX(fi.position)
-               FROM {feedback_completedtmp} fc, {feedback_valuetmp} fv, {feedback_item} fi
-              WHERE fc.id = fv.completed
-                    $userselect
-                    AND fc.feedback = :feedbackid
-                    $courseselect
-                    AND fi.id = fv.item
-         $usergroup";
-    $params['feedbackid'] = $feedbackid;
-
-    $lastpos = $DB->get_field_sql($sql, $params);
-
-    //the index of found pagebreak is the searched pagenumber
-    foreach ($allbreaks as $pagenr => $br) {
-        if ($lastpos < $br) {
-            return $pagenr;
-        }
-    }
-    return count($allbreaks);
+function feedback_get_page_to_continue() {
+    throw new coding_exception('feedback_get_page_to_continue() can not be used anymore.');
 }
 
 ////////////////////////////////////////////////
@@ -2200,77 +2055,26 @@ function feedback_get_page_to_continue($feedbackid, $courseid = false, $guestid
 ////////////////////////////////////////////////
 
 /**
- * cleans the userinput while submitting the form.
- *
  * @deprecated since Moodle 3.1
- * @param mixed $value
- * @return mixed
  */
-function feedback_clean_input_value($item, $value) {
-    debugging('Function feedback_clean_input_value() is deprecated and does nothing. '
-            . 'Items must implement complete_form_element()', DEBUG_DEVELOPER);
+function feedback_clean_input_value() {
+    throw new coding_exception('feedback_clean_input_value() can not be used anymore. '
+        . 'Items must implement complete_form_element().');
+
 }
 
 /**
- * this saves the values of an completed.
- * if the param $tmp is set true so the values are saved temporary in table feedback_valuetmp.
- * if there is already a completed and the userid is set so the values are updated.
- * on all other things new value records will be created.
- *
  * @deprecated since Moodle 3.1
- *
- * @param int $usrid
- * @param boolean $tmp
- * @return mixed false on error or the completeid
  */
-function feedback_save_values($usrid, $tmp = false) {
-    global $DB;
-
-    debugging('Function feedback_save_values() was deprecated because it did not have '.
-            'enough arguments, was not suitable for non-temporary table and was taking '.
-            'data directly from input', DEBUG_DEVELOPER);
-
-    $completedid = optional_param('completedid', 0, PARAM_INT);
-    $tmpstr = $tmp ? 'tmp' : '';
-    $time = time();
-    $timemodified = mktime(0, 0, 0, date('m', $time), date('d', $time), date('Y', $time));
-
-    if ($usrid == 0) {
-        return feedback_create_values($usrid, $timemodified, $tmp);
-    }
-    $completed = $DB->get_record('feedback_completed'.$tmpstr, array('id'=>$completedid));
-    if (!$completed) {
-        return feedback_create_values($usrid, $timemodified, $tmp);
-    } else {
-        $completed->timemodified = $timemodified;
-        return feedback_update_values($completed, $tmp);
-    }
+function feedback_save_values() {
+    throw new coding_exception('feedback_save_values() can not be used anymore.');
 }
 
 /**
- * this saves the values from anonymous user such as guest on the main-site
- *
  * @deprecated since Moodle 3.1
- *
- * @param string $guestid the unique guestidentifier
- * @return mixed false on error or the completeid
  */
-function feedback_save_guest_values($guestid) {
-    global $DB;
-
-    debugging('Function feedback_save_guest_values() was deprecated because it did not have '.
-            'enough arguments, was not suitable for non-temporary table and was taking '.
-            'data directly from input', DEBUG_DEVELOPER);
-
-    $completedid = optional_param('completedid', false, PARAM_INT);
-
-    $timemodified = time();
-    if (!$completed = $DB->get_record('feedback_completedtmp', array('id'=>$completedid))) {
-        return feedback_create_values(0, $timemodified, true, $guestid);
-    } else {
-        $completed->timemodified = $timemodified;
-        return feedback_update_values($completed, true);
-    }
+function feedback_save_guest_values() {
+    throw new coding_exception('feedback_save_guest_values() can not be used anymore.');
 }
 
 /**
@@ -2316,166 +2120,25 @@ function feedback_compare_item_value($completedid, $item, $dependvalue, $tmp = f
 }
 
 /**
- * this function checks the correctness of values.
- * the rules for this are implemented in the class of each item.
- * it can be the required attribute or the value self e.g. numeric.
- * the params first/lastitem are given to determine the visible range between pagebreaks.
- *
- * @global object
- * @param int $firstitem the position of firstitem for checking
- * @param int $lastitem the position of lastitem for checking
- * @return boolean
+ * @deprecated since Moodle 3.1
  */
-function feedback_check_values($firstitem, $lastitem) {
-    debugging('Function feedback_check_values() is deprecated and does nothing. '
-            . 'Items must implement complete_form_element()', DEBUG_DEVELOPER);
-    return true;
+function feedback_check_values() {
+    throw new coding_exception('feedback_check_values() can not be used anymore. '
+        . 'Items must implement complete_form_element().');
 }
 
 /**
- * this function create a complete-record and the related value-records.
- * depending on the $tmp (true/false) the values are saved temporary or permanently
- *
  * @deprecated since Moodle 3.1
- *
- * @param int $userid
- * @param int $timemodified
- * @param boolean $tmp
- * @param string $guestid a unique identifier to save temporary data
- * @return mixed false on error or the completedid
  */
-function feedback_create_values($usrid, $timemodified, $tmp = false, $guestid = false) {
-    global $DB;
-
-    debugging('Function feedback_create_values() was deprecated because it did not have '.
-            'enough arguments, was not suitable for non-temporary table and was taking '.
-            'data directly from input', DEBUG_DEVELOPER);
-
-    $tmpstr = $tmp ? 'tmp' : '';
-    //first we create a new completed record
-    $completed = new stdClass();
-    $completed->feedback           = $feedbackid;
-    $completed->userid             = $usrid;
-    $completed->guestid            = $guestid;
-    $completed->timemodified       = $timemodified;
-    $completed->anonymous_response = $anonymous_response;
-
-    $completedid = $DB->insert_record('feedback_completed'.$tmpstr, $completed);
-
-    $completed = $DB->get_record('feedback_completed'.$tmpstr, array('id'=>$completedid));
-
-    //the keys are in the form like abc_xxx
-    //with explode we make an array with(abc, xxx) and (abc=typ und xxx=itemnr)
-
-    //get the items of the feedback
-    if (!$allitems = $DB->get_records('feedback_item', array('feedback'=>$completed->feedback))) {
-        return false;
-    }
-    foreach ($allitems as $item) {
-        if (!$item->hasvalue) {
-            continue;
-        }
-        //get the class of item-typ
-        $itemobj = feedback_get_item_class($item->typ);
-
-        $keyname = $item->typ.'_'.$item->id;
-
-        if ($item->typ === 'multichoice') {
-            $itemvalue = optional_param_array($keyname, null, PARAM_INT);
-        } else {
-            $itemvalue = optional_param($keyname, null, PARAM_NOTAGS);
-        }
-
-        if (is_null($itemvalue)) {
-            continue;
-        }
-
-        $value = new stdClass();
-        $value->item = $item->id;
-        $value->completed = $completed->id;
-        $value->course_id = $courseid;
-
-        //the kind of values can be absolutely different
-        //so we run create_value directly by the item-class
-        $value->value = $itemobj->create_value($itemvalue);
-        $DB->insert_record('feedback_value'.$tmpstr, $value);
-    }
-    return $completed->id;
+function feedback_create_values() {
+    throw new coding_exception('feedback_create_values() can not be used anymore.');
 }
 
 /**
- * this function updates a complete-record and the related value-records.
- * depending on the $tmp (true/false) the values are saved temporary or permanently
- *
- * @global object
- * @param object $completed
- * @param boolean $tmp
- * @return int the completedid
+ * @deprecated since Moodle 3.1
  */
-function feedback_update_values($completed, $tmp = false) {
-    global $DB;
-
-    debugging('Function feedback_update_values() was deprecated because it did not have '.
-            'enough arguments, was not suitable for non-temporary table and was taking '.
-            'data directly from input', DEBUG_DEVELOPER);
-
-    $courseid = optional_param('courseid', false, PARAM_INT);
-    $tmpstr = $tmp ? 'tmp' : '';
-
-    $DB->update_record('feedback_completed'.$tmpstr, $completed);
-    //get the values of this completed
-    $values = $DB->get_records('feedback_value'.$tmpstr, array('completed'=>$completed->id));
-
-    //get the items of the feedback
-    if (!$allitems = $DB->get_records('feedback_item', array('feedback'=>$completed->feedback))) {
-        return false;
-    }
-    foreach ($allitems as $item) {
-        if (!$item->hasvalue) {
-            continue;
-        }
-        //get the class of item-typ
-        $itemobj = feedback_get_item_class($item->typ);
-
-        $keyname = $item->typ.'_'.$item->id;
-
-        if ($item->typ === 'multichoice') {
-            $itemvalue = optional_param_array($keyname, null, PARAM_INT);
-        } else {
-            $itemvalue = optional_param($keyname, null, PARAM_NOTAGS);
-        }
-
-        //is the itemvalue set (could be a subset of items because pagebreak)?
-        if (is_null($itemvalue)) {
-            continue;
-        }
-
-        $newvalue = new stdClass();
-        $newvalue->item = $item->id;
-        $newvalue->completed = $completed->id;
-        $newvalue->course_id = $courseid;
-
-        //the kind of values can be absolutely different
-        //so we run create_value directly by the item-class
-        $newvalue->value = $itemobj->create_value($itemvalue);
-
-        //check, if we have to create or update the value
-        $exist = false;
-        foreach ($values as $value) {
-            if ($value->item == $newvalue->item) {
-                $newvalue->id = $value->id;
-                $exist = true;
-                break;
-            }
-        }
-        if ($exist) {
-            $DB->update_record('feedback_value'.$tmpstr, $newvalue);
-        } else {
-            $DB->insert_record('feedback_value'.$tmpstr, $newvalue);
-        }
-    }
-
-    return $completed->id;
+function feedback_update_values() {
+    throw new coding_exception('feedback_update_values() can not be used anymore.');
 }
 
 /**
@@ -2573,66 +2236,11 @@ function feedback_is_already_submitted($feedbackid, $courseid = false) {
 }
 
 /**
- * if the completion of a feedback will be continued eg.
- * by pagebreak or by multiple submit so the complete must be found.
- * if the param $tmp is set true so all things are related to temporary completeds
- *
- * @deprecated since Moodle 3.1
- * @param int $feedbackid
- * @param boolean $tmp
- * @param int $courseid
- * @param string $guestid
- * @return int the id of the found completed
+ * @deprecated since Moodle 3.1. Use feedback_get_current_completed_tmp() or feedback_get_last_completed.
  */
-function feedback_get_current_completed($feedbackid,
-                                        $tmp = false,
-                                        $courseid = false,
-                                        $guestid = false) {
-
-    debugging('Function feedback_get_current_completed() is deprecated. Please use either '.
-            'feedback_get_current_completed_tmp() or feedback_get_last_completed()',
-            DEBUG_DEVELOPER);
-
-    global $USER, $CFG, $DB;
-
-    $tmpstr = $tmp ? 'tmp' : '';
-
-    if (!$courseid) {
-        if ($guestid) {
-            $params = array('feedback'=>$feedbackid, 'guestid'=>$guestid);
-            return $DB->get_record('feedback_completed'.$tmpstr, $params);
-        } else {
-            $params = array('feedback'=>$feedbackid, 'userid'=>$USER->id);
-            return $DB->get_record('feedback_completed'.$tmpstr, $params);
-        }
-    }
-
-    $params = array();
-
-    if ($guestid) {
-        $userselect = "AND fc.guestid = :guestid";
-        $params['guestid'] = $guestid;
-    } else {
-        $userselect = "AND fc.userid = :userid";
-        $params['userid'] = $USER->id;
-    }
-    //if courseid is set the feedback is global.
-    //there can be more than one completed on one feedback
-    $sql =  "SELECT DISTINCT fc.*
-               FROM {feedback_value{$tmpstr}} fv, {feedback_completed{$tmpstr}} fc
-              WHERE fv.course_id = :courseid
-                    AND fv.completed = fc.id
-                    $userselect
-                    AND fc.feedback = :feedbackid";
-    $params['courseid']   = intval($courseid);
-    $params['feedbackid'] = $feedbackid;
-
-    if (!$sqlresult = $DB->get_records_sql($sql, $params)) {
-        return false;
-    }
-    foreach ($sqlresult as $r) {
-        return $DB->get_record('feedback_completed'.$tmpstr, array('id'=>$r->id));
-    }
+function feedback_get_current_completed() {
+    throw new coding_exception('feedback_get_current_completed() can not be used anymore. Please ' .
+            'use either feedback_get_current_completed_tmp() or feedback_get_last_completed()');
 }
 
 /**
@@ -2803,33 +2411,17 @@ function feedback_delete_completed($completed, $feedback = null, $cm = null, $co
 ////////////////////////////////////////////////
 
 /**
- * checks if the course and the feedback is in the table feedback_sitecourse_map.
- *
  * @deprecated since 3.1
- * @param int $feedbackid
- * @param int $courseid
- * @return int the count of records
  */
-function feedback_is_course_in_sitecourse_map($feedbackid, $courseid) {
-    debugging('Function feedback_is_course_in_sitecourse_map() is deprecated because it was not used.',
-            DEBUG_DEVELOPER);
-    global $DB;
-    $params = array('feedbackid'=>$feedbackid, 'courseid'=>$courseid);
-    return $DB->count_records('feedback_sitecourse_map', $params);
+function feedback_is_course_in_sitecourse_map() {
+    throw new coding_exception('feedback_is_course_in_sitecourse_map() can not be used anymore.');
 }
 
 /**
- * checks if the feedback is in the table feedback_sitecourse_map.
- *
  * @deprecated since 3.1
- * @param int $feedbackid
- * @return boolean
  */
-function feedback_is_feedback_in_sitecourse_map($feedbackid) {
-    debugging('Function feedback_is_feedback_in_sitecourse_map() is deprecated because it was not used.',
-            DEBUG_DEVELOPER);
-    global $DB;
-    return $DB->record_exists('feedback_sitecourse_map', array('feedbackid'=>$feedbackid));
+function feedback_is_feedback_in_sitecourse_map() {
+    throw new coding_exception('feedback_is_feedback_in_sitecourse_map() can not be used anymore.');
 }
 
 /**
@@ -2935,33 +2527,10 @@ function feedback_update_sitecourse_map($feedback, $courses) {
 }
 
 /**
- * removes non existing courses or feedbacks from sitecourse_map.
- * it shouldn't be called all too often
- * a good place for it could be the mapcourse.php or unmapcourse.php
- *
  * @deprecated since 3.1
- * @global object
- * @return void
  */
 function feedback_clean_up_sitecourse_map() {
-    global $DB;
-    debugging('Function feedback_clean_up_sitecourse_map() is deprecated because it was not used.',
-            DEBUG_DEVELOPER);
-
-    $maps = $DB->get_records('feedback_sitecourse_map');
-    foreach ($maps as $map) {
-        if (!$DB->get_record('course', array('id'=>$map->courseid))) {
-            $params = array('courseid'=>$map->courseid, 'feedbackid'=>$map->feedbackid);
-            $DB->delete_records('feedback_sitecourse_map', $params);
-            continue;
-        }
-        if (!$DB->get_record('feedback', array('id'=>$map->feedbackid))) {
-            $params = array('courseid'=>$map->courseid, 'feedbackid'=>$map->feedbackid);
-            $DB->delete_records('feedback_sitecourse_map', $params);
-            continue;
-        }
-
-    }
+    throw new coding_exception('feedback_clean_up_sitecourse_map() can not be used anymore.');
 }
 
 ////////////////////////////////////////////////
@@ -2971,25 +2540,10 @@ function feedback_clean_up_sitecourse_map() {
 ////////////////////////////////////////////////
 
 /**
- * prints the option items of a selection-input item (dropdownlist).
  * @deprecated since 3.1
- * @param int $startval the first value of the list
- * @param int $endval the last value of the list
- * @param int $selectval which item should be selected
- * @param int $interval the stepsize from the first to the last value
- * @return void
  */
-function feedback_print_numeric_option_list($startval, $endval, $selectval = '', $interval = 1) {
-    debugging('Function feedback_print_numeric_option_list() is deprecated because it was not used.',
-            DEBUG_DEVELOPER);
-    for ($i = $startval; $i <= $endval; $i += $interval) {
-        if ($selectval == ($i)) {
-            $selected = 'selected="selected"';
-        } else {
-            $selected = '';
-        }
-        echo '<option '.$selected.'>'.$i.'</option>';
-    }
+function feedback_print_numeric_option_list() {
+    throw new coding_exception('feedback_print_numeric_option_list() can not be used anymore.');
 }
 
 /**
index f5d4733..705655e 100644 (file)
@@ -1,3 +1,25 @@
+=== 3.6 ===
+
+* The following functions have been finally deprecated and can not be used anymore:
+  * feedback_print_item_preview()
+  * feedback_print_item_complete()
+  * feedback_print_item_show_value()
+  * feedback_check_values()
+  * feedback_clean_input_value()
+  * feedback_get_context()
+  * feedback_create_item()
+  * feedback_delete_completedtmp()
+  * feedback_get_page_to_continue()
+  * feedback_save_values()
+  * feedback_save_guest_values()
+  * feedback_create_values()
+  * feedback_update_values()
+  * feedback_get_current_completed()
+  * feedback_is_course_in_sitecourse_map()
+  * feedback_is_feedback_in_sitecourse_map()
+  * feedback_clean_up_sitecourse_map()
+  * feedback_print_numeric_option_list()
+
 === 3.5 ===
 
 * The following functions, previously used (exclusively) by upgrade steps are not available
index fa122ea..e97ad19 100644 (file)
@@ -5590,7 +5590,7 @@ function forum_print_latest_discussions($course, $forum, $maxdiscussions = -1, $
 
     if ($displayformat == 'header') {
         echo '<table cellspacing="0" class="forumheaderlist">';
-        echo '<thead>';
+        echo '<thead class="text-left">';
         echo '<tr>';
         echo '<th class="header topic" scope="col">'.get_string('discussion', 'forum').'</th>';
         echo '<th class="header author" scope="col">'.get_string('startedby', 'forum').'</th>';
index cd40e78..212fcb8 100644 (file)
@@ -505,10 +505,14 @@ class mod_lesson_renderer extends plugin_renderer_base {
             $progress = $lesson->calculate_progress();
         }
 
-        // print out the Progress Bar.  Attempted to put as much as possible in the style sheets.
-        $content = '<br />' . html_writer::tag('div', $progress . '%', array('class' => 'progress_bar_completed', 'style' => 'width: '. $progress . '%;'));
-        $printprogress = html_writer::tag('div', get_string('progresscompleted', 'lesson', $progress) . $content, array('class' => 'progress_bar'));
-
+        $content = html_writer::start_tag('div');
+        $content .= html_writer::start_tag('div', array('class' => 'progress'));
+        $content .= html_writer::start_tag('div', array('class' => 'progress-bar bar', 'role' => 'progressbar',
+            'style' => 'width: ' . $progress .'%', 'aria-valuenow' => $progress, 'aria-valuemin' => 0, 'aria-valuemax' => 100));
+        $content .= $progress . "%";
+        $content .= html_writer::end_tag('div');
+        $content .= html_writer::end_tag('div');
+        $printprogress = html_writer::tag('div', get_string('progresscompleted', 'lesson', $progress) . $content);
         return $this->output->box($printprogress, 'progress_bar');
     }
 
index ab9df0e..96bf055 100644 (file)
@@ -231,28 +231,6 @@ function lti_get_shortcuts($defaultitem) {
 
     // Add items defined in ltisource plugins.
     foreach (core_component::get_plugin_list('ltisource') as $pluginname => $dir) {
-        if ($moretypes = component_callback("ltisource_$pluginname", 'get_types')) {
-            // Callback 'get_types()' in 'ltisource' plugins is deprecated in 3.1 and will be removed in 3.5, TODO MDL-53697.
-            debugging('Deprecated callback get_types() is found in ltisource_' . $pluginname .
-                ', use get_shortcuts() instead', DEBUG_DEVELOPER);
-            $grouptitle = get_string('modulenameplural', 'mod_lti');
-            foreach ($moretypes as $subtype) {
-                // Instead of adding subitems combine the name of the group with the name of the subtype.
-                $subtype->title = get_string('activitytypetitle', '',
-                    (object)['activity' => $grouptitle, 'type' => $subtype->typestr]);
-                // Re-implement the logic of get_module_metadata() in Moodle 3.0 and below for converting
-                // subtypes into items in activity chooser.
-                $subtype->type = str_replace('&amp;', '&', $subtype->type);
-                $subtype->name = preg_replace('/.*type=/', '', $subtype->type);
-                $subtype->link = new moodle_url($defaultitem->link, array('type' => $subtype->name));
-                if (empty($subtype->help) && !empty($subtype->name) &&
-                        get_string_manager()->string_exists('help' . $subtype->name, $pluginname)) {
-                    $subtype->help = get_string('help' . $subtype->name, $pluginname);
-                }
-                unset($subtype->typestr);
-                $types[] = $subtype;
-            }
-        }
         // LTISOURCE plugins can also implement callback get_shortcuts() to add items to the activity chooser.
         // The return values are the same as of the 'mod' callbacks except that $defaultitem is only passed for reference and
         // should not be added to the return value.
index 5c8838e..36cc053 100644 (file)
@@ -529,7 +529,8 @@ class quiz_statistics_report extends quiz_default_report {
         $questions = quiz_report_get_significant_questions($quiz);
 
         // Only load main question not sub questions.
-        $questionstatistics = $DB->get_records_select('question_statistics', 'hashcode = ? AND slot IS NOT NULL',
+        $questionstatistics = $DB->get_records_select('question_statistics',
+                'hashcode = ? AND slot IS NOT NULL AND variant IS NULL',
             [$qubaids->get_hash_code()]);
 
         // Configure what to display.
index e3b02a4..ac520e9 100644 (file)
@@ -120,31 +120,28 @@ function SCORMapi1_3(def, cmiobj, cmiint, cmicommentsuser, cmicommentslms, scorm
     }
 
     var correct_responses = {
-        'true-false':{'pre':'', 'max':1, 'delimiter':'', 'unique':false, 'duplicate':false,
+        'true-false':{'max':1, 'delimiter':'', 'unique':false, 'duplicate':false,
                       'format':'^true$|^false$',
                       'limit':1},
-        'choice':{'pre':'', 'max':36, 'delimiter':'[,]', 'unique':true, 'duplicate':false,
+        'choice':{'max':36, 'delimiter':'[,]', 'unique':true, 'duplicate':false,
                   'format':CMIShortIdentifier},
-//        'fill-in':{'pre':'^(((\{case_matters=(true|false)\})(\{order_matters=(true|false)\})?)|((\{order_matters=(true|false)\})(\{case_matters=(true|false)\})?))(.*?)$',
-        'fill-in':{'pre':'',
-                   'max':10, 'delimiter':'[,]', 'unique':false, 'duplicate':false,
+        'fill-in':{'max':10, 'delimiter':'[,]', 'unique':false, 'duplicate':false,
                    'format':CMILangString250cr},
-        'long-fill-in':{'pre':'^(\{case_matters=(true|false)\})?', 'max':1, 'delimiter':'', 'unique':false, 'duplicate':true,
+        'long-fill-in':{'max':1, 'delimiter':'', 'unique':false, 'duplicate':true,
                         'format':CMILangString4000},
-        'matching':{'pre':'', 'max':36, 'delimiter':'[,]', 'delimiter2':'[.]', 'unique':false, 'duplicate':false,
+        'matching':{'max':36, 'delimiter':'[,]', 'delimiter2':'[.]', 'unique':false, 'duplicate':false,
                     'format':CMIShortIdentifier, 'format2':CMIShortIdentifier},
-        'performance':{'pre':'^(\{order_matters=(true|false)\})?',
-                       'max':250, 'delimiter':'[,]', 'delimiter2':'[.]', 'unique':false, 'duplicate':false,
+        'performance':{'max':250, 'delimiter':'[,]', 'delimiter2':'[.]', 'unique':false, 'duplicate':false,
                        'format':'^$|' + CMIShortIdentifier, 'format2':CMIDecimal + '|^$|' + CMIShortIdentifier},
-        'sequencing':{'pre':'', 'max':36, 'delimiter':'[,]', 'unique':false, 'duplicate':false,
+        'sequencing':{'max':36, 'delimiter':'[,]', 'unique':false, 'duplicate':false,
                       'format':CMIShortIdentifier},
-        'likert':{'pre':'', 'max':1, 'delimiter':'', 'unique':false, 'duplicate':false,
+        'likert':{'max':1, 'delimiter':'', 'unique':false, 'duplicate':false,
                   'format':CMIShortIdentifier,
                   'limit':1},
-        'numeric':{'pre':'', 'max':2, 'delimiter':'[:]', 'unique':false, 'duplicate':false,
+        'numeric':{'max':2, 'delimiter':'[:]', 'unique':false, 'duplicate':false,
                    'format':CMIDecimal,
                    'limit':1},
-        'other':{'pre':'', 'max':1, 'delimiter':'', 'unique':false, 'duplicate':false,
+        'other':{'max':1, 'delimiter':'', 'unique':false, 'duplicate':false,
                  'format':CMIString4000,
                  'limit':1}
     }
@@ -862,14 +859,6 @@ function SCORMapi1_3(def, cmiobj, cmiint, cmicommentsuser, cmicommentslms, scorm
                 nodes[i] = result.node;
             }
 
-            // check for prefix on each node
-            if (correct_responses[interactiontype].pre != '') {
-                matches = nodes[i].match(correct_responses[interactiontype].pre);
-                if (matches != null) {
-                    nodes[i] = nodes[i].substr(matches[1].length);
-                }
-            }
-
             if (correct_responses[interactiontype].delimiter2 != undefined) {
                 values = nodes[i].split(correct_responses[interactiontype].delimiter2);
                 if (values.length == 2) {
index bdd4d74..2688662 100644 (file)
@@ -1,6 +1,12 @@
 This files describes API changes in /mod/* - activity modules,
 information provided here is intended especially for developers.
 
+=== 3.6 ===
+
+* The final deprecation of xxx_get_types() callback means that this function will no longer be called.
+  Please use get_shortcuts() instead.
+* lti_get_shortcuts has been deprecated. Please use get_shortcuts() instead to add items to the activity chooser.
+
 === 3.5 ===
 
 * There is a new privacy API that every subsystem and plugin has to implement so that the site can become GDPR
index a785217..865a88b 100644 (file)
@@ -38,8 +38,12 @@ abstract class action_column_base extends column_base {
         echo '<a title="' . $title . '" href="' . $url . '">' . $OUTPUT->pix_icon($icon, $title) . '</a>';
     }
 
+    public function get_extra_joins() {
+        return array('qc' => 'JOIN {question_categories} qc ON qc.id = q.category');
+    }
+
     public function get_required_fields() {
         // Createdby is required for permission checks.
-        return array('q.id', 'q.createdby');
+        return array('q.id', 'q.createdby', 'qc.contextid');
     }
 }
index 518e36c..ec5b81f 100644 (file)
@@ -69,8 +69,8 @@ class view {
 
     /**
      * Constructor
-     * @param question_edit_contexts $contexts
-     * @param moodle_url $pageurl
+     * @param \question_edit_contexts $contexts
+     * @param \moodle_url $pageurl
      * @param object $course course settings
      * @param object $cm (optional) activity settings.
      */
index 022a5c3..8fef597 100644 (file)
@@ -338,6 +338,10 @@ class qformat_gift extends qformat_default {
                 return $question;
 
             case 'multichoice':
+                // "Temporary" solution to enable choice of answernumbering on GIFT import
+                // by respecting default set for multichoice questions (MDL-59447)
+                $question->answernumbering = get_config('qtype_multichoice', 'answernumbering');
+
                 if (strpos($answertext, "=") === false) {
                     $question->single = 0; // Multiple answers are enabled if no single answer is 100% correct.
                 } else {
index 2d3b9ab..90c89ef 100644 (file)
@@ -267,7 +267,18 @@ class qformat_gift_test extends question_testcase {
         $this->assert_same_gift($expectedgift, $gift);
     }
 
-    public function test_import_multichoice() {
+    /**
+     * Test import of multichoice question in GIFT format
+     *
+     * @dataProvider numberingstyle_provider
+     *
+     * @param string $numberingstyle multichoice numbering style to set for qtype_multichoice
+     *
+     */
+    public function test_import_multichoice($numberingstyle) {
+        $this->resetAfterTest(true);
+
+        set_config('answernumbering', $numberingstyle, 'qtype_multichoice');
         $gift = "
 // multiple choice with specified feedback for right and wrong answers
 ::Q2:: What's between orange and green in the spectrum?
@@ -293,7 +304,7 @@ class qformat_gift_test extends question_testcase {
             'length' => 1,
             'single' => 1,
             'shuffleanswers' => '1',
-            'answernumbering' => 'abc',
+            'answernumbering' => $numberingstyle,
             'correctfeedback' => array(
                 'text' => '',
                 'format' => FORMAT_MOODLE,
@@ -352,6 +363,23 @@ class qformat_gift_test extends question_testcase {
         $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
     }
 
+    /**
+     * Return a list of numbering styles (see question/type/multichoice/questiontype.php
+     * for valid choices)
+     *
+     * @return array Array of 1-element arrays of qtype_multichoice numbering styles
+     */
+    public function numberingstyle_provider() {
+        return [
+            ['abc'],
+            ['ABCD'],
+            ['123'],
+            ['iii'],
+            ['IIII'],
+            ['none']
+        ];
+    }
+
     public function test_import_multichoice_multi() {
         $gift = "
 // multiple choice, multiple response with specified feedback for right and wrong answers
diff --git a/question/tests/bank_view_test.php b/question/tests/bank_view_test.php
new file mode 100644 (file)
index 0000000..c9987ff
--- /dev/null
@@ -0,0 +1,73 @@
+<?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 question bank view class.
+ *
+ * @package    core_question
+ * @category   test
+ * @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/editlib.php');
+
+
+/**
+ * Unit tests for the question bank view class.
+ *
+ * @copyright  2018 the Open University
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_question_bank_view_testcase extends advanced_testcase {
+
+    public function test_viewing_question_bank_should_not_load_individual_questions() {
+        $this->resetAfterTest();
+        $this->setAdminUser();
+        $generator = $this->getDataGenerator();
+        /** @var core_question_generator $questiongenerator */
+        $questiongenerator = $generator->get_plugin_generator('core_question');
+
+        // Cerate a course.
+        $course = $generator->create_course();
+        $context = context_course::instance($course->id);
+
+        // Create a question in the default category.
+        $contexts = new question_edit_contexts($context);
+        $cat = question_make_default_categories($contexts->all());
+        $questiondata = $questiongenerator->create_question('numerical', null,
+                ['name' => 'Example question', 'category' => $cat->id]);
+
+        // Ensure the qusetion is not in the cache.
+        $cache = cache::make('core', 'questiondata');
+        $cache->delete($questiondata->id);
+
+        // Generate the view.
+        $view = new core_question\bank\view($contexts, new moodle_url('/'), $course);
+        ob_start();
+        $view->display('editq', 0, 20, $cat->id . ',' . $context->id, false, false, false);
+        $html = ob_get_clean();
+
+        // Verify the output includes the expected question.
+        $this->assertContains('Example question', $html);
+
+        // Verify the qusetion has not been loaded into the cache.
+        $this->assertFalse($cache->has($questiondata->id));
+    }
+}
index 6760e7d..3f4de0e 100644 (file)
@@ -261,15 +261,17 @@ class core_question_privacy_provider_testcase extends \core_privacy\tests\provid
         // Run the delete functions as default user.
         $this->setUser();
 
+        // Find out how many questions are in the question bank to start with.
+        $questioncount = $DB->count_records('question');
+
         // The delete functions should do nothing here.
-        $this->assertCount(6, $DB->get_records('question'));
 
         // Delete for all users in context.
         provider::delete_data_for_all_users_in_context($expectedcontext);
-        $this->assertCount(6, $DB->get_records('question'));
+        $this->assertEquals($questioncount, $DB->count_records('question'));
 
         provider::delete_data_for_user($approvedcontextlist);
-        $this->assertCount(6, $DB->get_records('question'));
+        $this->assertEquals($questioncount, $DB->count_records('question'));
     }
 
     /**
@@ -321,11 +323,14 @@ class core_question_privacy_provider_testcase extends \core_privacy\tests\provid
             [$context->id]
         );
 
+        // Find out how many questions are in the question bank to start with.
+        $questioncount = $DB->count_records('question');
+
         // Delete the data and check it is removed.
         $this->setUser();
         provider::delete_data_for_user($approvedcontextlist);
 
-        $this->assertCount(5, $DB->get_records('question'));
+        $this->assertEquals($questioncount, $DB->count_records('question'));
 
         $qrecord = $DB->get_record('question', ['id' => $q1->id]);
         $this->assertEquals(0, $qrecord->createdby);
@@ -391,11 +396,14 @@ class core_question_privacy_provider_testcase extends \core_privacy\tests\provid
         $questiongenerator->update_question($q3);
         $q5 = $questiongenerator->create_question('shortanswer', null, array('category' => $othercat->id));
 
+        // Find out how many questions are in the question bank to start with.
+        $questioncount = $DB->count_records('question');
+
         // Delete the data and check it is removed.
         $this->setUser();
         provider::delete_data_for_all_users_in_context($context);
 
-        $this->assertCount(5, $DB->get_records('question'));
+        $this->assertEquals($questioncount, $DB->count_records('question'));
 
         $qrecord = $DB->get_record('question', ['id' => $q1->id]);
         $this->assertEquals(0, $qrecord->createdby);
index 984a6fb..ed7856e 100644 (file)
@@ -240,6 +240,15 @@ div#dock {
 #page-mod-lesson-view .branchbuttoncontainer .singlebutton button[type="submit"] {
     white-space: normal;
 }
+#page-mod-lesson-view {
+    .vertical .singlebutton {
+        display: block;
+        + .singlebutton {
+            margin-left: 0;
+            margin-top: 1rem;
+        }
+    }
+}
 .path-mod-lesson .generaltable td {
     vertical-align: middle;
     label {
index 324b930..75d8bfa 100644 (file)
@@ -15414,6 +15414,12 @@ div#dock {
 #page-mod-lesson-view .branchbuttoncontainer .singlebutton button[type="submit"] {
   white-space: normal; }
 
+#page-mod-lesson-view .vertical .singlebutton {
+  display: block; }
+  #page-mod-lesson-view .vertical .singlebutton + .singlebutton {
+    margin-left: 0;
+    margin-top: 1rem; }
+
 .path-mod-lesson .generaltable td {
   vertical-align: middle; }
   .path-mod-lesson .generaltable td label {
diff --git a/user/amd/build/name_page_filter.min.js b/user/amd/build/name_page_filter.min.js
deleted file mode 100644 (file)
index 2d4fcac..0000000
Binary files a/user/amd/build/name_page_filter.min.js and /dev/null differ
diff --git a/user/amd/src/name_page_filter.js b/user/amd/src/name_page_filter.js
deleted file mode 100644 (file)
index 8ebf4e7..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-// 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/>.
-
-/**
- * Name and page filter JS module for the course participants page.
- *
- * @module     core_user/name_page_filter
- * @package    core_user
- * @copyright  2017 Mihail Geshoski
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-define(['jquery', 'core_user/unified_filter'],
-        function($, UnifiedFilter) {
-
-    /**
-     * Selectors.
-     *
-     * @access private
-     * @type {{NAME_FILTERS: string, PAGE_FILTERS: string}}
-     */
-    var SELECTORS = {
-        NAME_FILTERS: 'a.letter',
-        PAGE_FILTERS: 'a.page-link'
-    };
-
-    /**
-     * Init function.
-     *
-     * @method init
-     * @private
-     */
-    var init = function() {
-        $(SELECTORS.NAME_FILTERS + ', ' + SELECTORS.PAGE_FILTERS).on('click', function(e) {
-            e.preventDefault();
-            var href = $(this).attr('href');
-            UnifiedFilter.getForm().attr('action', href);
-            UnifiedFilter.getForm().submit();
-        });
-    };
-
-    return /** @alias module:core/form-autocomplete */ {
-        // Public variables and functions.
-        /**
-         * Initialise the name and page user filter.
-         *
-         * @method init
-         */
-        'init': function() {
-            init();
-        }
-    };
-});
index 1ee8596..958cb05 100644 (file)
@@ -224,6 +224,10 @@ echo $renderer->unified_filter($course, $context, $filtersapplied, $baseurl);
 
 echo '<div class="userlist">';
 
+// Add filters to the baseurl after creating unified_filter to avoid losing them.
+foreach ($filtersapplied as $filter) {
+    $baseurl->param('unified-filters[]', $filter);
+}
 $participanttable = new \core_user\participants_table($course->id, $groupid, $lastaccess, $roleid, $enrolid, $status,
     $searchkeywords, $bulkoperations, $selectall);
 $participanttable->define_baseurl($baseurl);
@@ -245,8 +249,6 @@ if ($bulkoperations) {
 
 echo $participanttablehtml;
 
-$PAGE->requires->js_call_amd('core_user/name_page_filter', 'init');
-
 $perpageurl = clone($baseurl);
 $perpageurl->remove_params('perpage');
 if ($perpage == SHOW_ALL_PAGE_SIZE && $participanttable->totalrows > DEFAULT_PAGE_SIZE) {
index 44be4f4..985c402 100644 (file)
@@ -122,6 +122,9 @@ function user_create_user($user, $updatepassword = true, $triggerevent = true) {
         \core\event\user_created::create_from_userid($newuserid)->trigger();
     }
 
+    // Purge the associated caches.
+    cache_helper::purge_by_event('createduser');
+
     return $newuserid;
 }
 
index 29cb7b7..25b719d 100644 (file)
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-define ('PROFILE_VISIBLE_ALL',     '2'); // Only visible for users with moodle/user:update capability.
-define ('PROFILE_VISIBLE_PRIVATE', '1'); // Either we are viewing our own profile or we have moodle/user:update capability.
-define ('PROFILE_VISIBLE_NONE',    '0'); // Only visible for moodle/user:update capability.
+/**
+ * Visible to anyone who can view the user.
+ * Editable by the profile owner if they have the moodle/user:editownprofile capability
+ * or any user with the moodle/user:update capability.
+ */
+define('PROFILE_VISIBLE_ALL', '2');
+/**
+ * Visible to the profile owner or anyone with the moodle/user:viewalldetails capability.
+ * Editable by the profile owner if they have the moodle/user:editownprofile capability
+ * or any user with moodle/user:viewalldetails and moodle/user:update capabilities.
+ */
+define('PROFILE_VISIBLE_PRIVATE', '1');
+/**
+ * Only visible to users with the moodle/user:viewalldetails capability.
+ * Only editable by users with the moodle/user:viewalldetails and moodle/user:update capabilities.
+ */
+define('PROFILE_VISIBLE_NONE', '0');
 
 /**
  * Base class for the customisable profile fields.
@@ -131,15 +145,14 @@ class profile_field_base {
      * @return bool
      */
     public function edit_field($mform) {
-        if ($this->field->visible != PROFILE_VISIBLE_NONE
-          or has_capability('moodle/user:update', context_system::instance())) {
-
-            $this->edit_field_add($mform);
-            $this->edit_field_set_default($mform);
-            $this->edit_field_set_required($mform);
-            return true;
+        if (!$this->is_editable()) {
+            return false;
         }
-        return false;
+
+        $this->edit_field_add($mform);
+        $this->edit_field_set_default($mform);
+        $this->edit_field_set_required($mform);
+        return true;
     }
 
     /**
@@ -148,12 +161,12 @@ class profile_field_base {
      * @return bool
      */
     public function edit_after_data($mform) {
-        if ($this->field->visible != PROFILE_VISIBLE_NONE
-          or has_capability('moodle/user:update', context_system::instance())) {
-            $this->edit_field_set_locked($mform);
-            return true;
+        if (!$this->is_editable()) {
+            return false;
         }
-        return false;
+
+        $this->edit_field_set_locked($mform);
+        return true;
     }
 
     /**
@@ -429,6 +442,31 @@ class profile_field_base {
         }
     }
 
+    /**
+     * Check if the field data is editable for the current user
+     * This method should not generally be overwritten by child classes.
+     * @return bool
+     */
+    public function is_editable() {
+        global $USER;
+
+        if (!$this->is_visible()) {
+            return false;
+        }
+
+        $systemcontext = context_system::instance();
+
+        if ($this->userid == $USER->id && has_capability('moodle/user:editownprofile', $systemcontext)) {
+            return true;
+        }
+
+        if (has_capability('moodle/user:update', $systemcontext)) {
+            return true;
+        }
+
+        return false;
+    }
+
     /**
      * Check if the field data is considered empty
      * @internal This method should not generally be overwritten by child classes.
@@ -565,27 +603,25 @@ function profile_load_data($user) {
  * @param int $userid id of user whose profile is being edited.
  */
 function profile_definition($mform, $userid = 0) {
-    global $CFG, $DB;
-
-    // If user is "admin" fields are displayed regardless.
-    $update = has_capability('moodle/user:update', context_system::instance());
-
     $categories = profile_get_user_fields_with_data_by_category($userid);
     foreach ($categories as $categoryid => $fields) {
         // Check first if *any* fields will be displayed.
-        $display = false;
+        $fieldstodisplay = [];
+
         foreach ($fields as $formfield) {
-            if ($formfield->is_visible()) {
-                $display = true;
+            if ($formfield->is_editable()) {
+                $fieldstodisplay[] = $formfield;
             }
         }
 
+        if (empty($fieldstodisplay)) {
+            continue;
+        }
+
         // Display the header and the fields.
-        if ($display or $update) {
-            $mform->addElement('header', 'category_'.$categoryid, format_string($formfield->get_category_name()));
-            foreach ($fields as $formfield) {
-                $formfield->edit_field($mform);
-            }
+        $mform->addElement('header', 'category_'.$categoryid, format_string($fields[0]->get_category_name()));
+        foreach ($fieldstodisplay as $formfield) {
+            $formfield->edit_field($mform);
         }
     }
 }
diff --git a/user/tests/behat/custom_profile_fields.feature b/user/tests/behat/custom_profile_fields.feature
new file mode 100644 (file)
index 0000000..9359113
--- /dev/null
@@ -0,0 +1,188 @@
+@core @core_user
+Feature: Custom profile fields should be visible and editable by those with the correct permissions.
+
+  Background: Attempting to self-register as a new user with empty names
+    Given the following "users" exist:
+      | username            | firstname           | lastname | email                           |
+      | userwithinformation | userwithinformation | 1        | userwithinformation@example.com |
+    And the following "courses" exist:
+      | fullname | shortname | category | groupmode |
+      | Course 1 | C1        | 0        | 1         |
+    And the following "course enrolments" exist:
+      | user                | course | role    |
+      | userwithinformation | C1     | student |
+
+    And I log in as "admin"
+    And I navigate to "User profile fields" node in "Site administration > Users > Accounts"
+    And I set the field "datatype" to "Text input"
+    And I set the following fields to these values:
+      | Short name                    | notvisible_field |
+      | Name                          | notvisible_field |
+      | Who is this field visible to? | Not visible      |
+    And I click on "Save changes" "button"
+
+    And I set the field "datatype" to "Text input"
+    And I set the following fields to these values:
+      | Short name                    | uservisible_field |
+      | Name                          | uservisible_field |
+      | Who is this field visible to? | Visible to user   |
+    And I click on "Save changes" "button"
+
+    And I set the field "datatype" to "Text input"
+    And I set the following fields to these values:
+      | Short name                    | everyonevisible_field |
+      | Name                          | everyonevisible_field |
+      | Who is this field visible to? | Visible to everyone   |
+    And I click on "Save changes" "button"
+
+    And I navigate to "Browse list of users" node in "Site administration > Users > Accounts"
+    And I click on ".icon[title=Edit]" "css_element" in the "userwithinformation@example.com" "table_row"
+    And I expand all fieldsets
+    And I set the field "notvisible_field" to "notvisible_field_information"
+    And I set the field "uservisible_field" to "uservisible_field_information"
+    And I set the field "everyonevisible_field" to "everyonevisible_field_information"
+    And I click on "Update profile" "button"
+    And I log out
+
+  @javascript
+  Scenario: User with moodle/user:update but without moodle/user:viewalldetails can only update visible profile fields.
+    Given the following "roles" exist:
+      | name         | shortname   | description | archetype |
+      | Update Users | updateusers | updateusers |           |
+    And the following "permission overrides" exist:
+      | capability         | permission | role        | contextlevel | reference |
+      | moodle/user:update | Allow      | updateusers | System       |           |
+    And the following "users" exist:
+      | username         | firstname   | lastname | email                   |
+      | user_updateusers | updateusers | 1        | updateusers@example.com |
+    And the following "role assigns" exist:
+      | user             | role        | contextlevel | reference |
+      | user_updateusers | updateusers | System       |           |
+    And the following "course enrolments" exist:
+      | user             | course | role           |
+      | user_updateusers | C1     | editingteacher |
+    And I log in as "user_updateusers"
+    And I am on "Course 1" course homepage
+    And I navigate to course participants
+    And I follow "userwithinformation 1"
+
+    Then I should see "everyonevisible_field"
+    And I should see "everyonevisible_field_information"
+    And I should not see "uservisible_field"
+    And I should not see "uservisible_field_information"
+    And I should not see "notvisible_field"
+    And I should not see "notvisible_field_information"
+    And I follow "Edit profile"
+    And the following fields match these values:
+      | everyonevisible_field | everyonevisible_field_information |
+    And I should not see "uservisible_field"
+    And I should not see "notvisible_field"
+
+  @javascript
+  Scenario: User with moodle/user:viewalldetails but without moodle/user:update can view all profile fields.
+    Given the following "roles" exist:
+      | name             | shortname      | description    | archetype |
+      | View All Details | viewalldetails | viewalldetails |           |
+    And the following "permission overrides" exist:
+      | capability                 | permission | role           | contextlevel | reference |
+      | moodle/user:viewalldetails | Allow      | viewalldetails | System       |           |
+    And the following "users" exist:
+      | username            | firstname      | lastname | email                      |
+      | user_viewalldetails | viewalldetails | 1        | viewalldetails@example.com |
+    And the following "role assigns" exist:
+      | user                | role           | contextlevel | reference |
+      | user_viewalldetails | viewalldetails | System       |           |
+    And the following "course enrolments" exist:
+      | user                | course | role           |
+      | user_viewalldetails | C1     | editingteacher |
+    And I log in as "user_viewalldetails"
+    And I am on "Course 1" course homepage
+    And I navigate to course participants
+    And I follow "userwithinformation 1"
+
+    Then I should see "everyonevisible_field"
+    And I should see "everyonevisible_field_information"
+    And I should see "uservisible_field"
+    And I should see "uservisible_field_information"
+    And I should see "notvisible_field"
+    And I should see "notvisible_field_information"
+    And I should not see "Edit profile"
+
+  @javascript
+  Scenario: User with moodle/user:viewalldetails and moodle/user:update capabilities can view and edit all profile fields.
+    Given the following "roles" exist:
+      | name                              | shortname                    | description                  | archetype |
+      | View All Details and Update Users | viewalldetailsandupdateusers | viewalldetailsandupdateusers |           |
+    And the following "permission overrides" exist:
+      | capability                 | permission | role                         | contextlevel | reference |
+      | moodle/user:viewalldetails | Allow      | viewalldetailsandupdateusers | System       |           |
+      | moodle/user:update         | Allow      | viewalldetailsandupdateusers | System       |           |
+    And the following "users" exist:
+      | username                          | firstname                    | lastname | email                                    |
+      | user_viewalldetailsandupdateusers | viewalldetailsandupdateusers | 1        | viewalldetailsandupdateusers@example.com |
+    And the following "role assigns" exist:
+      | user                              | role                         | contextlevel | reference |
+      | user_viewalldetailsandupdateusers | viewalldetailsandupdateusers | System       |           |
+    And the following "course enrolments" exist:
+      | user                              | course | role           |
+      | user_viewalldetailsandupdateusers | C1     | editingteacher |
+    And I log in as "user_viewalldetailsandupdateusers"
+    And I am on "Course 1" course homepage
+    And I navigate to course participants
+    And I follow "userwithinformation 1"
+
+    Then I should see "everyonevisible_field"
+    And I should see "everyonevisible_field_information"
+    And I should see "uservisible_field"
+    And I should see "uservisible_field_information"
+    And I should see "notvisible_field"
+    And I should see "notvisible_field_information"
+    And I follow "Edit profile"
+    And the following fields match these values:
+      | everyonevisible_field | everyonevisible_field_information |
+      | uservisible_field     | uservisible_field_information     |
+      | notvisible_field      | notvisible_field_information      |
+
+  @javascript
+  Scenario: Users can view and edit custom profile fields except those marked as not visible.
+    Given I log in as "userwithinformation"
+    And I follow "Profile" in the user menu
+
+    Then I should see "everyonevisible_field"
+    And I should see "everyonevisible_field_information"
+    And I should see "uservisible_field"
+    And I should see "uservisible_field_information"
+    And I should not see "notvisible_field"
+    And I should not see "notvisible_field_information"
+
+    And I click on "Edit profile" "link" in the "region-main" "region"
+    Then the following fields match these values:
+      | everyonevisible_field | everyonevisible_field_information |
+      | uservisible_field     | uservisible_field_information     |
+    And I should not see "notvisible_field"
+    And I should not see "notvisible_field_information"
+
+  @javascript
+  Scenario: Users can view but not edit custom profile fields when denied the edit own profile capability.
+    Given the following "roles" exist:
+      | name                | shortname          | description        | archetype |
+      | Deny editownprofile | denyeditownprofile | denyeditownprofile |           |
+
+    And the following "permission overrides" exist:
+      | capability                 | permission | role               | contextlevel | reference |
+      | moodle/user:editownprofile | Prohibit   | denyeditownprofile | System       |           |
+    And the following "role assigns" exist:
+      | user                | role               | contextlevel | reference |
+      | userwithinformation | denyeditownprofile | System       |           |
+
+    And I log in as "userwithinformation"
+    And I follow "Profile" in the user menu
+
+    Then I should see "everyonevisible_field"
+    And I should see "everyonevisible_field_information"
+    And I should see "uservisible_field"
+    And I should see "uservisible_field_information"
+    And I should not see "notvisible_field"
+    And I should not see "notvisible_field_information"
+
+    And I should not see "Edit profile"
\ No newline at end of file
index 5e022b5..073ce49 100644 (file)
@@ -117,6 +117,21 @@ Feature: Course participants can be filtered
     And I should see "Student 4" in the "participants" "table"
     And I should not see "Teacher 1" in the "participants" "table"
 
+  @javascript
+  Scenario: Reorder users without losing filter
+    Given I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    And I navigate to course participants
+    And I open the autocomplete suggestions list
+    And I click on "Role: Student" item in the autocomplete list
+    When I click on "Surname" "link"
+    Then I should see "Role: Student"
+    And I should see "Student 1" in the "participants" "table"
+    And I should see "Student 2" in the "participants" "table"
+    And I should see "Student 3" in the "participants" "table"
+    And I should see "Student 4" in the "participants" "table"
+    And I should not see "Teacher 1" in the "participants" "table"
+
   @javascript
   Scenario: Rendering filter options for teachers in a course that don't support groups
     Given I log in as "teacher1"
diff --git a/user/tests/behat/filter_participants_showall.feature b/user/tests/behat/filter_participants_showall.feature
new file mode 100644 (file)
index 0000000..7f1ac9a
--- /dev/null
@@ -0,0 +1,90 @@
+@core @core_user
+Feature: Course participants can be filtered to display all the users
+  In order to filter the list of course participants
+  As a user
+  I need to visit the course participants page, apply the appropriate filters and show all users per page
+
+  Background:
+    Given the following "courses" exist:
+      | fullname | shortname | groupmode |
+      | Course 1 | C1        |     1     |
+      | Course 2 | C2        |     0     |
+    And the following "users" exist:
+      | username  | firstname | lastname | email                 |
+      | student1  | Student   | 1        | student1@example.com  |
+      | student2  | Student   | 2        | student2@example.com  |
+      | student3  | Student   | 3        | student3@example.com  |
+      | student4  | Student   | 4        | student4@example.com  |
+      | student5  | Student   | 5        | student5@example.com  |
+      | student6  | Student   | 6        | student6@example.com  |
+      | student7  | Student   | 7        | student7@example.com  |
+      | student8  | Student   | 8        | student8@example.com  |
+      | student9  | Student   | 9        | student9@example.com  |
+      | student10 | Student   | 10       | student10@example.com |
+      | student11 | Student   | 11       | student11@example.com |
+      | student12 | Student   | 12       | student12@example.com |
+      | student13 | Student   | 13       | student13@example.com |
+      | student14 | Student   | 14       | student14@example.com |
+      | student15 | Student   | 15       | student15@example.com |
+      | student16 | Student   | 16       | student16@example.com |
+      | student17 | Student   | 17       | student17@example.com |
+      | student18 | Student   | 18       | student18@example.com |
+      | student19 | Student   | 19       | student19@example.com |
+      | student20 | Student   | 20       | student20@example.com |
+      | student21 | Student   | 21       | student21@example.com |
+      | student22 | Student   | 22       | student22@example.com |
+      | student23 | Student   | 23       | student23@example.com |
+      | student24 | Student   | 24       | student24@example.com |
+      | teacher1  | Teacher   | 1        | teacher1@example.com |
+    And the following "course enrolments" exist:
+      | user      | course | role           | status | timeend       |
+      | student1  | C1     | student        |    0   |               |
+      | student2  | C1     | student        |    1   |               |
+      | student3  | C1     | student        |    0   |               |
+      | student4  | C1     | student        |    0   |               |
+      | student5  | C1     | student        |    0   |               |
+      | student6  | C1     | student        |    0   |               |
+      | student7  | C1     | student        |    0   |               |
+      | student8  | C1     | student        |    0   |               |
+      | student9  | C1     | student        |    0   |               |
+      | student10 | C1     | student        |    0   |               |
+      | student11 | C1     | student        |    0   |               |
+      | student12 | C1     | student        |    0   |               |
+      | student13 | C1     | student        |    0   |               |
+      | student14 | C1     | student        |    0   |               |
+      | student15 | C1     | student        |    0   |               |
+      | student16 | C1     | student        |    0   |               |
+      | student17 | C1     | student        |    0   |               |
+      | student18 | C1     | student        |    0   |               |
+      | student19 | C1     | student        |    0   |               |
+      | student20 | C1     | student        |    0   |               |
+      | student21 | C1     | student        |    0   |               |
+      | student22 | C1     | student        |    0   |               |
+      | student23 | C1     | student        |    0   |               |
+      | student24 | C1     | student        |    0   |               |
+      | student1  | C2     | student        |    0   |               |
+      | student2  | C2     | student        |    0   |               |
+      | student3  | C2     | student        |    0   |               |
+      | teacher1  | C1     | editingteacher |    0   |               |
+      | teacher1  | C2     | editingteacher |    0   |               |
+    And the following "groups" exist:
+      | name    | course | idnumber |
+      | Group 1 | C1     | G1       |
+      | Group 2 | C1     | G2       |
+    And the following "group members" exist:
+      | user     | group |
+      | student2 | G1    |
+      | student2 | G2    |
+      | student3 | G2    |
+
+  @javascript
+  Scenario: Show all filtered users for a course
+    Given I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    And I navigate to course participants
+    When I open the autocomplete suggestions list
+    And I click on "Role: Student" item in the autocomplete list
+    And I click on "Show all 24" "link"
+    Then I should see "Role: Student"
+    And I should see "Number of participants: 24" in the "//div[@class='userlist']" "xpath_element"
+    And I should see "Show 20 per page"
index 99eb18d..c7589be 100644 (file)
 
 defined('MOODLE_INTERNAL') || die();
 
-$version  = 2018071300.00;              // YYYYMMDD      = weekly release date of this DEV branch.
+$version  = 2018072000.01;              // YYYYMMDD      = weekly release date of this DEV branch.
                                         //         RR    = release increments - 00 in DEV branches.
                                         //           .XX = incremental changes.
 
-$release  = '3.6dev (Build: 20180713)'; // Human-friendly version name
+$release  = '3.6dev (Build: 20180720)'; // Human-friendly version name
 
 $branch   = '36';                       // This version's branch.
 $maturity = MATURITY_ALPHA;             // This version's maturity level.