Merge branch 'MDL-69156_310' of https://github.com/ffhs/moodle into MOODLE_310_STABLE
authorAndrew Nicols <andrew@nicols.co.uk>
Fri, 30 Oct 2020 03:52:07 +0000 (11:52 +0800)
committerAndrew Nicols <andrew@nicols.co.uk>
Fri, 30 Oct 2020 03:52:07 +0000 (11:52 +0800)
67 files changed:
.nvmrc
admin/tool/customlang/lang/en/tool_customlang.php
admin/tool/mobile/classes/api.php
admin/tool/mobile/lang/en/tool_mobile.php
admin/tool/replace/lang/en/tool_replace.php
admin/tool/task/lang/en/tool_task.php
admin/tool/task/tests/behat/running_tasks.feature
blocks/myoverview/templates/nav-display-selector.mustache
blocks/myoverview/templates/nav-grouping-selector.mustache
blocks/myoverview/templates/nav-sort-selector.mustache
blocks/news_items/block_news_items.php
blocks/timeline/amd/build/view_nav.min.js
blocks/timeline/amd/build/view_nav.min.js.map
blocks/timeline/amd/src/view_nav.js
blocks/timeline/templates/nav-day-filter.mustache
blocks/timeline/templates/nav-view-selector.mustache
enrol/manual/lang/en/enrol_manual.php
grade/report/singleview/lang/en/gradereport_singleview.php
group/tests/behat/overview.feature
lang/en/admin.php
lang/en/analytics.php
lang/en/antivirus.php
lang/en/backup.php
lang/en/completion.php
lang/en/contentbank.php
lang/en/error.php
lang/en/group.php
lang/en/moodle.php
lang/en/repository.php
lang/en/role.php
lang/en/user.php
lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button-debug.js
lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button-min.js
lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button.js
lib/editor/atto/plugins/equation/yui/src/button/js/button.js
lib/form/filemanager.js
lib/templates/paged_content_paging_bar.mustache
message/output/popup/db/upgrade.php
mod/feedback/lang/en/feedback.php
mod/feedback/tests/behat/question_types_non_anon.feature
mod/forum/classes/local/exporters/post.php
mod/forum/tests/exporters_post_test.php
mod/forum/tests/externallib_test.php
mod/quiz/lib.php
package.json
question/type/multichoice/amd/build/answers.min.js [new file with mode: 0644]
question/type/multichoice/amd/build/answers.min.js.map [new file with mode: 0644]
question/type/multichoice/amd/build/clearchoice.min.js
question/type/multichoice/amd/build/clearchoice.min.js.map
question/type/multichoice/amd/src/answers.js [new file with mode: 0644]
question/type/multichoice/amd/src/clearchoice.js
question/type/multichoice/renderer.php
question/type/multichoice/tests/behat/behat_qtype_multichoice.php [new file with mode: 0644]
question/type/multichoice/tests/behat/clearanswers.feature
question/type/multichoice/tests/behat/preview.feature
question/type/multichoice/upgrade.txt [new file with mode: 0644]
report/infectedfiles/lang/en/report_infectedfiles.php
repository/contentbank/tests/behat/file_update.feature
repository/filepicker.js
repository/onedrive/lang/en/repository_onedrive.php
repository/tests/behat/create_shortcut.feature
theme/boost/scss/moodle/core.scss
theme/boost/scss/moodle/forms.scss
theme/boost/scss/preset/default.scss
theme/boost/style/moodle.css
theme/classic/scss/preset/default.scss
theme/classic/style/moodle.css

diff --git a/.nvmrc b/.nvmrc
index 01f1a56..55d1782 100644 (file)
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-v14.0.0
+v14.15.0
index 29dd1e2..882f920 100644 (file)
@@ -33,8 +33,8 @@ $string['checkoutinprogress'] = 'Loading language pack';
 $string['cliexportfileexists'] = 'File for {$a->lang} already exists, skipping. If you want to overwrite add the --override=true option.';
 $string['cliexportheading'] = 'Starting to export lang files.';
 $string['cliexportnofilefoundforlang'] = 'No file found to export. Skipping export for this language.';
-$string['cliexportfilenotfoundforcomponent'] = 'File {$a->filepath} not found for language {$a->lang}.Skipping this file.';
-$string['cliexportstartexport'] = 'Exporting language "{$a}"';
+$string['cliexportfilenotfoundforcomponent'] = 'File {$a->filepath} not found for language {$a->lang}. Skipping this file.';
+$string['cliexportstartexport'] = 'Exporting language {$a}';
 $string['cliexportzipdone'] = 'Zip created: {$a}';
 $string['cliexportzipfail'] = 'Cannot create zip {$a}';
 $string['clifiles'] = 'Files to import into {$a}';
index 9ea41b0..45f0d25 100644 (file)
@@ -603,8 +603,21 @@ class api {
                 $timenow = time();
                 $expectedissuer = null;
                 foreach ($info['certinfo'] as $cert) {
+
+                    // Due to a bug in certain curl/openssl versions the signature algorithm isn't always correctly parsed.
+                    // See https://github.com/curl/curl/issues/3706 for reference.
+                    if (!array_key_exists('Signature Algorithm', $cert)) {
+                        // The malformed field that does contain the algorithm we're looking for looks like the following:
+                        // <WHITESPACE>Signature Algorithm: <ALGORITHM><CRLF><ALGORITHM>.
+                        preg_match('/\s+Signature Algorithm: (?<algorithm>[^\s]+)/', $cert['Public Key Algorithm'], $matches);
+
+                        $signaturealgorithm = $matches['algorithm'] ?? '';
+                    } else {
+                        $signaturealgorithm = $cert['Signature Algorithm'];
+                    }
+
                     // Check if the signature algorithm is weak (Android won't work with SHA-1).
-                    if ($cert['Signature Algorithm'] == 'sha1WithRSAEncryption' || $cert['Signature Algorithm'] == 'sha1WithRSA') {
+                    if ($signaturealgorithm == 'sha1WithRSAEncryption' || $signaturealgorithm == 'sha1WithRSA') {
                         $warnings[] = ['insecurealgorithmwarning', 'tool_mobile'];
                     }
                     // Check certificate start date.
index f3e6d7d..6eedc75 100644 (file)
@@ -64,7 +64,7 @@ $string['downloadcourses'] = 'Download courses';
 $string['enablesmartappbanners'] = 'Enable App Banners';
 $string['enablesmartappbanners_desc'] = 'If enabled, a banner promoting the mobile app will be displayed when accessing the site using a mobile browser.';
 $string['filetypeexclusionlist'] = 'File type exclusion list';
-$string['filetypeexclusionlist_desc'] = 'List of file types that we don\'t want users to try and open in the app. These files will still be listed on the app\'s course screen, but attempting to open them on iOS or Android would display a warning to the user indicating that this file type is not intended for use on a mobile device. They can then either cancel the open, or ignore the warning and open anyway.';
+$string['filetypeexclusionlist_desc'] = 'Select all file types which are not for use on a mobile device. Such files will be listed in the course, then if a user attempts to open them, a warning will be displayed advising that the file type is not intended for use on a mobile device. The user can then cancel or ignore the warning and open the file anyway.';
 $string['filetypeexclusionlistplaceholder'] = 'Mobile file type exclusion list';
 $string['forcedurlscheme'] = 'If you want to allow only your custom branded app to be opened via a browser window, then specify its URL scheme here. If you want to allow only the official app, then set the default value. Leave the field empty if you want to allow any app.';
 $string['forcedurlscheme_key'] = 'URL scheme';
index e8dce48..55e960c 100644 (file)
@@ -23,7 +23,7 @@
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 $string['additionalskiptables'] = 'Additional skip tables';
-$string['additionalskiptables_desc'] = 'Please specify the additional tables (comma separated list) you want to skip while running DB search and replace.';
+$string['additionalskiptables_desc'] = 'A list of tables (separated by commas) which should be skipped when running the database search and replace.';
 $string['cannotfit'] = 'The replacement is longer than the original and shortening is not allowed; cannot continue.';
 $string['disclaimer'] = 'I understand the risks of this operation';
 $string['doit'] = 'Yes, do it!';
index 08cf819..8532fff 100644 (file)
@@ -22,9 +22,9 @@
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-$string['adhoc'] = 'Ad-hoc';
-$string['adhoctaskid'] = 'Ad-hoc task id: {$a}';
-$string['adhoctasks'] = 'Ad-hoc tasks';
+$string['adhoc'] = 'Ad hoc';
+$string['adhoctaskid'] = 'Ad hoc task ID: {$a}';
+$string['adhoctasks'] = 'Ad hoc tasks';
 $string['asap'] = 'ASAP';
 $string['adhocempty'] = 'Ad hoc task queue is empty';
 $string['adhocqueuesize'] = 'Ad hoc task queue has {$a} tasks';
index e5db69c..5725d5c 100644 (file)
@@ -28,13 +28,13 @@ Feature: See running scheduled tasks
     And I should see "1914" in the "Automated backups" "table_row"
 
     # Check the "asynchronous_backup_task" adhoc task details.
-    And I should see "Ad-hoc" in the "\core\task\asynchronous_backup_task" "table_row"
+    And I should see "Ad hoc" in the "\core\task\asynchronous_backup_task" "table_row"
     And I should see "2 hours" in the "core\task\asynchronous_backup_task" "table_row"
     And I should see "c69335460f7f" in the "core\task\asynchronous_backup_task" "table_row"
     And I should see "1915" in the "core\task\asynchronous_backup_task" "table_row"
 
     # Check the "asynchronous_restore_task" adhoc task details.
-    And I should see "Ad-hoc" in the "\core\task\asynchronous_restore_task" "table_row"
+    And I should see "Ad hoc" in the "\core\task\asynchronous_restore_task" "table_row"
     And I should see "2 days" in the "core\task\asynchronous_restore_task" "table_row"
     And I should see "c69335460f7f" in the "core\task\asynchronous_restore_task" "table_row"
     And I should see "1916" in the "core\task\asynchronous_restore_task" "table_row"
index 8a1cb32..8c5cc4c 100644 (file)
             {{#summary}}{{#str}} summary, block_myoverview {{/str}}{{/summary}}
         </span>
     </button>
-    <ul class="dropdown-menu" data-show-active-item aria-labelledby="displaydropdown">
+    <ul class="dropdown-menu" role="menu" data-show-active-item data-skip-active-class="true" aria-labelledby="displaydropdown">
     {{#layouts}}
             <li>
-                <a class="dropdown-item {{#active}}active{{/active}}" href="#" data-display-option="display" data-value="{{id}}" data-pref="{{id}}" aria-label="{{arialabel}}" aria-controls="courses-view-{{uniqid}}">
+                <a class="dropdown-item" href="#" data-display-option="display" data-value="{{id}}" data-pref="{{id}}" aria-label="{{arialabel}}" aria-controls="courses-view-{{uniqid}}" role="menuitem" {{#active}}aria-current="true"{{/active}}>
                     {{name}}
                 </a>
             </li>
index ed38dc9..c7ce41b 100644 (file)
             {{selectedcustomfield}}
         </span>
     </button>
-    <ul class="dropdown-menu" data-show-active-item data-active-item-text aria-labelledby="groupingdropdown">
+    <ul class="dropdown-menu" role="menu" data-show-active-item data-skip-active-class="true" data-active-item-text aria-labelledby="groupingdropdown">
         {{#displaygroupingallincludinghidden}}
         <li>
-            <a class="dropdown-item {{#allincludinghidden}}active{{/allincludinghidden}}" href="#" data-filter="grouping" data-value="allincludinghidden" data-pref="allincludinghidden" aria-label="{{#str}} aria:allcoursesincludinghidden, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
+            <a class="dropdown-item" href="#" data-filter="grouping" data-value="allincludinghidden" data-pref="allincludinghidden" aria-label="{{#str}} aria:allcoursesincludinghidden, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}" role="menuitem" {{#allincludinghidden}}aria-current="true"{{/allincludinghidden}}>
                 {{#str}} allincludinghidden, block_myoverview {{/str}}
             </a>
         </li>
@@ -66,7 +66,7 @@
             <span class="filler">&nbsp;</span>
         </li>
         <li>
-            <a class="dropdown-item {{#all}}active{{/all}}" href="#" data-filter="grouping" data-value="all" data-pref="all" aria-label="{{#str}} aria:allcourses, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
+            <a class="dropdown-item" href="#" data-filter="grouping" data-value="all" data-pref="all" aria-label="{{#str}} aria:allcourses, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}" role="menuitem" {{#all}}aria-current="true"{{/all}}>
                 {{#str}} all, block_myoverview {{/str}}
             </a>
         </li>
@@ -76,7 +76,7 @@
             <span class="filler">&nbsp;</span>
         </li>
         <li>
-            <a class="dropdown-item {{#inprogress}}active{{/inprogress}}" href="#" data-filter="grouping" data-value="inprogress" data-pref="inprogress" aria-label="{{#str}} aria:inprogress, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
+            <a class="dropdown-item" href="#" data-filter="grouping" data-value="inprogress" data-pref="inprogress" aria-label="{{#str}} aria:inprogress, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}" role="menuitem" {{#inprogress}}aria-current="true"{{/inprogress}}>
                 {{#str}} inprogress, block_myoverview {{/str}}
             </a>
         </li>
@@ -88,7 +88,7 @@
             </li>
             {{/displaygroupinginprogress}}
         <li>
-            <a class="dropdown-item {{#future}}active{{/future}}" href="#" data-filter="grouping" data-value="future" data-pref="future" aria-label="{{#str}} aria:future, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
+            <a class="dropdown-item" href="#" data-filter="grouping" data-value="future" data-pref="future" aria-label="{{#str}} aria:future, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}" role="menuitem" {{#future}}aria-current="true"{{/future}}>
                 {{#str}} future, block_myoverview {{/str}}
             </a>
         </li>
                 {{/displaygroupingfuture}}
             {{/displaygroupinginprogress}}
         <li>
-            <a class="dropdown-item {{#past}}active{{/past}}" href="#" data-filter="grouping" data-value="past" data-pref="past" aria-label="{{#str}} aria:past, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
+            <a class="dropdown-item" href="#" data-filter="grouping" data-value="past" data-pref="past" aria-label="{{#str}} aria:past, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}" role="menuitem" {{#past}}aria-current="true"{{/past}}>
                 {{#str}} past, block_myoverview {{/str}}
             </a>
         </li>
             </li>
             {{#customfieldvalues}}
                 <li>
-                    <a class="dropdown-item {{#active}}active{{/active}}" href="#" data-filter="grouping"
+                    <a class="dropdown-item" href="#" data-filter="grouping"
                        data-value="customfield" data-pref="customfield" data-customfieldvalue="{{value}}"
                        aria-label="{{#str}}aria:customfield, block_myoverview, {{name}}{{/str}}"
-                       aria-controls="courses-view-{{uniqid}}">
+                       aria-controls="courses-view-{{uniqid}}" role="menuitem" {{#active}}aria-current="true"{{/active}}>
                         {{name}}
                     </a>
                 </li>
             <span class="filler">&nbsp;</span>
         </li>
         <li>
-            <a class="dropdown-item {{#favourites}}active{{/favourites}}" href="#" data-filter="grouping" data-value="favourites"  data-pref="favourites" aria-label="{{#str}} aria:favourites, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
+            <a class="dropdown-item" href="#" data-filter="grouping" data-value="favourites"  data-pref="favourites" aria-label="{{#str}} aria:favourites, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}" role="menuitem" {{#favourites}}aria-current="true"{{/favourites}}>
                 {{#str}} favourites, block_myoverview {{/str}}
             </a>
         {{/displaygroupingfavourites}}
             <span class="filler">&nbsp;</span>
         </li>
         <li>
-            <a class="dropdown-item {{#hidden}}active{{/hidden}}" href="#" data-filter="grouping" data-value="hidden"  data-pref="hidden" aria-label="{{#str}} aria:hiddencourses, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
+            <a class="dropdown-item" href="#" data-filter="grouping" data-value="hidden"  data-pref="hidden" aria-label="{{#str}} aria:hiddencourses, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}" role="menuitem" {{#hidden}}aria-current="true"{{/hidden}}>
                 {{#str}} hiddencourses, block_myoverview {{/str}}
             </a>
         </li>
index 55a425d..4dde517 100644 (file)
                 {{#shortname}}{{#str}} shortname, block_myoverview {{/str}}{{/shortname}}
             </span>
         </button>
-        <ul class="dropdown-menu" data-show-active-item aria-labelledby="sortingdropdown">
+        <ul class="dropdown-menu" role="menu" data-show-active-item data-skip-active-class="true" aria-labelledby="sortingdropdown">
             <li>
-                <a class="dropdown-item {{#title}}active{{/title}}" href="#" data-filter="sort" data-pref="title" data-value="fullname" aria-label="{{#str}} aria:title, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
+                <a class="dropdown-item" href="#" data-filter="sort" data-pref="title" data-value="fullname" aria-label="{{#str}} aria:title, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}" role="menuitem" {{#title}}aria-current="true"{{/title}}>
                     {{#str}} title, block_myoverview {{/str}}
                 </a>
             </li>
             {{#showsortbyshortname}}
             <li>
-                <a class="dropdown-item {{#shortname}}active{{/shortname}}" href="#" data-filter="sort" data-pref="shortname" data-value="shortname" aria-label="{{#str}} aria:shortname, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
+                <a class="dropdown-item" href="#" data-filter="sort" data-pref="shortname" data-value="shortname" aria-label="{{#str}} aria:shortname, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}" role="menuitem" {{#shortname}}aria-current="true"{{/shortname}}>
                     {{#str}} shortname, block_myoverview {{/str}}
                 </a>
             </li>
              {{/showsortbyshortname}}
             <li>
-                <a class="dropdown-item {{#lastaccessed}}active{{/lastaccessed}}" href="#" data-filter="sort" data-pref="lastaccessed" data-value="ul.timeaccess desc" aria-label="{{#str}} aria:lastaccessed, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
+                <a class="dropdown-item" href="#" data-filter="sort" data-pref="lastaccessed" data-value="ul.timeaccess desc" aria-label="{{#str}} aria:lastaccessed, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}" role="menuitem" {{#lastaccessed}}aria-current="true"{{/lastaccessed}}>
                     {{#str}} lastaccessed, block_myoverview {{/str}}
                 </a>
             </li>
index 9cd9406..482227c 100644 (file)
@@ -115,9 +115,13 @@ class block_news_items extends block_base {
 
                 $discussion->subject = format_string($discussion->subject, true, $forum->course);
 
+                $posttime = $discussion->modified;
+                if (!empty($CFG->forum_enabletimedposts) && ($discussion->timestart > $posttime)) {
+                    $posttime = $discussion->timestart;
+                }
                 $text .= '<li class="post">'.
                          '<div class="head clearfix">'.
-                         '<div class="date">'.userdate($discussion->modified, $strftimerecent).'</div>'.
+                         '<div class="date">'.userdate($posttime, $strftimerecent).'</div>'.
                          '<div class="name">'.fullname($discussion).'</div></div>'.
                          '<div class="info"><a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->discussion.'">'.$discussion->subject.'</a></div>'.
                          "</li>\n";
index cb794f9..0ed7161 100644 (file)
Binary files a/blocks/timeline/amd/build/view_nav.min.js and b/blocks/timeline/amd/build/view_nav.min.js differ
index 1e2b36f..2b6c842 100644 (file)
Binary files a/blocks/timeline/amd/build/view_nav.min.js.map and b/blocks/timeline/amd/build/view_nav.min.js.map differ
index 6fd32a6..596c75b 100644 (file)
@@ -89,7 +89,7 @@ function(
 
                 var option = $(e.target).closest(SELECTORS.TIMELINE_DAY_FILTER_OPTION);
 
-                if (option.hasClass('active')) {
+                if (option.attr('aria-current') == 'true') {
                     // If it's already active then we don't need to do anything.
                     return;
                 }
@@ -130,10 +130,12 @@ function(
 
         // Listen for when the user changes tab so that we can show the first set of courses
         // and load their events when they request the sort by courses view for the first time.
-        viewSelector.on('shown shown.bs.tab', function() {
+        viewSelector.on('shown shown.bs.tab', function(e) {
             View.shown(timelineViewRoot);
+            $(e.target).removeClass('active');
         });
 
+
         // Event selector for user_sort
         CustomEvents.define(viewSelector, [CustomEvents.events.activate]);
         viewSelector.on(CustomEvents.events.activate, "[data-toggle='tab']", function(e) {
index 52d92a6..980afd5 100644 (file)
             {{#next6months}} {{#str}}next6months, block_timeline {{/str}} {{/next6months}}
         </span>
     </button>
-    <div id="menudayfilter" role="menu" class="dropdown-menu" data-show-active-item>
+    <div id="menudayfilter" role="menu" class="dropdown-menu" data-show-active-item data-skip-active-class="true">
         <a
-            class="dropdown-item {{#all}} active {{/all}}"
+            class="dropdown-item"
             href="#"
             data-from="-14"
             data-filtername="all"
+            {{#all}}aria-current="true"{{/all}}
             aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} all, core {{/str}}{{/str}}"
             role="menuitem"
-            {{#all}}aria-current="true"{{/all}}
         >
             {{#str}} all, core {{/str}}
         </a>
         <a
-            class="dropdown-item {{#overdue}} active {{/overdue}}"
+            class="dropdown-item"
             href="#"
             data-from="-14"
             data-to="0"
             data-filtername="overdue"
+            {{#overdue}}aria-current="true"{{/overdue}}
             aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} overdue, block_timeline {{/str}}{{/str}}"
             role="menuitem"
-            {{#overdue}}aria-current="true"{{/overdue}}
         >
             {{#str}} overdue, block_timeline {{/str}}
         </a>
         <div class="dropdown-divider" role="separator"></div>
         <h6 class="dropdown-header">{{#str}} duedate, block_timeline {{/str}}</h6>
         <a
-            class="dropdown-item {{#next7days}} active {{/next7days}}"
+            class="dropdown-item"
             href="#"
             data-from="0"
             data-to="7"
             data-filtername="next7days"
+            {{#next7days}}aria-current="true"{{/next7days}}
             aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next7days, block_timeline {{/str}}{{/str}}"
             role="menuitem"
-            {{#next7days}}aria-current="true"{{/next7days}}
         >
             {{#str}} next7days, block_timeline {{/str}}
         </a>
         <a
-            class="dropdown-item {{#next30days}} active {{/next30days}}"
+            class="dropdown-item"
             href="#"
             data-from="0"
             data-to="30"
             data-filtername="next30days"
+            {{#next30days}}aria-current="true"{{/next30days}}
             aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next30days, block_timeline {{/str}}{{/str}}"
             role="menuitem"
-            {{#next30days}}aria-current="true"{{/next30days}}
         >
             {{#str}} next30days, block_timeline {{/str}}
         </a>
         <a
-            class="dropdown-item {{#next3months}} active {{/next3months}}"
+            class="dropdown-item"
             href="#"
             data-from="0"
             data-to="90"
             data-filtername="next3months"
+            {{#next3months}}aria-current="true"{{/next3months}}
             aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next3months, block_timeline {{/str}}{{/str}}"
             role="menuitem"
-            {{#next3months}}aria-current="true"{{/next3months}}
         >
             {{#str}} next3months, block_timeline {{/str}}
         </a>
             data-from="0"
             data-to="180"
             data-filtername="next6months"
+            {{#next6months}}aria-current="true"{{/next6months}}
             aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next6months, block_timeline {{/str}}{{/str}}"
             role="menuitem"
-            {{#next6months}}aria-current="true"{{/next6months}}
         >
             {{#str}} next6months, block_timeline {{/str}}
         </a>
index 106bf75..913f43c 100644 (file)
     </button>
     <div id="menusortby" role="menu" class="dropdown-menu dropdown-menu-right list-group hidden" data-show-active-item data-skip-active-class="true">
         <a
-            class="dropdown-item {{#sorttimelinedates}}active{{/sorttimelinedates}}"
+            class="dropdown-item"
             href="#view_dates_{{uniqid}}"
             data-toggle="tab"
             data-filtername="sortbydates"
+            {{#sorttimelinedates}}aria-current="true"{{/sorttimelinedates}}
             aria-label="{{#str}} ariaviewselectoroption, block_timeline, {{#str}} sortbydates, block_timeline {{/str}}{{/str}}"
             role="menuitem"
-            {{#sorttimelinedates}}aria-current="true"{{/sorttimelinedates}}
         >
             {{#str}} sortbydates, block_timeline {{/str}}
         </a>
         <a
-            class="dropdown-item {{#sorttimelinecourses}}active{{/sorttimelinecourses}}"
+            class="dropdown-item"
             href="#view_courses_{{uniqid}}"
             data-toggle="tab"
             data-filtername="sortbycourses"
+            {{#sorttimelinecourses}}aria-current="true"{{/sorttimelinecourses}}
             aria-label="{{#str}} ariaviewselectoroption, block_timeline, {{#str}} sortbycourses, block_timeline {{/str}}{{/str}}"
             role="menuitem"
-            {{#sorttimelinecourses}}aria-current="true"{{/sorttimelinecourses}}
         >
             {{#str}} sortbycourses, block_timeline {{/str}}
         </a>
index ca36639..a8a5c1c 100644 (file)
@@ -80,6 +80,6 @@ $string['unenroluser'] = 'Do you really want to unenrol "{$a->user}" from course
 $string['unenrolusers'] = 'Unenrol users';
 $string['wscannotenrol'] = 'Plugin instance cannot manually enrol a user in the course id = {$a->courseid}';
 $string['wsnoinstance'] = 'Manual enrolment plugin instance doesn\'t exist or is disabled for the course (id = {$a->courseid})';
-$string['wsusercannotassign'] = 'You don\'t have the permission to assign this role ({$a->roleid}) to this user ({$a->userid}) in this course({$a->courseid}).';
+$string['wsusercannotassign'] = 'You don\'t have the permission to assign this role ({$a->roleid}) to this user ({$a->userid}) in this course ({$a->courseid}).';
 $string['manualpluginnotinstalled'] = 'The "Manual" plugin has not yet been installed';
 $string['privacy:metadata'] = 'The Manual enrolments plugin does not store any personal data.';
index d27feb9..5cce878 100644 (file)
@@ -50,7 +50,7 @@ $string['override'] = 'Override';
 $string['overrideall'] = 'Override all grades';
 $string['overridefor'] = 'Override for {$a}';
 $string['overridenone'] = 'Do not override any grades';
-$string['overridenoneconfirm'] = 'You are trying to disable all grade overrides. After saving, all the previously overridden grades will be lost. Do you want to continue?';
+$string['overridenoneconfirm'] = 'You are about to disable grade overrides. This will remove all previously overridden grades. Are you sure you want to continue?';
 $string['pluginname'] = 'Single view';
 $string['privacy:metadata'] = 'The Grade single view report only shows data stored in other locations.';
 $string['savegrades'] = 'Saving grades';
index 0419563..5eb8d74 100644 (file)
@@ -67,8 +67,8 @@ Feature: Group overview
     # Following groups should exist in groupings.
     Then the group overview should include groups "Group 1, Group 2" in grouping "Grouping 1"
     And the group overview should include groups "Group 2,Group 3" in grouping "Grouping 2"
-    And the group overview should include groups "Group 4" in grouping "[Not in a grouping]"
-    And the group overview should include groups "No group" in grouping "[Not in a group]"
+    And the group overview should include groups "Group 4" in grouping "Not in a grouping"
+    And the group overview should include groups "No group" in grouping "Not in a group"
     # Following members should exit in group.
     And "Student 0" "text" should exist in the "Group 1" "table_row"
     And "Student 1" "text" should exist in the "Group 1" "table_row"
@@ -121,8 +121,8 @@ Feature: Group overview
     And I select "No grouping" from the "Grouping" singleselect
     And I select "All" from the "group" singleselect
     # Following groups should exist in groupings.
-    And the group overview should include groups "Group 4" in grouping "[Not in a grouping]"
-    And the group overview should include groups "No group" in grouping "[Not in a group]"
+    And the group overview should include groups "Group 4" in grouping "Not in a grouping"
+    And the group overview should include groups "No group" in grouping "Not in a group"
     # Following groups should not exits
     And "Group 1" "table_row" should not exist
     And "Group 2" "table_row" should not exist
@@ -185,7 +185,7 @@ Feature: Group overview
     And I select "All" from the "Grouping" singleselect
     And I select "No group" from the "group" singleselect
     # Following groups should exist in groupings.
-    And the group overview should include groups "No group" in grouping "[Not in a group]"
+    And the group overview should include groups "No group" in grouping "Not in a group"
     # Following groups should not exits
     And "Group 1" "table_row" should not exist
     And "Group 2" "table_row" should not exist
index f6768fb..e6103e5 100644 (file)
@@ -78,7 +78,7 @@ $string['authpreventaccountcreation'] = 'Prevent account creation when authentic
 $string['authpreventaccountcreation_help'] = 'When a user authenticates, an account on the site is automatically created if it doesn\'t yet exist. If an external database, such as LDAP, is used for authentication, but you wish to restrict access to the site to users with an existing account only, then this option should be enabled. New accounts will need to be created manually or via the upload users feature. Note that this setting doesn\'t apply to MNet authentication.';
 $string['authsettings'] = 'Manage authentication';
 $string['autolang'] = 'Language autodetect';
-$string['autolangusercreation'] = 'Use language that is auto detected from users browser during user creation';
+$string['autolangusercreation'] = 'On account creation set user\'s browser language as their preferred language';
 $string['autologinguests'] = 'Auto-login guests';
 $string['searchareas'] = 'Search areas';
 $string['availableto'] = 'Available to';
@@ -174,7 +174,7 @@ $string['configallowview'] = 'Select which roles a user will see, be able to fil
 $string['configallusersaresitestudents'] = 'For activities on the front page of the site, should ALL users be considered as students?  If you answer "Yes", then any confirmed user account will be allowed to participate as a student in those activities.  If you answer "No", then only users who are already a participant in at least one course will be able to take part in those front page activities. Only admins and specially assigned teachers can act as teachers for these front page activities.';
 $string['configauthenticationplugins'] = 'Please choose the authentication plugins you wish to use and arrange them in order of failthrough.';
 $string['configautolang'] = 'Detect default language from browser setting, if disabled site default is used.';
-$string['configautolangusercreation'] = 'Use language from users browser during user creation';
+$string['configautolangusercreation'] = 'If enabled, when a user\'s account is created automatically on first login (e.g. using LDAP or OAuth 2 authentication), the user\'s browser language is set as their preferred language. Otherwise, the default language for the site is set as the user\'s preferred language.';
 $string['configautologinguests'] = 'Should visitors be logged in as guests automatically when entering courses with guest access?';
 $string['configbloglevel'] = 'This setting allows you to restrict the level to which user blogs can be viewed on this site.  Note that they specify the maximum context of the VIEWER not the poster or the types of blog posts.  Blogs can also be disabled completely if you don\'t want them at all.';
 $string['configcalendarcustomexport'] = 'Enable custom date range export of calendar';
@@ -428,7 +428,7 @@ $string['creatornewroleid'] = 'Creators\' role in new courses';
 $string['creatornewroleid_help'] = 'If the user does not already have the permission to manage the new course, the user is automatically enrolled using this role.';
 $string['cron'] = 'Cron';
 $string['cron_enabled'] = 'Enable cron';
-$string['cron_enabled_desc'] = 'If disabled prevents the system from starting new background tasks. This option is intended for temporary use only, e.g. before a restart. Leaving it off for a long time will prevent important functionality from working.';
+$string['cron_enabled_desc'] = 'Cron should normally be enabled, however this setting allows it to be disabled temporarily, for example before a server restart. If disabled, the system is prevented from starting new background tasks. Note that the cron should not be disabled for a long time, as this will prevent important functionality from working.';
 $string['cron_help'] = 'The cron.php script runs a number of tasks at different scheduled intervals, such as sending forum post notification emails. The script should be run regularly - ideally every minute.';
 $string['cron_link'] = 'admin/cron';
 $string['cronclionly'] = 'Cron execution via command line only';
@@ -532,9 +532,9 @@ $string['editorspelling'] = 'Editor spelling';
 $string['editorspellinghelp'] = 'Enable or disable spell-checking. When enabled, <strong>aspell</strong> must be installed on the server.';
 $string['editstrings'] = 'Edit words or phrases';
 $string['emailchangeconfirmation'] = 'Email change confirmation';
-$string['emaildkim'] = 'DKIM email signing';
+$string['emaildkim'] = 'DomainKeys Identified Mail (DKIM) email signing';
 $string['emaildkimselector'] = 'DKIM selector';
-$string['emaildkiminfo'] = 'If both the DKIM selector is set and a private certificate file is found which matches the emails "From" address domain in $CFG->dataroot/dkim/[domain]/[selector].private then the email will be signed. In most cases (eg if allowedemaildomains is empty) then only a single certificate is needed in: <code>{$a->path}</code>. For more setup details see <a href="{$a->docs}">{$a->docs}</a>.';
+$string['emaildkiminfo'] = 'If both the DKIM selector is set and a private certificate file is found which matches the email\'s "From" address domain in $CFG->dataroot/dkim/[domain]/[selector].private then the email will be signed. In most cases (for example if allowedemaildomains is empty) only a single certificate is needed in <pre>{$a->path}</pre>. For more setup details, see the documentation <a href="{$a->docs}">Mail configuration</a>.';
 $string['emailfromvia'] = 'Email via information';
 $string['emailheaders'] = 'Email headers';
 $string['emailsubjectprefix'] = 'Email subject prefix text';
@@ -608,7 +608,7 @@ $string['fatalsessionautostart'] = '<p>Serious configuration error detected, ple
 $string['feedbacksettings'] = 'Feedback settings';
 $string['filescleanupperiod'] = 'Clean up trash pool files';
 $string['filescleanupperiod_help'] = 'How often trash pool files are deleted. These are files that are associated with a context that no longer exists, for example when a course is deleted. Please note: This setting can result in missing files in a course which is backed up, deleted and then restored if the setting \'Include files\' (backup_auto_files) in \'Automated backup settings\' is disabled.';
-$string['fileconversioncleanuptask'] = 'Cleanup of temporary records for file conversions.';
+$string['fileconversioncleanuptask'] = 'Cleanup of temporary records for file conversions';
 $string['filecreated'] = 'New file created';
 $string['filesizeunits'] = 'file size units';
 $string['filestoredin'] = 'Save file into folder :';
@@ -1460,7 +1460,7 @@ $string['user'] = 'User';
 $string['userbulk'] = 'Bulk user actions';
 $string['userbulkdownload'] = 'Export users as';
 $string['userfeedbackafterupgrade'] = 'After every major upgrade';
-$string['userfeedbackencouragement'] = '<p>Moodle 3.9 includes a new feature that gives users the option to provide feedback about the Moodle software to Moodle HQ via an external survey site hosted by Moodle HQ. No user-identifying information is forwarded to the survey site.</p>
+$string['userfeedbackencouragement'] = '<p>In Moodle 3.9 onwards, a new feature is included which gives users the option to provide feedback about Moodle software to Moodle HQ via an external survey site hosted by Moodle HQ. No user-identifying information is forwarded to the survey site.</p>
 <p>Moodle HQ strives to be open and transparent about its data collection practices. Thus, we want to make sure that you are aware and in control of this functionality.</p>
 <p>Feedback from users will greatly assist Moodle HQ in improving the Moodle software. To enable this feature, please go to <a href="{$a}">Feedback settings</a>.</p>';
 $string['userfeedbacknextreminder'] = 'Next feedback reminder';
index f6e97b8..719cc65 100644 (file)
@@ -93,7 +93,7 @@ $string['modeinstructionfacetoface'] = 'Face to face';
 $string['modeinstructionblendedhybrid'] = 'Blended or hybrid';
 $string['modeinstructionfullyonline'] = 'Fully online';
 $string['modeloutputdir'] = 'Models output directory';
-$string['modeloutputdirwithdefaultinfo'] = 'Directory where prediction processors store all evaluation info. Useful for debugging and research. If empty, then \'<strong>{$a}</strong>\' will be used as default.';
+$string['modeloutputdirwithdefaultinfo'] = 'Directory where prediction processors store all evaluation info. Useful for debugging and research. If empty, then {$a} will be used as default.';
 $string['modeltimelimit'] = 'Analysis time limit per model';
 $string['modeltimelimitinfo'] = 'This setting limits the time each model spends analysing the site contents.';
 $string['neutral'] = 'Neutral';
index 907bf31..8723e04 100644 (file)
@@ -39,26 +39,23 @@ $string['emailfilename'] = 'Filename: ';
 $string['emailfilesize'] = 'File size: ';
 $string['emailgeoinfo'] = 'Geolocation: ';
 $string['emailinfectedfiledetected'] = 'Infected file detected';
-$string['emailipaddress'] = 'IP Address: ';
+$string['emailipaddress'] = 'IP address:';
 $string['emailreferer'] = 'Referer: ';
 $string['emailreport'] = 'Report: ';
 $string['emailscanner'] = 'Scanner: ';
 $string['emailscannererrordetected'] = 'A scanner error occured';
 $string['emailsubject'] = '{$a} :: Antivirus notification';
 $string['enablequarantine'] = 'Enable quarantine';
-$string['enablequarantine_help'] = 'When quarantine is enabled, any files which are detected as viruses will be kept in a quarantine folder for later inspection ([dataroot]/{$a}).
-The upload into Moodle will still fail.
-If you have any file system level virus scanning in place, the quarantine folder should be excluded from the antivirus check to avoid detecting the quarantined files.';
+$string['enablequarantine_help'] = 'If enabled, any files which are detected as viruses will be placed in a quarantine folder ([dataroot]/{$a}) for later inspection. The upload into Moodle will fail. If you have any file system level virus scanning in place, the quarantine folder should be excluded from the antivirus check to avoid detecting the quarantined files.';
 $string['fileinfecteddesc'] = 'An infected file was detected.';
 $string['fileinfectedname'] = 'File infected';
-$string['notifyemail'] = 'Antivirus alert email';
-$string['notifyemail_help'] = 'If set, then only the specified email will be notified when a virus is detected.
-If blank, then all site admins will be notified by email when a virus is detected.';
+$string['notifyemail'] = 'Antivirus alert notification email';
+$string['notifyemail_help'] = 'The email address for notifications of when a virus is detected. If left blank, then all site administrators will be sent notifications.';
 $string['privacy:metadata'] = 'The Antivirus system does not store any personal data.';
-$string['quarantinedisabled'] = 'Quarantine disabled, file not stored.';
+$string['quarantinedisabled'] = 'Quarantine is disabled. The file is not stored.';
 $string['quarantinedfiles'] = 'Antivirus quarantined files';
 $string['quarantinetime'] = 'Maximum quarantine time';
-$string['quarantinetime_desc'] = 'Quarantined files older than specified period will be removed.';
+$string['quarantinetime_desc'] = 'Quarantined files older than the specified period will be removed.';
 $string['taskcleanup'] = 'Clean up quarantined files.';
 $string['unknown'] = 'Unknown';
 $string['virusfound'] = '{$a->item} has been scanned by a virus checker and found to be infected!';
index 7ff69a1..4eea412 100644 (file)
@@ -137,7 +137,7 @@ $string['configgeneralgroups'] = 'Sets the default for including groups and grou
 $string['configgeneralroleassignments'] = 'If enabled by default roles assignments will also be backed up.';
 $string['configgeneraluserscompletion'] = 'If enabled user completion information will be included in backups by default.';
 $string['configgeneralusers'] = 'Sets the default for whether to include users in backups.';
-$string['configlegacyfiles'] = 'If disabled, legacy course files will not be included';
+$string['configlegacyfiles'] = 'Sets the default for including legacy course files in a backup. Legacy course files are from versions of Moodle prior to 2.0.';
 $string['configloglifetime'] = 'This specifies the length of time you want to keep backup logs information. Logs that are older than this age are automatically deleted. It is recommended to keep this value small, because backup logged information can be huge.';
 $string['configrestoreactivities'] = 'Sets the default for restoring activities.';
 $string['configrestorebadges'] = 'Sets the default for restoring badges.';
@@ -306,7 +306,7 @@ $string['questionegorycannotberestored'] = 'The questions "{$a->name}" cannot be
 $string['restoreactivity'] = 'Restore activity';
 $string['restorecourse'] = 'Restore course';
 $string['restorecoursesettings'] = 'Course settings';
-$string['restoredcourseid'] = 'Restored course id: {$a}';
+$string['restoredcourseid'] = 'Restored course ID: {$a}';
 $string['restoreexecutionsuccess'] = 'The course was restored successfully, clicking the continue button below will take you to view the course you restored.';
 $string['restorefileweremissing'] = 'Some files could not be restored because they were missing in the backup.';
 $string['restorenewcoursefullname'] = 'New course name';
index 5a84a6f..a8beaa4 100644 (file)
@@ -122,7 +122,7 @@ $string['courseaggregation_any'] = 'ANY selected courses to be completed';
 $string['coursealreadycompleted'] = 'You have already completed this course';
 $string['coursecomplete'] = 'Course complete';
 $string['coursecompleted'] = 'Course completed';
-$string['coursecompletedmessage'] = '<p>Congratulations!</p><p>You just completed the following course: <a href="{$a->courselink}">{$a->coursename}</a>.</p>';
+$string['coursecompletedmessage'] = '<p>Congratulations!</p><p>You have completed the course <a href="{$a->courselink}">{$a->coursename}</a>.</p>';
 $string['coursecompletion'] = 'Course completion';
 $string['coursecompletioncondition'] = 'Condition: {$a}';
 $string['coursegrade'] = 'Course grade';
index acbfc0d..1eb4463 100644 (file)
@@ -33,7 +33,7 @@ $string['contentrenamed'] = 'The content has been renamed.';
 $string['contentsmoved'] = 'Content bank contents moved to {$a}.';
 $string['contenttypenoaccess'] = 'You cannot view this {$a} instance.';
 $string['contenttypenoedit'] = 'You can not edit this content';
-$string['contextnotallowed'] = 'Context is not allowed';
+$string['contextnotallowed'] = 'You are not allowed to access the content bank in this context.';
 $string['emptynamenotallowed'] = 'Empty name is not allowed';
 $string['eventcontentcreated'] = 'Content created';
 $string['eventcontentdeleted'] = 'Content deleted';
index ef16de3..8be1b32 100644 (file)
@@ -195,7 +195,7 @@ $string['coursemisconf'] = 'Course is misconfigured';
 $string['courserequestdisabled'] = 'Sorry, but course requests have been disabled by the administrator.';
 $string['csvcolumnduplicates'] = 'Duplicate columns detected';
 $string['csvemptyfile'] = 'The CSV file is empty';
-$string['csvfileerror'] = 'There is something wrong with the format of the CSV file. Please check the number of headings and columns match, and that the delimiter and file encoding are correct: {$a}';
+$string['csvfileerror'] = 'There is something wrong with the format of the CSV file. Please check the number of headings and columns match, and that the delimiter and file encoding are correct. {$a}';
 $string['csvfewcolumns'] = 'Not enough columns, please verify the delimiter setting';
 $string['csvinvalidcols'] = '<b>Invalid CSV file:</b> First line must include "Header Fields" and the file must be type of <br />"Expanded Fields/Comma Separated"<br />or<br /> "Expanded Fields with CAVV Result Code/Comma Separated"';
 $string['csvinvalidcolsnum'] = 'Invalid CSV file - each line must include 49 or 70 fields';
@@ -349,7 +349,7 @@ $string['invalidmodule'] = 'Invalid module';
 $string['invalidmoduleid'] = 'Invalid module ID: {$a}';
 $string['invalidmodulename'] = 'Invalid module name: {$a}';
 $string['invalidnum'] = 'Invalid numeric value';
-$string['invalidnumkey'] = '$conditions array may not contain numeric keys, please fix the code!';
+$string['invalidnumkey'] = 'The array $conditions may not contain numeric keys. Please fix the code!';
 $string['invalidoutcome'] = 'Incorrect outcome ID';
 $string['invalidpagesize'] = 'Invalid page size';
 $string['invalidpasswordpolicy'] = 'Invalid password policy';
index ab1ce3e..2f34472 100644 (file)
@@ -165,8 +165,8 @@ $string['nogroupsassigned'] = 'No groups assigned';
 $string['nopermissionforcreation'] = 'Can\'t create group "{$a}" as you don\'t have the required permissions';
 $string['nosmallgroups'] = 'Prevent last small group';
 $string['notingroup'] = 'Ignore users in groups';
-$string['notingrouping'] = '[Not in a grouping]';
-$string['notingrouplist'] = '[Not in a group]';
+$string['notingrouping'] = 'Not in a grouping';
+$string['notingrouplist'] = 'Not in a group';
 $string['nousersinrole'] = 'There are no suitable users in the selected role';
 $string['number'] = 'Group/member count';
 $string['numgroups'] = 'Number of groups';
index 94f3595..8fbcca4 100644 (file)
@@ -279,7 +279,7 @@ $string['considereddigitalminor'] = 'You are too young to create an account on t
 $string['content'] = 'Content';
 $string['contentexport_aboutthiscourse'] = 'Course summary';
 $string['contentexport_coursesummary'] = 'This file is part of the content downloaded from <a href="{$a->courselink}">{$a->coursename}</a>.';
-$string['contentexport_footersummary'] = 'This file is part of the content downloaded from <a href="{$a->courselink}">{$a->coursename}</a> by {$a->userfullname} on {$a->date}';
+$string['contentexport_footersummary'] = 'This file is part of the content downloaded from <a href="{$a->courselink}">{$a->coursename}</a> by {$a->userfullname} on {$a->date}.';
 $string['contentexport_modulesummary'] = 'This page is part of the content downloaded from <a href="{$a->modulelink}">{$a->modulename}</a> on {$a->date}. Note that some content and any files larger than {$a->maxfilesize} are not downloaded.';
 $string['contentexport_viewfilename'] = 'View the file {$a}';
 $string['contentbank'] = 'Content bank';
index ddb5ecc..16b62f4 100644 (file)
@@ -58,7 +58,7 @@ $string['cannotdownload'] = 'Cannot download this file';
 $string['cannotdownloaddir'] = 'Cannot download this folder';
 $string['cannotinitplugin'] = 'Call plugin_init failed';
 $string['cannotunzipcontentunreadable'] = 'Cannot unzip this file because the contents of the file cannot be read.';
-$string['cannotunzipextractfileerror'] = 'Cannot unzip this file because one or more of it\'s files cannot be read.';
+$string['cannotunzipextractfileerror'] = 'Cannot unzip this file because one or more of its files cannot be read.';
 $string['cannotunzipquotaexceeded'] = 'Cannot unzip this file because the maximum size allowed in this draft area will be exceeded.';
 $string['cleancache'] = 'Clean my cache files';
 $string['close'] = 'Close';
@@ -165,8 +165,8 @@ $string['logintoaccount'] = 'Log in to your {$a} account';
 $string['logout'] = 'Logout';
 $string['lostsource'] = 'Error. Source is missing. {$a}';
 $string['makefileinternal'] = 'Make a copy of the file';
-$string['makefilelink'] = 'Link to the file directly';
-$string['makefilereference'] = 'Create an alias/shortcut to the file';
+$string['makefilelink'] = 'Link to the external file';
+$string['makefilereference'] = 'Link to the file';
 $string['makefilecontrolledlink'] = 'Create an access controlled link to the file';
 $string['manage'] = 'Manage repositories';
 $string['manageinstances'] = 'Manage instances';
index 26551c3..a9500d7 100644 (file)
@@ -153,7 +153,7 @@ $string['confirmunassignno'] = 'Cancel';
 $string['contentbank:access'] = 'Access the content bank';
 $string['contentbank:deleteanycontent'] = 'Delete any content from the content bank';
 $string['contentbank:deleteowncontent'] = 'Delete content from own content bank';
-$string['contentbank:downloadcontent'] = 'Download content from the content bank';
+$string['contentbank:downloadcontent'] = 'Download content from the content bank';
 $string['contentbank:manageanycontent'] = 'Manage any content from the content bank';
 $string['contentbank:manageowncontent'] = 'Manage content from own content bank';
 $string['contentbank:upload'] = 'Upload new content to the content bank';
@@ -300,7 +300,7 @@ $string['mainadmin'] = 'Main administrator';
 $string['mainadminset'] = 'Set main admin';
 $string['manageadmins'] = 'Manage site administrators';
 $string['manager'] = 'Manager';
-$string['managerdescription'] = 'Managers can access course and modify them, they usually do not participate in courses.';
+$string['managerdescription'] = 'Managers can access courses and modify them, but usually do not participate in them.';
 $string['manageroles'] = 'Manage roles';
 $string['maybeassignedin'] = 'Context types where this role may be assigned';
 $string['morethan'] = 'More than {$a}';
index 9da8476..c6dbdd8 100644 (file)
@@ -36,7 +36,7 @@ $string['filtersetmatchdescription'] = 'How multiple filters should be combined'
 $string['match'] = 'Match';
 $string['matchofthefollowing'] = 'of the following:';
 $string['moodlenetprofile'] = 'MoodleNet profile';
-$string['moodlenetprofile_help'] = 'This field is to link your MoodleNet profile to Moodle. It expects a WebFinger compliant URI';
+$string['moodlenetprofile_help'] = 'This field is to link your MoodleNet profile to Moodle. It expects a WebFinger-compliant URI.';
 $string['placeholdertypeorselect'] = 'Type or select...';
 $string['placeholdertype'] = 'Type...';
 $string['privacy:courserequestpath'] = 'Requested courses';
index 1b4fdf4..9b92082 100644 (file)
Binary files a/lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button-debug.js and b/lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button-debug.js differ
index 8e720ff..4fb26eb 100644 (file)
Binary files a/lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button-min.js and b/lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button-min.js differ
index 9e38bd2..bd82437 100644 (file)
Binary files a/lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button.js and b/lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button.js differ
index 4c1cb08..00779ca 100644 (file)
@@ -74,6 +74,7 @@ var COMPONENTNAME = 'atto_equation',
                     '{{#each library}}' +
                         '<li  class="nav-item">' +
                             '<a class="nav-link" href="#{{../elementid}}_{{../CSS.LIBRARY_GROUP_PREFIX}}_{{@key}}" ' +
+                                ' data-target="#{{../elementidescaped}}_{{../CSS.LIBRARY_GROUP_PREFIX}}_{{@key}}"' +
                                 ' role="tab" data-toggle="tab">' +
                                 '{{get_string groupname ../component}}' +
                             '</a>' +
@@ -686,6 +687,7 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
         });
         content = template({
             elementid: this.get('host').get('elementid'),
+            elementidescaped: this._escapeQuerySelector(this.get('host').get('elementid')),
             component: COMPONENTNAME,
             library: library,
             CSS: CSS,
@@ -710,7 +712,23 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
             content = preview.responseText;
         }
         return content;
+    },
+
+    /**
+     * Escape special characters in string used as a JS query selector
+     *
+     * @method _excapeQuerySelector
+     * @param {string} selector
+     * @returns {string}
+     */
+    _escapeQuerySelector: function(selector) {
+
+        // Bootstrap requires that query selectors have special chars excaped.
+        // See: https://getbootstrap.com/docs/4.2/getting-started/javascript/#selectors
+
+        return selector.replace(/(:|\.|\[|\]|,|=|@)/g, '\\$1');
     }
+
 }, {
     ATTRS: {
         /**
index e70d24d..5bde425 100644 (file)
@@ -714,7 +714,8 @@ M.form_filemanager.init = function(Y, options) {
             if (filenode) {
                 // File has a license already, use it.
                 selectedlicense = filenode.license;
-            } else if (this.filepicker_options.rememberuserlicensepref) {
+            } else if (this.filepicker_options.rememberuserlicensepref && this.get_preference('recentlicense')) {
+                // When 'Remember user licence preference' is enabled use the last license selected by the user, if any.
                 selectedlicense = this.get_preference('recentlicense');
             }
             var licenses = this.filepicker_options.licenses;
index f8af357..f1b3e79 100644 (file)
@@ -83,6 +83,7 @@
                     role="menu"
                     class="dropdown-menu"
                     data-show-active-item
+                    data-skip-active-class="true"
                     {{#arialabels.itemsperpagecomponents}}
                         data-active-item-button-aria-label-components="{{.}}"
                     {{/arialabels.itemsperpagecomponents}}
index 69daf6f..b8f213b 100644 (file)
@@ -51,19 +51,21 @@ function xmldb_message_popup_upgrade($oldversion) {
                       ON mpn.notificationid = n.id
                    WHERE n.id IS NULL";
         $total = $DB->count_records_sql("SELECT COUNT(mpn.id) " . $fromsql);
-        $i = 0;
-        $pbar = new progress_bar('deletepopupnotification', 500, true);
-        do {
-            if ($popupnotifications = $DB->get_records_sql("SELECT mpn.id " . $fromsql, null, 0, 1000)) {
-                list($insql, $inparams) = $DB->get_in_or_equal(array_keys($popupnotifications));
-                $DB->delete_records_select('message_popup_notifications', "id $insql", $inparams);
-                // Update progress.
-                $i += count($inparams);
-                $pbar->update($i, $total, "Cleaning up orphaned popup notification records - $i/$total.");
-            }
-        } while ($popupnotifications);
+        if ($total > 0) {
+            $i = 0;
+            $pbar = new progress_bar('deletepopupnotification', 500, true);
+            do {
+                if ($popupnotifications = $DB->get_records_sql("SELECT mpn.id " . $fromsql, null, 0, 1000)) {
+                    list($insql, $inparams) = $DB->get_in_or_equal(array_keys($popupnotifications));
+                    $DB->delete_records_select('message_popup_notifications', "id $insql", $inparams);
+                    // Update progress.
+                    $i += count($inparams);
+                    $pbar->update($i, $total, "Cleaning up orphaned popup notification records - $i/$total.");
+                }
+            } while ($popupnotifications);
+        }
 
-        // Reportbuilder savepoint reached.
+        // Popup message processor savepoint reached.
         upgrade_plugin_savepoint(true, 2020020600, 'message', 'popup');
     }
 
index ae333eb..c1bc05e 100644 (file)
@@ -247,7 +247,7 @@ $string['resetting_data'] = 'Reset feedback responses';
 $string['resetting_feedbacks'] = 'Resetting feedbacks';
 $string['response_nr'] = 'Response number';
 $string['responses'] = 'Responses';
-$string['responsetime'] = 'Responsestime';
+$string['responsetime'] = 'Responses time';
 $string['save_as_new_item'] = 'Save as new question';
 $string['save_as_new_template'] = 'Save as new template';
 $string['save_entries'] = 'Submit your answers';
index 2054274..dbcfb60 100644 (file)
@@ -33,7 +33,7 @@ Feature: Test creating different types of feedback questions for non-anonymous f
     And I add a "Information" question to the feedback with:
       | Question         | this is a response time question |
       | Label            | curtime                          |
-      | Information type | Responsestime                    |
+      | Information type | Responses time                   |
     And I add a "Label" question to the feedback with:
       | Contents | label text |
     And I add a "Longer text answer" question to the feedback with:
index a1919f6..6b361c5 100644 (file)
@@ -27,6 +27,7 @@ namespace mod_forum\local\exporters;
 defined('MOODLE_INTERNAL') || die();
 
 use mod_forum\local\entities\post as post_entity;
+use mod_forum\local\entities\discussion as discussion_entity;
 use mod_forum\local\exporters\author as author_exporter;
 use mod_forum\local\factories\exporter as exporter_factory;
 use core\external\exporter;
@@ -402,7 +403,7 @@ class post extends exporter {
 
         if ($loadcontent) {
             $subject = $post->get_subject();
-            $timecreated = $post->get_time_created();
+            $timecreated = $this->get_start_time($discussion, $post);
             $message = $this->get_message($post);
         } else {
             $subject = $isdeleted ? get_string('forumsubjectdeleted', 'forum') : get_string('forumsubjecthidden', 'forum');
@@ -642,4 +643,22 @@ class post extends exporter {
         $date = userdate_htmltime($timecreated, get_string('strftimedaydatetime', 'core_langconfig'));
         return get_string('bynameondate', 'mod_forum', ['name' => $name, 'date' => $date]);
     }
+
+    /**
+     * Get the start time for a post.
+     *
+     * @param discussion_entity $discussion entity
+     * @param post_entity $post entity
+     * @return int The start time (timestamp) for a post
+     */
+    private function get_start_time(discussion_entity $discussion, post_entity $post) {
+        global $CFG;
+
+        $posttime = $post->get_time_created();
+        $discussiontime = $discussion->get_time_start();
+        if (!empty($CFG->forum_enabletimedposts) && ($discussiontime > $posttime)) {
+            return $discussiontime;
+        }
+        return $posttime;
+    }
 }
index 7f6e463..c182e6c 100644 (file)
@@ -46,8 +46,12 @@ class mod_forum_exporters_post_testcase extends advanced_testcase {
 
     /**
      * Test the export function returns expected values.
+     *
+     * @dataProvider export_post_provider
+     * @param bool $istimed True if this is a timed post
+     * @param int $addtime Seconds to be added to the current time
      */
-    public function test_export_post() {
+    public function test_export_post($istimed = false, $addtime = 0) {
         global $CFG, $PAGE;
         $this->resetAfterTest();
 
@@ -61,12 +65,18 @@ class mod_forum_exporters_post_testcase extends advanced_testcase {
         $forum = $datagenerator->create_module('forum', ['course' => $course->id]);
         $coursemodule = get_coursemodule_from_instance('forum', $forum->id);
         $context = context_module::instance($coursemodule->id);
-        $discussion = $forumgenerator->create_discussion((object) [
+        $now = time();
+
+        $forumgenparams = [
             'course' => $forum->course,
             'userid' => $user->id,
-            'forum' => $forum->id
-        ]);
-        $now = time();
+            'forum' => $forum->id,
+        ];
+        if ($istimed) {
+            $forumgenparams['timestart'] = $now + $addtime;
+        }
+        $discussion = $forumgenerator->create_discussion((object) $forumgenparams);
+
         $post = $forumgenerator->create_post((object) [
             'discussion' => $discussion->id,
             'parent' => 0,
@@ -150,7 +160,11 @@ class mod_forum_exporters_post_testcase extends advanced_testcase {
         $this->assertEquals($discussion->get_id(), $exportedpost->discussionid);
         $this->assertEquals(false, $exportedpost->hasparent);
         $this->assertEquals(null, $exportedpost->parentid);
-        $this->assertEquals($now, $exportedpost->timecreated);
+        if ($istimed && ($addtime > 0)) {
+            $this->assertEquals($now + $addtime, $exportedpost->timecreated);
+        } else {
+            $this->assertEquals($now, $exportedpost->timecreated);
+        }
         $this->assertEquals(null, $exportedpost->unread);
         $this->assertEquals(false, $exportedpost->isdeleted);
         $this->assertEquals($canview, $exportedpost->capabilities['view']);
@@ -179,6 +193,26 @@ class mod_forum_exporters_post_testcase extends advanced_testcase {
         $this->assertNotEmpty($exportedpost->html['authorsubheading']);
     }
 
+    /**
+     * Data provider for test_export_post().
+     *
+     * @return array
+     */
+    public function export_post_provider(): array {
+        return [
+            'Simple export' => [
+            ],
+            'Test timed post future' => [
+                true,
+                1000
+            ],
+            'Test timed post past' => [
+                true,
+                -1000
+            ],
+        ];
+    }
+
     /**
      * Test exporting of a deleted post.
      */
index 657b457..62e33a3 100644 (file)
@@ -2663,11 +2663,12 @@ class mod_forum_external_testcase extends externallib_advanced_testcase {
         $forum1context = context_module::instance($forum1->cmid);
 
         // Add discussions to the forums.
+        $time = time();
         $record = new stdClass();
         $record->course = $course1->id;
         $record->userid = $user1->id;
         $record->forum = $forum1->id;
-        $record->timemodified = 1;
+        $record->timemodified = $time + 100;
         $discussion1 = $forumgenerator->create_discussion($record);
         $discussion1firstpost = $postvault->get_first_post_for_discussion_ids([$discussion1->id]);
         $discussion1firstpost = $discussion1firstpost[$discussion1->firstpost];
@@ -2677,7 +2678,7 @@ class mod_forum_external_testcase extends externallib_advanced_testcase {
         $record->course = $course1->id;
         $record->userid = $user1->id;
         $record->forum = $forum1->id;
-        $record->timemodified = 2;
+        $record->timemodified = $time + 200;
         $discussion2 = $forumgenerator->create_discussion($record);
         $discussion2firstpost = $postvault->get_first_post_for_discussion_ids([$discussion2->id]);
         $discussion2firstpost = $discussion2firstpost[$discussion2->firstpost];
@@ -2726,7 +2727,6 @@ class mod_forum_external_testcase extends externallib_advanced_testcase {
         $this->getDataGenerator()->enrol_user($user1->id, $course1->id, 'teacher');
         $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
         // Changed display period for the discussions in past.
-        $time = time();
         $discussion = new \stdClass();
         $discussion->id = $discussion1->id;
         $discussion->timestart = $time - 200;
index 18074d7..eb9b090 100644 (file)
@@ -561,8 +561,8 @@ function quiz_user_complete($course, $user, $mod, $quiz) {
                 } else {
                     echo get_string('hidden', 'grades');
                 }
+                echo ' - '.userdate($attempt->timefinish).'<br />';
             }
-            echo ' - '.userdate($attempt->timefinish).'<br />';
         }
     } else {
         print_string('noattempts', 'quiz');
index 803145c..6a2eb48 100644 (file)
@@ -39,6 +39,6 @@
     "xpath": "0.0.27"
   },
   "engines": {
-    "node": ">=14.0.0 <15"
+    "node": ">=14.15.0 <15"
   }
 }
diff --git a/question/type/multichoice/amd/build/answers.min.js b/question/type/multichoice/amd/build/answers.min.js
new file mode 100644 (file)
index 0000000..5f519e3
Binary files /dev/null and b/question/type/multichoice/amd/build/answers.min.js differ
diff --git a/question/type/multichoice/amd/build/answers.min.js.map b/question/type/multichoice/amd/build/answers.min.js.map
new file mode 100644 (file)
index 0000000..f93028e
Binary files /dev/null and b/question/type/multichoice/amd/build/answers.min.js.map differ
index 3c4443a..627d017 100644 (file)
Binary files a/question/type/multichoice/amd/build/clearchoice.min.js and b/question/type/multichoice/amd/build/clearchoice.min.js differ
index 0defcae..28de62b 100644 (file)
Binary files a/question/type/multichoice/amd/build/clearchoice.min.js.map and b/question/type/multichoice/amd/build/clearchoice.min.js.map differ
diff --git a/question/type/multichoice/amd/src/answers.js b/question/type/multichoice/amd/src/answers.js
new file mode 100644 (file)
index 0000000..bd5cd27
--- /dev/null
@@ -0,0 +1,57 @@
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Handles events related to the multiple-choice question type answers.
+ *
+ * @module     qtype_multichoice/answers
+ * @package    qtype_multichoice
+ * @copyright  2020 Jun Pataleta <jun@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Selectors for this module.
+ *
+ * @type {{ANSWER_LABEL: string}}
+ */
+const SELECTORS = {
+    ANSWER_LABEL: '[data-region=answer-label]',
+};
+
+/**
+ * Init method.
+ *
+ * @param {string} rootId The ID of the question container.
+ */
+const init = (rootId) => {
+    const root = document.getElementById(rootId);
+
+    // Add click event handlers for the divs containing the answer since these cannot be enclosed in a label element.
+    const answerLabels = root.querySelectorAll(SELECTORS.ANSWER_LABEL);
+    answerLabels.forEach((answerLabel) => {
+        answerLabel.addEventListener('click', (e) => {
+            const labelId = e.currentTarget.id;
+            // Fetch the answer this label is assigned to.
+            const linkedOption = root.querySelector(`[aria-labelledby="${labelId}"]`);
+            // Trigger the click event.
+            linkedOption.click();
+        });
+    });
+};
+
+export default {
+    init: init
+};
index 7fed1d4..5990fe2 100644 (file)
@@ -25,7 +25,7 @@ define(['jquery', 'core/custom_interaction_events'], function($, CustomEvents) {
 
     var SELECTORS = {
         CHOICE_ELEMENT: '.answer input',
-        LINK: 'label',
+        LINK: 'a',
         RADIO: 'input[type="radio"]'
     };
 
index c886cda..89563b2 100644 (file)
@@ -88,6 +88,7 @@ abstract class qtype_multichoice_renderer_base extends qtype_with_combined_feedb
             $inputattributes['name'] = $this->get_input_name($qa, $value);
             $inputattributes['value'] = $this->get_input_value($value);
             $inputattributes['id'] = $this->get_input_id($qa, $value);
+            $inputattributes['aria-labelledby'] = $inputattributes['id'] . '_label';
             $isselected = $question->is_choice_selected($response, $value);
             if ($isselected) {
                 $inputattributes['checked'] = 'checked';
@@ -102,15 +103,16 @@ abstract class qtype_multichoice_renderer_base extends qtype_with_combined_feedb
                     'value' => 0,
                 ));
             }
+
+            $questionnumber = html_writer::span($this->number_in_style($value, $question->answernumbering), 'answernumber');
+            $answertext = $question->format_text($ans->answer, $ans->answerformat, $qa, 'question', 'answer', $ansid);
+            $questionanswer = html_writer::div($answertext, 'flex-fill ml-1');
+
             $radiobuttons[] = $hidden . html_writer::empty_tag('input', $inputattributes) .
-                    html_writer::tag('label',
-                        html_writer::span($this->number_in_style($value, $question->answernumbering), 'answernumber') .
-                        html_writer::tag('div',
-                        $question->format_text(
-                                    $ans->answer, $ans->answerformat,
-                                    $qa, 'question', 'answer', $ansid),
-                        array('class' => 'flex-fill ml-1')),
-                        array('for' => $inputattributes['id'], 'class' => 'd-flex w-100'));
+                    html_writer::div($questionnumber . $questionanswer, 'd-flex w-100', [
+                        'id' => $inputattributes['id'] . '_label',
+                        'data-region' => 'answer-label',
+                    ]);
 
             // Param $options->suppresschoicefeedback is a hack specific to the
             // oumultiresponse question type. It would be good to refactor to
@@ -151,6 +153,9 @@ abstract class qtype_multichoice_renderer_base extends qtype_with_combined_feedb
         }
         $result .= html_writer::end_tag('div'); // Answer.
 
+        // Load JS module for the question answers.
+        $this->page->requires->js_call_amd('qtype_multichoice/answers', 'init',
+            [$qa->get_outer_question_div_unique_id()]);
         $result .= $this->after_choices($qa, $options);
 
         $result .= html_writer::end_tag('div'); // Ablock.
@@ -313,9 +318,9 @@ class qtype_multichoice_single_renderer extends qtype_multichoice_renderer_base
         }
         // Adds an hidden radio that will be checked to give the impression the choice has been cleared.
         $clearchoiceradio = html_writer::empty_tag('input', $clearchoiceradioattrs);
-        $clearchoiceradio .= html_writer::tag('label', get_string('clearchoice', 'qtype_multichoice'),
-            ['for' => $clearchoiceid, 'role' => 'button', 'tabindex' => $linktabindex,
-            'class' => 'btn btn-link ml-4 pl-1 mt-2']);
+        $clearchoice = html_writer::link('#', get_string('clearchoice', 'qtype_multichoice'),
+            ['tabindex' => $linktabindex, 'role' => 'button', 'class' => 'btn btn-link ml-3 mt-n1 mb-n1']);
+        $clearchoiceradio .= html_writer::label($clearchoice, $clearchoiceid);
 
         // Now wrap the radio and label inside a div.
         $result = html_writer::tag('div', $clearchoiceradio, $clearchoicewrapperattrs);
diff --git a/question/type/multichoice/tests/behat/behat_qtype_multichoice.php b/question/type/multichoice/tests/behat/behat_qtype_multichoice.php
new file mode 100644 (file)
index 0000000..0d24f91
--- /dev/null
@@ -0,0 +1,52 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Behat qtype_multichoice-related steps definitions.
+ *
+ * @package    qtype_multichoice
+ * @category   test
+ * @copyright  2020 Jun Pataleta
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Behat custom step definitions and partial named selectors for qtype_multichoice.
+ *
+ * @package    qtype_multichoice
+ * @category   test
+ * @copyright  2020 Jun Pataleta
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class behat_qtype_multichoice extends behat_base {
+
+    /**
+     * Return the list of partial named selectors for this plugin.
+     *
+     * @return behat_component_named_selector[]
+     */
+    public static function get_partial_named_selectors(): array {
+        return [
+            new behat_component_named_selector(
+                'Answer', [
+                    <<<XPATH
+    .//div[@data-region='answer-label']//*[contains(text(), %locator%)]
+XPATH
+                ]
+            ),
+        ];
+    }
+}
index 83effee..214446f 100644 (file)
@@ -44,7 +44,7 @@ Feature: Clear my answers
     And I follow "Quiz 1"
     And I press "Attempt quiz now"
     And I should see "Question One"
-    And I click on "Four" "radio" in the "Question One" "question"
+    And I click on "Four" "qtype_multichoice > Answer" in the "Question One" "question"
     And I should see "Clear my choice"
     And I click on "Clear my choice" "button" in the "Question One" "question"
     Then I should not see "Clear my choice"
@@ -58,7 +58,7 @@ Feature: Clear my answers
     And I follow "Quiz 1"
     And I press "Attempt quiz now"
     And I should see "Question One"
-    And I click on "Four" "radio" in the "Question One" "question"
+    And I click on "Four" "qtype_multichoice > Answer" in the "Question One" "question"
     And I follow "Finish attempt ..."
     And I click on "Return to attempt" "button"
     And I click on "Clear my choice" "button" in the "Question One" "question"
index 8ba3b9e..39e5a17 100644 (file)
@@ -31,8 +31,8 @@ Feature: Preview a Multiple choice question
     And I switch to "questionpreview" window
     And I set the field "How questions behave" to "Immediate feedback"
     And I press "Start again with these options"
-    And I click on "One" "checkbox"
-    And I click on "Two" "checkbox"
+    And I click on "One" "qtype_multichoice > Answer"
+    And I click on "Two" "qtype_multichoice > Answer"
     And I press "Check"
     Then I should see "One is odd"
     And I should see "Two is even"
@@ -46,8 +46,8 @@ Feature: Preview a Multiple choice question
     And I switch to "questionpreview" window
     And I set the field "How questions behave" to "Immediate feedback"
     And I press "Start again with these options"
-    And I click on "One" "checkbox"
-    And I click on "Three" "checkbox"
+    And I click on "One" "qtype_multichoice > Answer"
+    And I click on "Three" "qtype_multichoice > Answer"
     And I press "Check"
     Then I should see "One is odd"
     And I should see "Three is odd"
@@ -63,7 +63,7 @@ Feature: Preview a Multiple choice question
     And I switch to "questionpreview" window
     And I set the field "How questions behave" to "Immediate feedback"
     And I press "Start again with these options"
-    And I click on "One" "radio"
+    And I click on "One" "qtype_multichoice > Answer"
     And I press "Check"
     Then I should see "The oddest number is One."
     And I should see "Mark 1.00 out of 1.00"
@@ -77,7 +77,7 @@ Feature: Preview a Multiple choice question
     And I switch to "questionpreview" window
     And I set the field "How questions behave" to "Immediate feedback"
     And I press "Start again with these options"
-    And I click on "One" "radio"
+    And I click on "One" "qtype_multichoice > Answer"
     Then I should see "Clear my choice"
     And I click on "Clear my choice" "text"
     And I should not see "Clear my choice"
diff --git a/question/type/multichoice/upgrade.txt b/question/type/multichoice/upgrade.txt
new file mode 100644 (file)
index 0000000..334f1c4
--- /dev/null
@@ -0,0 +1,11 @@
+This file describes API changes in /question/type/multichoice/*.
+
+=== 3.10 ===
+* The label for the multiple choice answers are being removed and the inputs (radio buttons/checkboxes) are now being labelled
+by the answer texts via the aria-labelledby attribute. Because of this, Behat steps that used to click on the labels for the
+multiple choice answer such as
+    And I click on "One" "checkbox"
+won't work anymore. This has been replaced by having Behat click on the answer text using the custom partial named selector
+"qtype_multichoice > Answer". So the above behat step would now be
+    And I click on "One" "qtype_multichoice > Answer"
+This applies to both single-answer and multiple-answer multiple choice question types.
index dd487fa..fe565f5 100644 (file)
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 $string['author'] = 'Author';
-$string['confirmdelete'] = 'Do you really wish to delete this file?';
-$string['confirmdeleteall'] = 'Do you really wish to delete all files?';
-$string['confirmdownload'] = 'Do you really wish to download this file?';
-$string['confirmdownloadall'] = 'Do you really wish to download all files?';
+$string['confirmdelete'] = 'Are you sure you want to delete this file?';
+$string['confirmdeleteall'] = 'Are you sure you want to delete all files?';
+$string['confirmdownload'] = 'Are you sure you want to download this file?';
+$string['confirmdownloadall'] = 'Are you sure you want to download all files?';
 $string['filename'] = 'File name';
 $string['infectedfiles'] = 'Antivirus failures';
 $string['privacy:metadata:infected_files'] = 'This table stores information on antivirus failures detected by the system.';
 $string['privacy:metadata:infected_files:filename'] = 'The name of the infected file uploaded by the user.';
 $string['privacy:metadata:infected_files:timecreated'] = 'The timestamp of when a user uploaded an infected file.';
-$string['privacy:metadata:infected_files:userid'] = 'The userid of the user who uploaded an infected file.';
+$string['privacy:metadata:infected_files:userid'] = 'The user ID of the user who uploaded an infected file.';
 $string['privacy:metadata:infected_files_subcontext'] = 'Antivirus failures';
 $string['pluginname'] = 'Infected files';
 $string['quarantinedfile'] = 'Quarantined file';
index 96cb40a..fbd7629 100644 (file)
@@ -27,7 +27,7 @@ Feature: Updating a file in the content bank after using in a course
     And I click on "Browse repositories..." "button" in the "Insert H5P" "dialogue"
     And I select "Content bank" repository in file picker
     And I click on "package.h5p" "file" in repository content area
-    And I click on "Create an alias/shortcut to the file" "radio"
+    And I click on "Link to the file" "radio"
     And I click on "Select this file" "button"
     And I click on "Insert H5P" "button" in the "Insert H5P" "dialogue"
     And I wait until the page is ready
index 2a41fac..5c5f0fa 100644 (file)
@@ -1784,7 +1784,8 @@ M.core_filepicker.init = function(Y, options) {
             if (filenode) {
                 // File has a license already, use it.
                 selectedlicense = filenode.license;
-            } else if (this.options.rememberuserlicensepref) {
+            } else if (this.options.rememberuserlicensepref && this.get_preference('recentlicense')) {
+                // When 'Remember user licence preference' is enabled use the last license selected by the user, if any.
                 selectedlicense = this.get_preference('recentlicense');
             }
             var licenses = this.options.licenses;
index dfa298a..3e6c599 100644 (file)
@@ -39,7 +39,7 @@ $string['mysitenotfound'] = 'You have never logged into OneDrive before. You mus
 $string['oauth2serviceslink'] = '<a href="{$a}" title="Link to OAuth 2 services configuration">OAuth 2 services configuration</a>';
 $string['owner'] = 'Owned by: {$a}';
 $string['pluginname'] = 'Microsoft OneDrive';
-$string['removetempaccesstask'] = 'Remove temporary write access from controlled links.';
+$string['removetempaccesstask'] = 'Remove temporary write access from controlled links';
 $string['searchfor'] = 'Search for {$a}';
 $string['servicenotenabled'] = 'Access not configured.';
 $string['skydrivefilesexist'] = 'Files found in the Microsoft SkyDrive repository. This repository has been deprecated by Microsoft, however the files may be imported to the Microsoft OneDrive repository.';
index 1cac3cb..71f07a5 100644 (file)
@@ -31,7 +31,7 @@ Feature: Create shortcuts
     And ".fp-content .fp-file.fp-isreference" "css_element" should not exist
     And I add "empty.txt" file from "Private files" to "Files" filemanager as:
       | Save as | empty_ref.txt |
-      | Create an alias/shortcut to the file | 1 |
+      | Link to the file | 1 |
     And I should see "2" elements in "Files" filemanager
     And I should see "empty_ref.txt" in the ".fp-content .fp-file.fp-isreference" "css_element"
     And I press "Save and display"
@@ -54,7 +54,7 @@ Feature: Create shortcuts
     # ------ Overwriting non-reference with a reference ---------
     And I add and overwrite "empty.txt" file from "Private files" to "Files" filemanager as:
       | Save as | empty_ref.txt |
-      | Create an alias/shortcut to the file | 1 |
+      | Link to the file | 1 |
     And I should see "2" elements in "Files" filemanager
     And I should see "empty_ref.txt" in the ".fp-content .fp-file.fp-isreference" "css_element"
     And I press "Save changes"
index ff80107..5d9a952 100644 (file)
@@ -166,7 +166,6 @@ input[type="radio"],
 input[type="file"],
 input[type="image"],
 .sr-only-focusable,
-a.dropdown-item,
 a.dropdown-toggle,
 .modal-dialog[tabindex="0"],
 .moodle-dialogue-base .closebutton,
@@ -192,19 +191,6 @@ button.close {
     }
 }
 
-.usermenu,
-div.dropdown-item {
-    a,
-    a[role="button"] {
-        outline: 0;
-        box-shadow: none;
-    }
-    &:focus-within {
-        outline: 0;
-        box-shadow: $input-btn-focus-box-shadow;
-    }
-}
-
 .unlist,
 .unlist li,
 .inline-list,
@@ -219,7 +205,7 @@ div.dropdown-item {
     padding: 0;
 }
 
-.section li.movehere a.movehere {
+.section li.movehere a {
     display: block;
     width: 100%;
     height: 2rem;
@@ -2340,13 +2326,34 @@ $footer-link-color: $bg-inverse-link-color !default;
 }
 
 // Make links in a menu clickable anywhere in the row.
-.dropdown-item a {
-    display: block;
-    width: 100%;
-    color: $body-color;
-}
-.dropdown-item:active a {
-    color: $dropdown-link-active-color;
+.dropdown-item {
+    a {
+        display: block;
+        width: 100%;
+        color: $body-color;
+    }
+    &:active,
+    &:hover,
+    &:focus,
+    &:focus-within {
+        outline: 0;
+        background-color: $dropdown-link-hover-bg;
+        a {
+            color: $dropdown-link-active-color;
+        }
+    }
+    &[aria-current="true"] {
+        position: relative;
+        display: flex;
+        align-items: center;
+        &:before {
+            @include fa-icon();
+            content: $fa-var-circle;
+            position: absolute;
+            left: 0.4rem;
+            font-size: 0.7rem;
+        }
+    }
 }
 
 .competency-tree {
index f4aa981..a8ef676 100644 (file)
@@ -287,33 +287,27 @@ fieldset.coursesearchbox label {
 .form-autocomplete-suggestions {
     position: absolute;
     background-color: white;
-    border: 2px solid $gray-300;
-    border-radius: 3px;
+    border: $border-width solid $input-border-color;
     min-width: 206px;
     max-height: 20em;
     overflow: auto;
-    margin: 0;
     padding: 0;
-    margin-top: 0.4em;
+    margin: 2px 0 0 0;
     z-index: 1;
 }
 
 .form-autocomplete-suggestions li {
     list-style-type: none;
-    padding: 0.2em;
+    padding: $dropdown-item-padding-y $dropdown-item-padding-x;
     margin: 0;
     cursor: pointer;
     color: $body-color;
-}
-
-.form-autocomplete-suggestions li:hover {
-    background-color: lighten($dropdown-link-active-bg, 15%);
-    color: $dropdown-link-active-color;
-}
-
-.form-autocomplete-suggestions li[aria-selected=true] {
-    background-color: darken($dropdown-bg, 5%);
-    color: $gray-700;
+    &:hover,
+    &:focus,
+    &[aria-selected="true"] {
+        background-color: $dropdown-link-active-bg;
+        color: $dropdown-link-active-color;
+    }
 }
 
 .form-autocomplete-downarrow {
index 34fec42..7d9b33d 100644 (file)
@@ -73,6 +73,9 @@ $input-btn-focus-color: rgba($primary, .75) !default;
 
 $input-border-color: $gray-500 !default;
 
+$dropdown-link-hover-color: $white;
+$dropdown-link-hover-bg: $primary;
+
 // stylelint-disable
 $theme-colors: () !default;
 $theme-colors: map-merge((
index 716dd83..5efe4e3 100644 (file)
@@ -4775,9 +4775,9 @@ input[type="button"].btn-block {
   background-color: transparent;
   border: 0; }
   .dropdown-item:hover, .dropdown-item:focus {
-    color: #16181b;
+    color: #fff;
     text-decoration: none;
-    background-color: #f8f9fa; }
+    background-color: #0f6fc5; }
   .dropdown-item.active, .dropdown-item:active {
     color: #fff;
     text-decoration: none;
@@ -9779,8 +9779,6 @@ input[type="image"].focus,
 input[type="image"]:focus,
 .sr-only-focusable.focus,
 .sr-only-focusable:focus,
-a.dropdown-item.focus,
-a.dropdown-item:focus,
 a.dropdown-toggle.focus,
 a.dropdown-toggle:focus,
 .modal-dialog[tabindex="0"].focus,
@@ -9803,7 +9801,6 @@ input[type="radio"]:focus:hover,
 input[type="file"]:focus:hover,
 input[type="image"]:focus:hover,
 .sr-only-focusable:focus:hover,
-a.dropdown-item:focus:hover,
 a.dropdown-toggle:focus:hover,
 .modal-dialog[tabindex="0"]:focus:hover,
 .moodle-dialogue-base .closebutton:focus:hover,
@@ -9815,18 +9812,6 @@ button.close:focus:hover {
 .safari input[type="radio"]:focus {
   outline: auto; }
 
-.usermenu a,
-.usermenu a[role="button"],
-div.dropdown-item a,
-div.dropdown-item a[role="button"] {
-  outline: 0;
-  box-shadow: none; }
-
-.usermenu:focus-within,
-div.dropdown-item:focus-within {
-  outline: 0;
-  box-shadow: 0 0 0 0.2rem rgba(15, 111, 197, 0.75); }
-
 .unlist,
 .unlist li,
 .inline-list,
@@ -9840,7 +9825,7 @@ div.dropdown-item:focus-within {
   margin: 0;
   padding: 0; }
 
-.section li.movehere a.movehere {
+.section li.movehere a {
   display: block;
   width: 100%;
   height: 2rem;
@@ -11529,8 +11514,27 @@ ul {
   width: 100%;
   color: #212529; }
 
-.dropdown-item:active a {
-  color: #fff; }
+.dropdown-item:active, .dropdown-item:hover, .dropdown-item:focus, .dropdown-item:focus-within {
+  outline: 0;
+  background-color: #0f6fc5; }
+  .dropdown-item:active a, .dropdown-item:hover a, .dropdown-item:focus a, .dropdown-item:focus-within a {
+    color: #fff; }
+
+.dropdown-item[aria-current="true"] {
+  position: relative;
+  display: flex;
+  align-items: center; }
+  .dropdown-item[aria-current="true"]:before {
+    display: inline-block;
+    font: normal normal normal 14px/1 FontAwesome;
+    font-size: inherit;
+    text-rendering: auto;
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+    content: "";
+    position: absolute;
+    left: 0.4rem;
+    font-size: 0.7rem; }
 
 .competency-tree ul {
   padding-left: 1.5rem; }
@@ -16528,30 +16532,23 @@ fieldset.coursesearchbox label {
 .form-autocomplete-suggestions {
   position: absolute;
   background-color: white;
-  border: 2px solid #dee2e6;
-  border-radius: 3px;
+  border: 1px solid #8f959e;
   min-width: 206px;
   max-height: 20em;
   overflow: auto;
-  margin: 0;
   padding: 0;
-  margin-top: 0.4em;
+  margin: 2px 0 0 0;
   z-index: 1; }
 
 .form-autocomplete-suggestions li {
   list-style-type: none;
-  padding: 0.2em;
+  padding: 0.25rem 1.5rem;
   margin: 0;
   cursor: pointer;
   color: #212529; }
-
-.form-autocomplete-suggestions li:hover {
-  background-color: #3195ef;
-  color: #fff; }
-
-.form-autocomplete-suggestions li[aria-selected=true] {
-  background-color: #f2f2f2;
-  color: #495057; }
+  .form-autocomplete-suggestions li:hover, .form-autocomplete-suggestions li:focus, .form-autocomplete-suggestions li[aria-selected="true"] {
+    background-color: #0f6fc5;
+    color: #fff; }
 
 .form-autocomplete-downarrow {
   color: #212529;
index c5b65aa..8eba96a 100644 (file)
@@ -67,6 +67,9 @@ $input-btn-focus-color: rgba($primary, .75) !default;
 
 $input-border-color: $gray-500 !default;
 
+$dropdown-link-hover-color: $white;
+$dropdown-link-hover-bg: $primary;
+
 // stylelint-disable
 $theme-colors: () !default;
 $theme-colors: map-merge((
index 0f71bdf..62ff744 100644 (file)
@@ -4782,9 +4782,9 @@ input[type="button"].btn-block {
   background-color: transparent;
   border: 0; }
   .dropdown-item:hover, .dropdown-item:focus {
-    color: #16181b;
+    color: #fff;
     text-decoration: none;
-    background-color: #f8f9fa; }
+    background-color: #0f6fc5; }
   .dropdown-item.active, .dropdown-item:active {
     color: #fff;
     text-decoration: none;
@@ -9983,8 +9983,6 @@ input[type="image"].focus,
 input[type="image"]:focus,
 .sr-only-focusable.focus,
 .sr-only-focusable:focus,
-a.dropdown-item.focus,
-a.dropdown-item:focus,
 a.dropdown-toggle.focus,
 a.dropdown-toggle:focus,
 .modal-dialog[tabindex="0"].focus,
@@ -10007,7 +10005,6 @@ input[type="radio"]:focus:hover,
 input[type="file"]:focus:hover,
 input[type="image"]:focus:hover,
 .sr-only-focusable:focus:hover,
-a.dropdown-item:focus:hover,
 a.dropdown-toggle:focus:hover,
 .modal-dialog[tabindex="0"]:focus:hover,
 .moodle-dialogue-base .closebutton:focus:hover,
@@ -10019,18 +10016,6 @@ button.close:focus:hover {
 .safari input[type="radio"]:focus {
   outline: auto; }
 
-.usermenu a,
-.usermenu a[role="button"],
-div.dropdown-item a,
-div.dropdown-item a[role="button"] {
-  outline: 0;
-  box-shadow: none; }
-
-.usermenu:focus-within,
-div.dropdown-item:focus-within {
-  outline: 0;
-  box-shadow: 0 0 0 0.2rem rgba(15, 111, 197, 0.75); }
-
 .unlist,
 .unlist li,
 .inline-list,
@@ -10044,7 +10029,7 @@ div.dropdown-item:focus-within {
   margin: 0;
   padding: 0; }
 
-.section li.movehere a.movehere {
+.section li.movehere a {
   display: block;
   width: 100%;
   height: 2rem;
@@ -11739,8 +11724,27 @@ ul {
   width: 100%;
   color: #212529; }
 
-.dropdown-item:active a {
-  color: #fff; }
+.dropdown-item:active, .dropdown-item:hover, .dropdown-item:focus, .dropdown-item:focus-within {
+  outline: 0;
+  background-color: #0f6fc5; }
+  .dropdown-item:active a, .dropdown-item:hover a, .dropdown-item:focus a, .dropdown-item:focus-within a {
+    color: #fff; }
+
+.dropdown-item[aria-current="true"] {
+  position: relative;
+  display: flex;
+  align-items: center; }
+  .dropdown-item[aria-current="true"]:before {
+    display: inline-block;
+    font: normal normal normal 14px/1 FontAwesome;
+    font-size: inherit;
+    text-rendering: auto;
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+    content: "";
+    position: absolute;
+    left: 0.4rem;
+    font-size: 0.7rem; }
 
 .competency-tree ul {
   padding-left: 1.5rem; }
@@ -16754,30 +16758,23 @@ fieldset.coursesearchbox label {
 .form-autocomplete-suggestions {
   position: absolute;
   background-color: white;
-  border: 2px solid #dee2e6;
-  border-radius: 3px;
+  border: 1px solid #8f959e;
   min-width: 206px;
   max-height: 20em;
   overflow: auto;
-  margin: 0;
   padding: 0;
-  margin-top: 0.4em;
+  margin: 2px 0 0 0;
   z-index: 1; }
 
 .form-autocomplete-suggestions li {
   list-style-type: none;
-  padding: 0.2em;
+  padding: 0.25rem 1.5rem;
   margin: 0;
   cursor: pointer;
   color: #212529; }
-
-.form-autocomplete-suggestions li:hover {
-  background-color: #3195ef;
-  color: #fff; }
-
-.form-autocomplete-suggestions li[aria-selected=true] {
-  background-color: #f2f2f2;
-  color: #495057; }
+  .form-autocomplete-suggestions li:hover, .form-autocomplete-suggestions li:focus, .form-autocomplete-suggestions li[aria-selected="true"] {
+    background-color: #0f6fc5;
+    color: #fff; }
 
 .form-autocomplete-downarrow {
   color: #212529;