Merge branch 'MDL-70966-master' of git://github.com/marinaglancy/moodle
authorSara Arjona <sara@moodle.com>
Wed, 3 Mar 2021 08:30:41 +0000 (09:30 +0100)
committerSara Arjona <sara@moodle.com>
Wed, 3 Mar 2021 08:30:41 +0000 (09:30 +0100)
85 files changed:
admin/environment.xml
admin/tool/behat/tests/behat/data_generators.feature
admin/tool/customlang/locallib.php
admin/tool/xmldb/actions/create_xml_file/create_xml_file.class.php
analytics/classes/default_bulk_actions.php
auth/email/auth.php
auth/ldap/auth.php
auth/oauth2/classes/auth.php
backup/util/settings/tests/settings_test.php
backup/util/ui/classes/copy/copy.php
badges/renderer.php
badges/tests/badgeslib_test.php
blocks/online_users/block_online_users.php
blocks/online_users/classes/fetcher.php
blocks/online_users/db/access.php
blocks/online_users/lang/en/block_online_users.php
blocks/online_users/styles.css
blocks/online_users/tests/behat/block_online_users_dashboard.feature
blocks/online_users/tests/behat/block_online_users_frontpage.feature
blocks/online_users/version.php
contentbank/classes/content.php
contentbank/classes/external/rename_content.php
contentbank/tests/contenttype_test.php
contentbank/tests/external/rename_content_test.php
course/lib.php
course/renderer.php
course/tests/behat/course_browsing.feature
enrol/externallib.php
enrol/self/tests/behat/self_enrolment.feature
enrol/tests/externallib_test.php
lang/en/admin.php
lang/en/debug.php
lang/en/enrol.php
lang/en/error.php
lang/en/hub.php
lang/en/message.php
lang/en/moodle.php
lang/en/plugin.php
lang/en/question.php
lang/en/repository.php
lang/en/role.php
lang/en/webservice.php
lib/badgeslib.php
lib/behat/lib.php
lib/classes/user.php
lib/db/upgrade.php
lib/dml/tests/fixtures/read_slave_moodle_database.php
lib/environmentlib.php
lib/externallib.php
lib/filestorage/file_exceptions.php
lib/filestorage/file_system.php
lib/filestorage/tests/file_storage_test.php
lib/filestorage/tests/file_system_filedir_test.php
lib/oauthlib.php
lib/outputrenderers.php
lib/pear/HTML/QuickForm/date.php
lib/pear/README_MOODLE.txt
lib/setuplib.php
lib/xmldb/xmldb_structure.php
mod/assign/lang/en/assign.php
mod/data/field/textarea/field.class.php
mod/data/tests/behat/add_entries.feature
mod/feedback/lang/en/feedback.php
mod/forum/classes/local/renderers/discussion_list.php
mod/forum/view.php
mod/quiz/lang/en/quiz.php
mod/workshop/classes/external.php
mod/workshop/db/install.xml
mod/workshop/db/upgrade.php
mod/workshop/form/accumulative/assessment_form.php
mod/workshop/form/accumulative/lib.php
mod/workshop/version.php
question/classes/bank/question_name_idnumber_tags_column.php
question/format/xml/format.php
question/format/xml/tests/xmlformat_test.php
question/tests/behat/sort_questions.feature
question/type/essay/tests/behat/edit_min_max_fields.feature
question/type/essay/tests/behat/import.feature
question/type/essay/tests/fixtures/testquestion.moodle.xml
theme/boost/scss/moodle/core.scss
theme/boost/scss/moodle/message.scss
theme/boost/style/moodle.css
theme/classic/style/moodle.css
user/classes/output/participants_filter.php
version.php

index 857a8d3..3a71aef 100644 (file)
       <VENDOR name="oracle" version="11.2" />
     </DATABASE>
     <PHP version="7.1.0" level="required">
+      <RESTRICT function="restrict_php_version_80" message="unsupportedphpversion80" />
     </PHP>
     <PCREUNICODE level="optional">
       <FEEDBACK>
       <VENDOR name="oracle" version="11.2" />
     </DATABASE>
     <PHP version="7.2.0" level="required">
+      <RESTRICT function="restrict_php_version_80" message="unsupportedphpversion80" />
     </PHP>
     <PCREUNICODE level="optional">
       <FEEDBACK>
       <VENDOR name="oracle" version="11.2" />
     </DATABASE>
     <PHP version="7.2.0" level="required">
+      <RESTRICT function="restrict_php_version_80" message="unsupportedphpversion80" />
     </PHP>
     <PCREUNICODE level="optional">
       <FEEDBACK>
index 3617ccc..9423a39 100644 (file)
@@ -156,7 +156,7 @@ Feature: Set up contextual data for tests
     And I log in as "user5"
     And I should see "You are logged in as"
     And I am on "Course 1" course homepage
-    And I should see "You can not enrol yourself in this course."
+    And I should see "You cannot enrol yourself in this course."
 
   Scenario: Add modules
     Given the following "courses" exist:
index 5bbb8c2..0b5df65 100644 (file)
@@ -39,7 +39,7 @@ class tool_customlang_utils {
     const ROUGH_NUMBER_OF_STRINGS = 16500;
 
     /** @var array cache of {@link self::list_components()} results */
-    protected static $components = null;
+    private static $components = null;
 
     /**
      * This class can not be instantiated
@@ -54,28 +54,30 @@ class tool_customlang_utils {
      */
     public static function list_components() {
 
-        $list['moodle'] = 'core';
+        if (self::$components === null) {
+            $list['moodle'] = 'core';
 
-        $coresubsystems = core_component::get_core_subsystems();
-        ksort($coresubsystems); // should be but just in case
-        foreach ($coresubsystems as $name => $location) {
-            $list[$name] = 'core_'.$name;
-        }
+            $coresubsystems = core_component::get_core_subsystems();
+            ksort($coresubsystems); // Should be but just in case.
+            foreach ($coresubsystems as $name => $location) {
+                $list[$name] = 'core_' . $name;
+            }
 
-        $plugintypes = core_component::get_plugin_types();
-        foreach ($plugintypes as $type => $location) {
-            $pluginlist = core_component::get_plugin_list($type);
-            foreach ($pluginlist as $name => $ununsed) {
-                if ($type == 'mod') {
-                    // Plugin names are now automatically validated.
-                    $list[$name] = $type.'_'.$name;
-                } else {
-                    $list[$type.'_'.$name] = $type.'_'.$name;
+            $plugintypes = core_component::get_plugin_types();
+            foreach ($plugintypes as $type => $location) {
+                $pluginlist = core_component::get_plugin_list($type);
+                foreach ($pluginlist as $name => $ununsed) {
+                    if ($type == 'mod') {
+                        // Plugin names are now automatically validated.
+                        $list[$name] = $type . '_' . $name;
+                    } else {
+                        $list[$type . '_' . $name] = $type . '_' . $name;
+                    }
                 }
             }
+            self::$components = $list;
         }
-
-        return $list;
+        return self::$components;
     }
 
     /**
@@ -211,14 +213,18 @@ class tool_customlang_utils {
             return false;
         }
 
-        // get all customized strings from updated components
+        list($insql, $inparams) = $DB->get_in_or_equal(self::list_components());
+
+        // Get all customized strings from updated valid components.
         $sql = "SELECT s.*, c.name AS component
                   FROM {tool_customlang} s
                   JOIN {tool_customlang_components} c ON s.componentid = c.id
                  WHERE s.lang = ?
                        AND (s.local IS NOT NULL OR s.modified = 1)
+                       AND c.name $insql
               ORDER BY componentid, stringid";
-        $strings = $DB->get_records_sql($sql, array($lang));
+        array_unshift($inparams, $lang);
+        $strings = $DB->get_records_sql($sql, $inparams);
 
         $files = array();
         foreach ($strings as $string) {
@@ -336,11 +342,9 @@ EOF
      * @return string|boolean filename eg 'moodle.php' or 'workshop.php', false if not found
      */
     protected static function get_component_filename($component) {
-        if (is_null(self::$components)) {
-            self::$components = self::list_components();
-        }
+
         $return = false;
-        foreach (self::$components as $legacy => $normalized) {
+        foreach (self::list_components() as $legacy => $normalized) {
             if ($component === $normalized) {
                 $return = $legacy.'.php';
                 break;
index 1f76c1b..4b74613 100644 (file)
@@ -93,7 +93,7 @@ class create_xml_file extends XMLDBAction {
         $c.= '        </KEYS>' . "\n";
         $c.= '      </TABLE>' . "\n";
         $c.= '    </TABLES>' . "\n";
-        $c.= '  </XMLDB>';
+        $c.= '  </XMLDB>' . "\n";
 
         if (!file_put_contents($file, $c)) {
             $errormsg = 'Error creando fichero ' . $file;
index e07d389..dd4388a 100644 (file)
@@ -116,7 +116,7 @@ class default_bulk_actions {
      *
      * @return array
      */
-    private static final function bulk_action_base_attrs() {
+    private static function bulk_action_base_attrs() {
         return [
             'disabled' => 'disabled',
             'data-toggle' => 'action',
index f207f03..1998ba0 100644 (file)
@@ -178,10 +178,10 @@ class auth_plugin_email extends auth_plugin_base {
             if ($user->auth != $this->authtype) {
                 return AUTH_CONFIRM_ERROR;
 
-            } else if ($user->secret == $confirmsecret && $user->confirmed) {
+            } else if ($user->secret === $confirmsecret && $user->confirmed) {
                 return AUTH_CONFIRM_ALREADY;
 
-            } else if ($user->secret == $confirmsecret) {   // They have provided the secret key to get in
+            } else if ($user->secret === $confirmsecret) {   // They have provided the secret key to get in
                 $DB->set_field("user", "confirmed", 1, array("id"=>$user->id));
 
                 if ($wantsurl = get_user_preferences('auth_email_wantsurl', false, $user)) {
@@ -257,5 +257,3 @@ class auth_plugin_email extends auth_plugin_base {
     }
 
 }
-
-
index dfdd0b4..8ab37e4 100644 (file)
@@ -598,10 +598,10 @@ class auth_plugin_ldap extends auth_plugin_base {
             if ($user->auth != $this->authtype) {
                 return AUTH_CONFIRM_ERROR;
 
-            } else if ($user->secret == $confirmsecret && $user->confirmed) {
+            } else if ($user->secret === $confirmsecret && $user->confirmed) {
                 return AUTH_CONFIRM_ALREADY;
 
-            } else if ($user->secret == $confirmsecret) {   // They have provided the secret key to get in
+            } else if ($user->secret === $confirmsecret) {   // They have provided the secret key to get in
                 if (!$this->user_activate($username)) {
                     return AUTH_CONFIRM_FAIL;
                 }
index cf6fcfd..4152729 100644 (file)
@@ -372,10 +372,10 @@ class auth extends \auth_plugin_base {
             if ($user->auth != $this->authtype) {
                 return AUTH_CONFIRM_ERROR;
 
-            } else if ($user->secret == $confirmsecret && $user->confirmed) {
+            } else if ($user->secret === $confirmsecret && $user->confirmed) {
                 return AUTH_CONFIRM_ALREADY;
 
-            } else if ($user->secret == $confirmsecret) {   // They have provided the secret key to get in.
+            } else if ($user->secret === $confirmsecret) {   // They have provided the secret key to get in.
                 $DB->set_field("user", "confirmed", 1, array("id" => $user->id));
                 return AUTH_CONFIRM_OK;
             }
index 63999fe..d151fe3 100644 (file)
@@ -504,10 +504,9 @@ class mock_course_backup_setting extends course_backup_setting {
  * @param string $errstr
  * @param string $errfile
  * @param int $errline
- * @param array $errcontext
  * @return null
  */
-function backup_setting_error_handler($errno, $errstr, $errfile, $errline, $errcontext) {
+function backup_setting_error_handler($errno, $errstr, $errfile, $errline) {
     if ($errno !== E_RECOVERABLE_ERROR) {
         // Currently we only want to deal with type hinting errors
         return false;
index 496cf02..fbf2695 100644 (file)
@@ -95,7 +95,7 @@ class copy  {
      * @param \stdClass $formdata Data from the validated course copy form.
      * @return array $keptroles The roles to keep.
      */
-    private final function get_enrollment_roles(\stdClass $formdata): array {
+    private function get_enrollment_roles(\stdClass $formdata): array {
         $keptroles = array();
 
         foreach ($formdata as $key => $value) {
@@ -114,7 +114,7 @@ class copy  {
      * @return \stdClass $copydata Data required for course copy operations.
      * @throws \moodle_exception If one of the required copy fields is missing
      */
-    private final function get_copy_data(\stdClass $formdata): \stdClass {
+    private function get_copy_data(\stdClass $formdata): \stdClass {
         $copydata = new \stdClass();
 
         foreach ($this->copyfields as $field) {
index 13415fd..b98aae1 100644 (file)
@@ -322,7 +322,15 @@ class core_badges_renderer extends plugin_renderer_base {
         $badgeclass = $ibadge->badgeclass;
         $badge = new badge($ibadge->badgeid);
         $now = time();
-        $expiration = isset($issued['expires']) ? $issued['expires'] : $now + 86400;
+        if (isset($issued['expires'])) {
+            if (!is_numeric($issued['expires'])) {
+                $issued['expires'] = strtotime($issued['expires']);
+            }
+            $expiration = $issued['expires'];
+        } else {
+            $expiration = $now + 86400;
+        }
+
         $badgeimage = is_array($badgeclass['image']) ? $badgeclass['image']['id'] : $badgeclass['image'];
         $languages = get_string_manager()->get_list_of_languages();
 
@@ -432,9 +440,6 @@ class core_badges_renderer extends plugin_renderer_base {
         }
         $dl[get_string('dateawarded', 'badges')] = userdate($issued['issuedOn']);
         if (isset($issued['expires'])) {
-            if (!is_numeric($issued['expires'])) {
-                $issued['expires'] = strtotime($issued['expires']);
-            }
             if ($issued['expires'] < $now) {
                 $dl[get_string('expirydate', 'badges')] = userdate($issued['expires']) . get_string('warnexpired', 'badges');
 
index f764c8a..2c09684 100644 (file)
@@ -1145,6 +1145,7 @@ class core_badges_badgeslib_testcase extends advanced_testcase {
         $data['password'] = 'test';
         if ($isadmin || $updatetest) {
             $this->setAdminUser();
+            $lastmax = $DB->get_field_sql('SELECT MAX(sortorder) FROM {badge_external_backpack}');
             $backpack = badges_create_site_backpack((object) $data);
         }
 
@@ -1156,8 +1157,9 @@ class core_badges_badgeslib_testcase extends advanced_testcase {
                 badges_update_site_backpack($backpack, (object)$data);
             }
             $record = $DB->get_record('badge_external_backpack', ['id' => $backpack]);
-            $this->assertEquals($record->backpackweburl, $data['backpackweburl']);
-            $this->assertEquals($record->backpackapiurl, $data['backpackapiurl']);
+            $this->assertEquals($data['backpackweburl'], $record->backpackweburl);
+            $this->assertEquals($data['backpackapiurl'], $record->backpackapiurl);
+            $this->assertEquals($lastmax + 1, $record->sortorder);
             $record = $DB->get_record('badge_backpack', ['userid' => 0]);
             $this->assertNotEmpty($record);
         } else {
index e44468c..2fd93d4 100644 (file)
@@ -88,14 +88,23 @@ class block_online_users extends block_base {
 
         $this->content->text = '<div class="info">'.$usercount.' ('.$periodminutes.')</div>';
 
-        // Verify if we can see the list of users, if not just print number of users
-        if (!has_capability('block/online_users:viewlist', $this->page->context)) {
+        // Verify if we can see the list of users, if not just print number of users.
+        // If the current user is not logged in OR it's a guest then don't show any users.
+        if (!has_capability('block/online_users:viewlist', $this->page->context)
+                || isguestuser() || !isloggedin()) {
             return $this->content;
         }
 
         $userlimit = 50; // We'll just take the most recent 50 maximum.
+        $initialcount = 0;
         if ($users = $onlineusers->get_users($userlimit)) {
+            require_once($CFG->dirroot . '/user/lib.php');
+            $initialcount = count($users);
             foreach ($users as $user) {
+                if (!user_can_view_profile($user)) {
+                    unset($users[$user->id]);
+                    continue;
+                }
                 $users[$user->id]->fullname = fullname($user);
             }
         } else {
@@ -153,6 +162,14 @@ class block_online_users extends block_base {
                 }
                 $this->content->text .= "</li>\n";
             }
+            if ($initialcount - count($users) > 0) {
+                $this->content->text .= '<li class="listentry"><div class="otherusers">';
+                $this->content->text .= html_writer::span(
+                    get_string('otherusers', 'block_online_users', $initialcount - count($users))
+                );
+                $this->content->text .= "</div>";
+                $this->content->text .= "</li>\n";
+            }
             $this->content->text .= '</ul><div class="clearer"><!-- --></div>';
         }
 
index a18e625..a63e0ec 100644 (file)
@@ -86,7 +86,7 @@ class fetcher {
         }
         $params = array();
 
-        $userfields = \user_picture::fields('u', array('username'));
+        $userfields = \user_picture::fields('u', array('username', 'deleted'));
 
         // Add this to the SQL to show only group users.
         if ($currentgroup !== null) {
index f238b73..6e8b95b 100644 (file)
@@ -55,7 +55,7 @@ $capabilities = array(
         'contextlevel' => CONTEXT_BLOCK,
         'archetypes' => array(
             'user' => CAP_ALLOW,
-            'guest' => CAP_ALLOW,
+            'guest' => CAP_PREVENT,
             'student' => CAP_ALLOW,
             'teacher' => CAP_ALLOW,
             'editingteacher' => CAP_ALLOW,
index f34f5d6..bf9cf6c 100644 (file)
@@ -23,6 +23,7 @@
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
+$string['otherusers'] = 'Other Users ({$a})';
 $string['onlinestatushiding_desc'] = 'If enabled, users have the option to hide their online status from other users.';
 $string['configtimetosee'] = 'Number of minutes determining the period of inactivity after which a user is no longer considered to be online.';
 $string['onlinestatushiding'] = 'Online status hiding';
index bebee5a..8c89508 100644 (file)
@@ -7,6 +7,10 @@
     position: relative;
 }
 
+.block_online_users .content .list li.listentry .otherusers {
+    margin-left: 1.5rem;
+}
+
 .block_online_users .content .list li.listentry .user .userpicture {
     vertical-align: text-bottom;
 }
index 2d32fc6..d15e46a 100644 (file)
@@ -1,5 +1,6 @@
 @block @block_online_users
 Feature: The online users block allow you to see who is currently online on dashboard
+  There should be some commonality for the users to show up
   In order to use the online users block on the dashboard
   As a user
   I can view the online users block on my dashboard
@@ -10,6 +11,14 @@ Feature: The online users block allow you to see who is currently online on dash
       | teacher1 | Teacher   | 1        | teacher1@example.com |
       | student1 | Student   | 1        | student1@example.com |
       | student2 | Student   | 2        | student2@example.com |
+    And the following "courses" exist:
+      | fullname | shortname | category |
+      | Course 1 | C1        | 0        |
+    And the following "course enrolments" exist:
+      | user | course | role           |
+      | teacher1 | C1 | editingteacher |
+      | student1 | C1 | student        |
+      | student2 | C1 | student        |
 
   Scenario: View the online users block on the dashboard and see myself
     Given I log in as "teacher1"
index ca817e8..1f141b0 100644 (file)
@@ -1,14 +1,22 @@
 @block @block_online_users
 Feature: The online users block allow you to see who is currently online on frontpage
-  In order to enable the online users block on the front page page
+  There should be some commonality for the users to show up
+  In order to enable the online users block on the frontpage
   As an admin
-  I can add the online users block to the front page page
+  I can add the online users block to the frontpage
 
   Background:
     Given the following "users" exist:
       | username | firstname | lastname | email                |
       | student1 | Student   | 1        | student1@example.com |
       | student2 | Student   | 2        | student2@example.com |
+    And the following "courses" exist:
+      | fullname | shortname | category |
+      | Course 1 | C1        | 0        |
+    And the following "course enrolments" exist:
+      | user | course | role           |
+      | student1 | C1 | student        |
+      | student2 | C1 | student        |
 
   Scenario: View the online users block on the front page and see myself
     Given I log in as "admin"
@@ -28,7 +36,8 @@ Feature: The online users block allow you to see who is currently online on fron
     And I log out
     When I log in as "student1"
     And I am on site homepage
-    Then I should see "Admin User" in the "Online users" "block"
+    Then I should not see "Admin User" in the "Online users" "block"
+    And I should see "Other Users (1)" in the "Online users" "block"
     And I should see "Student 1" in the "Online users" "block"
     And I should see "Student 2" in the "Online users" "block"
     And I should see "3 online users" in the "Online users" "block"
@@ -45,9 +54,9 @@ Feature: The online users block allow you to see who is currently online on fron
     And I log out
     When I log in as "guest"
     And I am on site homepage
-    Then I should see "Admin User" in the "Online users" "block"
-    And I should see "Student 1" in the "Online users" "block"
-    And I should see "Student 2" in the "Online users" "block"
+    Then I should not see "Admin User" in the "Online users" "block"
+    And I should not see "Student 1" in the "Online users" "block"
+    And I should not see "Student 2" in the "Online users" "block"
     And I should see "3 online users" in the "Online users" "block"
 
   @javascript
@@ -69,7 +78,8 @@ Feature: The online users block allow you to see who is currently online on fron
     When I log in as "student2"
     And I am on site homepage
     Then I should see "2 online user" in the "Online users" "block"
-    And I should see "Admin" in the "Online users" "block"
+    And I should not see "Admin" in the "Online users" "block"
+    And I should see "Other Users (1)" in the "Online users" "block"
     And I should see "Student 2" in the "Online users" "block"
     And I should not see "Student 1" in the "Online users" "block"
     And I log out
@@ -83,7 +93,8 @@ Feature: The online users block allow you to see who is currently online on fron
     When I log in as "student2"
     And I am on site homepage
     Then I should see "3 online users" in the "Online users" "block"
-    And I should see "Admin" in the "Online users" "block"
+    And I should not see "Admin" in the "Online users" "block"
+    And I should see "Other Users (1)" in the "Online users" "block"
     And I should see "Student 2" in the "Online users" "block"
     And I should see "Student 1" in the "Online users" "block"
 
@@ -126,7 +137,8 @@ Feature: The online users block allow you to see who is currently online on fron
     And I log in as "student2"
     And I am on site homepage
     And I should see "2 online user" in the "Online users" "block"
-    And I should see "Admin" in the "Online users" "block"
+    And I should not see "Admin" in the "Online users" "block"
+    And I should see "Other Users (1)" in the "Online users" "block"
     And I should see "Student 2" in the "Online users" "block"
     And I should not see "Student 1" in the "Online users" "block"
     And I log out
@@ -135,6 +147,7 @@ Feature: The online users block allow you to see who is currently online on fron
     And I log in as "student2"
     When I am on site homepage
     Then I should see "3 online users" in the "Online users" "block"
-    And I should see "Admin" in the "Online users" "block"
+    And I should not see "Admin" in the "Online users" "block"
+    And I should see "Other Users (1)" in the "Online users" "block"
     And I should see "Student 2" in the "Online users" "block"
     And I should see "Student 1" in the "Online users" "block"
index 1d951b5..c86963d 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2021052500;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version   = 2021052501;        // The current plugin version (Date: YYYYMMDDXX)
 $plugin->requires  = 2021052500;        // Requires this Moodle version
 $plugin->component = 'block_online_users'; // Full name of the plugin (used for diagnostics)
index d9ba668..f1e84b7 100644 (file)
@@ -151,7 +151,7 @@ abstract class content {
      */
     public function set_name(string $name): bool {
         $name = trim($name);
-        if (empty($name)) {
+        if ($name === '') {
             return false;
         }
 
index 1374aae..48c270f 100644 (file)
@@ -79,15 +79,15 @@ class rename_content extends external_api {
         $params['name'] = clean_param($params['name'], PARAM_TEXT);
 
         // If name is empty don't try to rename and return a more detailed message.
-        if (empty(trim($params['name']))) {
+        if (trim($params['name']) === '') {
             $warnings[] = [
-                'item' => $contentid,
+                'item' => $params['contentid'],
                 'warningcode' => 'emptynamenotallowed',
                 'message' => get_string('emptynamenotallowed', 'core_contentbank')
             ];
         } else {
             try {
-                $record = $DB->get_record('contentbank_content', ['id' => $contentid], '*', MUST_EXIST);
+                $record = $DB->get_record('contentbank_content', ['id' => $params['contentid']], '*', MUST_EXIST);
                 $cb = new contentbank();
                 $content = $cb->get_content_from_id($record->id);
                 $contenttype = $content->get_content_type_instance();
@@ -100,7 +100,7 @@ class rename_content extends external_api {
                         $result = true;
                     } else {
                         $warnings[] = [
-                            'item' => $contentid,
+                            'item' => $params['contentid'],
                             'warningcode' => 'contentnotrenamed',
                             'message' => get_string('contentnotrenamed', 'core_contentbank')
                         ];
@@ -108,7 +108,7 @@ class rename_content extends external_api {
                 } else {
                     // The user has no permission to manage this content.
                     $warnings[] = [
-                        'item' => $contentid,
+                        'item' => $params['contentid'],
                         'warningcode' => 'nopermissiontomanage',
                         'message' => get_string('nopermissiontomanage', 'core_contentbank')
                     ];
@@ -116,7 +116,7 @@ class rename_content extends external_api {
             } catch (\moodle_exception $e) {
                 // The content or the context don't exist.
                 $warnings[] = [
-                    'item' => $contentid,
+                    'item' => $params['contentid'],
                     'warningcode' => 'exception',
                     'message' => $e->getMessage()
                 ];
index 09c0a39..d3894ff 100644 (file)
@@ -464,6 +464,7 @@ class core_contenttype_contenttype_testcase extends \advanced_testcase {
             'Too long name' => [str_repeat('a', 300), str_repeat('a', 255), true],
             'Empty name' => ['', 'Test content ', false],
             'Blanks only' => ['  ', 'Test content ', false],
+            'Zero name' => ['0', '0', true],
         ];
     }
 
index d369bb2..889baf7 100644 (file)
@@ -60,6 +60,7 @@ class rename_content_testcase extends \externallib_advanced_testcase {
             'Too long name' => [str_repeat('a', 300), str_repeat('a', 255), true],
             'Empty name' => ['', 'Test content ', false],
             'Blanks only' => ['  ', 'Test content ', false],
+            'Zero name' => ['0', '0', true],
         ];
     }
 
index 220fbdf..d1b676e 100644 (file)
@@ -2016,10 +2016,17 @@ function course_get_cm_move(cm_info $mod, $sr = null) {
             $pixicon = 't/move';
         }
 
+        $attributes = [
+            'class' => 'editing_move',
+            'data-action' => 'move',
+            'data-sectionreturn' => $sr,
+            'title' => $str->move,
+            'aria-label' => $str->move,
+        ];
         return html_writer::link(
-            new moodle_url($baseurl, array('copy' => $mod->id)),
-            $OUTPUT->pix_icon($pixicon, $str->move, 'moodle', array('class' => 'iconsmall', 'title' => '')),
-            array('class' => 'editing_move', 'data-action' => 'move', 'data-sectionreturn' => $sr)
+            new moodle_url($baseurl, ['copy' => $mod->id]),
+            $OUTPUT->pix_icon($pixicon, '', 'moodle', ['class' => 'iconsmall']),
+            $attributes
         );
     }
     return '';
index 8d25c22..a015676 100644 (file)
@@ -2294,7 +2294,8 @@ class core_course_renderer extends plugin_renderer_base {
             if ($editing && has_capability('moodle/course:update', $context)) {
                 $streditsummary = get_string('editsummary');
                 $editsectionurl = new moodle_url('/course/editsection.php', ['id' => $section->id]);
-                $output .= html_writer::link($editsectionurl, $this->pix_icon('t/edit', $streditsummary)) .
+                $attributes = ['title' => $streditsummary, 'aria-label' => $streditsummary];
+                $output .= html_writer::link($editsectionurl, $this->pix_icon('t/edit', ''), $attributes) .
                     "<br /><br />";
             }
 
index 8fefc5b..4993d07 100644 (file)
@@ -79,7 +79,7 @@ Feature: Restricting access to course lists
     And I should not see "Humanities"
     And I should not see "Other category"
     And I follow "English Y2"
-    And I should see "You can not enrol yourself in this course."
+    And I should see "You cannot enrol yourself in this course."
     And I log out
 
   Scenario: Browse courses as a user who has access to several but not all categories
@@ -102,5 +102,5 @@ Feature: Restricting access to course lists
     And the "Course categories" select box should contain "English category"
     And the "Course categories" select box should not contain "Other category"
     And I follow "Biology Y1"
-    And I should see "You can not enrol yourself in this course."
+    And I should see "You cannot enrol yourself in this course."
     And I log out
index 5189a3a..8281f3c 100644 (file)
@@ -307,6 +307,7 @@ class core_enrol_external extends external_api {
         global $CFG, $USER, $DB;
 
         require_once($CFG->dirroot . '/course/lib.php');
+        require_once($CFG->dirroot . '/user/lib.php');
         require_once($CFG->libdir . '/completionlib.php');
 
         // Do basic automatic PARAM checks on incoming data, using params description
@@ -346,8 +347,8 @@ class core_enrol_external extends external_api {
                 continue;
             }
 
-            if (!$sameuser and !course_can_view_participants($context)) {
-                // we need capability to view participants
+            // If viewing details of another user, then we must be able to view participants as well as profile of that user.
+            if (!$sameuser && (!course_can_view_participants($context) || !user_can_view_profile($user, $course))) {
                 continue;
             }
 
index 35ddfce..91551f0 100644 (file)
@@ -62,7 +62,7 @@ Feature: Users can auto-enrol themself in courses where self enrolment is allowe
   Scenario: Self-enrolment disabled
     Given I log in as "student1"
     When I am on "Course 1" course homepage
-    Then I should see "You can not enrol yourself in this course"
+    Then I should see "You cannot enrol yourself in this course"
 
   Scenario: Self-enrolment enabled requiring a group enrolment key
     Given I log in as "teacher1"
index c98df46..c126c90 100644 (file)
@@ -610,6 +610,81 @@ class core_enrol_externallib_testcase extends externallib_advanced_testcase {
         $this->assertEquals(0, $enrolledincourses[0]['lastaccess']); // I can't see this, hidden by global setting.
     }
 
+    /**
+     * Test that get_users_courses respects the capability to view participants when viewing courses of other user
+     */
+    public function test_get_users_courses_can_view_participants(): void {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        $course = $this->getDataGenerator()->create_course();
+        $context = context_course::instance($course->id);
+
+        $user1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
+        $user2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
+
+        $this->setUser($user1);
+
+        $courses = core_enrol_external::clean_returnvalue(
+            core_enrol_external::get_users_courses_returns(),
+            core_enrol_external::get_users_courses($user2->id, false)
+        );
+
+        $this->assertCount(1, $courses);
+        $this->assertEquals($course->id, reset($courses)['id']);
+
+        // Prohibit the capability for viewing course participants.
+        $studentrole = $DB->get_field('role', 'id', ['shortname' => 'student']);
+        assign_capability('moodle/course:viewparticipants', CAP_PROHIBIT, $studentrole, $context->id);
+
+        $courses = core_enrol_external::clean_returnvalue(
+            core_enrol_external::get_users_courses_returns(),
+            core_enrol_external::get_users_courses($user2->id, false)
+        );
+        $this->assertEmpty($courses);
+    }
+
+    /*
+     * Test that get_users_courses respects the capability to view a users profile when viewing courses of other user
+     */
+    public function test_get_users_courses_can_view_profile(): void {
+        $this->resetAfterTest();
+
+        $course = $this->getDataGenerator()->create_course([
+            'groupmode' => VISIBLEGROUPS,
+        ]);
+
+        $user1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
+        $user2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
+
+        // Create separate groups for each of our students.
+        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
+        groups_add_member($group1, $user1);
+        $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
+        groups_add_member($group2, $user2);
+
+        $this->setUser($user1);
+
+        $courses = core_enrol_external::clean_returnvalue(
+            core_enrol_external::get_users_courses_returns(),
+            core_enrol_external::get_users_courses($user2->id, false)
+        );
+
+        $this->assertCount(1, $courses);
+        $this->assertEquals($course->id, reset($courses)['id']);
+
+        // Change to separate groups mode, so students can't view information about each other in different groups.
+        $course->groupmode = SEPARATEGROUPS;
+        update_course($course);
+
+        $courses = core_enrol_external::clean_returnvalue(
+            core_enrol_external::get_users_courses_returns(),
+            core_enrol_external::get_users_courses($user2->id, false)
+        );
+        $this->assertEmpty($courses);
+    }
+
     /**
      * Test get_users_courses with mathjax in the name.
      */
index d6617ed..a766853 100644 (file)
@@ -154,7 +154,7 @@ $string['configallcountrycodes'] = 'This is the list of countries that may be se
 $string['configallowassign'] = 'You can allow people who have the roles on the left side to assign some of the column roles to other people';
 $string['configallowcategorythemes'] = 'If you enable this, then themes can be set at the category level. This will affect all child categories and courses unless they have specifically set their own theme. WARNING: Enabling category themes may affect performance.';
 $string['configallowcohortthemes'] = 'If you enable this, then themes can be set at the cohort level. This will affect all users with only one cohort or more than one but with the same theme.';
-$string['configallowcoursethemes'] = 'If you enable this, then courses will be allowed to set their own themes.  Course themes override all other theme choices (site, user, or session themes)';
+$string['configallowcoursethemes'] = 'If enabled, then courses will be allowed to set their own themes.  Course themes override all other theme choices (site, user, category, cohort or URL-defined themes).';
 $string['configallowedemaildomains'] = 'List email domains that are allowed to be disclosed in the "From" section of outgoing email. The default of "Empty" will use the No-reply address for all outgoing email. The use of wildcards is allowed e.g. *.example.com will allow emails sent from any subdomain of example.com, but not example.com itself. This will require separate entry.';
 $string['configallowemailaddresses'] = 'To restrict new email addresses to particular domains, list them here separated by spaces. All other domains will be rejected. To allow subdomains, add the domain with a preceding \'.\'. To allow a root domain together with its subdomains, add the domain twice - once with a preceding \'.\' and once without e.g. .ourcollege.edu.au ourcollege.edu.au.';
 $string['configallowemojipicker'] = 'The emoji picker enables users to select emojis, such as smilies, to add to messages and other text areas via an emoji picker button in the Atto toolbar.';
@@ -696,7 +696,7 @@ $string['incompatibleblocks'] = 'Incompatible blocks';
 $string['indexdata'] = 'Index data';
 $string['indexinginfo'] = 'The recommended way to index your site\'s contents is by using the \'Global search indexing\' scheduled task.';
 $string['installhijacked'] = 'Installation must be finished from the original IP address, sorry.';
-$string['installsessionerror'] = 'Can not initialise PHP session, please verify that your browser accepts cookies.';
+$string['installsessionerror'] = 'Cannot initialise PHP session. Please verify that your browser accepts cookies.';
 $string['intlrecommended'] = 'Intl extension is used to improve internationalization support, such as locale aware sorting.';
 $string['intlrequired'] = 'Intl extension is required to improve internationalization support, such as locale aware sorting and international domain names.';
 $string['invalidagedigitalconsent'] = 'The digital age of consent is not valid: {$a}';
@@ -1398,6 +1398,7 @@ $string['unsupportedphpversion71'] = 'PHP version 7.1 is not supported.';
 $string['unsupportedphpversion72'] = 'PHP version 7.2 is not supported.';
 $string['unsupportedphpversion73'] = 'PHP version 7.3 is not supported.';
 $string['unsupportedphpversion74'] = 'PHP version 7.4 is not supported.';
+$string['unsupportedphpversion80'] = 'PHP version 8.0 is not supported.';
 $string['unsuspenduser'] = 'Activate user account';
 $string['updateaccounts'] = 'Update existing accounts';
 $string['updatecomponent'] = 'Update component';
@@ -1426,7 +1427,7 @@ $string['upgradepluginsfirst'] = 'It is recommended to install all available upd
 $string['upgradepluginsinfo'] = 'Updating plugins';
 $string['upgradepluginsinfo_help'] = 'There are available updates for some of your plugins. You should install them all prior to upgrading Moodle database. If your site does not support automatic updates deployment, you have to download and install new plugin versions at your server manually.';
 $string['upgradepluginsinfo_link'] = 'admin/upgradepluginsinfo';
-$string['upgradeerror'] = 'Unknown error upgrading {$a->plugin} to version {$a->version}, can not continue.';
+$string['upgradeerror'] = 'Unknown error upgrading {$a->plugin} to version {$a->version}. Cannot continue.';
 $string['upgradeforumread'] = 'A new feature has been added in Moodle 1.5 to track read/unread forum posts.<br />To use this functionality you need to <a href="{$a}">update your tables</a>.';
 $string['upgradeforumreadinfo'] = 'A new feature has been added in Moodle 1.5 to track read/unread forum posts.  To use this functionality you need to update your tables with all the tracking information for existing posts.  Depending on the size of your site this can take a long time (hours) and can be quite taxing on the database, so it\'s best to do it during a quiet period.  However, your site will continue functioning during this upgrade and users won\'t be affected.  Once you start this process you should let it finish (keep your browser window open).  However, if you stop the process by closing the window: don\'t worry, you can start over.<br /><br />Do you want to start the upgrading process now?';
 $string['upgradekeyreq'] = 'Upgrade key required';
@@ -1497,7 +1498,7 @@ $string['verifychangedemail'] = 'Restrict domains when changing email';
 $string['warningcurrentsetting'] = 'Invalid current value: {$a}';
 $string['warningiconvbuggy'] = 'Your version of the iconv library does not support the //IGNORE modifier. You should install the mbstring extension which can be used instead for cleaning strings containing invalid UTF-8 characters.';
 $string['webproxy'] = 'Web proxy';
-$string['webproxyinfo'] = 'Fill in following options if your Moodle server can not access internet directly. Internet access is required for download of environment data, language packs, RSS feeds, timezones, etc.<br /><em>PHP cURL extension is highly recommended.</em>';
+$string['webproxyinfo'] = 'Fill in the following options if your Moodle server cannot access the internet directly. Internet access is required for the download of environment data, language packs, RSS feeds, timezones, etc.<br /><em>The PHP cURL extension is highly recommended.</em>';
 $string['xmlrpcrecommended'] = 'The XMLRPC extension is useful for web services and Moodle networking.';
 $string['yuicomboloading'] = 'YUI combo loading';
 $string['ziprequired'] = 'The Zip PHP extension is now required by Moodle, info-ZIP binaries or PclZip library are not used anymore.';
index b03181e..3e97493 100644 (file)
@@ -38,7 +38,7 @@ $string['invalideventdata'] = 'Incorrect event data submitted: {$a}';
 $string['invalidparameter'] = 'Invalid parameter value detected';
 $string['invalidresponse'] = 'Invalid response value detected';
 $string['line'] = 'Line';
-$string['missingconfigversion'] = 'Config table does not contain version, can not continue, sorry.';
+$string['missingconfigversion'] = 'Config table does not contain the version. You cannot continue.';
 $string['modulenotexist'] = '{$a} module doesn\'t exist';
 $string['morethanonerecordinfetch'] = 'Found more than one record in fetch() !';
 $string['mustbeoveride'] = 'Abstract {$a} method must be overridden.';
index 6fcb077..cae283c 100644 (file)
@@ -108,7 +108,7 @@ $string['noexistingparticipants'] = 'No existing participants';
 $string['nogroup'] = 'No group';
 $string['noguestaccess'] = 'Guests cannot access this course. Please log in.';
 $string['none'] = 'None';
-$string['notenrollable'] = 'You can not enrol yourself in this course.';
+$string['notenrollable'] = 'You cannot enrol yourself in this course.';
 $string['notenrolledusers'] = 'Other users';
 $string['otheruserdesc'] = 'The following users are not enrolled in this course but do have roles, inherited or assigned within it.';
 $string['participationactive'] = 'Active';
@@ -140,7 +140,7 @@ $string['unenrol'] = 'Unenrol';
 $string['unenrolleduser'] = 'The user "{$a->fullname}" was unenrolled from the course';
 $string['unenrolconfirm'] = 'Do you really want to unenrol "{$a->user}" (previously enrolled via "{$a->enrolinstancename}") from "{$a->course}"?';
 $string['unenrolme'] = 'Unenrol me from {$a}';
-$string['unenrolnotpermitted'] = 'You do not have permission or can not unenrol this user from this course.';
+$string['unenrolnotpermitted'] = 'You do not have permission to unenrol this user from the course.';
 $string['unenrolroleusers'] = 'Unenrol users';
 $string['uninstallmigrating'] = 'Migrating "{$a}" enrolments';
 $string['unknowajaxaction'] = 'Unknown action requested';
index 92ae88e..52f787e 100644 (file)
@@ -73,7 +73,7 @@ $string['cannotdeletefile'] = 'Cannot delete this file';
 $string['cannotdeleterole'] = 'It cannot be deleted, because {$a}';
 $string['cannotdeleterolewithid'] = 'Could not delete role with ID {$a}';
 $string['cannotdeletethisrole'] = 'You cannot delete this role because it is used by the system, or because it is the last role with administrator capabilities.';
-$string['cannotdisableformat'] = 'You can not disable the default format';
+$string['cannotdisableformat'] = 'The default format cannot be disabled.';
 $string['cannotdownloadcomponents'] = 'Cannot download components';
 $string['cannotdownloadlanguageupdatelist'] = 'Cannot download list of language updates from download.moodle.org';
 $string['cannotdownloadzipfile'] = 'Cannot download ZIP file';
@@ -122,7 +122,7 @@ $string['cannotopencsv'] = 'Cannot open CSV file';
 $string['cannotopenfile'] = 'Cannot open file ({$a})';
 $string['cannotopenforwrit'] = 'Cannot open for writing: {$a}';
 $string['cannotopentemplate'] = 'Cannot open template file ({$a})';
-$string['cannotopenzip'] = 'Can not open zip file, probably zip extension bug on 64bit os';
+$string['cannotopenzip'] = 'Cannot open zip file, probably because of a zip extension bug on 64-bit OS.';
 $string['cannotoverridebaserole'] = 'Cannot override base role capabilities';
 $string['cannotoverriderolehere'] = 'You are not allowed to override this role (id = {$a->roleid}) in this context ({$a->context})';
 $string['cannotreadfile'] = 'Cannot read file ({$a})';
@@ -143,7 +143,7 @@ $string['cannotsavedata'] = 'Cannot save data';
 $string['cannotsavefile'] = 'Cannot save the file "{$a}"!';
 $string['cannotsavemd5file'] = 'Cannot save md5 file';
 $string['cannotsavezipfile'] = 'Cannot save ZIP file';
-$string['cannotservefile'] = 'Can not serve file - server configuration problem.';
+$string['cannotservefile'] = 'Cannot serve file due to a server configuration problem.';
 $string['cannotsetparentforcatoritem'] = 'Cannot set parent for category or course item!';
 $string['cannotsetpassword'] = 'Could not set user password!';
 $string['cannotsetprefgrade'] = 'Could not set preference aggregationview to {$a} for this grade category';
@@ -222,7 +222,7 @@ $string['ddltablenotexist'] = 'Table "{$a}" does not exist';
 $string['ddlunknownerror'] = 'Unknown DDL library error';
 $string['ddlxmlfileerror'] = 'XML database file errors found';
 $string['destinationcmnotexit'] = 'The destination course module does not exist';
-$string['detectedbrokenplugin'] = 'Plugin "{$a}" is defective or outdated, can not continue, sorry.';
+$string['detectedbrokenplugin'] = 'The plugin {$a} is defective or outdated; sorry you cannot continue.';
 $string['dmlexceptiononinstall'] = '<p>A database error has occurred [{$a->errorcode}].<br />{$a->debuginfo}</p>';
 $string['dmlparseexception'] = 'Error parsing SQL query';
 $string['dmlreadexception'] = 'Error reading from database';
@@ -385,10 +385,10 @@ $string['invalidxmlfile'] = '"{$a}" is not a valid XML file';
 $string['iplookupfailed'] = 'Cannot find geo information about this IP address {$a}';
 $string['iplookupprivate'] = 'Cannot display lookup of private IP address';
 $string['ipmismatch'] = 'Client IP address mismatch';
-$string['listcantmovedown'] = 'Failed to move item down, as it is the last of it\'s peers';
+$string['listcantmovedown'] = 'Failed to move item down, as it is the last of its peers.';
 $string['listcantmoveleft'] = 'Failed to move item left, as it has no parent';
 $string['listcantmoveright'] = 'Failed to move item right, as there is no peer to make it a child of. Move it below another peer and then you can move it right.';
-$string['listcantmoveup'] = 'Failed to move the item up, as it is the first of it\'s peers';
+$string['listcantmoveup'] = 'Failed to move item up, as it is the first of its peers.';
 $string['listnochildren'] = 'No children of item found';
 $string['listnoitem'] = 'Item not found';
 $string['listnopeers'] = 'No peers of item found';
@@ -418,7 +418,7 @@ $string['moduledoesnotexist'] = 'This module does not exist';
 $string['moduleinstancedoesnotexist'] = 'The instance of this module does not exist';
 $string['modulemissingcode'] = 'Module {$a} is missing the code needed to perform this function';
 $string['movecatcontentstoroot'] = 'Moving the category content to root is not allowed. You must move the contents to an existing category!';
-$string['movecatcontentstoselected'] = 'Some of the category content can not be moved into selected category.';
+$string['movecatcontentstoselected'] = 'Some category content cannot be moved into the selected category.';
 $string['movecategorynotpossible'] = 'You cannot move category \'{$a}\' into the selected category.';
 $string['movecategoryownparent'] = 'You cannot make category \'{$a}\' a parent of itself.';
 $string['movecategoryparentconflict'] = 'You cannot make category \'{$a}\' a subcategory of one of its own subcategories.';
@@ -460,7 +460,7 @@ $string['nopermissiontorate'] = 'Rating of items not allowed!';
 $string['nopermissiontoshow'] = 'No permission to see this!';
 $string['nopermissiontounlock'] = 'No permission to unlock!';
 $string['nopermissiontoupdatecalendar'] = 'Sorry, but you do not have permission to update the calendar event.';
-$string['nopermissiontoviewgrades'] = 'Can not view grades.';
+$string['nopermissiontoviewgrades'] = 'Cannot view grades.';
 $string['nopermissiontoviewletergrade'] = 'Missing permission to view letter grades';
 $string['nopermissiontoviewpage'] = 'You are not allowed to look at this page';
 $string['nosite'] = 'Could not find a top-level course!';
@@ -510,7 +510,7 @@ $string['restore_path_element_missingmethod'] = 'Restore method {$a} is missing.
 $string['restore_path_element_noobject'] = 'Restore object {$a} is not an object.';
 $string['restrictedcontextexception'] = 'Sorry, execution of external function violates context restriction.';
 $string['restricteduser'] = 'Sorry, but your current account "{$a}" is restricted from doing that';
-$string['reverseproxyabused'] = 'Reverse proxy enabled, server can not be accessed directly, sorry.<br />Please contact server administrator.';
+$string['reverseproxyabused'] = 'Reverse proxy enabled so the server cannot be accessed directly.<br />Please contact the server administrator.';
 $string['rpcerror'] = 'Ooops! Your MNET communication has failed! Here\'s that error message to pass on to your administrator: {$a}';
 $string['secretalreadyused'] = 'Change password confirmation link was already used, password was not changed';
 $string['sectionnotexist'] = 'This section does not exist';
@@ -537,11 +537,11 @@ $string['sslonlyaccess'] = 'For security reasons only https connections are allo
 $string['statscatchupmode'] = 'Statistics is currently in catchup mode. So far {$a->daysdone} day(s) have been processed and {$a->dayspending} are pending. Check back soon!';
 $string['statsdisable'] = 'Statistics are not enabled.';
 $string['statsnodata'] = 'There is no available data for that combination of course and time period';
-$string['storedfilecannotcreatefile'] = 'Can not create local file pool file, please verify permissions in dataroot and available disk space.';
-$string['storedfilecannotcreatefiledirs'] = 'Can not create local file pool directories, please verify permissions in dataroot.';
+$string['storedfilecannotcreatefile'] = 'Cannot create local file pool file. Please verify permissions in dataroot and available disk space.';
+$string['storedfilecannotcreatefiledirs'] = 'Cannot create local file pool directories. Please verify permissions in dataroot.';
 $string['storedfilecannotread'] = 'Cannot read file. Either the file does not exist or there is a permission problem.';
 $string['storedfilecannotreadfile'] = 'Cannot read file \'{$a}\'. Either the file does not exist or there is a permission problem.';
-$string['storedfilenotcreated'] = 'Can not create file "{$a->contextid}/{$a->component}/{$a->filearea}/{$a->itemid}{$a->filepath}{$a->filename}"';
+$string['storedfilenotcreated'] = 'Cannot create file {$a->contextid}/{$a->component}/{$a->filearea}/{$a->itemid}{$a->filepath}{$a->filename}';
 $string['storedfileproblem'] = 'Unknown exception related to local files ({$a})';
 $string['tagdisabled'] = 'Tags are disabled!';
 $string['tagnotfound'] = 'The specified tag was not found in the database';
@@ -568,7 +568,7 @@ $string['unknownevent'] = 'Unknown event';
 $string['unknownfiletype'] = 'Error unknown filtertype';
 $string['unknowngroup'] = 'Unknown group "{$a}"';
 $string['unknownhelp'] = 'Unknown help topic {$a}';
-$string['unknownjsinrequirejs'] = 'Can not find JS library: {$a}';
+$string['unknownjsinrequirejs'] = 'Cannot find JavaScript library {$a}.';
 $string['unknownmodulename'] = 'Unknown module name for form';
 $string['unknownrole'] = 'Unknown role "{$a}"';
 $string['unknownsortcolumn'] = 'Unknown sort column {$a}';
@@ -586,7 +586,7 @@ $string['upgraderequires19'] = 'Error: New Moodle version was installed on serve
 $string['upgraderunning'] = 'Site is being upgraded, please retry later.';
 $string['urlnotdefinerss'] = 'URL not defined for RSS feed';
 $string['useradmineditadmin'] = 'Only administrators are allowed to modify other administrator accounts';
-$string['useradminodelete'] = 'Administrator accounts can not be deleted';
+$string['useradminodelete'] = 'Administrator accounts cannot be deleted.';
 $string['userautherror'] = 'Unknown auth plugin';
 $string['userauthunsupported'] = 'Auth plugin not supported here';
 $string['useremailduplicate'] = 'Duplicate address';
@@ -595,7 +595,7 @@ $string['usernamelowercase'] = 'The username must be in lower case';
 $string['usernotaddederror'] = 'User not added - error';
 $string['usernotaddedregistered'] = 'User not added - already registered';
 $string['usernotavailable'] = 'The details of this user are not available to you';
-$string['usernotdeletedadmin'] = 'User not deleted - can not delete administrator accounts';
+$string['usernotdeletedadmin'] = 'User not deleted as administrator accounts cannot be deleted.';
 $string['usernotdeleteddeleted'] = 'This user has already been deleted.';
 $string['usernotdeletederror'] = 'User not deleted - error';
 $string['usernotdeletedmissing'] = 'User not deleted - could not find the username';
index f931fd9..d083fd2 100644 (file)
@@ -110,7 +110,7 @@ $string['registerwithmoodleorginfo'] = 'We\'d love to stay in touch and provide
 $string['registerwithmoodleorginfoapp'] = 'About the Moodle app';
 $string['registerwithmoodleorginfostats'] = 'Moodle statistics';
 $string['registerwithmoodleorginfosites'] = 'Other sites in my country';
-$string['registerwithmoodleorgremove'] = 'You are going to unregister your site. If you continue, you will no longer have access to important notifications and security alerts. Your users will not be able to receive push notifications from your site to their Moodle mobile app. Are you sure you want to unregister your site?';
+$string['registerwithmoodleorgremove'] = 'You are about to unregister your site. You will no longer receive security alert notifications and users will not be able to receive push notifications from your site to their Moodle app. However, you will be able to re-register at any time! Are you sure you want to continue?';
 $string['registrationconfirmed'] = 'Site registration confirmed';
 $string['registrationconfirmedon'] = 'Thank you for registering your site. Registration information will be kept up to date by the \'Site registration\' scheduled task.';
 $string['renewregistration'] = 'Renew registration';
index a2ec609..a10d483 100644 (file)
@@ -247,7 +247,7 @@ $string['shownotificationwindowwithcount'] = 'Show notification window with {$a}
 $string['togglenotificationmenu'] = 'Toggle notifications menu';
 $string['togglemessagemenu'] = 'Toggle messaging drawer';
 $string['totalconversations'] = '{$a} total conversations';
-$string['touserdoesntexist'] = 'You can not send a message to a user id ({$a}) that doesn\'t exist';
+$string['touserdoesntexist'] = 'You cannot send a message to a user ID ({$a}) that doesn\'t exist.';
 $string['unabletomessage'] = 'You are unable to message this user';
 $string['unblock'] = 'Unblock';
 $string['unblockcontact'] = 'Unblock contact';
index b4e29fb..a812263 100644 (file)
@@ -648,7 +648,7 @@ $string['emaildisable'] = 'This email address is disabled';
 $string['emaildisableclick'] = 'Click here to disable all email from being sent to this address';
 $string['emaildisplay'] = 'Email display';
 $string['emaildisplay_help'] = 'Privileged users (such as teachers and managers) will always be able to see your email address.';
-$string['emaildisplaycourse'] = 'Allow only other course members to see my email address';
+$string['emaildisplaycourse'] = 'Allow only other course participants to see my email address';
 $string['emaildisplaycoursemembersonly'] = '(Visible to other course participants)';
 $string['emaildisplayeveryone'] = '(Visible to everyone)';
 $string['emaildisplayhide'] = '(Hidden from all non-privileged users)';
@@ -1188,6 +1188,7 @@ $string['loginstepsnone'] = '<p>Hi!</p>
 <p>If someone else has already chosen your username then you\'ll have to try again using a different username.</p>';
 $string['loginto'] = 'Log in to {$a}';
 $string['loginagain'] = 'Log in again';
+$string['logoof'] = 'Logo of {$a}';
 $string['logout'] = 'Log out';
 $string['logoutconfirm'] = 'Do you really want to log out?';
 $string['logs'] = 'Logs';
index d25ee12..0820cb8 100644 (file)
@@ -67,7 +67,7 @@ $string['moodleversion'] = 'Moodle {$a}';
 $string['noneinstalled'] = 'No plugins of this type are installed';
 $string['notes'] = 'Notes';
 $string['notdownloadable'] = 'Can not download the package';
-$string['notdownloadable_help'] = 'ZIP package with the update can not be downloaded automatically. Please refer to the documentation page for more help.';
+$string['notdownloadable_help'] = 'The ZIP package with the update cannot be downloaded automatically.';
 $string['notdownloadable_link'] = 'admin/mdeploy/notdownloadable';
 $string['notsupported'] = 'Plugin may not be compatible with Moodle version {$a}';
 $string['notwritable'] = 'Plugin files not writable';
index 10086fd..6e08e54 100644 (file)
@@ -183,7 +183,7 @@ $string['formquestionnotinids'] = 'Form contained question that is not in questi
 $string['fractionsnomax'] = 'One of the answers should have a score of 100% so it is possible to get full marks for this question.';
 $string['getcategoryfromfile'] = 'Get category from file';
 $string['getcontextfromfile'] = 'Get context from file';
-$string['changepublishstatuscat'] = '<a href="{$a->caturl}">Category "{$a->name}"</a> in course "{$a->coursename}" will have it\'s sharing status changed from <strong>{$a->changefrom} to {$a->changeto}</strong>.';
+$string['changepublishstatuscat'] = '<a href="{$a->caturl}">Category "{$a->name}"</a> in course "{$a->coursename}" will have its sharing status changed from {$a->changefrom} to {$a->changeto}.';
 $string['chooseqtypetoadd'] = 'Choose a question type to add';
 $string['editquestions'] = 'Edit questions';
 $string['idnumber'] = 'ID number';
index 16b62f4..62ff535 100644 (file)
@@ -29,7 +29,7 @@ $string['add'] = 'Add';
 $string['addfile'] = 'Add...';
 $string['addfiletext'] = 'Add file';
 $string['addplugin'] = 'Add a repository plugin';
-$string['aliaseschange'] = 'There are {$a} alias/shortcut files that use this file as their source. If you proceed then those aliases will be converted to true copies.';
+$string['aliaseschange'] = 'There are {$a} links to this file. If you proceed then locations which currently link to the file will be automatically updated to use a copy of the file instead.';
 $string['allowexternallinks'] = 'Allow external links';
 $string['areamainfile'] = 'Main file';
 $string['coursebackup'] = 'Course backups';
@@ -73,10 +73,10 @@ $string['confirmdelete'] = 'Are you sure you want to delete the repository {$a}?
 $string['confirmdeletefile'] = 'Are you sure you want to delete this file?';
 $string['confirmdeleteselectedfile'] = 'Are you sure you want to delete the selected {$a} file(s)?';
 $string['confirmrenamefile'] = 'Are you sure you want to rename/move this file?';
-$string['confirmdeletefilewithhref'] = 'Are you sure you want to delete this file? There are {$a} alias/shortcut files that use this file as their source. If you proceed then those aliases will be converted to true copies.';
+$string['confirmdeletefilewithhref'] = 'Are you sure you want to delete this file? There are {$a} links to this file. If you proceed then locations which currently link to the file will be automatically updated to use a copy of the file instead.';
 $string['confirmdeletefolder'] = 'Are you sure you want to delete this folder? All files and subfolders will be deleted.';
 $string['confirmremove'] = 'Are you sure you want to remove this repository plugin, its options and <strong>all of its instances</strong> - {$a}? If you choose "Continue and download", file references to external contents will be downloaded to Moodle. This could take a long time to process.';
-$string['confirmrenamefolder'] = ' Are you sure you want to move/rename this folder? Any alias/shortcut files that reference files in this folder will be converted into true copies.';
+$string['confirmrenamefolder'] = 'Are you sure you want to move/rename this folder? Any locations which currently link to files in this folder will be automatically updated to use copies of the file instead.';
 $string['continueuninstall'] = 'Continue';
 $string['continueuninstallanddownload'] = 'Continue and download';
 $string['copying'] = 'Copying';
@@ -113,7 +113,7 @@ $string['enter'] = 'Enter';
 $string['entername'] = 'Please enter folder name';
 $string['enternewname'] = 'Please enter the new file name';
 $string['error'] = 'An unknown error occurred!';
-$string['errordoublereference'] = 'Unable to overwrite file with a shortcut/alias because shortcuts to this file already exist.';
+$string['errordoublereference'] = 'Unable to overwrite file with a link because links to this file already exist.';
 $string['errornotyourfile'] = 'You can only pick files which you added.';
 $string['erroruniquename'] = 'Repository instance name should be unique';
 $string['errorpostmaxsize'] = 'The file you tried to upload is too large for the server to process.';
@@ -132,7 +132,7 @@ $string['filepicker'] = 'File picker';
 $string['filesizenull'] = 'File size cannot be determined';
 $string['folderexists'] = 'Folder name already being used, please use another name';
 $string['foldernotfound'] = 'Folder not found';
-$string['folderrecurse'] = 'Folder can not be moved to it\'s own subfolder';
+$string['folderrecurse'] = 'Folder cannot be moved to its own subfolder.';
 $string['getfile'] = 'Select this file';
 $string['getfiletimeout'] = 'Get file timeout';
 $string['help'] = 'Help';
@@ -206,8 +206,8 @@ $string['popupblockeddownload'] = 'The downloading window is blocked, please all
 $string['preview'] = 'Preview';
 $string['privatefilesof'] = '{$a} Private files';
 $string['readonlyinstance'] = 'You cannot edit/delete a read-only instance';
-$string['referencesexist'] = 'There are {$a} alias/shortcut files that use this file as their source';
-$string['referenceslist'] = 'Aliases/Shortcuts';
+$string['referencesexist'] = 'There are {$a} links to this file';
+$string['referenceslist'] = 'Links';
 $string['refresh'] = 'Refresh';
 $string['refreshnonjsfilepicker'] = 'Please close this window and refresh non-javascript file picker';
 $string['removed'] = 'Repository removed';
index 318e874..d155930 100644 (file)
@@ -97,7 +97,7 @@ $string['calendar:managegroupentries'] = 'Manage group calendar entries';
 $string['calendar:manageownentries'] = 'Manage own calendar entries';
 $string['capabilities'] = 'Capabilities';
 $string['capability'] = 'Capability';
-$string['category:viewcourselist'] = 'View list of courses you are not enrolled in';
+$string['category:viewcourselist'] = 'View course category and courses within it';
 $string['category:create'] = 'Create categories';
 $string['category:delete'] = 'Delete categories';
 $string['category:manage'] = 'Manage categories';
index 2cfd47c..eef36b1 100644 (file)
@@ -78,7 +78,7 @@ $string['errorcodes'] = 'Error message';
 $string['errorcoursecontextnotvalid'] = 'You cannot execute functions in the course context (course id:{$a->courseid}). The context error message was: {$a->message}';
 $string['errorinvalidparam'] = 'The param "{$a}" is invalid.';
 $string['errornotemptydefaultparamarray'] = 'The web service description parameter named \'{$a}\' is an single or multiple structure. The default can only be empty array. Check web service description.';
-$string['erroroptionalparamarray'] = 'The web service description parameter named \'{$a}\' is an single or multiple structure. It can not be set as VALUE_OPTIONAL. Check web service description.';
+$string['erroroptionalparamarray'] = 'The web service description parameter named \'{$a}\' is a single or multiple structure. It cannot be set as VALUE_OPTIONAL. Check the web service description.';
 $string['eventwebservicefunctioncalled'] = 'Web service function called';
 $string['eventwebserviceloginfailed'] = 'Web service login failed';
 $string['eventwebserviceservicecreated'] = 'Web service created';
index 2c6e44b..d67c1de 100644 (file)
@@ -772,8 +772,9 @@ function badges_create_site_backpack($data) {
     $context = context_system::instance();
     require_capability('moodle/badges:manageglobalsettings', $context);
 
-    $count = $DB->count_records('badge_external_backpack');
-    $data->sortorder = $count;
+    $max = $DB->get_field_sql('SELECT MAX(sortorder) FROM {badge_external_backpack}');
+    $data->sortorder = $max + 1;
+
     return badges_save_external_backpack($data);
 }
 
@@ -983,7 +984,7 @@ function badges_get_site_primary_backpack() {
 function badges_get_site_backpacks() {
     global $DB, $CFG;
 
-    $all = $DB->get_records('badge_external_backpack');
+    $all = $DB->get_records('badge_external_backpack', null, 'sortorder ASC');
 
     foreach ($all as $key => $bp) {
         if ($bp->id == $CFG->badges_site_backpack) {
@@ -1379,4 +1380,4 @@ function badges_generate_badgr_open_url($backpack, $type, $externalid) {
         return "{$url->get_scheme()}://{$url->get_host()}/public/{$entity}s/$externalid";
 
     }
-}
\ No newline at end of file
+}
index 03880b0..9c85e79 100644 (file)
@@ -124,10 +124,9 @@ function behat_get_error_string($errtype) {
  * @param string $errstr
  * @param string $errfile
  * @param int $errline
- * @param array $errcontext
  * @return bool
  */
-function behat_error_handler($errno, $errstr, $errfile, $errline, $errcontext) {
+function behat_error_handler($errno, $errstr, $errfile, $errline) {
 
     // If is preceded by an @ we don't show it.
     if (!error_reporting()) {
@@ -149,7 +148,7 @@ function behat_error_handler($errno, $errstr, $errfile, $errline, $errcontext) {
     }
 
     // Using the default one in case there is a fatal catchable error.
-    default_error_handler($errno, $errstr, $errfile, $errline, $errcontext);
+    default_error_handler($errno, $errstr, $errfile, $errline);
 
     $errnostr = behat_get_error_string($errno);
 
index 84f00e2..ac24300 100644 (file)
@@ -710,7 +710,7 @@ class core_user {
         $fields['lastlogin'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED);
         $fields['currentlogin'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED);
         $fields['lastip'] = array('type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED);
-        $fields['secret'] = array('type' => PARAM_RAW, 'null' => NULL_NOT_ALLOWED);
+        $fields['secret'] = array('type' => PARAM_ALPHANUM, 'null' => NULL_NOT_ALLOWED);
         $fields['picture'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED);
         $fields['url'] = array('type' => PARAM_URL, 'null' => NULL_NOT_ALLOWED);
         $fields['description'] = array('type' => PARAM_RAW, 'null' => NULL_ALLOWED);
index 85a4cad..532b238 100644 (file)
@@ -2458,5 +2458,18 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2021052500.60);
     }
 
+    if ($oldversion < 2021052500.64) {
+        // Get all the external backpacks and update the sortorder column, to avoid repeated/wrong values. As sortorder was not
+        // used since now, the id column will be the criteria to follow for re-ordering them with a valid value.
+        $i = 1;
+        $records = $DB->get_records('badge_external_backpack', null, 'id ASC');
+        foreach ($records as $record) {
+            $record->sortorder = $i++;
+            $DB->update_record('badge_external_backpack', $record);
+        }
+
+        upgrade_main_savepoint(true, 2021052500.64);
+    }
+
     return true;
 }
index 6029e91..982dbad 100644 (file)
@@ -200,7 +200,7 @@ class read_slave_moodle_database extends test_moodle_database {
      * @param string $dbh
      * @return void
      */
-    protected function set_db_handle($dbh) {
+    protected function set_db_handle($dbh): void {
         $this->handle = $dbh;
     }
 
index df11fa9..5068d85 100644 (file)
@@ -1653,3 +1653,14 @@ function restrict_php_version_73(&$result) {
 function restrict_php_version_74(&$result) {
     return restrict_php_version($result, '7.4');
 }
+
+/**
+ * Check if the current PHP version is greater than or equal to
+ * PHP version 8.0
+ *
+ * @param object $result an environment_results instance
+ * @return bool result of version check
+ */
+function restrict_php_version_80($result) {
+    return restrict_php_version($result, '8.0');
+}
index 17b4a29..da78def 100644 (file)
@@ -1232,12 +1232,6 @@ class external_settings {
         }
     }
 
-    /**
-     * Clone - private - can not be cloned
-     */
-    private final function __clone() {
-    }
-
     /**
      * Return only one instance
      *
index 61304f8..7639810 100644 (file)
@@ -46,7 +46,7 @@ class file_exception extends moodle_exception {
 }
 
 /**
- * Can not create file exception.
+ * Cannot create file exception.
  *
  * @package   core_files
  * @category  files
index e8a78fb..2339c13 100644 (file)
@@ -34,20 +34,6 @@ defined('MOODLE_INTERNAL') || die();
  */
 abstract class file_system {
 
-    /**
-     * Private clone method to prevent cloning of the instance.
-     */
-    final protected function __clone() {
-        return;
-    }
-
-    /**
-     * Private wakeup method to prevent unserialising of the instance.
-     */
-    final protected function __wakeup() {
-        return;
-    }
-
     /**
      * Output the content of the specified stored file.
      *
index 62c19f3..3134458 100644 (file)
@@ -324,7 +324,7 @@ class core_files_file_storage_testcase extends advanced_testcase {
 
         // Try break it.
         $this->expectException('file_exception');
-        $this->expectExceptionMessage('Can not create file "1/core/unittest/0/test/newtest.txt" (file exists, cannot rename)');
+        $this->expectExceptionMessage('Cannot create file 1/core/unittest/0/test/newtest.txt (file exists, cannot rename)');
         // This shall throw exception.
         $originalfile->rename($newpath, $newname);
     }
@@ -1478,7 +1478,7 @@ class core_files_file_storage_testcase extends advanced_testcase {
 
         // Creating a file validating unique constraint.
         $this->expectException(stored_file_creation_exception::class);
-        $this->expectExceptionMessage('Can not create file "1/core/phpunit/0/testfile.txt"');
+        $this->expectExceptionMessage('Cannot create file 1/core/phpunit/0/testfile.txt');
         $fs->create_file_from_storedfile($filerecord, $file1->get_id());
     }
 
@@ -1835,7 +1835,7 @@ class core_files_file_storage_testcase extends advanced_testcase {
 
         // Creating a file validating unique constraint.
         $this->expectException(stored_file_creation_exception::class);
-        $this->expectExceptionMessage('Can not create file "1/core/phpunit/0/testfile.txt"');
+        $this->expectExceptionMessage('Cannot create file 1/core/phpunit/0/testfile.txt');
         $file2 = $fs->create_file_from_pathname($filerecord, $path);
     }
 
index 3896770..bc6c784 100644 (file)
@@ -154,7 +154,7 @@ class core_files_file_system_filedir_testcase extends advanced_testcase {
         // This should generate an exception.
         $this->expectException('file_exception');
         $this->expectExceptionMessageMatches(
-            '/Can not create local file pool directories, please verify permissions in dataroot./');
+            '/Cannot create local file pool directories. Please verify permissions in dataroot./');
 
         new file_system_filedir();
     }
@@ -178,7 +178,7 @@ class core_files_file_system_filedir_testcase extends advanced_testcase {
         // This should generate an exception.
         $this->expectException('file_exception');
         $this->expectExceptionMessageMatches(
-            '/Can not create local file pool directories, please verify permissions in dataroot./');
+            '/Cannot create local file pool directories. Please verify permissions in dataroot./');
 
         new file_system_filedir();
     }
@@ -835,7 +835,7 @@ class core_files_file_system_filedir_testcase extends advanced_testcase {
 
         $this->expectException('file_exception');
         $this->expectExceptionMessageMatches(
-            "/Can not create local file pool directories, please verify permissions in dataroot./");
+            "/Cannot create local file pool directories. Please verify permissions in dataroot./");
 
         // Attempt to add the file to the file pool.
         $fs = new file_system_filedir();
@@ -893,7 +893,7 @@ class core_files_file_system_filedir_testcase extends advanced_testcase {
 
         $this->expectException('file_exception');
         $this->expectExceptionMessageMatches(
-            "/Can not create local file pool directories, please verify permissions in dataroot./");
+            "/Cannot create local file pool directories. Please verify permissions in dataroot./");
 
         // Attempt to add the file to the file pool.
         $fs = new file_system_filedir();
index f91a9f9..7a657eb 100644 (file)
@@ -511,14 +511,22 @@ abstract class oauth2_client extends curl {
     public function get_login_url() {
 
         $callbackurl = self::callback_url();
+        $defaultparams = [
+            'client_id' => $this->clientid,
+            'response_type' => 'code',
+            'redirect_uri' => $callbackurl->out(false),
+            'state' => $this->returnurl->out_as_local_url(false),
+
+        ];
+        if (!empty($this->scope)) {
+            // The scope should only be included if a value is set.
+            // If none provided, the server MUST process the request and provide an appropriate documented response.
+            // See spec https://tools.ietf.org/html/rfc6749#section-3.3
+            $defaultparams['scope'] = $this->scope;
+        }
+
         $params = array_merge(
-            [
-                'client_id' => $this->clientid,
-                'response_type' => 'code',
-                'redirect_uri' => $callbackurl->out(false),
-                'state' => $this->returnurl->out_as_local_url(false),
-                'scope' => $this->scope,
-            ],
+            $defaultparams,
             $this->get_additional_login_parameters()
         );
 
index 0e64173..4b43557 100644 (file)
@@ -4133,12 +4133,6 @@ EOD;
         $subheader = null;
         $userbuttons = null;
 
-        if ($this->should_display_main_logo($headinglevel)) {
-            $sitename = format_string($SITE->fullname, true, array('context' => context_course::instance(SITEID)));
-            return html_writer::div(html_writer::empty_tag('img', [
-                    'src' => $this->get_logo_url(null, 150), 'alt' => $sitename, 'class' => 'img-fluid']), 'logo');
-        }
-
         // Make sure to use the heading if it has been set.
         if (isset($headerinfo['heading'])) {
             $heading = $headerinfo['heading'];
@@ -4214,6 +4208,26 @@ EOD;
             }
         }
 
+        if ($this->should_display_main_logo($headinglevel)) {
+            $sitename = format_string($SITE->fullname, true, ['context' => context_course::instance(SITEID)]);
+            // Logo.
+            $html = html_writer::div(
+                html_writer::empty_tag('img', [
+                    'src' => $this->get_logo_url(null, 150),
+                    'alt' => get_string('logoof', '', $sitename),
+                    'class' => 'img-fluid'
+                ]),
+                'logo'
+            );
+            // Heading.
+            if (!isset($heading)) {
+                $html .= $this->heading($this->page->heading, $headinglevel, 'sr-only');
+            } else {
+                $html .= $this->heading($heading, $headinglevel, 'sr-only');
+            }
+            return $html;
+        }
+
         $contextheader = new context_header($heading, $headinglevel, $imagedata, $userbuttons);
         return $this->render_context_header($contextheader);
     }
index e6ff7f8..4056383 100644 (file)
@@ -308,7 +308,7 @@ class HTML_QuickForm_date extends HTML_QuickForm_group
         $locale    =& $this->_locale[$this->_options['language']];
         $backslash =  false;
         for ($i = 0, $length = strlen($this->_options['format']); $i < $length; $i++) {
-            $sign = $this->_options['format']{$i};
+            $sign = $this->_options['format'][$i];
             if ($backslash) {
                 $backslash  = false;
                 $separator .= $sign;
@@ -507,4 +507,4 @@ class HTML_QuickForm_date extends HTML_QuickForm_group
 
     // }}}
 }
-?>
\ No newline at end of file
+?>
index c89c646..8423a44 100644 (file)
@@ -12,6 +12,10 @@ Quickforms
 Full of our custom hacks, no way to upgrade to latest upstream.
 Most probably we will stop using this library in the future.
 
+Just dropping a couple of links here, for whenever we update/switch or whatever:
+- QF2: https://github.com/pear/HTML_QuickForm2 (https://moodle.org/mod/forum/discuss.php?d=200124)
+- Quickform (fork): https://github.com/openpsa/quickform
+
 MDL-20876 - replaced split() with explode() or preg_split() where appropriate
 MDL-40267 - Moodle core_text strlen functions used for range rule rule to be utf8 safe.
 MDL-46467 - $mform->hardfreeze causes labels to loose their for HTML attribute
@@ -24,6 +28,7 @@ MDL-60281 - replaced deprecated create_function() with lambda functions for PHP7
 MDL-70711 - removed unnecessary if-else conditional block in HTML_QuickForm as the given
             condition always evaluates to false due to the deprecated get_magic_quotes_gpc()
             which always returns false
+MDL-70457 - PHP 7.4 curly brackets string access fix.
 
 Pear
 ====
index 9bfb3e3..9dd220f 100644 (file)
@@ -419,10 +419,9 @@ function default_exception_handler($ex) {
  * @param string $errstr
  * @param string $errfile
  * @param int $errline
- * @param array $errcontext
  * @return bool false means use default error handler
  */
-function default_error_handler($errno, $errstr, $errfile, $errline, $errcontext) {
+function default_error_handler($errno, $errstr, $errfile, $errline) {
     if ($errno == 4096) {
         //fatal catchable error
         throw new coding_exception('PHP catchable fatal error', $errstr);
index ad681ef..22793fa 100644 (file)
@@ -359,7 +359,7 @@ class xmldb_structure extends xmldb_object {
             }
             $o.= '  </TABLES>' . "\n";
         }
-        $o.= '</XMLDB>';
+        $o.= '</XMLDB>' . "\n";
 
         return $o;
     }
index a7eee1b..2e903c1 100644 (file)
@@ -110,7 +110,7 @@ $string['batchoperationreverttodraft'] = 'revert submissions to draft';
 $string['batchsetallocatedmarker'] = 'Set allocated marker for {$a} selected user(s).';
 $string['batchsetmarkingworkflowstateforusers'] = 'Set marking workflow state for {$a} selected user(s).';
 $string['blindmarking'] = 'Anonymous submissions';
-$string['blindmarkingenabledwarning'] = 'Anonymous submissions are enabled for this activity.';
+$string['blindmarkingenabledwarning'] = 'Anonymous submissions are enabled for this activity. Grades will not be added to the gradebook until student identities are revealed via the grading action menu.';
 $string['blindmarking_help'] = 'Anonymous submissions hide the identity of students from markers. Anonymous submission settings will be locked once a submission or grade has been made in relation to this assignment.';
 $string['calendardue'] = '{$a} is due';
 $string['calendargradingdue'] = '{$a} is due to be graded';
index 6dd703f..9619461 100644 (file)
@@ -138,9 +138,19 @@ class data_field_textarea extends data_field_base {
             $link_options->env = 'editor';
             $link_options->itemid = $draftitemid;
 
+            // H5P plugin.
+            $args->accepted_types = ['h5p'];
+            $h5poptions = initialise_filepicker($args);
+            $h5poptions->context = $this->context;
+            $h5poptions->client_id = uniqid();
+            $h5poptions->maxbytes  = $options['maxbytes'];
+            $h5poptions->env = 'editor';
+            $h5poptions->itemid = $draftitemid;
+
             $fpoptions['image'] = $image_options;
             $fpoptions['media'] = $media_options;
             $fpoptions['link'] = $link_options;
+            $fpoptions['h5p'] = $h5poptions;
         }
 
         $editor = editors_get_preferred_editor($format);
index 1316920..febc2e6 100644 (file)
@@ -4,8 +4,7 @@ Feature: Users can add entries to database activities
   As a user
   I need to add entries to databases
 
-  @javascript
-  Scenario: Students can add entries to a database
+  Background:
     Given the following "users" exist:
       | username | firstname | lastname | email |
       | student1 | Student | 1 | student1@example.com |
@@ -20,7 +19,10 @@ Feature: Users can add entries to database activities
     And the following "activities" exist:
       | activity | name               | intro | course | idnumber |
       | data     | Test database name | n     | C1     | data1    |
-    And I log in as "teacher1"
+
+  @javascript
+  Scenario: Students can add entries to a database
+    Given I log in as "teacher1"
     And I am on "Course 1" course homepage
     And I add a "Text input" field to "Test database name" database and I fill the form with:
       | Field name | Test field name |
@@ -76,3 +78,14 @@ Feature: Users can add entries to database activities
     And I press "Delete selected"
     And I press "Delete"
     And I should see "No entries in database"
+
+  @javascript @editor @editor_atto @atto @atto_h5p
+  Scenario: If a new text area entry is added, the filepicker is displayed in the H5P Atto button
+    Given I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    And I add a "Text area" field to "Test database name" database and I fill the form with:
+      | Field name | Textarea field name |
+    When I add an entry to "Test database name" database with:
+      | Textarea field name | This is the content |
+    And I click on "Insert H5P" "button"
+    Then I should see "Browse repositories..."
index c1bc05e..49ee6b3 100644 (file)
@@ -259,7 +259,7 @@ $string['searchcourses'] = 'Search courses';
 $string['searchcourses_help'] = 'Search for the code or name of the course(s) that you wish to associate with this feedback.';
 $string['selected_dump'] = 'Selected indexes of $SESSION variable are dumped below:';
 $string['send'] = 'Send';
-$string['send_message'] = 'Send message';
+$string['send_message'] = 'Send notification';
 $string['show_all'] = 'Show all';
 $string['show_analysepage_after_submit'] = 'Show analysis page';
 $string['show_entries'] = 'Show responses';
index 96a0725..fcb7692 100644 (file)
@@ -186,9 +186,9 @@ class discussion_list {
             'forum' => (array) $forumexporter->export($this->renderer),
             'contextid' => $forum->get_context()->id,
             'cmid' => $cm->id,
-            'name' => $forum->get_name(),
+            'name' => format_string($forum->get_name()),
             'courseid' => $course->id,
-            'coursename' => $course->shortname,
+            'coursename' => format_string($course->shortname),
             'experimentaldisplaymode' => $displaymode == FORUM_MODE_NESTED_V2,
             'gradingcomponent' => $this->forumgradeitem->get_grading_component_name(),
             'gradingcomponentsubtype' => $this->forumgradeitem->get_grading_component_subtype(),
index 9514e74..d682823 100644 (file)
@@ -179,9 +179,9 @@ switch ($forum->get_type()) {
                 $gradeobj = (object) [
                     'contextid' => $forum->get_context()->id,
                     'cmid' => $cmid,
-                    'name' => $forum->get_name(),
+                    'name' => format_string($forum->get_name()),
                     'courseid' => $course->id,
-                    'coursename' => $course->shortname,
+                    'coursename' => format_string($course->shortname),
                     'experimentaldisplaymode' => $displaymode == FORUM_MODE_NESTED_V2,
                     'groupid' => $groupid,
                     'gradingcomponent' => $forumgradeitem->get_grading_component_name(),
@@ -196,9 +196,9 @@ switch ($forum->get_type()) {
                 $gradeobj = (object) [
                     'contextid' => $forum->get_context()->id,
                     'cmid' => $cmid,
-                    'name' => $forum->get_name(),
+                    'name' => format_string($forum->get_name()),
                     'courseid' => $course->id,
-                    'coursename' => $course->shortname,
+                    'coursename' => format_string($course->shortname),
                     'groupid' => $groupid,
                     'userid' => $USER->id,
                     'gradingcomponent' => $forumgradeitem->get_grading_component_name(),
index 95d73f7..a10474f 100644 (file)
@@ -741,7 +741,7 @@ $string['quizopenclose'] = 'Open and close dates';
 $string['quizopenclose_help'] = 'Students can only start their attempt(s) after the open time and they must complete their attempts before the close time.';
 $string['quizopenclose_link'] = 'mod/quiz/timing';
 $string['quizopened'] = 'This quiz is open.';
-$string['quizopenedon'] = 'This quiz opened at {$a}';
+$string['quizopenedon'] = 'This quiz opened on {$a}';
 $string['quizopens'] = 'Quiz opens';
 $string['quizopenwillclose'] = 'This quiz is open, will close on {$a} at';
 $string['quizordernotrandom'] = 'Order of quiz not shuffled';
index f3ce788..f185649 100644 (file)
@@ -1271,10 +1271,7 @@ class mod_workshop_external extends external_api {
             if (!empty($formdata[$typeofdata])) {
                 $alldata = (array) $formdata[$typeofdata];
                 foreach ($alldata as $key => $val) {
-                    if (strpos($key, 'peercomment__idx_') === 0) {
-                        // Format reviewer comment.
-                        list($val, $format) = external_format_text($val, FORMAT_MOODLE, $context->id);
-                    } else if (strpos($key, 'description__idx_')) {
+                    if (strpos($key, 'description__idx_')) {
                         // Format dimension description.
                         $id = str_replace('description__idx_', '', $key);
                         list($val, $format) = external_format_text($val, $alldata['dimensionid__idx_' . $id . 'format'],
index d3ffcff..d9e72cf 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="mod/workshop/db" VERSION="20180626" COMMENT="XMLDB file for Moodle mod/workshop"
+<XMLDB PATH="mod/workshop/db" VERSION="20210302" COMMENT="XMLDB file for Moodle mod/workshop"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
 >
         <FIELD NAME="assessmentid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Part of which assessment this grade is of"/>
         <FIELD NAME="strategy" TYPE="char" LENGTH="30" NOTNULL="true" SEQUENCE="false"/>
         <FIELD NAME="dimensionid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Foreign key. References dimension id in one of the grading strategy tables."/>
-        <FIELD NAME="grade" TYPE="number" LENGTH="10" NOTNULL="true" SEQUENCE="false" DECIMALS="5" COMMENT="Given grade in the referenced assessment dimension."/>
+        <FIELD NAME="grade" TYPE="number" LENGTH="10" NOTNULL="false" SEQUENCE="false" DECIMALS="5" COMMENT="Given grade in the referenced assessment dimension."/>
         <FIELD NAME="peercomment" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Reviewer's comment to the grade value."/>
         <FIELD NAME="peercommentformat" TYPE="int" LENGTH="3" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="The format of peercomment field"/>
       </FIELDS>
index 74b2e72..2bf08d2 100644 (file)
@@ -51,5 +51,18 @@ function xmldb_workshop_upgrade($oldversion) {
     // Automatically generated Moodle v3.9.0 release upgrade line.
     // Put any upgrade step following this.
 
+    if ($oldversion < 2021052501) {
+
+        // Changing nullability of field grade on table workshop_grades to null.
+        $table = new xmldb_table('workshop_grades');
+        $field = new xmldb_field('grade', XMLDB_TYPE_NUMBER, '10, 5', null, null, null, null, 'dimensionid');
+
+        // Launch change of nullability for field grade.
+        $dbman->change_field_notnull($table, $field);
+
+        // Workshop savepoint reached.
+        upgrade_mod_savepoint(true, 2021052501, 'workshop');
+    }
+
     return true;
 }
index 95f6132..12ae8b4 100644 (file)
@@ -76,11 +76,13 @@ class workshop_accumulative_assessment_form extends workshop_assessment_form {
 
             // grade for this aspect
             $label = get_string('dimensiongradefor', 'workshopform_accumulative', $dimtitle);
-            $options = make_grades_menu($fields->{'grade__idx_' . $i});
-            $options = array('-1' => get_string('choosedots')) + $options;
-            $mform->addElement('select', 'grade__idx_' . $i, $label, $options);
-            $mform->addRule(array('grade__idx_' . $i, 'minusone') , get_string('mustchoosegrade', 'workshopform_accumulative'), 'compare', 'gt');
-
+            if ($fields->{'grade__idx_' . $i}) {
+                $options = make_grades_menu($fields->{'grade__idx_' . $i});
+                $options = array('-1' => get_string('choosedots')) + $options;
+                $mform->addElement('select', 'grade__idx_' . $i, $label, $options);
+                $mform->addRule(array('grade__idx_' . $i, 'minusone'),
+                    get_string('mustchoosegrade', 'workshopform_accumulative'), 'compare', 'gt');
+            }
             // comment
             $label = get_string('dimensioncommentfor', 'workshopform_accumulative', $dimtitle);
             //$mform->addElement('editor', 'peercomment__idx_' . $i, $label, null, array('maxfiles' => 0));
index 87a5f99..bac0ec6 100644 (file)
@@ -269,7 +269,9 @@ class workshop_accumulative_strategy implements workshop_strategy {
             $grade->assessmentid = $assessment->id;
             $grade->strategy = 'accumulative';
             $grade->dimensionid = $data->{'dimensionid__idx_' . $i};
-            $grade->grade = $data->{'grade__idx_' . $i};
+            if (isset($data->{'grade__idx_' . $i})) {
+                $grade->grade = $data->{'grade__idx_' . $i};
+            }
             $grade->peercomment = $data->{'peercomment__idx_' . $i};
             $grade->peercommentformat = FORMAT_MOODLE;
             if (empty($grade->id)) {
index 2ed59f7..8c92669 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2021052500;        // The current module version (YYYYMMDDXX)
+$plugin->version   = 2021052501;        // The current module version (YYYYMMDDXX).
 $plugin->requires  = 2021052500;        // Requires this Moodle version.
 $plugin->component = 'mod_workshop';
index 872e1d8..85cea05 100644 (file)
@@ -57,7 +57,7 @@ class question_name_idnumber_tags_column extends question_name_column {
         if ($question->idnumber !== null && $question->idnumber !== '') {
             echo ' ' . \html_writer::span(
                             \html_writer::span(get_string('idnumber', 'question'), 'accesshide') . ' ' .
-                            \html_writer::span($question->idnumber, 'badge badge-primary'), 'ml-1');
+                            \html_writer::span(s($question->idnumber), 'badge badge-primary'), 'ml-1');
         }
 
         // Question tags.
index cb6b56c..e32d1ac 100644 (file)
@@ -772,8 +772,10 @@ class qformat_xml extends qformat_default {
                 array('#', 'responserequired', 0, '#'), 1);
         $qo->minwordlimit = $this->getpath($question,
                 array('#', 'minwordlimit', 0, '#'), null);
+        $qo->minwordenabled = !empty($qo->minwordlimit);
         $qo->maxwordlimit = $this->getpath($question,
                 array('#', 'maxwordlimit', 0, '#'), null);
+        $qo->maxwordenabled = !empty($qo->maxwordlimit);
         $qo->attachments = $this->getpath($question,
                 array('#', 'attachments', 0, '#'), 0);
         $qo->attachmentsrequired = $this->getpath($question,
index bddd49f..7c1a0b1 100644 (file)
@@ -407,7 +407,9 @@ END;
         $expectedq->responserequired = 1;
         $expectedq->responsefieldlines = 15;
         $expectedq->minwordlimit = null;
+        $expectedq->minwordenabled = false;
         $expectedq->maxwordlimit = null;
+        $expectedq->maxwordenabled = false;
         $expectedq->attachments = 0;
         $expectedq->attachmentsrequired = 0;
         $expectedq->maxbytes = 0;
@@ -470,7 +472,9 @@ END;
         $expectedq->responserequired = 0;
         $expectedq->responsefieldlines = 42;
         $expectedq->minwordlimit = null;
+        $expectedq->minwordenabled = false;
         $expectedq->maxwordlimit = null;
+        $expectedq->maxwordenabled = false;
         $expectedq->attachments = -1;
         $expectedq->attachmentsrequired = 1;
         $expectedq->maxbytes = 0;
@@ -537,7 +541,9 @@ END;
         $expectedq->responserequired = 0;
         $expectedq->responsefieldlines = 42;
         $expectedq->minwordlimit = 10;
+        $expectedq->minwordenabled = true;
         $expectedq->maxwordlimit = 20;
+        $expectedq->maxwordenabled = true;
         $expectedq->attachments = -1;
         $expectedq->attachmentsrequired = 1;
         $expectedq->maxbytes = 52428800; // 50MB.
index 25a103e..af57ff9 100644 (file)
@@ -18,10 +18,10 @@ Feature: The questions in the question bank can be sorted in various ways
       | contextlevel | reference | name           |
       | Course       | C1        | Test questions |
     And the following "questions" exist:
-      | questioncategory | qtype     | name              | user     | questiontext    | idnumber  |
-      | Test questions   | essay     | A question 1 name | admin    | Question 1 text | numidnuma |
-      | Test questions   | essay     | B question 2 name | teacher1 | Question 2 text |           |
-      | Test questions   | numerical | C question 3 name | teacher1 | Question 3 text | numidnumc |
+      | questioncategory | qtype     | name              | user     | questiontext    | idnumber    |
+      | Test questions   | essay     | A question 1 name | admin    | Question 1 text | numidnum</a |
+      | Test questions   | essay     | B question 2 name | teacher1 | Question 2 text |             |
+      | Test questions   | numerical | C question 3 name | teacher1 | Question 3 text | numidnum</c |
     And I log in as "teacher1"
     And I am on "Course 1" course homepage
     And I navigate to "Question bank > Questions" in current page administration
@@ -34,7 +34,7 @@ Feature: The questions in the question bank can be sorted in various ways
   Scenario: The questions can be sorted by idnumber
     When I follow "Sort by ID number ascending"
     Then "C question 3 name" "checkbox" should appear after "A question 1 name" "checkbox"
-    And I should see "numidnumc" in the "C question 3 name" "table_row"
+    And I should see "numidnum</c" in the "C question 3 name" "table_row"
     And I follow "Sort by ID number descending"
     And "C question 3 name" "checkbox" should appear before "A question 1 name" "checkbox"
 
index 44105c5..e73f937 100644 (file)
@@ -18,7 +18,7 @@ I need to choose the appropriate minimum and/or maximum number of words for inpu
       | Course       | C1        | Test questions |
     And the following "questions" exist:
       | questioncategory | qtype | name          | template | minwordlimit | maxwordlimit |
-      | Test questions   | essay | essay-min-max | editor   | null         | null         |
+      | Test questions   | essay | essay-min-max | editor   | 0            | 0            |
     Given I log in as "teacher1"
     And I am on "Course 1" course homepage
     And I navigate to "Question bank" in current page administration
@@ -56,3 +56,27 @@ I need to choose the appropriate minimum and/or maximum number of words for inpu
     When I set the field "Require text" to "Text input is optional"
     Then I should not see "Minimum word limit"
     And I should not see "Minimum word limit"
+
+  @javascript
+  Scenario: Minimum/Maximum word limit can be unset after being set.
+    Given I choose "Edit question" action for "essay-min-max" in the question bank
+    When I set the following fields to these values:
+      | minwordenabled  | 1   |
+      | id_minwordlimit | 100 |
+      | maxwordenabled  | 1   |
+      | id_maxwordlimit | 200 |
+    And I click on "Save changes and continue editing" "button"
+    Then the following fields match these values:
+      | minwordenabled  | 1   |
+      | id_minwordlimit | 100 |
+      | maxwordenabled  | 1   |
+      | id_maxwordlimit | 200 |
+    And I set the following fields to these values:
+      | minwordenabled  | 0 |
+      | maxwordenabled  | 0 |
+    And I click on "Save changes and continue editing" "button"
+    And the following fields match these values:
+      | minwordenabled  | 0 |
+      | id_minwordlimit |   |
+      | maxwordenabled  | 0 |
+      | id_maxwordlimit |   |
index 195b78c..e365569 100644 (file)
@@ -28,3 +28,5 @@ Feature: Test importing Essay questions
     And I should see "Write an essay with 500 words."
     And I press "Continue"
     And I should see "essay-001"
+    And I choose "Edit question" action for "essay-001" in the question bank
+    And the field "id_maxwordlimit" matches value "20"
index a6fb84b..811a687 100644 (file)
@@ -26,7 +26,7 @@
     <responserequired>1</responserequired>
     <responsefieldlines>15</responsefieldlines>
     <minwordlimit></minwordlimit>
-    <maxwordlimit></maxwordlimit>
+    <maxwordlimit>20</maxwordlimit>
     <attachments>0</attachments>
     <attachmentsrequired>0</attachmentsrequired>
     <maxbytes>0</maxbytes>
index 987fed9..8bba6e7 100644 (file)
@@ -1870,10 +1870,13 @@ ul.badges {
 }
 
 .badges li .expireimage {
+    background-image: url([[pix:i/expired]]);
+    background-repeat: no-repeat;
+    background-size: 100px 100px;
     width: 100px;
     height: 100px;
     left: 25px;
-    top: 0;
+    top: 15px;
     position: absolute;
     z-index: 10;
     opacity: 0.85;
@@ -1891,6 +1894,9 @@ ul.badges {
     margin-bottom: 20px;
 
     .expireimage {
+        background-image: url([[pix:i/expired]]);
+        background-repeat: no-repeat;
+        background-size: 100px 100px;
         width: 100px;
         height: 100px;
         left: 0;
index fff7cd4..593673a 100644 (file)
@@ -659,8 +659,9 @@ $message-day-color: color-yiq($message-app-bg) !default;
         height: 100%;
         margin-top: 0;
         .conversationcontainer {
-            max-height: calc(100vh - 50px);
-            overflow: auto;
+            .section {
+                max-height: calc(100vh - 50px);
+            }
         }
         div[role="main"] {
             height: 100%;
index 076e953..ee526e2 100644 (file)
@@ -11173,10 +11173,13 @@ ul.badges {
   position: relative; }
 
 .badges li .expireimage {
+  background-image: url([[pix:i/expired]]);
+  background-repeat: no-repeat;
+  background-size: 100px 100px;
   width: 100px;
   height: 100px;
   left: 25px;
-  top: 0;
+  top: 15px;
   position: absolute;
   z-index: 10;
   opacity: 0.85; }
@@ -11192,6 +11195,9 @@ ul.badges {
   margin-top: 17px;
   margin-bottom: 20px; }
   #badge-image .expireimage {
+    background-image: url([[pix:i/expired]]);
+    background-repeat: no-repeat;
+    background-size: 100px 100px;
     width: 100px;
     height: 100px;
     left: 0;
@@ -15468,9 +15474,8 @@ a.ygtvspacer:hover {
 #page-message-index #region-main {
   height: 100%;
   margin-top: 0; }
-  #page-message-index #region-main .conversationcontainer {
-    max-height: calc(100vh - 50px);
-    overflow: auto; }
+  #page-message-index #region-main .conversationcontainer .section {
+    max-height: calc(100vh - 50px); }
   #page-message-index #region-main div[role="main"] {
     height: 100%; }
     #page-message-index #region-main div[role="main"] #maincontent {
index 60e19a2..95975c5 100644 (file)
@@ -11383,10 +11383,13 @@ ul.badges {
   position: relative; }
 
 .badges li .expireimage {
+  background-image: url([[pix:i/expired]]);
+  background-repeat: no-repeat;
+  background-size: 100px 100px;
   width: 100px;
   height: 100px;
   left: 25px;
-  top: 0;
+  top: 15px;
   position: absolute;
   z-index: 10;
   opacity: 0.85; }
@@ -11402,6 +11405,9 @@ ul.badges {
   margin-top: 17px;
   margin-bottom: 20px; }
   #badge-image .expireimage {
+    background-image: url([[pix:i/expired]]);
+    background-repeat: no-repeat;
+    background-size: 100px 100px;
     width: 100px;
     height: 100px;
     left: 0;
@@ -15684,9 +15690,8 @@ a.ygtvspacer:hover {
 #page-message-index #region-main {
   height: 100%;
   margin-top: 0; }
-  #page-message-index #region-main .conversationcontainer {
-    max-height: calc(100vh - 50px);
-    overflow: auto; }
+  #page-message-index #region-main .conversationcontainer .section {
+    max-height: calc(100vh - 50px); }
   #page-message-index #region-main div[role="main"] {
     height: 100%; }
     #page-message-index #region-main div[role="main"] #maincontent {
index 17dd34c..31406cd 100644 (file)
@@ -224,7 +224,7 @@ class participants_filter implements renderable, templatable {
             array_map(function($group) {
                 return (object) [
                     'value' => $group->id,
-                    'title' => $group->name,
+                    'title' => format_string($group->name, true, ['context' => $this->context]),
                 ];
             }, array_values($groups))
         );
index 482be7b..3f7de16 100644 (file)
@@ -29,7 +29,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$version  = 2021052500.63;              // YYYYMMDD      = weekly release date of this DEV branch.
+$version  = 2021052500.64;              // YYYYMMDD      = weekly release date of this DEV branch.
                                         //         RR    = release increments - 00 in DEV branches.
                                         //           .XX = incremental changes.
 $release  = '4.0dev (Build: 20210226)'; // Human-friendly version name