$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}';
$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.
$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';
* @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!';
* @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';
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"
{{#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>
{{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>
<span class="filler"> </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>
<span class="filler"> </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>
</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"> </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"> </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>
{{#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>
$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";
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;
}
// 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) {
{{#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>
</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>
$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.';
$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';
# 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"
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
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
$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';
$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';
$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';
$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';
$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 :';
$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';
$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';
$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!';
$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.';
$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';
$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';
$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';
$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';
$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';
$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';
$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';
$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';
$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';
$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 a 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';
$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}';
$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';
'{{#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>' +
});
content = template({
elementid: this.get('host').get('elementid'),
+ elementidescaped: this._escapeQuerySelector(this.get('host').get('elementid')),
component: COMPONENTNAME,
library: library,
CSS: CSS,
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: {
/**
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;
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}}
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');
}
$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';
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:
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;
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');
$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;
+ }
}
/**
* 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();
$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,
$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']);
$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.
*/
$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];
$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];
$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;
} else {
echo get_string('hidden', 'grades');
}
+ echo ' - '.userdate($attempt->timefinish).'<br />';
}
- echo ' - '.userdate($attempt->timefinish).'<br />';
}
} else {
print_string('noattempts', 'quiz');
"xpath": "0.0.27"
},
"engines": {
- "node": ">=14.0.0 <15"
+ "node": ">=14.15.0 <15"
}
}
--- /dev/null
+// 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
+};
var SELECTORS = {
CHOICE_ELEMENT: '.answer input',
- LINK: 'label',
+ LINK: 'a',
RADIO: 'input[type="radio"]'
};
$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';
'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
}
$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.
}
// 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);
--- /dev/null
+<?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
+ ]
+ ),
+ ];
+ }
+}
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"
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"
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"
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"
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"
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"
--- /dev/null
+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.
* @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';
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
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;
$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.';
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"
# ------ 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"
input[type="file"],
input[type="image"],
.sr-only-focusable,
-a.dropdown-item,
a.dropdown-toggle,
.modal-dialog[tabindex="0"],
.moodle-dialogue-base .closebutton,
}
}
-.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,
padding: 0;
}
-.section li.movehere a.movehere {
+.section li.movehere a {
display: block;
width: 100%;
height: 2rem;
}
// 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 {
.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 {
$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((
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;
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,
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,
.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,
margin: 0;
padding: 0; }
-.section li.movehere a.movehere {
+.section li.movehere a {
display: block;
width: 100%;
height: 2rem;
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; }
.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;
$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((
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;
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,
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,
.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,
margin: 0;
padding: 0; }
-.section li.movehere a.movehere {
+.section li.movehere a {
display: block;
width: 100%;
height: 2rem;
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; }
.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;