Merge branch 'MDL-67813-master-1' of git://github.com/mihailges/moodle
authorVíctor Déniz Falcón <victor@moodle.com>
Thu, 28 May 2020 12:00:16 +0000 (13:00 +0100)
committerVíctor Déniz Falcón <victor@moodle.com>
Thu, 28 May 2020 12:00:16 +0000 (13:00 +0100)
195 files changed:
admin/tool/behat/lang/en/tool_behat.php
admin/tool/dataprivacy/tests/behat/manage_categories.feature
admin/tool/dataprivacy/tests/behat/manage_purposes.feature
admin/tool/usertours/amd/build/usertours.min.js
admin/tool/usertours/amd/build/usertours.min.js.map
admin/tool/usertours/amd/src/usertours.js
admin/tool/usertours/classes/external/tour.php
admin/tool/usertours/upgrade.txt
analytics/tests/behat/manage_models.feature
auth/email/tests/external_test.php
availability/condition/grade/tests/behat/availability_grade.feature
badges/classes/form/badge.php
badges/newbadge.php
badges/tests/behat/add_badge.feature
badges/tests/behat/backpack.feature
blocks/myoverview/templates/view-list.mustache
blocks/myoverview/templates/view-summary.mustache
calendar/amd/build/view_manager.min.js
calendar/amd/build/view_manager.min.js.map
calendar/amd/src/view_manager.js
calendar/templates/header.mustache
calendar/templates/month_detailed.mustache
completion/tests/behat/restrict_activity_by_grade.feature
completion/tests/behat/restrict_section_availability.feature
config-dist.php
contentbank/classes/contentbank.php
contentbank/classes/contenttype.php
contentbank/classes/form/edit_content.php [new file with mode: 0644]
contentbank/classes/output/bankcontent.php
contentbank/classes/output/viewcontent.php [new file with mode: 0644]
contentbank/contenttype/h5p/classes/contenttype.php
contentbank/contenttype/h5p/classes/form/editor.php [new file with mode: 0644]
contentbank/contenttype/h5p/db/access.php
contentbank/contenttype/h5p/lang/en/contenttype_h5p.php
contentbank/contenttype/h5p/version.php
contentbank/edit.php [new file with mode: 0644]
contentbank/index.php
contentbank/templates/bankcontent.mustache
contentbank/templates/bankcontent/toolbar.mustache
contentbank/templates/bankcontent/toolbar_dropdown.mustache [new file with mode: 0644]
contentbank/templates/viewcontent.mustache [new file with mode: 0644]
contentbank/templates/viewcontent/toolbarview.mustache [new file with mode: 0644]
contentbank/tests/behat/edit_content.feature [new file with mode: 0644]
contentbank/tests/behat/sort_content.feature
contentbank/tests/contentbank_test.php
contentbank/tests/fixtures/testable_contenttype.php
contentbank/view.php
course/classes/management_renderer.php
course/renderer.php
course/templates/coursecard.mustache
course/templates/local/activitychooser/item.mustache
course/tests/behat/behat_course.php
files/renderer.php
files/tests/behat/license_help_modal.feature [new file with mode: 0644]
grade/grading/form/guide/tests/behat/edit_guide.feature
grade/grading/tests/behat/behat_grading.php
grade/import/direct/styles.css [deleted file]
grade/lib.php
grade/report/grader/lib.php
grade/report/grader/styles.css
grade/report/singleview/classes/local/screen/grade.php
grade/report/singleview/classes/local/screen/tablelike.php
grade/report/singleview/classes/local/screen/user.php
grade/report/singleview/templates/text_attribute.mustache
grade/report/singleview/tests/behat/bulk_insert_grades.feature
grade/tests/behat/grade_scales.feature
grade/tests/behat/grade_single_item_scales.feature
h5p/h5plib/v124/lang/en/h5plib_v124.php
lang/en/admin.php
lang/en/auth.php
lang/en/backup.php
lang/en/badges.php
lang/en/contentbank.php
lang/en/moodle.php
lang/en/question.php
lang/en/repository.php
lang/en/role.php
lang/en/user.php
lib/amd/build/form-autocomplete.min.js
lib/amd/build/form-autocomplete.min.js.map
lib/amd/build/notification.min.js
lib/amd/build/notification.min.js.map
lib/amd/build/tag.min.js
lib/amd/build/tag.min.js.map
lib/amd/build/templates.min.js
lib/amd/build/templates.min.js.map
lib/amd/src/form-autocomplete.js
lib/amd/src/notification.js
lib/amd/src/tag.js
lib/amd/src/templates.js
lib/behat/classes/behat_core_generator.php
lib/behat/classes/partial_named_selector.php
lib/db/access.php
lib/form/dateselector.php
lib/form/datetimeselector.php
lib/form/tests/behat/modgrade_validation.feature
lib/licenselib.php
lib/outputcomponents.php
lib/outputrenderers.php
lib/table/classes/external/dynamic/fetch.php
lib/tablelib.php
lib/templates/action_menu_link.mustache
lib/templates/action_menu_trigger.mustache
lib/templates/filemanager_chooselicense.mustache [new file with mode: 0644]
lib/templates/filemanager_fileselect.mustache
lib/templates/filemanager_licenselinks.mustache [new file with mode: 0644]
lib/templates/filemanager_modal_generallayout.mustache
lib/templates/filemanager_page_generallayout.mustache
lib/templates/filemanager_selectlayout.mustache
lib/templates/filemanager_uploadform.mustache
lib/templates/form_autocomplete_input.mustache
lib/templates/form_autocomplete_layout.mustache [new file with mode: 0644]
lib/templates/inplace_editable.mustache
lib/templates/local/modal/alert.mustache
lib/templates/loginform.mustache
lib/tests/behat/app_behat_runtime.js
lib/tests/behat/behat_app.php
lib/yui/build/moodle-core-notification-alert/moodle-core-notification-alert-debug.js
lib/yui/build/moodle-core-notification-alert/moodle-core-notification-alert-min.js
lib/yui/build/moodle-core-notification-alert/moodle-core-notification-alert.js
lib/yui/src/notification/js/alert.js
mod/assign/feedback/editpdf/tests/behat/annotate_pdf.feature
mod/assign/feedback/editpdf/tests/behat/group_annotations.feature
mod/assign/feedback/editpdf/tests/behat/view_previous_annotations.feature
mod/assign/feedback/file/tests/behat/feedback_file.feature
mod/assign/tests/behat/allow_another_attempt.feature
mod/assign/tests/behat/comment_inline.feature
mod/assign/tests/behat/display_grade.feature
mod/assign/tests/behat/edit_previous_feedback.feature
mod/assign/tests/behat/filter_by_marker.feature
mod/assign/tests/behat/grading_status.feature
mod/assign/tests/behat/group_submission.feature
mod/assign/tests/behat/hide_grader.feature
mod/assign/tests/behat/outcome_grading.feature
mod/assign/tests/behat/quickgrading.feature
mod/assign/tests/behat/rescale_grades.feature
mod/assign/tests/behat/steps_blind_marking.feature
mod/assign/tests/behat/submission_comments.feature
mod/forum/report/summary/tests/behat/course_summary.feature
mod/forum/report/summary/tests/behat/summary_data_post_dates.feature
mod/h5pactivity/lang/en/h5pactivity.php
mod/h5pactivity/tests/behat/add_h5pactivity.feature
mod/h5pactivity/view.php
mod/quiz/accessrule/seb/lang/en/quizaccess_seb.php
mod/quiz/accessrule/seb/tests/behat/edit_form.feature
mod/quiz/accessrule/seb/tests/event_test.php
mod/quiz/accessrule/seb/tests/rule_test.php
mod/quiz/attemptlib.php
question/type/multichoice/lang/en/qtype_multichoice.php
repository/contentbank/tests/behat/select_content.feature
repository/draftfiles_ajax.php
repository/filepicker.js
repository/lib.php
repository/tests/behat/delete_files.feature
theme/boost/scss/moodle/buttons.scss
theme/boost/scss/moodle/contentbank.scss
theme/boost/scss/moodle/core.scss
theme/boost/scss/moodle/course.scss
theme/boost/scss/moodle/filemanager.scss
theme/boost/scss/moodle/grade.scss
theme/boost/scss/moodle/modules.scss
theme/boost/scss/moodle/user.scss
theme/boost/scss/preset/default.scss
theme/boost/style/moodle.css
theme/boost/templates/navbar.mustache
theme/classic/scss/preset/default.scss
theme/classic/style/moodle.css
theme/classic/templates/navbar.mustache
user/amd/build/local/participantsfilter/filter.min.js [new file with mode: 0644]
user/amd/build/local/participantsfilter/filter.min.js.map [new file with mode: 0644]
user/amd/build/local/participantsfilter/filtertypes/courseid.min.js [new file with mode: 0644]
user/amd/build/local/participantsfilter/filtertypes/courseid.min.js.map [new file with mode: 0644]
user/amd/build/local/participantsfilter/filtertypes/keyword.min.js [new file with mode: 0644]
user/amd/build/local/participantsfilter/filtertypes/keyword.min.js.map [new file with mode: 0644]
user/amd/build/local/participantsfilter/selectors.min.js [new file with mode: 0644]
user/amd/build/local/participantsfilter/selectors.min.js.map [new file with mode: 0644]
user/amd/build/participantsfilter.min.js [new file with mode: 0644]
user/amd/build/participantsfilter.min.js.map [new file with mode: 0644]
user/amd/src/local/participantsfilter/filter.js [new file with mode: 0644]
user/amd/src/local/participantsfilter/filtertypes/courseid.js [new file with mode: 0644]
user/amd/src/local/participantsfilter/filtertypes/keyword.js [new file with mode: 0644]
user/amd/src/local/participantsfilter/selectors.js [new file with mode: 0644]
user/amd/src/participantsfilter.js [new file with mode: 0644]
user/classes/output/participants_filter.php [new file with mode: 0644]
user/classes/table/participants_search.php
user/index.php
user/renderer.php
user/templates/local/participantsfilter/autocomplete_layout.mustache [new file with mode: 0644]
user/templates/local/participantsfilter/autocomplete_selection.mustache [new file with mode: 0644]
user/templates/local/participantsfilter/autocomplete_selection_items.mustache [new file with mode: 0644]
user/templates/local/participantsfilter/filterrow.mustache [new file with mode: 0644]
user/templates/local/participantsfilter/filtertype.mustache [new file with mode: 0644]
user/templates/local/participantsfilter/filtertypes.mustache [new file with mode: 0644]
user/templates/participantsfilter.mustache [new file with mode: 0644]
version.php

index f0664c5..eea7b51 100644 (file)
@@ -24,7 +24,7 @@
 
 $string['aim'] = 'This administration tool helps developers and test writers to create .feature files describing Moodle\'s functionalities and run them automatically. Step definitions available for use in .feature files are listed below.';
 $string['allavailablesteps'] = 'All available step definitions';
-$string['errorapproot'] = '$CFG->behat_ionic_dirroot is not pointing to a valid Moodle Mobile developer install.';
+$string['errorapproot'] = '$CFG->behat_ionic_dirroot is not pointing to a valid Moodle app developer install.';
 $string['errorbehatcommand'] = 'Error running behat CLI command. Try running "{$a} --help" manually from CLI to find out more about the problem.';
 $string['errorcomposer'] = 'Composer dependencies are not installed.';
 $string['errordataroot'] = '$CFG->behat_dataroot is not set or is invalid.';
index c6f7f0e..8384b2e 100644 (file)
@@ -12,7 +12,7 @@ Feature: Manage data categories
     And I press "Add category"
     And I set the field "Name" to "Category 1"
     And I set the field "Description" to "Category 1 description"
-    When I click on "Save" "button" in the "Delete category" "dialogue"
+    When I click on "Save" "button" in the "Add category" "dialogue"
     Then I should see "Category 1" in the "List of data categories" "table"
     And I should see "Category 1 description" in the "Category 1" "table_row"
 
@@ -30,5 +30,5 @@ Feature: Manage data categories
     And I choose "Delete" in the open action menu
     And I should see "Delete category"
     And I should see "Are you sure you want to delete the category 'Category 1'?"
-    When I click on "Delete" "button" in the "Confirm" "dialogue"
+    When I click on "Delete" "button" in the "Delete category" "dialogue"
     Then I should not see "Category 1" in the "List of data categories" "table"
index 772c590..dcd0c2a 100644 (file)
@@ -52,5 +52,5 @@ Feature: Manage data storage purposes
     And I choose "Delete" in the open action menu
     And I should see "Delete purpose"
     And I should see "Are you sure you want to delete the purpose 'Purpose 1'?"
-    When I click on "Delete" "button" in the "Confirm" "dialogue"
+    When I click on "Delete" "button" in the "Delete purpose" "dialogue"
     Then I should not see "Purpose 1" in the "List of data purposes" "table"
index 6895e75..6b16a51 100644 (file)
Binary files a/admin/tool/usertours/amd/build/usertours.min.js and b/admin/tool/usertours/amd/build/usertours.min.js differ
index ebfcda0..9f614ad 100644 (file)
Binary files a/admin/tool/usertours/amd/build/usertours.min.js.map and b/admin/tool/usertours/amd/build/usertours.min.js.map differ
index 70bd057..4bb1050 100644 (file)
@@ -69,6 +69,11 @@ function(ajax, BootstrapTour, $, templates, str, log, notification) {
                 templates.render('tool_usertours/tourstep', {})
             )
             .then(function(response, template) {
+                // If we don't have any tour config (because it doesn't need showing for the current user), return early.
+                if (!response.hasOwnProperty('tourconfig')) {
+                    return;
+                }
+
                 return usertours.startBootstrapTour(tourId, template[0], response.tourconfig);
             })
             .always(function() {
index d24c684..c16c5a0 100644 (file)
@@ -70,9 +70,9 @@ class tour extends external_api {
 
         \tool_usertours\event\tour_started::create([
             'contextid' => $context->id,
-            'objectid'  => $tourid,
+            'objectid'  => $tour->get_id(),
             'other'     => [
-                'pageurl' => $pageurl,
+                'pageurl' => $params['pageurl'],
             ],
         ])->trigger();
 
@@ -104,7 +104,7 @@ class tour extends external_api {
             'tourconfig'    => new external_single_structure([
                 'name'      => new external_value(PARAM_RAW, 'Tour Name'),
                 'steps'     => new external_multiple_structure(self::step_structure_returns()),
-            ])
+            ], 'Tour config', VALUE_OPTIONAL)
         ]);
     }
 
index c123bd4..cf5ddba 100644 (file)
@@ -1,4 +1,9 @@
 This files describes API changes in the tool_usertours code.
 
+=== 3.9 ===
+* The `tourconfig` property returned by the `tool_usertours_fetch_and_start_tour`
+  external method is now optional, and will be omitted if the tour shouldn't be
+  shown to the current user
+
 === 3.5 ===
 * Third party library Popper.js was moved from this plugin into core (core/popper)
index 9064a0b..816a253 100644 (file)
@@ -158,5 +158,5 @@ Feature: Manage analytics models
   Scenario: Delete model
     When I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row"
     And I choose "Delete" in the open action menu
-    And I click on "Delete" "button" in the "Confirm" "dialogue"
+    And I click on "Delete" "button" in the "Delete" "dialogue"
     Then I should not see "Students at risk of not meeting the course completion conditions"
index dcb2b06..0220640 100644 (file)
@@ -76,9 +76,21 @@ class auth_email_external_testcase extends externallib_advanced_testcase {
         $this->assertEquals(print_password_policy(), $result['passwordpolicy']);
         $this->assertNotContains('recaptchachallengehash', $result);
         $this->assertNotContains('recaptchachallengeimage', $result);
-        $this->assertCount(2, $result['profilefields']);
-        $this->assertEquals('text', $result['profilefields'][0]['datatype']);
-        $this->assertEquals('textarea', $result['profilefields'][1]['datatype']);
+
+        // Whip up a array with named entries to easily check against.
+        $namedarray = array();
+        foreach ($result['profilefields'] as $key => $value) {
+            $namedarray[$value['shortname']] = array(
+                'datatype' => $value['datatype']
+            );
+        }
+
+        // Just check if we have the fields from this test. If a plugin adds fields we'll let it slide.
+        $this->assertArrayHasKey('frogname', $namedarray);
+        $this->assertArrayHasKey('sometext', $namedarray);
+
+        $this->assertEquals('text', $namedarray['frogname']['datatype']);
+        $this->assertEquals('textarea', $namedarray['sometext']['datatype']);
     }
 
     public function test_signup_user() {
index 546e378..622047c 100644 (file)
@@ -116,7 +116,7 @@ Feature: availability_grade
     And I click on "Grade" "link" in the "s@example.com" "table_row"
     And I set the field "Grade out of 100" to "40"
     And I click on "Save changes" "button"
-    And I press "Ok"
+    And I press "OK"
     And I click on "Edit settings" "link"
 
     # Log back in as student.
index faa64a6..523403b 100644 (file)
@@ -93,9 +93,10 @@ class badge extends moodleform {
         $mform->setType('imagecaption', PARAM_TEXT);
         $mform->addHelpButton('imagecaption', 'imagecaption', 'badges');
 
-        $mform->addElement('header', 'issuerdetails', get_string('issuerdetails', 'badges'));
 
-        if (badges_open_badges_backpack_api() != OPEN_BADGES_V2) {
+        if (badges_open_badges_backpack_api() == OPEN_BADGES_V1) {
+            $mform->addElement('header', 'issuerdetails', get_string('issuerdetails', 'badges'));
+
             $mform->addElement('text', 'issuername', get_string('name'), array('size' => '70'));
             $mform->setType('issuername', PARAM_NOTAGS);
             $mform->addRule('issuername', null, 'required');
@@ -115,21 +116,6 @@ class badge extends moodleform {
             $url = parse_url($CFG->wwwroot);
             $mform->addElement('hidden', 'issuerurl', $url['scheme'] . '://' . $url['host']);
             $mform->setType('issuerurl', PARAM_URL);
-
-        } else {
-            $name = $CFG->badges_defaultissuername;
-            $mform->addElement('static', 'issuernamelabel', get_string('name'), $name);
-            $mform->addElement('hidden', 'issuername', $name);
-            $mform->setType('issuername', PARAM_NOTAGS);
-
-            $contact = $CFG->badges_defaultissuercontact;
-            $mform->addElement('static', 'issuercontactlabel', get_string('contact', 'badges'), $contact);
-            $mform->addElement('hidden', 'issuercontact', $contact);
-            $mform->setType('issuercontact', PARAM_RAW);
-
-            $url = parse_url($CFG->wwwroot);
-            $mform->addElement('hidden', 'issuerurl', $url['scheme'] . '://' . $url['host']);
-            $mform->setType('issuerurl', PARAM_URL);
         }
 
         $mform->addElement('header', 'issuancedetails', get_string('issuancedetails', 'badges'));
@@ -212,7 +198,7 @@ class badge extends moodleform {
         global $DB;
         $errors = parent::validation($data, $files);
 
-        if (badges_open_badges_backpack_api() != OPEN_BADGES_V2) {
+        if (badges_open_badges_backpack_api() == OPEN_BADGES_V1) {
             if (!empty($data['issuercontact']) && !validate_email($data['issuercontact'])) {
                 $errors['issuercontact'] = get_string('invalidemail');
             }
index 9087b2d..07785f3 100644 (file)
@@ -87,7 +87,7 @@ if ($form->is_cancelled()) {
     $fordb->usercreated = $USER->id;
     $fordb->usermodified = $USER->id;
 
-    if (badges_open_badges_backpack_api() != OPEN_BADGES_V2) {
+    if (badges_open_badges_backpack_api() == OPEN_BADGES_V1) {
         $fordb->issuername = $data->issuername;
         $fordb->issuerurl = $data->issuerurl;
         $fordb->issuercontact = $data->issuercontact;
index 81ecdb7..09db662 100644 (file)
@@ -8,18 +8,6 @@ Feature: Add badges to the system
     Given I am on homepage
     And I log in as "admin"
 
-  @javascript
-  Scenario: Setting badges settings
-    Given I navigate to "Badges > Badges settings" in site administration
-    And I set the field "Badge issuer name" to "Test Badge Site"
-    And I set the field "Badge issuer email address" to "testuser@example.com"
-    And I press "Save changes"
-    And I follow "Badges"
-    When I follow "Add a new badge"
-    And I press "Issuer details"
-    Then I should see "testuser@example.com"
-    And I should see "Test Badge Site"
-
   @javascript
   Scenario: Accessing the badges
     And I press "Customise this page"
@@ -31,7 +19,11 @@ Feature: Add badges to the system
 
   @javascript @_file_upload
   Scenario: Add a badge
-    Given I navigate to "Badges > Add a new badge" in site administration
+    Given I navigate to "Badges > Badges settings" in site administration
+    And I set the field "Badge issuer name" to "Test Badge Site"
+    And I set the field "Badge issuer email address" to "testuser@example.com"
+    And I press "Save changes"
+    And I navigate to "Badges > Add a new badge" in site administration
     And I set the following fields to these values:
       | Name | Test badge with 'apostrophe' and other friends (<>&@#) |
       | Version | v1 |
@@ -47,6 +39,11 @@ Feature: Add badges to the system
     And I should see "Related badges (0)"
     And I should see "Alignments (0)"
     And I should not see "Create badge"
+    And I should not see "Issuer details"
+    And I follow "Overview"
+    And I should see "Issuer details"
+    And I should see "Test Badge Site"
+    And I should see "testuser@example.com"
     And I follow "Manage badges"
     And I should see "Number of badges available: 1"
     And I should not see "There are no badges available."
index 3cb8e78..67376f0 100644 (file)
@@ -11,6 +11,11 @@ Feature: Backpack badges
     And the following "users" exist:
       | username | firstname | lastname | email                |
       | student1 | Student   | 1        | student1@example.com |
+    And I log in as "admin"
+    And I navigate to "Badges > Badges settings" in site administration
+    And I set the field "Badge issuer name" to "Test Badge Site"
+    And I set the field "Badge issuer email address" to "testuser@example.com"
+    And I log out
 
   @javascript
   Scenario: Verify backback settings
@@ -29,8 +34,6 @@ Feature: Backpack badges
       | Description   | Test badge description     |
       | Image author  | http://author.example.com  |
       | Image caption | Test caption image         |
-      | issuername    | Test Badge Site            |
-      | issuercontact | testuser@example.com       |
     And I upload "badges/tests/behat/badge.png" file to "Image" filemanager
     And I press "Create badge"
     And I set the field "type" to "Manual issue by role"
@@ -67,8 +70,6 @@ Feature: Backpack badges
       | Description    | Test badge description     |
       | Image author   | http://author.example.com  |
       | Image caption  | Test caption image         |
-      | issuername     | Test Badge Site            |
-      | issuercontact  | testuser@example.com       |
     And I upload "badges/tests/behat/badge.png" file to "Image" filemanager
     And I press "Create badge"
     And I set the field "type" to "Manual issue by role"
index c4d527c..f32e7e8 100644 (file)
@@ -63,7 +63,7 @@
                         <div>{{{shortname}}}</div>
                         {{/showshortname}}
                     </div>
-                    <a href="{{viewurl}}" class="coursename">
+                    <a href="{{viewurl}}" class="aalink coursename">
                         {{> core_course/favouriteicon }}
                         <span class="sr-only">
                             {{#str}}aria:coursename, core_course{{/str}}
index 9f92990..02dd284 100644 (file)
@@ -73,7 +73,7 @@
                     {{/showshortname}}
                 </div>
                 <div class="d-flex mb-1">
-                    <a href="{{viewurl}}" class="coursename">
+                    <a href="{{viewurl}}" class="aalink coursename">
                         {{> core_course/favouriteicon }}
                         <span class="sr-only">
                             {{#str}}aria:coursename, core_course{{/str}}
index 0e7cb63..f69895d 100644 (file)
Binary files a/calendar/amd/build/view_manager.min.js and b/calendar/amd/build/view_manager.min.js differ
index a0e67dc..9106713 100644 (file)
Binary files a/calendar/amd/build/view_manager.min.js.map and b/calendar/amd/build/view_manager.min.js.map differ
index e5edf31..37fca71 100644 (file)
@@ -162,7 +162,7 @@ export const refreshMonthContent = (root, year, month, courseId, categoryId, tar
             return Templates.replaceNode(target, html, js);
         })
         .then(() => {
-            document.querySelector('body').dispatchEvent(new Event(CalendarEvents.viewUpdated));
+            document.querySelector('body').dispatchEvent(new CustomEvent(CalendarEvents.viewUpdated));
             return;
         })
         .always(() => {
@@ -248,7 +248,7 @@ export const refreshDayContent = (root, year, month, day, courseId, categoryId,
             return Templates.replaceNode(target, html, js);
         })
         .then(() => {
-            document.querySelector('body').dispatchEvent(new Event(CalendarEvents.viewUpdated));
+            document.querySelector('body').dispatchEvent(new CustomEvent(CalendarEvents.viewUpdated));
             return;
         })
         .always(() => {
@@ -355,7 +355,7 @@ export const reloadCurrentUpcoming = (root, courseId = 0, categoryId = 0, target
             return Templates.replaceNode(target, html, js);
         })
         .then(() => {
-            document.querySelector('body').dispatchEvent(new Event(CalendarEvents.viewUpdated));
+            document.querySelector('body').dispatchEvent(new CustomEvent(CalendarEvents.viewUpdated));
             return;
         })
         .always(function() {
index ce7c76f..6bfc798 100644 (file)
@@ -31,7 +31,7 @@
     {
     }
 }}
-<div class="header d-flex flex-wrap">
+<div class="header d-flex flex-wrap p-1">
     {{> core_calendar/view_selector}}
     {{#filter_selector}}
         {{{filter_selector}}}
index 11608cd..a88f65e 100644 (file)
@@ -72,7 +72,7 @@
                         data-new-event-timestamp="{{neweventtimestamp}}">
                         <div class="d-none d-md-block hidden-phone text-xs-center">
                             {{#hasevents}}
-                                <a data-action="view-day-link" href="#" class="day" aria-label="{{viewdaylinktitle}}"
+                                <a data-action="view-day-link" href="#" class="aalink day" aria-label="{{viewdaylinktitle}}"
                                     data-year="{{date.year}}" data-month="{{date.mon}}" data-day="{{mday}}"
                                     data-courseid="{{courseid}}" data-categoryid="{{categoryid}}"
                                     data-timestamp="{{timestamp}}">{{mday}}</a>
                         </div>
                         <div class="d-md-none hidden-desktop hidden-tablet">
                             {{#hasevents}}
-                                <a data-action="view-day-link" href="#" class="day" aria-label="{{viewdaylinktitle}}"
+                                <a data-action="view-day-link" href="#" class="day aalink" aria-label="{{viewdaylinktitle}}"
                                     data-year="{{date.year}}" data-month="{{date.mon}}" data-day="{{mday}}"
                                     data-courseid="{{courseid}}" data-categoryid="{{categoryid}}"
                                     data-timestamp="{{timestamp}}">{{mday}}</a>
index 3d6a6cd..2cd824c 100644 (file)
@@ -58,7 +58,7 @@ Feature: Restrict activity availability through grade conditions
     And I set the following fields to these values:
       | Grade | 21 |
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I follow "Edit settings"
     And I log out
     And I log in as "student1"
index 63d8422..6785e90 100644 (file)
@@ -91,7 +91,7 @@ Feature: Restrict sections availability through completion or grade conditions
     And I set the following fields to these values:
       | Grade | 21 |
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I follow "Edit settings"
     And I log out
     And I log in as "student1"
index 96eb509..ccc760a 100644 (file)
@@ -981,8 +981,8 @@ $CFG->admin = 'admin';
 // Example:
 //   define('BEHAT_DISABLE_HISTOGRAM', true);
 //
-// Mobile app Behat testing requires this option, pointing to a developer Moodle Mobile directory:
-//   $CFG->behat_ionic_dirroot = '/where/I/keep/my/git/checkouts/moodlemobile2';
+// Mobile app Behat testing requires this option, pointing to a developer Moodle app directory:
+//   $CFG->behat_ionic_dirroot = '/where/I/keep/my/git/checkouts/moodleapp';
 //
 // The following option can be used to indicate a running Ionic server (otherwise Behat will start
 // one automatically for each test run, which is convenient but takes ages):
index 8393f6a..326e304 100644 (file)
@@ -24,6 +24,7 @@
 
 namespace core_contentbank;
 
+use core_plugin_manager;
 use stored_file;
 use context;
 
@@ -35,6 +36,8 @@ use context;
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class contentbank {
+    /** @var array Enabled content types. */
+    private $enabledcontenttypes = null;
 
     /**
      * Obtains the list of core_contentbank_content objects currently active.
@@ -44,16 +47,20 @@ class contentbank {
      * @return string[] Array of contentbank contenttypes.
      */
     public function get_enabled_content_types(): array {
+        if (!is_null($this->enabledcontenttypes)) {
+            return $this->enabledcontenttypes;
+        }
+
         $enabledtypes = \core\plugininfo\contenttype::get_enabled_plugins();
         $types = [];
         foreach ($enabledtypes as $name) {
             $contenttypeclassname = "\\contenttype_$name\\contenttype";
             $contentclassname = "\\contenttype_$name\\content";
             if (class_exists($contenttypeclassname) && class_exists($contentclassname)) {
-                $types[] = $name;
+                $types[$contenttypeclassname] = $name;
             }
         }
-        return $types;
+        return $this->enabledcontenttypes = $types;
     }
 
     /**
@@ -292,4 +299,37 @@ class contentbank {
         }
         return $result;
     }
+
+    /**
+     * Get the list of content types that have the requested feature.
+     *
+     * @param string $feature Feature code e.g CAN_UPLOAD.
+     * @param null|\context $context Optional context to check the permission to use the feature.
+     * @param bool $enabled Whether check only the enabled content types or all of them.
+     *
+     * @return string[] List of content types where the user has permission to access the feature.
+     */
+    public function get_contenttypes_with_capability_feature(string $feature, \context $context = null, bool $enabled = true): array {
+        $contenttypes = [];
+        // Check enabled content types or all of them.
+        if ($enabled) {
+            $contenttypestocheck = $this->get_enabled_content_types();
+        } else {
+            $plugins = core_plugin_manager::instance()->get_plugins_of_type('contenttype');
+            foreach ($plugins as $plugin) {
+                $contenttypeclassname = "\\{$plugin->type}_{$plugin->name}\\contenttype";
+                $contenttypestocheck[$contenttypeclassname] = $plugin->name;
+            }
+        }
+
+        foreach ($contenttypestocheck as $classname => $name) {
+            $contenttype = new $classname($context);
+            // The method names that check the features permissions must follow the pattern can_feature.
+            if ($contenttype->{"can_$feature"}()) {
+                $contenttypes[$classname] = $name;
+            }
+        }
+
+        return $contenttypes;
+    }
 }
index e2c1940..ba9442b 100644 (file)
@@ -41,7 +41,10 @@ abstract class contenttype {
     /** Plugin implements uploading feature */
     const CAN_UPLOAD = 'upload';
 
-    /** @var context This contenttype's context. **/
+    /** Plugin implements edition feature */
+    const CAN_EDIT = 'edit';
+
+    /** @var \context This contenttype's context. **/
     protected $context = null;
 
     /**
@@ -59,7 +62,7 @@ abstract class contenttype {
     /**
      * Fills content_bank table with appropiate information.
      *
-     * @param stdClass $record An optional content record compatible object (default null)
+     * @param \stdClass $record An optional content record compatible object (default null)
      * @return content  Object with content bank information.
      */
     public function create_content(\stdClass $record = null): ?content {
@@ -127,7 +130,7 @@ abstract class contenttype {
      * This method can be overwritten by the plugins if they need to change some other specific information.
      *
      * @param  content $content The content to rename.
-     * @param string $name  The name of the content.
+     * @param  string $name  The name of the content.
      * @return boolean true if the content has been renamed; false otherwise.
      */
     public function rename_content(content $content, string $name): bool {
@@ -139,7 +142,7 @@ abstract class contenttype {
      * This method can be overwritten by the plugins if they need to change some other specific information.
      *
      * @param  content $content The content to rename.
-     * @param context $context  The new context.
+     * @param  \context $context  The new context.
      * @return boolean true if the content has been renamed; false otherwise.
      */
     public function move_content(content $content, \context $context): bool {
@@ -325,6 +328,37 @@ abstract class contenttype {
         return true;
     }
 
+    /**
+     * Returns whether or not the user has permission to use the editor.
+     *
+     * @return bool     True if the user can edit content. False otherwise.
+     */
+    final public function can_edit(): bool {
+        if (!$this->is_feature_supported(self::CAN_EDIT)) {
+            return false;
+        }
+
+        if (!$this->can_access()) {
+            return false;
+        }
+
+        $classname = 'contenttype/'.$this->get_plugin_name();
+
+        $editioncap = $classname.':useeditor';
+        $hascapabilities = has_all_capabilities(['moodle/contentbank:useeditor', $editioncap], $this->context);
+        return $hascapabilities && $this->is_edit_allowed();
+    }
+
+    /**
+     * Returns plugin allows edition.
+     *
+     * @return bool     True if plugin allows edition. False otherwise.
+     */
+    protected function is_edit_allowed(): bool {
+        // Plugins can overwrite this function to add any check they need.
+        return true;
+    }
+
     /**
      * Returns the plugin supports the feature.
      *
@@ -348,4 +382,17 @@ abstract class contenttype {
      * @return array
      */
     abstract public function get_manageable_extensions(): array;
+
+    /**
+     * Returns the list of different types of the given content type.
+     *
+     * A content type can have one or more options for creating content. This method will report all of them or only the content
+     * type itself if it has no other options.
+     *
+     * @return array An object for each type:
+     *     - string typename: descriptive name of the type.
+     *     - string typeeditorparams: params required by this content type editor.
+     *     - url typeicon: this type icon.
+     */
+    abstract public function get_contenttype_types(): array;
 }
diff --git a/contentbank/classes/form/edit_content.php b/contentbank/classes/form/edit_content.php
new file mode 100644 (file)
index 0000000..e75b731
--- /dev/null
@@ -0,0 +1,90 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Provides {@see \core_contentbank\form\edit_content} class.
+ *
+ * @package    core_contentbank
+ * @copyright  2020 Victor Deniz <victor@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_contentbank\form;
+
+use moodleform;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir.'/formslib.php');
+
+/**
+ * Defines the form for editing a content.
+ *
+ * @package    core_contentbank
+ * @copyright  2020 Victor Deniz <victor@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class edit_content extends moodleform {
+
+    /** @var int Context the content belongs to. */
+    protected $contextid;
+
+    /** @var string Content type plugin name. */
+    protected $plugin;
+
+    /** @var int Content id in the content bank. */
+    protected $id;
+
+    /**
+     * Constructor.
+     *
+     * @param string $action The action attribute for the form.
+     * @param array $customdata Data to set during instance creation.
+     * @param string $method Form method.
+     */
+    public function __construct(string $action = null, array $customdata = null, string $method = 'post') {
+        parent::__construct($action, $customdata, $method);
+        $this->contextid = $customdata['contextid'];
+        $this->plugin = $customdata['plugin'];
+        $this->id = $customdata['id'];
+
+        $mform =& $this->_form;
+        $mform->addElement('hidden', 'contextid', $this->contextid);
+        $this->_form->setType('contextid', PARAM_INT);
+
+        $mform->addElement('hidden', 'plugin', $this->plugin);
+        $this->_form->setType('plugin', PARAM_PLUGIN);
+
+        $mform->addElement('hidden', 'id', $this->id);
+        $this->_form->setType('id', PARAM_INT);
+    }
+
+    /**
+     * Overrides formslib's add_action_buttons() method.
+     *
+     *
+     * @param bool $cancel
+     * @param string|null $submitlabel
+     *
+     * @return void
+     */
+    public function add_action_buttons($cancel = true, $submitlabel = null): void {
+        if (is_null($submitlabel)) {
+            $submitlabel = get_string('save');
+        }
+        parent::add_action_buttons($cancel, $submitlabel);
+    }
+}
index 6574b04..b851222 100644 (file)
@@ -98,7 +98,56 @@ class bankcontent implements renderable, templatable {
             );
         }
         $data->contents = $contentdata;
-        $data->tools = $this->toolbar;
+        // The tools are displayed in the action bar on the index page.
+        foreach ($this->toolbar as $tool) {
+            // Customize the output of a tool, like dropdowns.
+            $method = 'export_tool_'.$tool['name'];
+            if (method_exists($this, $method)) {
+                $this->$method($tool);
+            }
+            $data->tools[] = $tool;
+        }
+
         return $data;
     }
+
+    /**
+     * Adds the content type items to display to the Add dropdown.
+     *
+     * Each content type is represented as an object with the properties:
+     *     - name: the name of the content type.
+     *     - baseurl: the base content type editor URL.
+     *     - types: different types of the content type to display as dropdown items.
+     *
+     * @param array $tool Data for rendering the Add dropdown, including the editable content types.
+     */
+    private function export_tool_add(array &$tool) {
+        $editabletypes = $tool['contenttypes'];
+
+        $addoptions = [];
+        foreach ($editabletypes as $class => $type) {
+            $contentype = new $class($this->context);
+            // Get the creation options of each content type.
+            $types = $contentype->get_contenttype_types();
+            if ($types) {
+                // Add a text describing the content type as first option. This will be displayed in the drop down to
+                // separate the options for the different content types.
+                $contentdesc = new stdClass();
+                $contentdesc->typename = get_string('description', $contentype->get_contenttype_name());
+                array_unshift($types, $contentdesc);
+                // Context data for the template.
+                $addcontenttype = new stdClass();
+                // Content type name.
+                $addcontenttype->name = $type;
+                // Content type editor base URL.
+                $tool['link']->param('plugin', $type);
+                $addcontenttype->baseurl = $tool['link']->out();
+                // Different types of the content type.
+                $addcontenttype->types = $types;
+                $addoptions[] = $addcontenttype;
+            }
+        }
+
+        $tool['contenttypes'] = $addoptions;
+    }
 }
diff --git a/contentbank/classes/output/viewcontent.php b/contentbank/classes/output/viewcontent.php
new file mode 100644 (file)
index 0000000..efb403e
--- /dev/null
@@ -0,0 +1,94 @@
+<?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/>.
+
+/**
+ * Class containing data for a content view.
+ *
+ * @package    core_contentbank
+ * @copyright  2020 Victor Deniz <victor@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_contentbank\output;
+
+use core_contentbank\content;
+use core_contentbank\contenttype;
+use moodle_url;
+use renderable;
+use renderer_base;
+use stdClass;
+use templatable;
+
+/**
+ * Class containing data for the content view.
+ *
+ * @copyright  2020 Victor Deniz <victor@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class viewcontent implements renderable, templatable {
+    /**
+     * @var contenttype Content bank content type.
+     */
+    private $contenttype;
+
+    /**
+     * @var stdClass Record of the contentbank_content table.
+     */
+    private $content;
+
+    /**
+     * Construct this renderable.
+     *
+     * @param contenttype $contenttype Content bank content type.
+     * @param content $content Record of the contentbank_content table.
+     */
+    public function __construct(contenttype $contenttype, content $content) {
+        $this->contenttype = $contenttype;
+        $this->content = $content;
+    }
+
+    /**
+     * Export this data so it can be used as the context for a mustache template.
+     *
+     * @param renderer_base $output
+     *
+     * @return stdClass
+     */
+    public function export_for_template(renderer_base $output): stdClass {
+        $data = new stdClass();
+
+        // Get the content type html.
+        $contenthtml = $this->contenttype->get_view_content($this->content);
+        $data->contenthtml = $contenthtml;
+
+        // Check if the user can edit this content type.
+        if ($this->contenttype->can_edit()) {
+            $data->usercanedit = true;
+            $urlparams = [
+                'contextid' => $this->content->get_contextid(),
+                'plugin' => $this->contenttype->get_plugin_name(),
+                'id' => $this->content->get_id()
+            ];
+            $editcontenturl = new moodle_url('/contentbank/edit.php', $urlparams);
+            $data->editcontenturl = $editcontenturl->out(false);
+        }
+
+        $closeurl = new moodle_url('/contentbank/index.php', ['contextid' => $this->content->get_contextid()]);
+        $data->closeurl = $closeurl->out(false);
+
+        return $data;
+    }
+}
index d48941d..1c1f2ea 100644 (file)
 namespace contenttype_h5p;
 
 use core\event\contentbank_content_viewed;
-use html_writer;
+use stdClass;
+use core_h5p\editor_ajax;
+use core_h5p\file_storage;
+use core_h5p\local\library\autoloader;
+use H5PCore;
 
 /**
  * H5P content bank manager class
@@ -65,8 +69,7 @@ class contenttype extends \core_contentbank\contenttype {
         $event->trigger();
 
         $fileurl = $content->get_file_url();
-        $html = html_writer::tag('h2', $content->get_name());
-        $html .= \core_h5p\player::display($fileurl, new \stdClass(), true);
+        $html = \core_h5p\player::display($fileurl, new \stdClass(), true);
         return $html;
     }
 
@@ -107,7 +110,7 @@ class contenttype extends \core_contentbank\contenttype {
      * @return array
      */
     protected function get_implemented_features(): array {
-        return [self::CAN_UPLOAD];
+        return [self::CAN_UPLOAD, self::CAN_EDIT];
     }
 
     /**
@@ -127,4 +130,42 @@ class contenttype extends \core_contentbank\contenttype {
     protected function is_access_allowed(): bool {
         return true;
     }
+
+    /**
+     * Returns the list of different H5P content types the user can create.
+     *
+     * @return array An object for each H5P content type:
+     *     - string typename: descriptive name of the H5P content type.
+     *     - string typeeditorparams: params required by the H5P editor.
+     *     - url typeicon: H5P content type icon.
+     */
+    public function get_contenttype_types(): array {
+        // Get the H5P content types available.
+        autoloader::register();
+        $editorajax = new editor_ajax();
+        $h5pcontenttypes = $editorajax->getLatestLibraryVersions();
+
+        $types = [];
+        $h5pfilestorage = new file_storage();
+        foreach ($h5pcontenttypes as $h5pcontenttype) {
+            $library = [
+                'name' => $h5pcontenttype->machine_name,
+                'majorVersion' => $h5pcontenttype->major_version,
+                'minorVersion' => $h5pcontenttype->minor_version,
+            ];
+            $key = H5PCore::libraryToString($library);
+            $type = new stdClass();
+            $type->key = $key;
+            $type->typename = $h5pcontenttype->title;
+            $type->typeeditorparams = 'library=' . $key;
+            $type->typeicon = $h5pfilestorage->get_icon_url(
+                $h5pcontenttype->id,
+                $h5pcontenttype->machine_name,
+                $h5pcontenttype->major_version,
+                $h5pcontenttype->minor_version);
+            $types[] = $type;
+        }
+
+        return $types;
+    }
 }
diff --git a/contentbank/contenttype/h5p/classes/form/editor.php b/contentbank/contenttype/h5p/classes/form/editor.php
new file mode 100644 (file)
index 0000000..b7229b9
--- /dev/null
@@ -0,0 +1,152 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Provides the class that defines the form for the H5P authoring tool.
+ *
+ * @package    contenttype_h5p
+ * @copyright  2020 Victor Deniz <victor@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace contenttype_h5p\form;
+
+use contenttype_h5p\content;
+use contenttype_h5p\contenttype;
+use core_contentbank\form\edit_content;
+use core_h5p\api;
+use core_h5p\editor as h5peditor;
+use core_h5p\factory;
+use stdClass;
+
+/**
+ * Defines the form for editing an H5P content.
+ *
+ * @copyright 2020 Victor Deniz <victor@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class editor extends edit_content {
+
+    /** @var $h5peditor H5P editor object */
+    private $h5peditor;
+
+    /** @var $content The content being edited */
+    private $content;
+
+    /**
+     * Defines the form fields.
+     */
+    protected function definition() {
+        global $DB;
+
+        $mform = $this->_form;
+
+        // Id of the content to edit.
+        $id = $this->_customdata['id'];
+        // H5P content type to create.
+        $library = optional_param('library', null, PARAM_TEXT);
+
+        if (empty($id) && empty($library)) {
+            $returnurl = new \moodle_url('/contentbank/index.php', ['contextid' => $this->_customdata['contextid']]);
+            print_error('invalidcontentid', 'error', $returnurl);
+        }
+
+        $this->h5peditor = new h5peditor();
+
+        if ($id) {
+            // The H5P editor needs the H5P content id (h5p table).
+            $record = $DB->get_record('contentbank_content', ['id' => $id]);
+            $this->content = new content($record);
+            $file = $this->content->get_file();
+
+            $h5p = api::get_content_from_pathnamehash($file->get_pathnamehash());
+            $mform->addElement('hidden', 'h5pid', $h5p->id);
+            $mform->setType('h5pid', PARAM_INT);
+            $this->h5peditor->set_content($h5p->id);
+        } else {
+            // The H5P editor needs the H5P content type library name for a new content.
+            $mform->addElement('hidden', 'library', $library);
+            $mform->setType('library', PARAM_TEXT);
+            $this->h5peditor->set_library($library, $this->_customdata['contextid'], 'contentbank', 'public');
+        }
+
+        $mformid = 'coolh5peditor';
+        $mform->setAttributes(array('id' => $mformid) + $mform->getAttributes());
+
+        $this->add_action_buttons();
+
+        $this->h5peditor->add_editor_to_form($mform);
+
+        $this->add_action_buttons();
+    }
+
+    /**
+     * Modify or create an H5P content from the form data.
+     *
+     * @param stdClass $data Form data to create or modify an H5P content.
+     *
+     * @return int The id of the edited or created content.
+     */
+    public function save_content(stdClass $data): int {
+        global $DB;
+
+        // The H5P libraries expect data->id as the H5P content id.
+        // The method \H5PCore::saveContent throws an error if id is set but empty.
+        if (empty($data->id)) {
+            unset($data->id);
+        } else {
+            // The H5P libraries save in $data->id the H5P content id (h5p table), so the content id is saved in another var.
+            $contentid = $data->id;
+        }
+
+        $h5pcontentid = $this->h5peditor->save_content($data);
+
+        $factory = new factory();
+        $h5pfs = $factory->get_framework();
+
+        // Needs the H5P file id to create or update the content bank record.
+        $h5pcontent = $h5pfs->loadContent($h5pcontentid);
+        $fs = get_file_storage();
+        $file = $fs->get_file_by_hash($h5pcontent['pathnamehash']);
+        // Creating new content.
+        if (!isset($data->h5pid)) {
+            // The initial name of the content is the title of the H5P content.
+            $cbrecord = new stdClass();
+            $cbrecord->name = json_decode($data->h5pparams)->metadata->title;
+            $context = \context::instance_by_id($data->contextid, MUST_EXIST);
+            // Create entry in content bank.
+            $contenttype = new contenttype($context);
+            $newcontent = $contenttype->create_content($cbrecord);
+            if ($file && $newcontent) {
+                $updatedfilerecord = new stdClass();
+                $updatedfilerecord->id = $file->get_id();
+                $updatedfilerecord->itemid = $newcontent->get_id();
+                // As itemid changed, the pathnamehash has to be updated in the file table.
+                $pathnamehash = \file_storage::get_pathname_hash($file->get_contextid(), $file->get_component(),
+                    $file->get_filearea(), $updatedfilerecord->itemid, $file->get_filepath(), $file->get_filename());
+                $updatedfilerecord->pathnamehash = $pathnamehash;
+                $DB->update_record('files', $updatedfilerecord);
+                // The pathnamehash in the h5p table must match the file pathnamehash.
+                $h5pfs->updateContentFields($h5pcontentid, ['pathnamehash' => $pathnamehash]);
+            }
+        } else {
+            // Update content.
+            $this->content->update_content();
+        }
+
+        return $contentid ?? $newcontent->get_id();
+    }
+}
index 95db4aa..97076ae 100644 (file)
@@ -44,4 +44,14 @@ $capabilities = [
             'editingteacher' => CAP_ALLOW,
         ]
     ],
+    'contenttype/h5p:useeditor' => [
+        'riskbitmask' => RISK_SPAM,
+        'captype' => 'write',
+        'contextlevel' => CONTEXT_COURSE,
+        'archetypes' => [
+            'manager' => CAP_ALLOW,
+            'coursecreator' => CAP_ALLOW,
+            'editingteacher' => CAP_ALLOW,
+        ]
+    ],
 ];
index 179e80f..7e2e49e 100644 (file)
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
+$string['description'] = 'H5P Interactive Content';
 $string['pluginname'] = 'H5P';
 $string['pluginname_help'] = 'Content bank to upload and share H5P content';
 $string['privacy:metadata'] = 'The H5P content bank plugin does not store any personal data.';
 $string['h5p:access'] = 'Access H5P content in the content bank';
 $string['h5p:upload'] = 'Upload new H5P content';
+$string['h5p:useeditor'] = 'Create or edit content using the H5P editor';
index 548ef13..fe0e68b 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2020041500.00;         // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version   = 2020051500.01;         // The current plugin version (Date: YYYYMMDDXX)
 $plugin->requires  = 2020041500.00;         // Requires this Moodle version
 $plugin->component = 'contenttype_h5p'; // Full name of the plugin (used for diagnostics).
diff --git a/contentbank/edit.php b/contentbank/edit.php
new file mode 100644 (file)
index 0000000..832f1c2
--- /dev/null
@@ -0,0 +1,110 @@
+<?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/>.
+
+/**
+ * Create or update contents through the specific content type editor
+ *
+ * @package    core_contentbank
+ * @copyright  2020 Victor Deniz <victor@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require('../config.php');
+
+require_login();
+
+$contextid = required_param('contextid', PARAM_INT);
+$pluginname = required_param('plugin', PARAM_PLUGIN);
+$id = optional_param('id', null, PARAM_INT);
+$context = context::instance_by_id($contextid, MUST_EXIST);
+require_capability('moodle/contentbank:access', $context);
+
+$returnurl = new \moodle_url('/contentbank/view.php', ['id' => $id]);
+
+if (!empty($id)) {
+    $record = $DB->get_record('contentbank_content', ['id' => $id], '*', MUST_EXIST);
+    $contentclass = "$record->contenttype\\content";
+    $content = new $contentclass($record);
+    // Set the heading title.
+    $heading = $content->get_name();
+    // The content type of the content overwrites the pluginname param value.
+    $contenttypename = $content->get_content_type();
+} else {
+    $contenttypename = "contenttype_$pluginname";
+    $heading = get_string('addinganew', 'moodle', get_string('description', $contenttypename));
+}
+
+// Check plugin is enabled.
+$plugin = core_plugin_manager::instance()->get_plugin_info($contenttypename);
+if (!$plugin || !$plugin->is_enabled()) {
+    print_error('unsupported', 'core_contentbank', $returnurl);
+}
+
+// Create content type instance.
+$contenttypeclass = "$contenttypename\\contenttype";
+if (class_exists($contenttypeclass)) {
+    $contenttype = new $contenttypeclass($context);
+} else {
+    print_error('unsupported', 'core_contentbank', $returnurl);
+}
+
+// Checks the user can edit this content type.
+if (!$contenttype->can_edit()) {
+    print_error('contenttypenoedit', 'core_contentbank', $returnurl, $contenttype->get_plugin_name());
+}
+
+$values = [
+    'contextid' => $contextid,
+    'plugin' => $pluginname,
+    'id' => $id
+];
+
+$title = get_string('contentbank');
+\core_contentbank\helper::get_page_ready($context, $title, true);
+if ($PAGE->course) {
+    require_login($PAGE->course->id);
+}
+
+$PAGE->set_url(new \moodle_url('/contentbank/edit.php', $values));
+$PAGE->set_context($context);
+$PAGE->navbar->add(get_string('edit'));
+$PAGE->set_title($title);
+
+$PAGE->set_heading($heading);
+
+// Instantiate the content type form.
+$editorclass = "$contenttypename\\form\\editor";
+if (!class_exists($editorclass)) {
+    print_error('noformdesc');
+}
+
+$editorform = new $editorclass(null, $values);
+
+if ($editorform->is_cancelled()) {
+    if (empty($id)) {
+        $returnurl = new \moodle_url('/contentbank/index.php', ['contextid' => $context->id]);
+    }
+    redirect($returnurl);
+} else if ($data = $editorform->get_data()) {
+    $id = $editorform->save_content($data);
+    // Just in case we've created a new content.
+    $returnurl->param('id', $id);
+    redirect($returnurl);
+}
+
+echo $OUTPUT->header();
+$editorform->display();
+echo $OUTPUT->footer();
index 3b15b1e..e4bb6d6 100644 (file)
@@ -62,6 +62,19 @@ $foldercontents = $cb->search_contents($search, $contextid, $contenttypes);
 
 // Get the toolbar ready.
 $toolbar = array ();
+
+// Place the Add button in the toolbar.
+if (has_capability('moodle/contentbank:useeditor', $context)) {
+    // Get the content types for which the user can use an editor.
+    $editabletypes = $cb->get_contenttypes_with_capability_feature(\core_contentbank\contenttype::CAN_EDIT, $context);
+    if (!empty($editabletypes)) {
+        // Editor base URL.
+        $editbaseurl = new moodle_url('/contentbank/edit.php', ['contextid' => $contextid]);
+        $toolbar[] = ['name' => get_string('add'), 'link' => $editbaseurl, 'dropdown' => true, 'contenttypes' => $editabletypes];
+    }
+}
+
+// Place the Upload button in the toolbar.
 if (has_capability('moodle/contentbank:upload', $context)) {
     // Don' show upload button if there's no plugin to support any file extension.
     $accepted = $cb->get_supported_extensions_as_string($context);
index 0826a65..020b905 100644 (file)
@@ -15,7 +15,7 @@
     along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 }}
 {{!
-    @template core_contentbank/list
+    @template core_contentbank/bankcontent
 
     Example context (json):
     {
             },
             {
                 "name": "resume.pdf",
+                "title": "resume",
+                "timemodified": 1589792039,
+                "size": "699.3KB",
+                "bytes": 716126,
+                "type": "Archive (PDF)",
                 "icon": "http://something/theme/image.php/boost/core/1584597850/f/pdf-64"
             }
         ],
         "tools": [
+            {
+                "name": "Add",
+                "dropdown": true,
+                "link": "http://something/contentbank/edit.php?contextid=1",
+                "contenttypes": [
+                    {
+                        "name": "H5P Interactive Content",
+                        "baseurl": "http://something/contentbank/edit.php?contextid=1&plugin=h5p",
+                        "types": [
+                            {
+                                "typename": "H5P Interactive Content"
+                            },
+                            {
+                                "typename": "Accordion",
+                                "typeeditorparams": "library=Accordion-1.4",
+                                "typeicon": "http://something/pluginfile.php/1/core_h5p/libraries/13/H5P.Accordion-1.4/icon.svg"
+                            }
+                        ]
+                    }
+                ]
+            },
             {
                 "name": "Upload",
                 "link": "http://something/contentbank/contenttype/h5p/view.php?url=http://something/pluginfile.php/1/contentbank/public/accordion.h5p",
index 04c762a..242fa1a 100644 (file)
     Example context (json):
     {
         "tools": [
+            {
+                "name": "Add",
+                "dropdown": true,
+                "link": "http://something/contentbank/edit.php?contextid=1",
+                "contenttypes": [
+                    {
+                        "name": "h5p",
+                        "baseurl": "http://something/contentbank/edit.php?contextid=1&plugin=h5p",
+                        "types": [
+                            {
+                                "typename": "H5P Interactive Content"
+                            },
+                            {
+                                "typename": "Accordion",
+                                "typeeditorparams": "library=Accordion-1.4",
+                                "typeicon": "http://something/pluginfile.php/1/core_h5p/libraries/13/H5P.Accordion-1.4/icon.svg"
+                            }
+                        ]
+                    }
+                ]
+            },
             {
                 "name": "Upload",
                 "link": "http://something/contentbank/contenttype/h5p/view.php?url=http://something/pluginfile.php/1/contentbank/public/accordion.h5p",
 }}
 
 {{#tools}}
-    <a href="{{{ link }}}" class="icon-no-margin btn btn-secondary" title="{{{ name }}}">
-        {{#pix}} {{{ icon }}} {{/pix}} {{{ name }}}
-    </a>
+    {{#dropdown}}
+        {{>core_contentbank/bankcontent/toolbar_dropdown}}
+    {{/dropdown}}
+    {{^dropdown}}
+        <a href="{{{ link }}}" class="icon-no-margin btn btn-secondary" title="{{{ name }}}">
+            {{#pix}} {{{ icon }}} {{/pix}} {{{ name }}}
+        </a>
+    {{/dropdown}}
 {{/tools}}
 <button class="icon-no-margin btn btn-secondary active ml-2"
 title="{{#str}}  displayicons, contentbank  {{/str}}"
@@ -47,4 +73,4 @@ data-action="viewgrid">
 title="{{#str}} displaydetails, contentbank {{/str}}"
 data-action="viewlist">
     {{#pix}}t/viewdetails, core, {{#str}} displaydetails, contentbank {{/str}} {{/pix}}
-</button>
\ No newline at end of file
+</button>
diff --git a/contentbank/templates/bankcontent/toolbar_dropdown.mustache b/contentbank/templates/bankcontent/toolbar_dropdown.mustache
new file mode 100644 (file)
index 0000000..7a2fbf5
--- /dev/null
@@ -0,0 +1,64 @@
+{{!
+    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/>.
+}}
+{{!
+    @template core_contentbank/bankcontent/toolbar_dropdown
+
+    Example context (json):
+        {
+            "name": "Add",
+            "dropdown": true,
+            "link": "http://something/contentbank/edit.php?contextid=1",
+            "contenttypes": [
+                {
+                    "name": "h5p",
+                    "baseurl": "http://something/contentbank/edit.php?contextid=1&plugin=h5p",
+                    "types": [
+                        {
+                            "typename": "H5P Interactive Content"
+                        },
+                        {
+                            "typename": "Accordion",
+                            "typeeditorparams": "library=Accordion-1.4",
+                            "typeicon": "http://something/pluginfile.php/1/core_h5p/libraries/13/H5P.Accordion-1.4/icon.svg"
+                        }
+                    ]
+                }
+            ]
+        }
+
+}}
+<div class="btn-group mr-1" role="group">
+    <button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" data-action="{{name}}-content"
+            aria-haspopup="true" aria-expanded="false" {{^contenttypes}}title="{{#str}}nocontenttypes, core_contentbank{{/str}}"
+            disabled{{/contenttypes}}>
+        {{#name}} {{name}} {{/name}}
+    </button>
+    <div class="dropdown-menu dropdown-scrollable dropdown-menu-right">
+        {{#contenttypes}}
+            {{#types}}
+                {{^typeeditorparams}}
+                    <h6 class="dropdown-header">{{ typename }}</h6>
+                {{/typeeditorparams}}
+                {{#typeeditorparams}}
+                    <a class="dropdown-item icon-size-4" href="{{{ baseurl }}}&{{{ typeeditorparams }}}">
+                        <img alt="" class="icon" src="{{{ typeicon }}}"> {{ typename }}
+                    </a>
+                {{/typeeditorparams}}
+            {{/types}}
+        {{/contenttypes}}
+    </div>
+</div>
diff --git a/contentbank/templates/viewcontent.mustache b/contentbank/templates/viewcontent.mustache
new file mode 100644 (file)
index 0000000..7c7d5c0
--- /dev/null
@@ -0,0 +1,52 @@
+{{!
+    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 comments.
+
+    You should have received a copy of the GNU General Public License
+    along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+}}
+{{!
+    @template core_contentbank/view_content
+
+    View content page.
+
+    Classes required for JS:
+    * none
+
+    Data attributes required for JS:
+    * none
+
+    Context variables required for this template:
+    * contenthtml - string - content html.
+    * usercanedit - boolean - whether the user has permission to edit the content.
+    * editcontenturl - string - edit page URL.
+    * closeurl - string - close landing page.
+
+    Example context (json):
+    {
+        "contenthtml" : "<iframe src=\"http://something/h5p/embed.php?url=h5pfileurl\"></iframe>",
+        "usercanedit" : true,
+        "editcontenturl" : "http://something/contentbank/edit.php?contextid=1&plugin=h5p&id=1",
+        "closeurl" : "http://moodle.test/h5pcb/moodle/contentbank/index.php"
+    }
+}}
+<div class="core_contentbank_viewcontent">
+    <div class="d-flex justify-content-end flex-column flex-sm-row">
+        {{>core_contentbank/viewcontent/toolbarview}}
+    </div>
+    <div class="container mt-1 mb-1" data-region="viewcontent-content">
+        {{{ contenthtml }}}
+    </div>
+    <div class="d-flex justify-content-end flex-column flex-sm-row">
+        {{>core_contentbank/viewcontent/toolbarview}}
+    </div>
+</div>
diff --git a/contentbank/templates/viewcontent/toolbarview.mustache b/contentbank/templates/viewcontent/toolbarview.mustache
new file mode 100644 (file)
index 0000000..25eaaa5
--- /dev/null
@@ -0,0 +1,50 @@
+{{!
+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 comments.
+
+You should have received a copy of the GNU General Public License
+along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+}}
+{{!
+    @template core_contentbank/viewcontent/toolbarview
+
+    Contentbank view toolbar.
+
+    Classes required for JS:
+    * none
+
+    Data attributes required for JS:
+    * none
+
+    Context variables required for this template:
+    * contenthtml - string - content html.
+    * usercanedit - boolean - whether the user has permission to edit the content.
+    * editcontenturl - string - edit page URL.
+    * closeurl - string - close landing page.
+
+    Example context (json):
+    {
+        "usercanedit" : true,
+        "editcontenturl" : "http://something/contentbank/edit.php?contextid=1&plugin=h5p&id=1",
+        "closeurl" : "http://moodle.test/h5pcb/moodle/contentbank/index.php"
+    }
+}}
+{{#usercanedit}}
+<div class="cb-toolbar-container mb-2">
+    <a href="{{editcontenturl}}" class="btn btn-primary" data-action="edit-content">
+        {{#str}}edit{{/str}}
+    </a>
+    <a href="{{closeurl}}" class="btn btn-secondary" data-action="close-content">
+        {{#str}}close, core_contentbank{{/str}}
+    </a>
+</div>
+{{/usercanedit}}
diff --git a/contentbank/tests/behat/edit_content.feature b/contentbank/tests/behat/edit_content.feature
new file mode 100644 (file)
index 0000000..713768c
--- /dev/null
@@ -0,0 +1,99 @@
+@core @core_contentbank @contentbank_h5p @_file_upload @javascript
+Feature: Content bank use editor feature
+  In order to add/edit content
+  As a user
+  I need to be able to access the edition options
+
+  Background:
+    Given I log in as "admin"
+    And I am on site homepage
+    And I turn editing mode on
+    And I add the "Navigation" block if not present
+    And I configure the "Navigation" block
+    And I set the following fields to these values:
+      | Page contexts | Display throughout the entire site |
+    And I press "Save changes"
+
+  Scenario: Users see the Add button disabled if there is no content type available for creation
+    Given I click on "Site pages" "list_item" in the "Navigation" "block"
+    When I click on "Content bank" "link"
+    Then the "[data-action=Add-content]" "css_element" should be disabled
+
+  Scenario: Users can see the Add button if there is content type available for creation
+    Given I follow "Dashboard" in the user menu
+    And I follow "Manage private files..."
+    And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "Files" filemanager
+    And I click on "Save changes" "button"
+    And I click on "Site pages" "list_item" in the "Navigation" "block"
+    And I click on "Content bank" "link" in the "Navigation" "block"
+    And I click on "Upload" "link"
+    And I click on "Choose a file..." "button"
+    And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
+    And I click on "filltheblanks.h5p" "link"
+    And I click on "Select this file" "button"
+    And I click on "Save changes" "button"
+    When I click on "Content bank" "link"
+    And I click on "filltheblanks.h5p" "link"
+    And I click on "Close" "link"
+    Then I click on "[data-action=Add-content]" "css_element"
+    And I should see "Fill in the Blanks"
+
+  Scenario: Users can edit content if they have the required permission
+    Given I follow "Dashboard" in the user menu
+    And I follow "Manage private files..."
+    And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "Files" filemanager
+    And I click on "Save changes" "button"
+    And I click on "Site pages" "list_item" in the "Navigation" "block"
+    And I click on "Content bank" "link" in the "Navigation" "block"
+    And I click on "Upload" "link"
+    And I click on "Choose a file..." "button"
+    And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
+    And I click on "filltheblanks.h5p" "link"
+    And I click on "Select this file" "button"
+    And I click on "Save changes" "button"
+    When I click on "Content bank" "link"
+    And I click on "filltheblanks.h5p" "link"
+    Then I click on "Edit" "link"
+    And I switch to "h5p-editor-iframe" class iframe
+    And I switch to the main frame
+    And I click on "Cancel" "button"
+    And I should see "filltheblanks.h5p" in the "h1" "css_element"
+
+  Scenario: Users can create new content if they have the required permission
+    Given I navigate to "H5P > Manage H5P content types" in site administration
+    And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "H5P content type" filemanager
+    And I click on "Upload H5P content types" "button" in the "#fitem_id_uploadlibraries" "css_element"
+    And I should see "H5P content types uploaded successfully"
+    And I click on "Site pages" "list_item" in the "Navigation" "block"
+    When I click on "Content bank" "link" in the "Navigation" "block"
+    And I click on "[data-action=Add-content]" "css_element"
+    Then I click on "Fill in the Blanks" "link"
+    And I switch to "h5p-editor-iframe" class iframe
+    And I switch to the main frame
+    And I click on "Cancel" "button"
+
+  Scenario: Users can't edit content if they don't have the required permission
+    Given the following "users" exist:
+      | username | firstname | lastname | email             |
+      | teacher1 | Teacher   | 1        | user1@example.com |
+    And the following "courses" exist:
+      | fullname | shortname | category |
+      | Course 1 | C1        | 0        |
+    And the following "course enrolments" exist:
+      | user     | course | role              |
+      | teacher1 | C1     | editingteacher    |
+    And I navigate to "H5P > Manage H5P content types" in site administration
+    And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "H5P content type" filemanager
+    And I click on "Upload H5P content types" "button" in the "#fitem_id_uploadlibraries" "css_element"
+    And I should see "H5P content types uploaded successfully"
+    And I log out
+    And I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    And I click on "Site pages" "list_item" in the "Navigation" "block"
+    And I click on "Content bank" "link"
+    And "[data-action=Add-content]" "css_element" should exist
+    When the following "permission overrides" exist:
+      | capability                       | permission | role           | contextlevel | reference |
+      | moodle/contentbank:useeditor     | Prohibit   | editingteacher | System       |           |
+    And I reload the page
+    Then "[data-action=Add-content]" "css_element" should not exist
index b4ca7ad..6abcf95 100644 (file)
@@ -21,7 +21,7 @@ Feature: Sort content in the content bank
     And I add the "Navigation" block if not present
     And I expand "Site pages" node
     And I click on "Content bank" "link"
-    When I click on "Display contentbank with file details" "button"
+    When I click on "Display content bank with file details" "button"
     And I click on "Sort by Content name ascending" "button"
     And "Dragon_santjordi.h5p" "text" should appear before "historybook.h5p" "text"
     And "historybook.h5p" "text" should appear before "mathsbook.h5p" "text"
index ebb6888..d347a72 100644 (file)
@@ -507,4 +507,100 @@ class core_contentbank_testcase extends advanced_testcase {
         // Check there's no error when trying to move content context from an empty content bank.
         $this->assertTrue($cb->delete_contents($systemcontext, $coursecontext));
     }
+
+    /**
+     * Data provider for get_contenttypes_with_capability_feature.
+     *
+     * @return  array
+     */
+    public function get_contenttypes_with_capability_feature_provider(): array {
+        return [
+            'no-contenttypes_enabled' => [
+                'contenttypesenabled' => [],
+                'contenttypescanfeature' => [],
+            ],
+            'contenttype_enabled_noeditable' => [
+                'contenttypesenabled' => ['testable'],
+                'contenttypescanfeature' => [],
+            ],
+            'contenttype_enabled_editable' => [
+                'contenttypesenabled' => ['testable'],
+                'contenttypescanfeature' => ['testable'],
+            ],
+            'no-contenttype_enabled_editable' => [
+                'contenttypesenabled' => [],
+                'contenttypescanfeature' => ['testable'],
+            ],
+        ];
+    }
+
+    /**
+     * Tests for get_contenttypes_with_capability_feature() function.
+     *
+     * @dataProvider    get_contenttypes_with_capability_feature_provider
+     * @param   array $contenttypesenabled Content types enabled.
+     * @param   array $contenttypescanfeature Content types the user has the permission to use the feature.
+     *
+     * @covers ::get_contenttypes_with_capability_feature
+     */
+    public function test_get_contenttypes_with_capability_feature(array $contenttypesenabled, array $contenttypescanfeature): void {
+        $this->resetAfterTest();
+
+        $cb = new contentbank();
+
+        $plugins = [];
+
+        // Content types not enabled where the user has permission to use a feature.
+        if (empty($contenttypesenabled) && !empty($contenttypescanfeature)) {
+            $enabled = false;
+
+            // Mock core_plugin_manager class and the method get_plugins_of_type.
+            $pluginmanager = $this->getMockBuilder(\core_plugin_manager::class)
+                ->disableOriginalConstructor()
+                ->setMethods(['get_plugins_of_type'])
+                ->getMock();
+
+            // Replace protected singletoninstance reference (core_plugin_manager property) with mock object.
+            $ref = new \ReflectionProperty(\core_plugin_manager::class, 'singletoninstance');
+            $ref->setAccessible(true);
+            $ref->setValue(null, $pluginmanager);
+
+            // Return values of get_plugins_of_type method.
+            foreach ($contenttypescanfeature as $contenttypepluginname) {
+                $contenttypeplugin = new \stdClass();
+                $contenttypeplugin->name = $contenttypepluginname;
+                $contenttypeplugin->type = 'contenttype';
+                // Add the feature to the fake content type.
+                $classname = "\\contenttype_$contenttypepluginname\\contenttype";
+                $classname::$featurestotest = ['test2'];
+                $plugins[] = $contenttypeplugin;
+            }
+
+            // Set expectations and return values.
+            $pluginmanager->expects($this->once())
+                ->method('get_plugins_of_type')
+                ->with('contenttype')
+                ->willReturn($plugins);
+        } else {
+            $enabled = true;
+            // Get access to private property enabledcontenttypes.
+            $rc = new \ReflectionClass(\core_contentbank\contentbank::class);
+            $rcp = $rc->getProperty('enabledcontenttypes');
+            $rcp->setAccessible(true);
+
+            foreach ($contenttypesenabled as $contenttypename) {
+                $plugins["\\contenttype_$contenttypename\\contenttype"] = $contenttypename;
+                // Add to the testable contenttype the feature to test.
+                if (in_array($contenttypename, $contenttypescanfeature)) {
+                    $classname = "\\contenttype_$contenttypename\\contenttype";
+                    $classname::$featurestotest = ['test2'];
+                }
+            }
+            // Set as enabled content types only those in the test.
+            $rcp->setValue($cb, $plugins);
+        }
+
+        $actual = $cb->get_contenttypes_with_capability_feature('test2', null, $enabled);
+        $this->assertEquals($contenttypescanfeature, array_values($actual));
+    }
 }
index a70bccb..2a5411d 100644 (file)
@@ -37,6 +37,9 @@ class contenttype extends \core_contentbank\contenttype {
     /** Feature for testing */
     const CAN_TEST = 'test';
 
+    /** @var array Additional features for testing */
+    public static $featurestotest;
+
     /**
      * Returns the HTML code to render the icon for content bank contents.
      *
@@ -55,7 +58,13 @@ class contenttype extends \core_contentbank\contenttype {
      * @return array
      */
     protected function get_implemented_features(): array {
-        return [self::CAN_TEST];
+        $features = [self::CAN_TEST];
+
+        if (!empty(self::$featurestotest)) {
+            $features = array_merge($features, self::$featurestotest);
+        }
+
+        return $features;
     }
 
     /**
@@ -66,4 +75,29 @@ class contenttype extends \core_contentbank\contenttype {
     public function get_manageable_extensions(): array {
         return  ['.txt', '.png', '.h5p'];
     }
+
+    /**
+     * Returns the list of different types of the given content type.
+     *
+     * @return array
+     */
+    public function get_contenttype_types(): array {
+        $type = new \stdClass();
+        $type->typename = 'testable';
+
+        return [$type];
+    }
+
+    /**
+     * Returns true, so the user has permission on the feature.
+     *
+     * @return bool     True if content could be edited or created. False otherwise.
+     */
+    final public function can_test2(): bool {
+        if (!$this->is_feature_supported('test2')) {
+            return false;
+        }
+
+        return true;
+    }
 }
index c95d7fd..1cf7500 100644 (file)
@@ -53,7 +53,7 @@ if ($PAGE->course) {
 $PAGE->set_url(new \moodle_url('/contentbank/view.php', ['id' => $id]));
 $PAGE->set_context($context);
 $PAGE->navbar->add($record->name);
-$PAGE->set_heading($title);
+$PAGE->set_heading($record->name);
 $title .= ": ".$record->name;
 $PAGE->set_title($title);
 $PAGE->set_pagetype('contenbank');
@@ -109,7 +109,6 @@ $PAGE->add_header_action(html_writer::div(
 ));
 
 echo $OUTPUT->header();
-echo $OUTPUT->box_start('generalbox');
 
 // If needed, display notifications.
 if ($errormsg !== '') {
@@ -118,8 +117,11 @@ if ($errormsg !== '') {
     echo $OUTPUT->notification($statusmsg, 'notifysuccess');
 }
 if ($contenttype->can_access()) {
-    echo $contenttype->get_view_content($content);
+    $viewcontent = new core_contentbank\output\viewcontent($contenttype, $content);
+    echo $OUTPUT->render($viewcontent);
+} else {
+    $message = get_string('contenttypenoaccess', 'core_contentbank', $record->contenttype);
+    echo $OUTPUT->notification($message, 'error');
 }
 
-echo $OUTPUT->box_end();
 echo $OUTPUT->footer();
index 07394ca..ada3a69 100644 (file)
@@ -272,7 +272,7 @@ class core_course_management_renderer extends plugin_renderer_base {
         $html .= html_writer::end_div();
         $html .= $icon;
         if ($hasactions) {
-            $textattributes = array('class' => 'float-left categoryname');
+            $textattributes = array('class' => 'float-left categoryname aalink');
         } else {
             $textattributes = array('class' => 'float-left categoryname without-actions');
         }
@@ -647,7 +647,7 @@ class core_course_management_renderer extends plugin_renderer_base {
             'for' => 'courselistitem' . $course->id));
         $html .= html_writer::end_div();
         $html .= html_writer::end_div();
-        $html .= html_writer::link($viewcourseurl, $text, array('class' => 'float-left coursename'));
+        $html .= html_writer::link($viewcourseurl, $text, array('class' => 'float-left coursename aalink'));
         $html .= html_writer::start_div('float-right');
         if ($course->idnumber) {
             $html .= html_writer::tag('span', s($course->idnumber), array('class' => 'text-muted idnumber'));
@@ -1222,7 +1222,7 @@ class core_course_management_renderer extends plugin_renderer_base {
             $html .= html_writer::end_div();
         }
         $html .= html_writer::end_div();
-        $html .= html_writer::link($viewcourseurl, $text, array('class' => 'float-left coursename'));
+        $html .= html_writer::link($viewcourseurl, $text, array('class' => 'float-left coursename aalink'));
         $html .= html_writer::tag('span', $categoryname, array('class' => 'float-left ml-3 text-muted'));
         $html .= html_writer::start_div('float-right');
         $html .= $this->search_listitem_actions($course);
index b5260c4..bb7b3b9 100644 (file)
@@ -709,7 +709,7 @@ class core_course_renderer extends plugin_renderer_base {
                 'class' => 'iconlarge activityicon', 'alt' => '', 'role' => 'presentation', 'aria-hidden' => 'true')) .
                 html_writer::tag('span', $instancename . $altname, array('class' => 'instancename'));
         if ($mod->uservisible) {
-            $output .= html_writer::link($url, $activitylink, array('class' => $linkclasses, 'onclick' => $onclick));
+            $output .= html_writer::link($url, $activitylink, array('class' => 'aalink' . $linkclasses, 'onclick' => $onclick));
         } else {
             // We may be displaying this just in order to show information
             // about visibility, without the actual link ($mod->is_visible_on_course_page()).
@@ -1142,7 +1142,7 @@ class core_course_renderer extends plugin_renderer_base {
         }
         $coursename = $chelper->get_course_formatted_name($course);
         $coursenamelink = html_writer::link(new moodle_url('/course/view.php', ['id' => $course->id]),
-            $coursename, ['class' => $course->visible ? '' : 'dimmed']);
+            $coursename, ['class' => $course->visible ? 'aalink' : 'aalink dimmed']);
         $content .= html_writer::tag($nametag, $coursenamelink, ['class' => 'coursename']);
         // If we display course in collapsed form but the course has summary or course contacts, display the link to the info page.
         $content .= html_writer::start_tag('div', ['class' => 'moreinfo']);
@@ -1648,7 +1648,7 @@ class core_course_renderer extends plugin_renderer_base {
         }
         $content .= html_writer::start_tag('div', array('class' => 'info'));
 
-        $content .= html_writer::tag(($depth > 1) ? 'h4' : 'h3', $categoryname, array('class' => 'categoryname'));
+        $content .= html_writer::tag(($depth > 1) ? 'h4' : 'h3', $categoryname, array('class' => 'categoryname aabtn'));
         $content .= html_writer::end_tag('div'); // .info
 
         // add category content to the output
@@ -1682,7 +1682,7 @@ class core_course_renderer extends plugin_renderer_base {
 
         if ($coursecat->get_children_count()) {
             $classes = array(
-                'collapseexpand',
+                'collapseexpand', 'aabtn'
             );
 
             // Check if the category content contains subcategories with children's content loaded.
@@ -2391,7 +2391,7 @@ class core_course_renderer extends plugin_renderer_base {
         }
         $output = html_writer::link('#' . $skipdivid,
             get_string('skipa', 'access', core_text::strtolower(strip_tags($header))),
-            array('class' => 'skip-block skip'));
+            array('class' => 'skip-block skip aabtn'));
 
         // Wrap frontpage part in div container.
         $output .= html_writer::start_tag('div', array('id' => $contentsdivid));
index 74300b9..b24636c 100644 (file)
@@ -57,7 +57,7 @@
                     </div>
                     {{/showshortname}}
                 </div>
-                <a href="{{viewurl}}" class="coursename mr-2">
+                <a href="{{viewurl}}" class="aalink coursename mr-2">
                     {{> core_course/favouriteicon }}
                     <span class="sr-only">
                             {{#str}}aria:coursename, core_course{{/str}}
index ccebf94..82e6de3 100644 (file)
@@ -29,7 +29,7 @@
         "icon": "<img class='icon' src='http://urltooptionicon'>"
     }
 }}
-<div role="menuitem" tabindex="-1" aria-label="{{title}}" class="option d-block text-center py-1 mb-1" data-region="chooser-option-container" data-internal="{{name}}" data-modname="{{componentname}}_{{link}}">
+<div role="menuitem" tabindex="-1" aria-label="{{title}}" class="option d-block text-center py-1 mb-1 aabtn" data-region="chooser-option-container" data-internal="{{name}}" data-modname="{{componentname}}_{{link}}">
     <div class="optioninfo w-100" data-region="chooser-option-info-container">
         <a class="d-block" href="{{link}}" title="{{#str}} addnew, moodle, {{title}} {{/str}}" tabindex="-1" data-action="add-chooser-option">
             <span class="optionicon d-block">
index 6847675..ad17e53 100644 (file)
@@ -1883,7 +1883,8 @@ class behat_course extends behat_base {
 
         $exception = new ExpectationException('"' . $categoryname . '" category can not be found', $this->getSession());
         $categoryliteral = behat_context_helper::escape($categoryname);
-        $xpath = "//div[@class='info']/descendant::*[" . implode(' or ', $headingtags) . "][@class='categoryname'][./descendant::a[.=$categoryliteral]]";
+        $xpath = "//div[@class='info']/descendant::*[" . implode(' or ', $headingtags) .
+            "][contains(@class,'categoryname')][./descendant::a[.=$categoryliteral]]";
         $node = $this->find('xpath', $xpath, $exception);
         $node->click();
 
index 62e15b6..732f154 100644 (file)
@@ -166,7 +166,7 @@ class core_files_renderer extends plugin_renderer_base {
     protected function fm_js_template_iconfilename() {
         $rv = '
 <div class="fp-file">
-    <a href="#">
+    <a href="#" class="d-block aabtn">
     <div style="position:relative;">
         <div class="fp-thumbnail"></div>
         <div class="fp-reficons1"></div>
@@ -176,7 +176,8 @@ class core_files_renderer extends plugin_renderer_base {
         <div class="fp-filename text-truncate"></div>
     </div>
     </a>
-    <a class="fp-contextmenu" href="#">'.$this->pix_icon('i/menu', '▶').'</a>
+    <a class="fp-contextmenu btn btn-icon btn-light border icon-no-margin icon-size-3" href="#">
+        <span>'.$this->pix_icon('i/menu', '▶').'</span></a>
 </div>';
         return $rv;
     }
@@ -226,7 +227,7 @@ class core_files_renderer extends plugin_renderer_base {
 <div class="filemanager fp-mkdir-dlg" role="dialog" aria-live="assertive" aria-labelledby="fp-mkdir-dlg-title">
     <div class="fp-mkdir-dlg-text">
         <label id="fp-mkdir-dlg-title">' . get_string('newfoldername', 'repository') . '</label><br/>
-        <input type="text" />
+        <input type="text" class="form-control"/>
     </div>
     <button class="fp-dlg-butcreate btn-primary btn">'.get_string('makeafolder').'</button>
     <button class="fp-dlg-butcancel btn-cancel btn">'.get_string('cancel').'</button>
@@ -250,7 +251,9 @@ class core_files_renderer extends plugin_renderer_base {
      */
     protected function fm_js_template_fileselectlayout() {
         $context = [
-                'helpicon' => $this->help_icon('setmainfile', 'repository')
+                'helpicon' => $this->help_icon('setmainfile', 'repository'),
+                'licensehelpicon' => $this->create_license_help_icon_context(),
+                'columns' => true
         ];
         return $this->render_from_template('core/filemanager_fileselect', $context);
     }
@@ -403,7 +406,10 @@ class core_files_renderer extends plugin_renderer_base {
      * @return string
      */
     protected function fp_js_template_selectlayout() {
-        return $this->render_from_template('core/filemanager_selectlayout', []);
+        $context = [
+            'licensehelpicon' => $this->create_license_help_icon_context()
+        ];
+        return $this->render_from_template('core/filemanager_selectlayout', $context);
     }
 
     /**
@@ -412,7 +418,10 @@ class core_files_renderer extends plugin_renderer_base {
      * @return string
      */
     protected function fp_js_template_uploadform() {
-        return $this->render_from_template('core/filemanager_uploadform', []);
+        $context = [
+            'licensehelpicon' => $this->create_license_help_icon_context()
+        ];
+        return $this->render_from_template('core/filemanager_uploadform', $context);
     }
 
     /**
@@ -519,6 +528,32 @@ class core_files_renderer extends plugin_renderer_base {
     public function repository_default_searchform() {
         return $this->render_from_template('core/filemanager_default_searchform', []);
     }
+
+    /**
+     * Create the context for rendering help icon with license links displaying all licenses and sources.
+     *
+     * @return \stdClass $iconcontext the context for rendering license help info.
+     */
+    protected function create_license_help_icon_context() : stdClass {
+        $licensecontext = new stdClass();
+
+        $licenses = [];
+        // Discard licenses without a name or source from enabled licenses.
+        foreach (license_manager::get_active_licenses() as $license) {
+            if (!empty($license->fullname) && !empty($license->source)) {
+                $licenses[] = $license;
+            }
+        }
+
+        $licensecontext->licenses = $licenses;
+        $helptext = $this->render_from_template('core/filemanager_licenselinks', $licensecontext);
+
+        $iconcontext = new stdClass();
+        $iconcontext->text = $helptext;
+        $iconcontext->alt = get_string('helpprefix2', 'moodle', get_string('chooselicense', 'repository'));
+
+        return $iconcontext;
+    }
 }
 
 /**
diff --git a/files/tests/behat/license_help_modal.feature b/files/tests/behat/license_help_modal.feature
new file mode 100644 (file)
index 0000000..43a2a5a
--- /dev/null
@@ -0,0 +1,52 @@
+@core @core_files
+Feature: View license links
+  In order to get select the applicable license when uploading a file
+  As a user
+  I need to be able to navigate to a page containing license terms from the file manager
+
+  @javascript
+  Scenario: Uploading a file displays license list modal
+    Given I log in as "admin"
+    And I follow "Manage private files..."
+    And I wait until the page is ready
+    And I follow "Add..."
+    And I follow "Upload a file"
+    And I click on "Help with Choose license" "icon" in the "File picker" "dialogue"
+    Then I should see "Follow these links for further information on the available license options:"
+
+  @javascript @_file_upload
+  Scenario: Altering a file should display license list modal
+    Given I log in as "admin"
+    And I follow "Manage private files..."
+    And I upload "lib/tests/fixtures/empty.txt" file to "Files" filemanager
+    And I press "Save changes"
+    And I follow "Manage private files..."
+    And I click on "empty.txt" "link"
+    And I click on "Help with Choose license" "icon"
+    Then I should see "Follow these links for further information on the available license options:"
+
+  @javascript @_file_upload
+  Scenario: Recent files should display license list modal
+    Given I log in as "admin"
+    And I follow "Manage private files..."
+    And I upload "lib/tests/fixtures/empty.txt" file to "Files" filemanager
+    And I press "Save changes"
+    And I follow "Manage private files..."
+    And I follow "Add..."
+    And I click on "Recent files" "link" in the "File picker" "dialogue"
+    And I click on "empty.txt" "link" in the "File picker" "dialogue"
+    And I click on "Help with Choose license" "icon" in the ".fp-setlicense" "css_element"
+    Then I should see "Follow these links for further information on the available license options:"
+
+  @javascript @_file_upload
+  Scenario: Private files should display license list modal
+    Given I log in as "admin"
+    And I follow "Manage private files..."
+    And I upload "lib/tests/fixtures/empty.txt" file to "Files" filemanager
+    And I press "Save changes"
+    And I follow "Manage private files..."
+    And I follow "Add..."
+    And I click on "Private files" "link" in the "File picker" "dialogue"
+    And I click on "empty.txt" "link" in the "File picker" "dialogue"
+    And I click on "Help with Choose license" "icon" in the ".fp-setlicense" "css_element"
+    Then I should see "Follow these links for further information on the available license options:"
index db90d94..bd4cb77 100644 (file)
@@ -83,7 +83,7 @@ Feature: Marking guides can be created and edited
     And I wait "1" seconds
     Then the field "Guide criterion B criterion remark" matches value "Comment \"4\""
     When I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I follow "Edit settings"
     And I follow "Test assignment 1 name"
     And I navigate to "View all submissions" in current page administration
index 8f3abd1..3b3c784 100644 (file)
@@ -156,7 +156,7 @@ class behat_grading extends behat_base {
     public function i_save_the_advanced_grading_form() {
 
         $this->execute('behat_forms::press_button', get_string('savechanges'));
-        $this->execute('behat_forms::press_button', 'Ok');
+        $this->execute('behat_forms::press_button', 'OK');
         $this->execute('behat_general::i_click_on', array($this->escape(get_string('editsettings')), 'link'));
         $this->execute('behat_forms::press_button', get_string('cancel'));
         $this->execute('behat_navigation::i_navigate_to_in_current_page_administration',
diff --git a/grade/import/direct/styles.css b/grade/import/direct/styles.css
deleted file mode 100644 (file)
index fb1e634..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-.gradeimport_data_area {
-    margin: 0 0 10px;
-    width: 475px;
-    height: 209px;
-}
index 67857cb..2e8c8d3 100644 (file)
@@ -1791,8 +1791,9 @@ class grade_structure {
             return '';
         }
 
-        return $OUTPUT->action_icon($url, new pix_icon('t/preview',
-            get_string('gradeanalysis', 'core_grades')));
+        $title = get_string('gradeanalysis', 'core_grades');
+        return $OUTPUT->action_icon($url, new pix_icon('t/preview', ''), null,
+                ['title' => $title, 'aria-label' => $title]);
     }
 
     /**
index 53741c4..5227baf 100644 (file)
@@ -710,12 +710,16 @@ class grade_report_grader extends grade_report {
         $rows = $this->get_left_icons_row($rows, $colspan);
 
         $suspendedstring = null;
+
+        $usercount = 0;
         foreach ($this->users as $userid => $user) {
             $userrow = new html_table_row();
             $userrow->id = 'fixed_user_'.$userid;
+            $userrow->attributes['class'] = ($usercount % 2) ? 'userrow even' : 'userrow odd';
 
             $usercell = new html_table_cell();
-            $usercell->attributes['class'] = 'header user';
+            $usercell->attributes['class'] = ($usercount % 2) ? 'header user even' : 'header user odd';
+            $usercount++;
 
             $usercell->header = true;
             $usercell->scope = 'row';
index 3e54686..92d658e 100644 (file)
     white-space: nowrap;
 }
 
-/**
- * Stripped table.
- */
-.path-grade-report-grader .gradeparent tr:nth-of-type(even) .cell {
-    background-color: #f9f9f9;
-}
-
 /**
  * All the floating divs.
  */
     text-align: left;
 }
 
-/**
- * All the floating cells.
- */
-.path-grade-report-grader .gradeparent .floater .cell {
-    background-color: #f9f9f9;
-}
-
 /**
  * The user cells.
  */
     float: left;
 }
 
-.path-grade-report .gradeparent .floater .controls.cell,
-.path-grade-report-grader .gradeparent .controls {
-    background-color: #f3ead8;
-}
-
 .path-grade-report-grader .gradeparent .category {
     text-align: left;
 }
index ddebda7..f3b4ad7 100644 (file)
@@ -208,11 +208,12 @@ class grade extends tablelike implements selectable_items, filterable_items {
         $url = new moodle_url("/user/view.php", array('id' => $item->id, 'course' => $this->courseid));
         $iconstring = get_string('filtergrades', 'gradereport_singleview', $fullname);
         $grade->label = $fullname;
+        $userpic = $OUTPUT->user_picture($item, ['link' => false, 'visibletoscreenreaders' => false]);
 
         $line = array(
-            $OUTPUT->action_icon($this->format_link('user', $item->id), new pix_icon('t/editstring', $iconstring)),
-            $OUTPUT->user_picture($item, array('visibletoscreenreaders' => false)) .
-            html_writer::link($url, $fullname),
+            $OUTPUT->action_icon($this->format_link('user', $item->id), new pix_icon('t/editstring', ''), null,
+                    ['title' => $iconstring, 'aria-label' => $iconstring]),
+            html_writer::link($url, $userpic . $fullname),
             $this->item_range()
         );
         $lineclasses = array(
index 9c5f025..3498fe4 100644 (file)
@@ -172,7 +172,8 @@ abstract class tablelike extends screen {
 
         $summary = $this->summary();
         if (!empty($summary)) {
-            $table->summary = $summary;
+            $table->caption = $summary;
+            $table->captionhide = true;
         }
 
         // To be used for extra formatting.
index 3043a86..3e90a88 100644 (file)
@@ -190,7 +190,8 @@ class user extends tablelike implements selectable_items {
         $grade->label = $item->get_name();
 
         $line = array(
-            $OUTPUT->action_icon($this->format_link('grade', $item->id), new pix_icon('t/editstring', $iconstring)),
+            $OUTPUT->action_icon($this->format_link('grade', $item->id), new pix_icon('t/editstring', ''), null,
+                    ['title' => $iconstring, 'aria-label' => $iconstring]),
             $this->format_icon($item) . $lockicon . $itemlabel,
             $this->category($item),
             new range($item)
index 6c73b9c..47a1ae3 100644 (file)
@@ -28,6 +28,6 @@
         "disabled": "true"
     }
 }}
-<label for="{{name}}" class="accesshide">{{label}}</label>
+{{#label}}<label for="{{name}}" class="accesshide">{{label}}</label>{{/label}}
 <input id="{{name}}" name="{{name}}" type="text" value="{{value}}" class="form-control" {{#tabindex}}tabindex="{{.}}"{{/tabindex}} {{#disabled}}disabled{{/disabled}}>
 <input type="hidden" name="old{{name}}" value="{{value}}">
index 2f0d4bb..fd1153a 100644 (file)
@@ -38,7 +38,7 @@ Feature: We can bulk insert grades for students in a course
     And I set the following fields to these values:
       | Grade out of 100 | 50 |
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I am on "Course 1" course homepage
     And I navigate to "View > Grader report" in the course gradebook
     And I follow "Single view for Test assignment one"
@@ -79,7 +79,7 @@ Feature: We can bulk insert grades for students in a course
     And I set the following fields to these values:
       | Grade out of 100 | 50 |
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I am on "Course 1" course homepage
     And I navigate to "View > Grader report" in the course gradebook
     # And I click on "input[title='Dock Navigation block']" "css_element"
index 258217d..ec2377b 100644 (file)
@@ -55,23 +55,23 @@ Feature: View gradebook when scales are used
     And I click on "Grade" "link" in the "Student 1" "table_row"
     And I set the field "Grade" to "A"
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I click on "[data-action=next-user]" "css_element"
     And I set the field "Grade" to "B"
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I click on "[data-action=next-user]" "css_element"
     And I set the field "Grade" to "C"
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I click on "[data-action=next-user]" "css_element"
     And I set the field "Grade" to "D"
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I click on "[data-action=next-user]" "css_element"
     And I set the field "Grade" to "F"
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I am on "Course 1" course homepage
     And I navigate to "Setup > Course grade settings" in the course gradebook
     And I set the field "Show weightings" to "Show"
index b038517..7ebd7a7 100644 (file)
@@ -51,7 +51,7 @@ Feature: View gradebook when single item scales are used
     And I click on "Grade" "link" in the "Student 1" "table_row"
     And I set the field "Grade" to "A"
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I am on "Course 1" course homepage
     And I navigate to "Setup > Course grade settings" in the course gradebook
     And I set the field "Show weightings" to "Show"
index 10e1f71..b8f4222 100644 (file)
@@ -53,7 +53,7 @@ $string['editor:confirmremoveauthor'] = 'Are you sure you would like to remove t
 $string['editor:contenttypebackbuttonlabel'] = 'Back';
 $string['editor:contenttypecacheoutdated'] = 'Content type list outdated';
 $string['editor:contenttypecacheoutdateddesc'] = 'Your site is having difficulties connecting to H5P.org to check for content type updates. You may not be able to update or install new content types.';
-$string['editor:contenttypedemobuttonlabel'] = 'Content Demo';
+$string['editor:contenttypedemobuttonlabel'] = 'Content demo';
 $string['editor:contenttypedetailbuttonlabel'] = 'Details';
 $string['editor:contenttypegetbuttonlabel'] = 'Get';
 $string['editor:contenttypeiconalttext'] = 'Icon';
@@ -67,8 +67,8 @@ $string['editor:contenttypenotinstalleddesc'] = 'You do not have permission to i
 $string['editor:contenttypeowner'] = 'By :owner';
 $string['editor:contenttyperestricted'] = 'Restricted content type';
 $string['editor:contenttyperestricteddesc'] = 'The use of this content type has been restricted by an administrator.';
-$string['editor:contenttypesearchfieldplaceholder'] = 'Search for Content Types';
-$string['editor:contenttypesectionall'] = 'All Content Types';
+$string['editor:contenttypesearchfieldplaceholder'] = 'Search for content types';
+$string['editor:contenttypesectionall'] = 'All content types';
 $string['editor:contenttypeunsupportedapiversioncontent'] = 'Contact your system administrator to provide you with the necessary updates';
 $string['editor:contenttypeunsupportedapiversiontitle'] = 'This content type requires a newer core version';
 $string['editor:contenttypeupdateavailable'] = 'Update available';
@@ -76,12 +76,12 @@ $string['editor:contenttypeupdatebuttonlabel'] = 'Update';
 $string['editor:contenttypeupdatesuccess'] = ':contentType successfully updated!';
 $string['editor:contenttypeupdatingbuttonlabel'] = 'Updating';
 $string['editor:contenttypeusebuttonlabel'] = 'Use';
-$string['editor:contributetranslations'] = 'If you want to complete the translation for :language you can learn about <a href=":url" target="_new">contributing translations to H5P</a>';
+$string['editor:contributetranslations'] = 'If you want to complete the translation for :language, see <a href=":url" target="_new">contributing translations to H5P</a>.';
 $string['editor:copiedbutton'] = 'Copied';
 $string['editor:copiedtoclipboard'] = 'Content is copied to the clipboard';
 $string['editor:copybutton'] = 'Copy';
 $string['editor:copytoclipboard'] = 'Copy H5P content to the clipboard';
-$string['editor:createcontenttablabel'] = 'Create Content';
+$string['editor:createcontenttablabel'] = 'Create content';
 $string['editor:currentmenuselected'] = 'current selection';
 $string['editor:editcopyright'] = 'Edit copyright';
 $string['editor:editimage'] = 'Edit image';
@@ -105,9 +105,9 @@ $string['editor:expandcollapse'] = 'Expand/Collapse';
 $string['editor:filetolarge'] = 'The file you are trying to upload might be too large.';
 $string['editor:fillinthefieldsbelow'] = 'Fill in the fields below';
 $string['editor:gethelp'] = 'Get help';
-$string['editor:h5pfileuploadservererrorcontent'] = 'An unexpected error occured. Check your server error log for\' +  \' more details.';
+$string['editor:h5pfileuploadservererrorcontent'] = 'An unexpected error occurred. Check your server error log for more details.';
 $string['editor:h5pfileuploadservererrortitle'] = 'The H5P file could not be uploaded';
-$string['editor:h5pfilevalidationfailedcontent'] = 'Make sure the uploaded H5P contains valid H5P content. H5P\' +  \' files containing only libraries should be uploaded through the H5P Libraries page.';
+$string['editor:h5pfilevalidationfailedcontent'] = 'Make sure the uploaded H5P contains valid H5P content. H5P files containing only libraries should be uploaded through the H5P libraries page.';
 $string['editor:h5pfilevalidationfailedtitle'] = 'Could not validate H5P file.';
 $string['editor:h5pfilewrongextensioncontent'] = 'Only files with the .h5p extension are allowed.';
 $string['editor:h5pfilewrongextensiontitle'] = 'The selected file could not be uploaded';
@@ -126,15 +126,15 @@ $string['editor:librarymissing'] = 'Missing required library %lib.';
 $string['editor:licensecandistribute'] = 'Can distribute';
 $string['editor:licensecanholdliable'] = 'Can hold liable';
 $string['editor:licensecanmodify'] = 'Can modify';
-$string['editor:licensecansublicense'] = 'Can sublicense';
+$string['editor:licensecansublicense'] = 'Can sub-licence';
 $string['editor:licensecanusecommercially'] = 'Can use commercially';
 $string['editor:licensecannotholdliable'] = 'Cannot hold liable';
-$string['editor:licensedescription'] = 'Some of the features of this license are indicated below. Click the info icon above to read the original license text.';
-$string['editor:licensefetchdetailsfailed'] = 'Failed fetching license details';
-$string['editor:licensemodalsubtitle'] = 'Select a license to view information about proper usage';
-$string['editor:licensemodaltitle'] = 'License Details';
+$string['editor:licensedescription'] = 'Some of the features of this licence are indicated below. Click the info icon above to read the original licence text.';
+$string['editor:licensefetchdetailsfailed'] = 'Failed fetching licence details';
+$string['editor:licensemodalsubtitle'] = 'Select a licence to view information about proper usage';
+$string['editor:licensemodaltitle'] = 'Licence details';
 $string['editor:licensemustincludecopyright'] = 'Must include copyright';
-$string['editor:licensemustincludelicense'] = 'Must include license';
+$string['editor:licensemustincludelicense'] = 'Must include licence';
 $string['editor:licenseunspecified'] = 'Unspecified';
 $string['editor:listbelowmin'] = 'The list needs at least :min items for the content to function properly.';
 $string['editor:listexceedsmax'] = 'The list exceeds the maximum of :max items.';
@@ -145,11 +145,11 @@ $string['editor:logthischange'] = 'Log this change';
 $string['editor:loggedchanges'] = 'Logged changes';
 $string['editor:maxscoresemanticsmissing'] = 'Could not find the expected semantics in the content.';
 $string['editor:metadata'] = 'Metadata';
-$string['editor:metadatasharingandlicensinginfo'] = 'Metadata (sharing and licensing info)';
+$string['editor:metadatasharingandlicensinginfo'] = 'Metadata (sharing and licencing info)';
 $string['editor:missingproperty'] = 'Field :index is missing its :property property.';
 $string['editor:missingtranslation'] = '[Missing translation :key]';
 $string['editor:newchangehasbeenlogged'] = 'New change has been logged';
-$string['editor:newestfirst'] = 'Newest First';
+$string['editor:newestfirst'] = 'Newest first';
 $string['editor:nextimage'] = 'Next image';
 $string['editor:nochangeshavebeenlogged'] = 'No changes have been logged';
 $string['editor:nocontenttypesavailable'] = 'No content types are available';
@@ -159,7 +159,7 @@ $string['editor:nolanguagessupported'] = 'No languages supported';
 $string['editor:noresultsfound'] = 'No results found';
 $string['editor:noresultsfounddesc'] = 'There is no content type that matches your search criteria.';
 $string['editor:nosemantics'] = 'Error, could not load the content type form.';
-$string['editor:notalltextschanged'] = 'Not all texts were changed, there is only partial coverage for :language.';
+$string['editor:notalltextschanged'] = 'Not all texts were changed, as there is only partial coverage for :language.';
 $string['editor:notimagefield'] = '":path" is not an image.';
 $string['editor:notimageordimensionsfield'] = '":path" is not an image or dimensions field.';
 $string['editor:numresults'] = ':num results';
@@ -170,23 +170,23 @@ $string['editor:or'] = 'or';
 $string['editor:orderitemdown'] = 'Order item down';
 $string['editor:orderitemup'] = 'Order item up';
 $string['editor:outofstep'] = 'The :property value can only be changed in steps of :step.';
-$string['editor:pasteandreplacebutton'] = 'Paste & Replace';
-$string['editor:pasteandreplacefromclipboard'] = 'Replace existing content with H5P Content from the clipboard';
+$string['editor:pasteandreplacebutton'] = 'Paste and replace';
+$string['editor:pasteandreplacefromclipboard'] = 'Replace existing content with H5P content from the clipboard';
 $string['editor:pastebutton'] = 'Paste';
-$string['editor:pastecontent'] = 'Replace Content';
+$string['editor:pastecontent'] = 'Replace content';
 $string['editor:pastecontentnotsupported'] = 'The content in the H5P clipboard is not supported in this context';
 $string['editor:pastecontentrestricted'] = 'The content in the clipboard has been restricted on this site';
 $string['editor:pasteerror'] = 'Cannot paste from clipboard';
 $string['editor:pastefromclipboard'] = 'Paste H5P content from the clipboard';
 $string['editor:pastenocontent'] = 'No H5P content on the clipboard';
-$string['editor:pastetoonew'] = 'The content in the H5P clipboard is of a higher version (:clip) than what is supported in this context (:local), if possible try to have this content upgraded first, and then try pasting the content here again.';
-$string['editor:pastetooold'] = 'The content in the H5P clipboard is of a lower version (:clip) than what is supported in this context (:local), if possible try to have the content you want to paste upgraded, copy it again and try pasting it here.';
-$string['editor:popularfirst'] = 'Popular First';
+$string['editor:pastetoonew'] = 'The content in the H5P clipboard is of a higher version (:clip) than what is supported in this context (:local). If possible, try to have this content upgraded first, then try pasting the content here again.';
+$string['editor:pastetooold'] = 'The content in the H5P clipboard is of a lower version (:clip) than what is supported in this context (:local). If possible, try to have the content you want to paste upgraded, then copy it again and try pasting it here.';
+$string['editor:popularfirst'] = 'Popular first';
 $string['editor:previousimage'] = 'Previous image';
 $string['editor:proceedbuttonlabel'] = 'Proceed to save';
 $string['editor:readless'] = 'Read less';
 $string['editor:readmore'] = 'Read more';
-$string['editor:recentlyusedfirst'] = 'Recently Used First';
+$string['editor:recentlyusedfirst'] = 'Recently used first';
 $string['editor:reloadbuttonlabel'] = 'Reload';
 $string['editor:removefile'] = 'Remove file';
 $string['editor:removeimage'] = 'Remove image';
@@ -197,19 +197,19 @@ $string['editor:savelabel'] = 'Save';
 $string['editor:savemetadata'] = 'Save metadata';
 $string['editor:screenshots'] = 'Screenshots';
 $string['editor:scriptmissing'] = 'Could not load upgrades script for %lib.';
-$string['editor:searchresults'] = 'Search Results';
+$string['editor:searchresults'] = 'Search results';
 $string['editor:selectfiletoupload'] = 'Select file to upload';
 $string['editor:selectlibrary'] = 'Select the library you wish to use for your content.';
 $string['editor:semanticserror'] = 'Semantics error: :error';
 $string['editor:show'] = 'Show';
 $string['editor:showimportantinstructions'] = 'Show instructions';
-$string['editor:tabtitlebasicfileupload'] = 'File Upload';
+$string['editor:tabtitlebasicfileupload'] = 'File upload';
 $string['editor:tabtitleinputlinkurl'] = 'Link/URL';
 $string['editor:textfield'] = 'text field';
 $string['editor:thecontenttype'] = 'the content type';
 $string['editor:thiswillpotentially'] = 'This will potentially reset all the text and translations. You can\'t undo this. The content itself will not be changed. Do you want to proceed?';
 $string['editor:title'] = 'Title';
-$string['editor:toolong'] = 'Field value is too long, should contain :max letters or less.';
+$string['editor:toolong'] = 'Field value is too long; it should contain :max letters or less.';
 $string['editor:tryagain'] = 'Try again';
 $string['editor:tutorial'] = 'Tutorial';
 $string['editor:unabletointerpreterror'] = 'Unable to interpret response.';
@@ -219,7 +219,7 @@ $string['editor:unknownfileuploaderror'] = 'Unknown file upload error';
 $string['editor:unknownlibrary'] = 'Unfortunately, the selected content type \'%lib\' isn\'t installed on this system.';
 $string['editor:untitled'] = 'Untitled :libraryTitle';
 $string['editor:uploadaudiotitle'] = 'Upload audio file';
-$string['editor:uploaderror'] = 'File Upload Error';
+$string['editor:uploaderror'] = 'File upload error';
 $string['editor:uploadfilebuttonchangelabel'] = 'Change file';
 $string['editor:uploadfilebuttonlabel'] = 'Upload a file';
 $string['editor:uploadinstructionscontent'] = 'You may start with examples from <a href="https://h5p.org/content-types-and-applications" target="blank">H5P.org</a>.';
index 19c40d7..d47cd39 100644 (file)
@@ -242,7 +242,7 @@ $string['configenablewsdocumentation'] = 'Enable auto-generation of web services
 $string['configerrorlevel'] = 'Choose the amount of PHP warnings that you want to be displayed.  Normal is usually the best choice.';
 $string['configexportlookahead'] = 'Days to look ahead during export';
 $string['configexportlookback'] = 'Days to look back during export';
-$string['configextendedusernamechars'] = 'Enable this setting to allow students to use any characters in their usernames (note this does not affect their actual names).  The default is "false" which restricts usernames to be alphanumeric lowercase characters, underscore (_), hyphen (-), period (.) or at symbol (@).';
+$string['configextendedusernamechars'] = 'If enabled, usernames may include any characters except uppercase letters.  Otherwise, only alphanumeric characters with lowercase letters, underscore (_), hyphen (-), period (.) and at symbol (@) are allowed.';
 $string['configextramemorylimit'] = 'Some scripts like search, backup/restore or cron require more memory. Set higher values for large sites.';
 $string['configfilterall'] = 'Filter all strings, including headings, titles, navigation bar and so on.  This is mostly useful when using the multilang filter, otherwise it will just create extra load on your site for little gain.';
 $string['configfiltermatchoneperpage'] = 'Automatic linking filters will only generate a single link for the first matching text instance found on the complete page. All others are ignored.';
index 5caae68..37af329 100644 (file)
@@ -75,11 +75,11 @@ $string['emailnowexists'] = 'The email address you tried to assign to your profi
 $string['emailupdate'] = 'Email address update';
 $string['emailupdatemessage'] = 'Dear {$a->fullname},
 
-You have requested a change of your email address for your user account at {$a->site}. Please open the following URL in your browser in order to confirm this change.
+You have requested a change of your email address for your account on {$a->site}. To confirm this change, please go to the following web address:
 
-If you have any questions please contact support on: {$a->supportemail}
+{$a->url}
 
-{$a->url}';
+{$a->supportemail}';
 $string['emailupdatesuccess'] = 'Email address of user <em>{$a->fullname}</em> was successfully updated to <em>{$a->email}</em>.';
 $string['emailupdatetitle'] = 'Confirmation of email update at {$a->site}';
 $string['errormaxconsecutiveidentchars'] = 'Passwords must have at most {$a} consecutive identical characters.';
index 9ad8559..261eb57 100644 (file)
@@ -163,13 +163,13 @@ $string['confirmcancelno'] = 'Do not cancel';
 $string['confirmnewcoursecontinue'] = 'New course warning';
 $string['confirmnewcoursecontinuequestion'] = 'A temporary (hidden) course will be created by the course restoration process. To abort restoration click cancel. Do not close the browser while restoring.';
 $string['copiesinprogress'] = 'This course has copies in progress. <a href="{$a}">View in progress copies.</a>';
-$string['copycoursedesc'] = 'This course will be duplicated and put into the given course category.';
+$string['copycoursedesc'] = 'This course will be duplicated and put into the selected course category.';
 $string['copycourseheading'] = 'Copy a course';
 $string['copycoursetitle'] = 'Copy course: {$a}';
 $string['copydest'] = 'Destination';
 $string['copyingcourse'] = 'Course copying in progress';
 $string['copyingcourseshortname'] = 'copying';
-$string['copyformfail'] = 'Ajax submission of course copy form has failed.';
+$string['copyformfail'] = 'AJAX submission of course copy form has failed.';
 $string['copyop'] = 'Current operation';
 $string['copyprogressheading'] = 'Course copies in progress';
 $string['copyprogressheading_help'] = 'This table shows the status of all of your unfinished course copies.';
index 11bbe84..ef17226 100644 (file)
@@ -98,7 +98,7 @@ The only URL required for verification is [your-site-url]/badges/assertion.php s
 $string['backpackbadgessummary'] = 'You have {$a->totalbadges} badge(s) displayed from {$a->totalcollections} collection(s).';
 $string['backpackbadgessettings'] = 'Change backpack settings';
 $string['backpackcannotsendverification'] = 'Cannot send verification email';
-$string['backpackconnected'] = 'Backpack has been connected';
+$string['backpackconnected'] = 'Backpack is connected';
 $string['backpackconnection'] = 'Backpack connection';
 $string['backpackconnection_help'] = 'Connecting to a backpack enables you to share your badges from this site, and display public badge collections from your backpack on your profile page on this site.';
 $string['backpackconnectioncancelattempt'] = 'Connect using a different email address';
@@ -107,7 +107,7 @@ $string['backpackconnectionresendemail'] = 'Resend verification email';
 $string['backpackconnectionunexpectedresult'] = 'There was a problem connecting to your backpack. Please check the credentials and try again.';
 $string['backpackconnectionunexpectedmessage'] = 'The backpack returned the error: "{$a}".';
 $string['backpackdetails'] = 'Backpack settings';
-$string['backpackdisconnected'] = 'Backpack has been disconnected';
+$string['backpackdisconnected'] = 'Backpack is disconnected';
 $string['backpackemail'] = 'Email address';
 $string['backpackemail_help'] = 'The email address associated with your backpack. While you are connected, any badges earned on this site will be associated with this email address.';
 $string['backpackemailverificationpending'] = 'Verification pending';
@@ -471,11 +471,11 @@ $string['privacy:metadata:external:backpacks:description'] = 'The description of
 $string['privacy:metadata:external:backpacks:image'] = 'The image of the badge';
 $string['privacy:metadata:external:backpacks:issuer'] = 'Some information about the issuer';
 $string['privacy:metadata:external:backpacks:url'] = 'The Moodle URL where the issued badge information can be seen';
-$string['privacy:metadata:backpackoauth2'] = 'Information oauthorization when user connect to an external backpack';
+$string['privacy:metadata:backpackoauth2'] = 'OAuth 2 information when user connects to an external backpack';
 $string['privacy:metadata:backpackoauth2:userid'] = 'The ID of the user connect to backpack';
 $string['privacy:metadata:backpackoauth2:usermodified'] = 'The ID of the user modified connect';
-$string['privacy:metadata:backpackoauth2:token'] = 'The token of backpack connect';
-$string['privacy:metadata:backpackoauth2:issuerid'] = 'The ID of Oauth2 service';
+$string['privacy:metadata:backpackoauth2:token'] = 'Backpack connection token';
+$string['privacy:metadata:backpackoauth2:issuerid'] = 'OAuth 2 service ID';
 $string['privacy:metadata:backpackoauth2:scope'] = 'List scope of backpack connect';
 $string['privacy:metadata:issued'] = 'A record of badges awarded';
 $string['privacy:metadata:issued:dateexpire'] = 'The date when the badge expires';
index 55108d3..8ea78e9 100644 (file)
 
 $string['author'] = 'Author';
 $string['contentbank'] = 'Content bank';
+$string['close'] = 'Close';
 $string['contentdeleted'] = 'The content has been deleted.';
 $string['contentname'] = 'Content name';
 $string['contentnotdeleted'] = 'An error was encountered while trying to delete the content.';
 $string['contentnotrenamed'] = 'An error was encountered while trying to rename the content.';
 $string['contentrenamed'] = 'The content has been renamed.';
 $string['contentsmoved'] = 'Content bank contents moved to {$a}.';
+$string['contenttypenoaccess'] = 'You can not view this {$a} instance';
+$string['contenttypenoedit'] = 'You can not edit contents of the {$a} content type';
 $string['eventcontentcreated'] = 'Content created';
 $string['eventcontentdeleted'] = 'Content deleted';
 $string['eventcontentupdated'] = 'Content updated';
@@ -38,13 +41,14 @@ $string['eventcontentviewed'] = 'Content viewed';
 $string['errordeletingcontentfromcategory'] = 'Error deleting content from category {$a}.';
 $string['deletecontent'] = 'Delete content';
 $string['deletecontentconfirm'] = 'Are you sure you want to delete the content <em>\'{$a->name}\'</em> and all associated files? This action cannot be undone.';
-$string['displaydetails'] = 'Display contentbank with file details';
-$string['displayicons'] = 'Display contentbank with icons';
+$string['displaydetails'] = 'Display content bank with file details';
+$string['displayicons'] = 'Display content bank with icons';
 $string['file'] = 'Upload content';
 $string['file_help'] = 'Files may be stored in the content bank for use in courses. Only files used by content types enabled on the site may be uploaded.';
 $string['itemsfound'] = '{$a} items found';
 $string['lastmodified'] = 'Last modified';
 $string['name'] = 'Content';
+$string['nocontenttypes'] = 'No content types available';
 $string['nopermissiontodelete'] = 'You do not have permission to delete content.';
 $string['nopermissiontomanage'] = 'You do not have permission to manage content.';
 $string['privacy:metadata:content:contenttype'] = 'The contenttype plugin of the content in the content bank.';
index 75250a4..50ea6d2 100644 (file)
@@ -1500,7 +1500,6 @@ $string['numwords'] = '{$a} words';
 $string['numyear'] = '{$a} year';
 $string['numyears'] = '{$a} years';
 $string['ok'] = 'OK';
-$string['okay'] = 'Ok';
 $string['oldpassword'] = 'Current password';
 $string['olduserdirectory'] = 'This is the OLD users directory, and is no longer needed. You may safely delete it. The files it contains have been copied to the NEW user directory.';
 $string['optional'] = 'optional';
index 47788cb..10086fd 100644 (file)
@@ -124,7 +124,7 @@ $string['editingcategory'] = 'Editing a category';
 $string['editingquestion'] = 'Editing a question';
 $string['editquestion'] = 'Edit question';
 $string['editthiscategory'] = 'Edit this category';
-$string['emptyxml'] = 'Unkown error - empty imsmanifest.xml';
+$string['emptyxml'] = 'Unknown error - empty imsmanifest.xml';
 $string['enabled'] = 'Enabled';
 $string['erroraccessingcontext'] = 'Cannot access context';
 $string['errordeletingquestionsfromcategory'] = 'Error deleting questions from category {$a}.';
index 7ed41e6..3500ab8 100644 (file)
@@ -99,8 +99,6 @@ $string['displaytree'] = 'Display folder as file tree';
 $string['download'] = 'Download';
 $string['downloadallfiles'] = 'Download all files';
 $string['downloadfolder'] = 'Download all';
-$string['downloadselected'] = 'Download selected files';
-$string['deleteselected'] = 'Delete selected';
 $string['downloadsucc'] = 'The file has been downloaded successfully';
 $string['draftareanofiles'] = 'Cannot be downloaded because there is no files attached';
 $string['editrepositoryinstance'] = 'Edit repository instance';
@@ -137,6 +135,7 @@ $string['getfiletimeout'] = 'Get file timeout';
 $string['help'] = 'Help';
 $string['choosealink'] = 'Choose a link...';
 $string['chooselicense'] = 'Choose license';
+$string['chooselicense_help'] = 'Follow these links for further information on the available license options:';
 $string['createfolder'] = 'Create folder';
 $string['iconview'] = 'View as icons';
 $string['imagesize'] = '{$a->width} x {$a->height} px';
@@ -179,6 +178,7 @@ $string['noenter'] = 'Nothing entered';
 $string['nofilesattached'] = 'No files attached';
 $string['nofilesavailable'] = 'No files available';
 $string['nofilesselected'] = 'No files selected';
+$string['nolicenses'] = 'There are no licences available';
 $string['nomorefiles'] = 'No more attachments allowed';
 $string['nopathselected'] = 'No destination path select yet (double click tree node to select)';
 $string['nopermissiontoaccess'] = 'No permission to access this repository.';
index 21530de..b284206 100644 (file)
@@ -153,9 +153,10 @@ $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:manageanycontent'] = 'Manage any content from the content bank (rename, move, publish, share, etc.)';
-$string['contentbank:manageowncontent'] = 'Manage content from own content bank (rename, move, publish, share, etc.)';
-$string['contentbank:upload'] = 'Upload new content in 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['contentbank:useeditor'] = 'Create or edit content using a content type editor';
 $string['context'] = 'Context';
 $string['course:activityvisibility'] = 'Hide/show activities';
 $string['course:bulkmessaging'] = 'Send a message to many people';
index e010e09..5eee2cb 100644 (file)
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
+$string['addcondition'] = 'Add condition';
+$string['adverbfor_and'] = 'and';
+$string['adverbfor_andnot'] = 'and';
+$string['adverbfor_or'] = 'or';
+$string['applyfilters'] = 'Apply filters';
+$string['clearfilterrow'] = 'Remove filter row';
+$string['clearfilters'] = 'Clear filters';
 $string['countparticipantsfound'] = '{$a} participants found';
+$string['filtersetmatchdescription'] = 'How multiple filters should be combined';
+$string['match'] = 'Match';
+$string['matchofthefollowing'] = 'of the following:';
+$string['placeholdertypeorselect'] = 'Type or select...';
+$string['placeholdertype'] = 'Type...';
 $string['privacy:courserequestpath'] = 'Requested courses';
 $string['privacy:descriptionpath'] = 'Profile description';
 $string['privacy:devicespath'] = 'User devices';
@@ -126,6 +138,8 @@ $string['privacy:passwordresetpath'] = 'Password resets';
 $string['privacy:profileimagespath'] = 'Profile images';
 $string['privacy:privatefilespath'] = 'Private files';
 $string['privacy:sessionpath'] = 'Session data';
+$string['filterbykeyword'] = 'Keyword';
+$string['selectfiltertype'] = 'Select';
 $string['target:upcomingactivitiesdue'] = 'Upcoming activities due';
 $string['target:upcomingactivitiesdue_help'] = 'This target generates reminders for upcoming activities due.';
 $string['target:upcomingactivitiesdueinfo'] = 'All upcoming activities due insights are listed here. These students have received these insights directly.';
index 154a606..8ef27f0 100644 (file)
Binary files a/lib/amd/build/form-autocomplete.min.js and b/lib/amd/build/form-autocomplete.min.js differ
index 944df5c..77c999b 100644 (file)
Binary files a/lib/amd/build/form-autocomplete.min.js.map and b/lib/amd/build/form-autocomplete.min.js.map differ
index ae72462..42cdec9 100644 (file)
Binary files a/lib/amd/build/notification.min.js and b/lib/amd/build/notification.min.js differ
index ed26296..1d721de 100644 (file)
Binary files a/lib/amd/build/notification.min.js.map and b/lib/amd/build/notification.min.js.map differ
index 603a149..ba37d4e 100644 (file)
Binary files a/lib/amd/build/tag.min.js and b/lib/amd/build/tag.min.js differ
index 9749568..7db6808 100644 (file)
Binary files a/lib/amd/build/tag.min.js.map and b/lib/amd/build/tag.min.js.map differ
index 24665b1..bcc63b6 100644 (file)
Binary files a/lib/amd/build/templates.min.js and b/lib/amd/build/templates.min.js differ
index 119e917..b75e37f 100644 (file)
Binary files a/lib/amd/build/templates.min.js.map and b/lib/amd/build/templates.min.js.map differ
index 035f94e..b91c5fb 100644 (file)
@@ -114,7 +114,7 @@ function($, log, str, templates, notification, LoadingIcon) {
         });
         var context = $.extend({items: items}, options, state);
         // Render the template.
-        return templates.render('core/form_autocomplete_selection_items', context)
+        return templates.render(options.templates.items, context)
         .then(function(html, js) {
             // Add it to the page.
             templates.replaceNodeContents(newSelection, html, js);
@@ -970,10 +970,11 @@ function($, log, str, templates, notification, LoadingIcon) {
          * @param {Boolean} showSuggestions - If suggestions should be shown
          * @param {String} noSelectionString - Text to display when there is no selection
          * @param {Boolean} closeSuggestionsOnSelect - Whether to close the suggestions immediately after making a selection.
+         * @param {Object} templateOverrides A set of templates to use instead of the standard templates
          * @return {Promise}
          */
         enhance: function(selector, tags, ajax, placeholder, caseSensitive, showSuggestions, noSelectionString,
-                          closeSuggestionsOnSelect) {
+                          closeSuggestionsOnSelect, templateOverrides) {
             // Set some default values.
             var options = {
                 selector: selector,
@@ -982,7 +983,14 @@ function($, log, str, templates, notification, LoadingIcon) {
                 placeholder: placeholder,
                 caseSensitive: false,
                 showSuggestions: true,
-                noSelectionString: noSelectionString
+                noSelectionString: noSelectionString,
+                templates: $.extend({
+                        input: 'core/form_autocomplete_input',
+                        items: 'core/form_autocomplete_selection_items',
+                        layout: 'core/form_autocomplete_layout',
+                        selection: 'core/form_autocomplete_selection',
+                        suggestions: 'core/form_autocomplete_suggestions',
+                    }, templateOverrides),
             };
             var pendingKey = 'autocomplete-setup-' + selector;
             M.util.js_pending(pendingKey);
@@ -1058,27 +1066,35 @@ function($, log, str, templates, notification, LoadingIcon) {
             // Collect rendered inline JS to be executed once the HTML is shown.
             var collectedjs = '';
 
-            var renderInput = templates.render('core/form_autocomplete_input', context).then(function(html, js) {
+            var renderLayout = templates.render(options.templates.layout, {})
+            .then(function(html) {
+                return $(html);
+            });
+
+            var renderInput = templates.render(options.templates.input, context).then(function(html, js) {
                 collectedjs += js;
-                return html;
+                return $(html);
             });
 
-            var renderDatalist = templates.render('core/form_autocomplete_suggestions', context).then(function(html, js) {
+            var renderDatalist = templates.render(options.templates.suggestions, context).then(function(html, js) {
                 collectedjs += js;
-                return html;
+                return $(html);
             });
 
-            var renderSelection = templates.render('core/form_autocomplete_selection', context).then(function(html, js) {
+            var renderSelection = templates.render(options.templates.selection, context).then(function(html, js) {
                 collectedjs += js;
-                return html;
+                return $(html);
             });
 
-            return $.when(renderInput, renderDatalist, renderSelection)
-            .then(function(input, suggestions, selection) {
+            return $.when(renderLayout, renderInput, renderDatalist, renderSelection)
+            .then(function(layout, input, suggestions, selection) {
                 originalSelect.hide();
-                originalSelect.after(suggestions);
-                originalSelect.after(input);
-                originalSelect.after(selection);
+                var container = originalSelect.parent();
+
+                container.append(layout);
+                container.find('[data-region="form_autocomplete-input"]').replaceWith(input);
+                container.find('[data-region="form_autocomplete-suggestions"]').replaceWith(suggestions);
+                container.find('[data-region="form_autocomplete-selection"]').replaceWith(selection);
 
                 templates.runTemplateJS(collectedjs);
 
index 0822899..62cec7e 100644 (file)
@@ -292,8 +292,9 @@ export const exception = async ex => {
  *
  * @param {Number} contextId
  * @param {Array} notificationList
+ * @param {Boolean} userLoggedIn
  */
-export const init = (contextId, notificationList) => {
+export const init = (contextId, notificationList, userLoggedIn) => {
     currentContextId = contextId;
 
     // Setup the message target region if it isn't setup already
@@ -302,8 +303,11 @@ export const init = (contextId, notificationList) => {
     // Add provided notifications.
     addNotifications(notificationList);
 
-    // Perform an initial poll for any new notifications.
-    fetchNotifications();
+    // If the user is not logged in then we can not fetch anything for them.
+    if (userLoggedIn) {
+        // Perform an initial poll for any new notifications.
+        fetchNotifications();
+    }
 };
 
 // To maintain backwards compatability we export default here.
index 2527032..603cc9f 100644 (file)
@@ -266,12 +266,13 @@ define([
 
                     e.preventDefault(); // This will prevent default error dialogue.
                     str.get_strings([
+                        {key: 'confirm', component: 'core'},
                         {key: 'nameuseddocombine', component: 'tag'},
-                        {key: 'yes'},
-                        {key: 'cancel'},
+                        {key: 'yes', component: 'core'},
+                        {key: 'cancel', component: 'core'},
                     ])
                     .then(function(s) {
-                        return notification.confirm(e.message, s[0], s[1], s[2], function() {
+                        return notification.confirm(s[0], s[1], s[2], s[3], function() {
                             window.location.href = window.location.href + "&newname=" + encodeURIComponent(newvalue) +
                                 "&tagid=" + encodeURIComponent(tagid) +
                                 '&action=renamecombine&sesskey=' + M.cfg.sesskey;
index c145173..8ba0e8b 100644 (file)
@@ -876,6 +876,7 @@ define([
      * @param {String} newHTML - HTML to insert / replace.
      * @param {String} newJS - Javascript to run after the insertion.
      * @param {Boolean} replaceChildNodes - Replace only the childnodes, alternative is to replace the entire node.
+     * @return {Array} The list of new DOM Nodes
      */
     var domReplace = function(element, newHTML, newJS, replaceChildNodes) {
         var replaceNode = $(element);
@@ -904,7 +905,11 @@ define([
             runTemplateJS(newJS);
             // Notify all filters about the new content.
             event.notifyFilterContentUpdated(newNodes);
+
+            return newNodes.get();
         }
+
+        return [];
     };
 
     /**
@@ -1043,17 +1048,23 @@ define([
      * @param {jQuery|String} element - Element or selector to prepend HTML to
      * @param {String} html - HTML to prepend
      * @param {String} js - Javascript to run after we prepend the html
+     * @return {Array} The list of new DOM Nodes
      */
     var domPrepend = function(element, html, js) {
         var node = $(element);
         if (node.length) {
             // Prepend the html.
-            node.prepend(html);
+            var newContent = $(html);
+            node.prepend(newContent);
             // Run any javascript associated with the new HTML.
             runTemplateJS(js);
             // Notify all filters about the new content.
             event.notifyFilterContentUpdated(node);
+
+            return newContent.get();
         }
+
+        return [];
     };
 
     /**
@@ -1064,17 +1075,23 @@ define([
      * @param {jQuery|String} element - Element or selector to append HTML to
      * @param {String} html - HTML to append
      * @param {String} js - Javascript to run after we append the html
+     * @return {Array} The list of new DOM Nodes
      */
     var domAppend = function(element, html, js) {
         var node = $(element);
         if (node.length) {
             // Append the html.
-            node.append(html);
+            var newContent = $(html);
+            node.append(newContent);
             // Run any javascript associated with the new HTML.
             runTemplateJS(js);
             // Notify all filters about the new content.
             event.notifyFilterContentUpdated(node);
+
+            return newContent.get();
         }
+
+        return [];
     };
 
     return /** @alias module:core/templates */ {
@@ -1175,9 +1192,10 @@ define([
          * @param {JQuery} element - Element or selector to replace.
          * @param {String} newHTML - HTML to insert / replace.
          * @param {String} newJS - Javascript to run after the insertion.
+         * @return {Array} The list of new DOM Nodes
          */
         replaceNodeContents: function(element, newHTML, newJS) {
-            domReplace(element, newHTML, newJS, true);
+            return domReplace(element, newHTML, newJS, true);
         },
 
         /**
@@ -1187,9 +1205,10 @@ define([
          * @param {JQuery} element - Element or selector to replace.
          * @param {String} newHTML - HTML to insert / replace.
          * @param {String} newJS - Javascript to run after the insertion.
+         * @return {Array} The list of new DOM Nodes
          */
         replaceNode: function(element, newHTML, newJS) {
-            domReplace(element, newHTML, newJS, false);
+            return domReplace(element, newHTML, newJS, false);
         },
 
         /**
@@ -1199,9 +1218,10 @@ define([
          * @param {jQuery|String} element - Element or selector to prepend HTML to
          * @param {String} html - HTML to prepend
          * @param {String} js - Javascript to run after we prepend the html
+         * @return {Array} The list of new DOM Nodes
          */
         prependNodeContents: function(element, html, js) {
-            domPrepend(element, html, js);
+            return domPrepend(element, html, js);
         },
 
         /**
@@ -1211,9 +1231,10 @@ define([
          * @param {jQuery|String} element - Element or selector to append HTML to
          * @param {String} html - HTML to append
          * @param {String} js - Javascript to run after we append the html
+         * @return {Array} The list of new DOM Nodes
          */
         appendNodeContents: function(element, html, js) {
-            domAppend(element, html, js);
+            return domAppend(element, html, js);
         },
     };
 });
index 2fd9ea1..a1ae1ac 100644 (file)
@@ -882,6 +882,7 @@ class behat_core_generator extends behat_generator_base {
             $content = $contenttype->create_content($record);
 
             if (!empty($data['filepath'])) {
+                $filename = basename($data['filepath']);
                 $fs = get_file_storage();
                 $filerecord = array(
                     'component' => 'contentbank',
@@ -889,7 +890,7 @@ class behat_core_generator extends behat_generator_base {
                     'contextid' => $context->id,
                     'userid' => $data['userid'],
                     'itemid' => $content->get_id(),
-                    'filename' => $data['contentname'],
+                    'filename' => $filename,
                     'filepath' => '/'
                 );
                 $fs->create_file_from_pathname($filerecord, $CFG->dirroot . $data['filepath']);
index 87c602e..a1825a6 100644 (file)
@@ -161,7 +161,7 @@ XPATH
 .//div[
         contains(concat(' ', normalize-space(@class), ' '), ' modal ')
             and
-        normalize-space(descendant::*[contains(concat(' ', normalize-space(@class), ' '), ' modal-header ')] = %locator%)
+        normalize-space(descendant::*[contains(concat(' ', normalize-space(@class), ' '), ' modal-header ')]) = %locator%
     ]
 XPATH
         , 'group_message' => <<<XPATH
index 46af6d3..0ae1c2a 100644 (file)
@@ -969,7 +969,7 @@ $capabilities = array(
 
     'moodle/course:viewhiddenactivities' => array(
 
-        'captype' => 'write',
+        'captype' => 'read',
         'contextlevel' => CONTEXT_MODULE,
         'archetypes' => array(
             'teacher' => CAP_ALLOW,
@@ -1558,7 +1558,7 @@ $capabilities = array(
 
     'moodle/course:viewhiddensections' => array(
 
-        'captype' => 'write',
+        'captype' => 'read',
         'contextlevel' => CONTEXT_COURSE,
         'archetypes' => array(
             'editingteacher' => CAP_ALLOW,
@@ -2544,4 +2544,16 @@ $capabilities = array(
             'editingteacher' => CAP_ALLOW,
         )
     ],
+
+    // Allow users to create/edit content within the content bank.
+    'moodle/contentbank:useeditor' => [
+        'riskbitmask' => RISK_SPAM,
+        'captype' => 'write',
+        'contextlevel' => CONTEXT_COURSE,
+        'archetypes' => array(
+            'manager' => CAP_ALLOW,
+            'coursecreator' => CAP_ALLOW,
+            'editingteacher' => CAP_ALLOW,
+        )
+    ],
 );
index 7d24f66..4ae8400 100644 (file)
@@ -135,8 +135,7 @@ class MoodleQuickForm_date_selector extends MoodleQuickForm_group {
         if ($calendartype->get_name() === 'gregorian') {
             $image = $OUTPUT->pix_icon('i/calendar', get_string('calendar', 'calendar'), 'moodle');
             $this->_elements[] = $this->createFormElement('link', 'calendar',
-                    null, '#', $image,
-                    array('class' => 'visibleifjs'));
+                    null, '#', $image);
         }
         // If optional we add a checkbox which the user can use to turn if on
         if ($this->_options['optional']) {
index 9c1ff76..084768d 100644 (file)
@@ -154,8 +154,7 @@ class MoodleQuickForm_date_time_selector extends MoodleQuickForm_group {
         if ($calendartype->get_name() === 'gregorian') {
             $image = $OUTPUT->pix_icon('i/calendar', get_string('calendar', 'calendar'), 'moodle');
             $this->_elements[] = $this->createFormElement('link', 'calendar',
-                    null, '#', $image,
-                    array('class' => 'visibleifjs'));
+                    null, '#', $image);
         }
         // If optional we add a checkbox which the user can use to turn if on
         if ($this->_options['optional']) {
index 32136bd..65eacd4 100644 (file)
@@ -144,7 +144,7 @@ Feature: Using the activity grade form element
     And I click on "Grade" "link" in the "Student 1" "table_row"
     And I set the field "Grade" to "C"
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I follow "Edit settings"
     When I expand all fieldsets
     Then I should see "Some grades have already been awarded, so the grade type and scale cannot be changed"
@@ -203,7 +203,7 @@ Feature: Using the activity grade form element
     And I click on "Grade" "link" in the "Student 1" "table_row"
     And I set the field "Grade out of 100" to "50"
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I follow "Edit settings"
     When I expand all fieldsets
     Then I should see "Some grades have already been awarded, so the grade type cannot be changed. If you wish to change the maximum grade, you must first choose whether or not to rescale existing grades."
index d1038b8..3010ef9 100644 (file)
@@ -127,6 +127,8 @@ class license_manager {
 
         $DB->insert_record('license', $license);
         self::reset_license_cache();
+        // Update the config setting of active licenses.
+        self::set_active_licenses();
     }
 
     /**
@@ -203,6 +205,9 @@ class license_manager {
                     }
                 }
 
+                // Update the config setting of active licenses as well.
+                self::set_active_licenses();
+
             } else {
                 throw new moodle_exception('cannotdeletecore', 'license');
             }
@@ -229,6 +234,8 @@ class license_manager {
                 // Interpret core license strings for internationalisation.
                 if ($license->custom == self::CORE_LICENSE) {
                     $license->fullname = get_string($license->shortname, 'license');
+                } else {
+                    $license->fullname = format_string($license->fullname);
                 }
                 $licenses[$license->shortname] = $license;
             }
@@ -352,10 +359,6 @@ class license_manager {
             $licenses = self::get_licenses();
             foreach ($licenses as $license) {
                 if (in_array($license->shortname, $activelicenses)) {
-                    // Interpret core license strings for internationalisation.
-                    if (isset($license->custom) && $license->custom == self::CORE_LICENSE) {
-                        $license->fullname = get_string($license->shortname, 'license');
-                    }
                     $result[$license->shortname] = $license;
                 }
             }
index 75980f0..6c81e34 100644 (file)
@@ -2207,8 +2207,9 @@ class html_writer {
                     $heading->header = true;
                 }
 
-                if ($heading->header && empty($heading->scope)) {
-                    $heading->scope = 'col';
+                $tagtype = 'td';
+                if ($heading->header && (string)$heading->text != '') {
+                    $tagtype = 'th';
                 }
 
                 $heading->attributes['class'] .= ' header c' . $key;
@@ -2224,16 +2225,15 @@ class html_writer {
                     $heading->attributes['class'] .= ' ' . $table->colclasses[$key];
                 }
                 $heading->attributes['class'] = trim($heading->attributes['class']);
-                $attributes = array_merge($heading->attributes, array(
-                        'style'     => $table->align[$key] . $table->size[$key] . $heading->style,
-                        'scope'     => $heading->scope,
-                        'colspan'   => $heading->colspan,
-                    ));
+                $attributes = array_merge($heading->attributes, [
+                    'style'     => $table->align[$key] . $table->size[$key] . $heading->style,
+                    'colspan'   => $heading->colspan,
+                ]);
 
-                $tagtype = 'td';
-                if ($heading->header === true) {
-                    $tagtype = 'th';
+                if ($tagtype == 'th') {
+                    $attributes['scope'] = !empty($heading->scope) ? $heading->scope : 'col';
                 }
+
                 $output .= html_writer::tag($tagtype, $heading->text, $attributes) . "\n";
             }
             $output .= html_writer::end_tag('tr') . "\n";
index a72bb26..e3705ea 100644 (file)
@@ -1167,7 +1167,7 @@ class core_renderer extends renderer_base {
         if ($this->page->pagetype == 'site-index') {
             // Special case for site home page - please do not remove
             return '<div class="sitelink">' .
-                   '<a title="Moodle" href="http://moodle.org/">' .
+                   '<a title="Moodle" class="d-inline-block aalink" href="http://moodle.org/">' .
                    '<img src="' . $this->image_url('moodlelogo_grayhat') . '" alt="'.get_string('moodlelogo').'" /></a></div>';
 
         } else if (!empty($CFG->target_release) && $CFG->target_release != $CFG->release) {
@@ -1414,7 +1414,8 @@ class core_renderer extends renderer_base {
         if (!empty($this->page->context->id)) {
             $this->page->requires->js_call_amd('core/notification', 'init', array(
                 $this->page->context->id,
-                \core\notification::fetch_as_array($this)
+                \core\notification::fetch_as_array($this),
+                isloggedin()
             ));
         }
         $footer = str_replace($this->unique_end_html_token, $this->page->requires->get_end_code(), $footer);
@@ -2571,7 +2572,7 @@ class core_renderer extends renderer_base {
             $url = new moodle_url('/user/view.php', array('id' => $user->id, 'course' => $courseid));
         }
 
-        $attributes = array('href'=>$url);
+        $attributes = array('href' => $url, 'class' => 'd-inline-block aabtn');
         if (!$userpicture->visibletoscreenreaders) {
             $attributes['tabindex'] = '-1';
             $attributes['aria-hidden'] = 'true';
index b63c6c2..ad2fcda 100644 (file)
@@ -217,6 +217,7 @@ class fetch extends external_api {
         }
 
         $filterset = new $filtersetclass();
+        $filterset->set_join_type($jointype);
         foreach ($filters as $rawfilter) {
             $filterset->add_filter_from_params(
                 $rawfilter['name'],
index 79052b3..8e28ab4 100644 (file)
@@ -1503,7 +1503,7 @@ class flexible_table {
         $this->sortdata = [];
         foreach ($sortdata as $sortitem) {
             if (!array_key_exists($sortitem['sortby'], $this->sortdata)) {
-                $this->sortdata[$sortitem['sortby']] = $sortitem['sortorder'];
+                $this->sortdata[$sortitem['sortby']] = (int) $sortitem['sortorder'];
             }
         }
     }
index 0e9f42a..0184d49 100644 (file)
@@ -27,7 +27,7 @@
     }
 }}
 {{^disabled}}
-    <a href="{{url}}" class="{{classes}}" {{#attributes}}{{name}}={{#quote}}{{value}}{{/quote}} {{/attributes}}{{#showtext}}aria-labelledby="actionmenuaction-{{instance}}"{{/showtext}}>{{#icon}}{{#pix}}{{key}}, {{component}}, {{title}}{{/pix}}{{/icon}}{{#showtext}}<span class="menu-action-text" id="actionmenuaction-{{instance}}">{{{text}}}</span>{{/showtext}}</a>
+    <a href="{{url}}" class="aabtn {{classes}}" {{#attributes}}{{name}}={{#quote}}{{value}}{{/quote}} {{/attributes}}{{#showtext}}aria-labelledby="actionmenuaction-{{instance}}"{{/showtext}}>{{#icon}}{{#pix}}{{key}}, {{component}}, {{title}}{{/pix}}{{/icon}}{{#showtext}}<span class="menu-action-text" id="actionmenuaction-{{instance}}">{{{text}}}</span>{{/showtext}}</a>
 {{/disabled}}
 {{#disabled}}
     <span class="currentlink" role="menuitem">{{#icon}}{{#pix}}{{key}},{{component}},{{title}}{{/pix}}{{/icon}}{{{text}}}</span>
index cf34a04..8a172c9 100644 (file)
@@ -78,7 +78,7 @@
     }
 }}
 <div class="dropdown{{^secondary.items}} hidden{{/secondary.items}}">
-    <a href="#" tabindex="0" class="{{triggerextraclasses}} dropdown-toggle icon-no-margin" id="action-menu-toggle-{{instance}}" aria-label="{{title}}" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" aria-controls="action-menu-{{instance}}-menu">
+    <a href="#" tabindex="0" class="d-inline-block {{triggerextraclasses}} dropdown-toggle icon-no-margin" id="action-menu-toggle-{{instance}}" aria-label="{{title}}" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" aria-controls="action-menu-{{instance}}-menu">
         {{{actiontext}}}
         {{{menutrigger}}}
             {{#icon}}
diff --git a/lib/templates/filemanager_chooselicense.mustache b/lib/templates/filemanager_chooselicense.mustache
new file mode 100644 (file)
index 0000000..1c22b49
--- /dev/null
@@ -0,0 +1,39 @@
+{{!
+    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/>.
+}}
+{{!
+    @template core/filemanager_chooselicense
+
+    This template renders the form label and select element for choosing a license associated with a file.
+
+    Example context (json):
+    {
+        "licensehelpicon": {
+            "text": "<ul><li><a href='http://en.wikipedia.org/wiki/All_rights_reserved'>All rights reserved</a></li></ul>",
+            "alt": "Help with Choose license"
+        },
+        "columns": true
+    }
+}}
+<div class="col-form-label form-control-label px-0 {{#columns}}col-4{{/columns}}">
+    <label for="choose-license-{{uniqid}}">
+        {{#str}}chooselicense, repository{{/str}}
+    </label>
+    {{#licensehelpicon}}{{>core/help_icon}}{{/licensehelpicon}}
+</div>
+<div {{#columns}}class="col-8"{{/columns}}>
+    <select id="choose-license-{{uniqid}}" class="form-control"></select>
+</div>
index 83d9306..09084b6 100644 (file)
 
     Example context (json):
     {
-        "helpicon": "<a class='btn ..'><i class='icon fa fa-question-circle ..'></i></a>"
+        "helpicon": "<a class='btn ..'><i class='icon fa fa-question-circle ..'></i></a>",
+        "licensehelpicon": {
+            "text": "<ul><li><a href='http://en.wikipedia.org/wiki/All_rights_reserved'>All rights reserved</a></li></ul>",
+            "alt": "Help with Choose license"
+        },
+        "columns": true
     }
 }}
 <div class="filemanager fp-select">
 
                 </div>
                 <div class="fp-license form-group row mx-0">
-                    <label class="form-control-label col-4 px-0">{{#str}}chooselicense, repository{{/str}}</label>
-                    <div class="col-8 form-inline pr-0">
-                        <select class="custom-select form-control"></select>
-                    </div>
+                    {{>core/filemanager_chooselicense}}
                 </div>
                 <div class="fp-path form-group row mx-0">
                     <label class="form-control-label col-4 px-0">{{#str}}path, repository{{/str}}</label>
diff --git a/lib/templates/filemanager_licenselinks.mustache b/lib/templates/filemanager_licenselinks.mustache
new file mode 100644 (file)
index 0000000..0d4ec2d
--- /dev/null
@@ -0,0 +1,74 @@
+{{!
+    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/>.
+}}
+{{!
+    @template core/filemanager_licenselinks
+
+    This template renders the window with file information/actions.
+
+    Example context (json):
+    {
+        "licenses":[
+           {
+              "fullname":"All rights reserved",
+              "source":"http:\/\/en.wikipedia.org\/wiki\/All_rights_reserved"
+           },
+           {
+              "fullname":"Public Domain",
+              "source":"http:\/\/creativecommons.org\/licenses\/publicdomain\/"
+           },
+           {
+              "fullname":"Creative Commons",
+              "source":"http:\/\/creativecommons.org\/licenses\/by\/3.0\/"
+           },
+           {
+              "fullname":"Creative Commons - NoDerivs",
+              "source":"http:\/\/creativecommons.org\/licenses\/by-nd\/3.0\/"
+           },
+           {
+              "fullname":"Creative Commons - No Commercial NoDerivs",
+              "source":"http:\/\/creativecommons.org\/licenses\/by-nc-nd\/3.0\/"
+           },
+           {
+              "fullname":"Creative Commons - No Commercial",
+              "source":"http:\/\/creativecommons.org\/licenses\/by-nc\/3.0\/"
+           },
+           {
+              "fullname":"Creative Commons - No Commercial ShareAlike",
+              "source":"http:\/\/creativecommons.org\/licenses\/by-nc-sa\/3.0\/"
+           },
+           {
+              "fullname":"Creative Commons - ShareAlike",
+              "source":"http:\/\/creativecommons.org\/licenses\/by-sa\/3.0\/"
+           }
+        ]
+    }
+}}
+<p class="mb-1">
+    {{#str}}chooselicense_help, repository{{/str}}
+</p>
+<ul>
+{{#licenses}}
+    <li>
+        <a href="{{source}}" target="_blank">{{fullname}}</a>
+    </li>
+{{/licenses}}
+{{^licenses}}
+    <li>
+        {{#str}}nolicenses, repository{{/str}}
+    </li>
+{{/licenses}}
+</ul>
index 57ec021..c0bc89f 100644 (file)
@@ -77,7 +77,7 @@
                         <div class="fp-clear-left"></div>
                     </div>
                     <div class="fp-pathbar">
-                        <span class="fp-path-folder"><a class="fp-path-folder-name" href="#"></a></span>
+                        <span class="fp-path-folder"><a class="fp-path-folder-name aalink" href="#"></a></span>
                     </div>
                 </div>
                 <div class="fp-content card"></div>
index 7300170..dcdc356 100644 (file)
                     </a>
                 </div>
                 <div class="fp-btn-download">
-                    <a role="button" title="{{#str}}downloadselected, repository{{/str}}" class="btn btn-secondary btn-sm" href="#">
+                    <a role="button" title="{{#str}}download, repository{{/str}}" class="btn btn-secondary btn-sm" href="#">
                         {{#pix}}a/download_all{{/pix}}
                     </a>
                 </div>
                 <div class="fp-btn-delete">
-                    <a role="button" title="{{#str}}deleteselected, repository{{/str}}" class="btn btn-secondary btn-sm" href="#">
+                    <a role="button" title="{{#str}}delete{{/str}}" class="btn btn-secondary btn-sm" href="#">
                         {{#pix}}i/trash{{/pix}}
                     </a>
                 </div>
@@ -72,7 +72,7 @@
             </div>
         </div>
         <div class="fp-pathbar">
-            <span class="fp-path-folder"><a class="fp-path-folder-name" href="#"></a></span>
+            <span class="fp-path-folder"><a class="fp-path-folder-name aalink" href="#"></a></span>
         </div>
     </div>
     <div class="filemanager-loading mdl-align">{{#pix}}i/loading_small{{/pix}}<span class="sr-only">{{#str}}loadinghelp{{/str}}</span></div>
index 0f4f5b9..974c5be 100644 (file)
@@ -64,8 +64,7 @@
                 <input class="form-control" type="text">
             </div>
             <div class="fp-setlicense form-group row">
-                <label class="col-form-label">{{#str}}chooselicense, repository{{/str}}</label>
-                <select class="custom-select"></select>
+                {{>core/filemanager_chooselicense}}
             </div>
             <div class="form-group row">
                 <div class="fp-select-buttons">
index 7e83169..7f01067 100644 (file)
@@ -40,9 +40,8 @@
                     <label>{{#str}}author, repository{{/str}}</label>
                     <input type="text" class="form-control"/>
                 </div>
-                <div class="fp-setlicense control-group">
-                    <label>{{#str}}chooselicense, repository{{/str}}</label>
-                    <select class="custom-select"></select>
+                <div class="fp-setlicense form-group">
+                    {{>core/filemanager_chooselicense}}
                 </div>
             </div>
         </form>
index 1b2ec1a..ac76ed5 100644 (file)
     { "inputID": 1, "suggestionsId": 2, "selectionId": 3, "downArrowId": 4, "placeholder": "Select something" }
 }}
 {{#showSuggestions}}
-<div class="d-inline-block position-relative">
+<div class="d-md-inline-block mr-md-2 position-relative">
     <input type="text" id="{{inputId}}" class="form-control" list="{{suggestionsId}}" placeholder="{{placeholder}}" role="combobox" aria-expanded="false" autocomplete="off" autocorrect="off" autocapitalize="off" aria-autocomplete="list" aria-owns="{{suggestionsId}} {{selectionId}}" {{#tags}}data-tags="1"{{/tags}}/>
     <span class="form-autocomplete-downarrow position-absolute p-1" id="{{downArrowId}}">&#x25BC;</span>
 </div>
 {{/showSuggestions}}
 {{^showSuggestions}}
-<input type="text" id="{{inputId}}" placeholder="{{placeholder}}" role="textbox" aria-owns="{{selectionId}}" {{#tags}}data-tags="1"{{/tags}}/>
+<div class="d-md-inline-block mr-md-2">
+    <input type="text" id="{{inputId}}" class="form-control" placeholder="{{placeholder}}" role="textbox" aria-owns="{{selectionId}}" {{#tags}}data-tags="1"{{/tags}}/>
+</div>
 {{/showSuggestions}}
 
 {{#js}}
diff --git a/lib/templates/form_autocomplete_layout.mustache b/lib/templates/form_autocomplete_layout.mustache
new file mode 100644 (file)
index 0000000..f224fa8
--- /dev/null
@@ -0,0 +1,38 @@
+{{!
+    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/>.
+}}
+{{!
+    @template core/form_autocomplete_layout
+
+    Moodle template for the layout of autocomplete elements.
+
+    Classes required for JS:
+    * none
+
+    Data attributes required for JS:
+    * data-region="form_autocomplete-input"
+    * data-region="form_autocomplete-suggestions"
+    * data-region="form_autocomplete-selection"
+
+    Context variables required for this template:
+    * none
+
+    Example context (json):
+    {}
+}}
+<div data-region="form_autocomplete-selection"></div>
+<div data-region="form_autocomplete-input"></div>
+<div data-region="form_autocomplete-suggestions"></div>
index dc78b41..cfdf87c 100644 (file)
@@ -54,7 +54,7 @@
 <span class="inplaceeditable inplaceeditable-{{type}}" data-inplaceeditable="1" data-component="{{component}}" data-itemtype="{{itemtype}}" data-itemid="{{itemid}}"
     data-value="{{value}}" data-editlabel="{{editlabel}}" data-type="{{type}}" data-options="{{options}}">
         {{^ linkeverything }}{{{displayvalue}}}{{/ linkeverything }}
-        <a href="#" class="quickeditlink" data-inplaceeditablelink="1" title="{{edithint}}">
+        <a href="#" class="quickeditlink aalink" data-inplaceeditablelink="1" title="{{edithint}}">
             {{# linkeverything }}{{{displayvalue}}}{{/ linkeverything }}
             <span class="quickediticon visibleifjs">{{#pix}}t/editstring,core,{{{edithint}}}{{/pix}}</span>
         </a>
index 78ce7cd..5a415ae 100644 (file)
@@ -40,6 +40,6 @@
 
 {{< core/modal }}
     {{$footer}}
-        <button type="button" class="btn btn-primary" data-action="cancel">{{#str}}okay, moodle{{/str}}</button>
+        <button type="button" class="btn btn-primary" data-action="cancel">{{#str}}ok, moodle{{/str}}</button>
     {{/footer}}
 {{/ core/modal }}
index 27b8ec5..d41a055 100644 (file)
 
                 {{#hasidentityproviders}}
                     <h6 class="mt-2">{{#str}} potentialidps, auth {{/str}}</h6>
-                    <div class="potentialidplist" class="mt-3">
+                    <div class="potentialidplist mt-3">
                         {{#identityproviders}}
                             <div class="potentialidp">
                                 <a href="{{url}}" title={{#quote}}{{name}}{{/quote}} class="btn btn-secondary btn-block">
index 0112157..56fdbc3 100644 (file)
             case 'page menu' :
                 // This lang string was changed in app version 3.6.
                 selector = 'core-context-menu > button[aria-label=Info], ' +
-                        'core-context-menu > button[aria-label=Information]';
+                        'core-context-menu > button[aria-label=Information], ' +
+                        'core-context-menu > button[aria-label="Display options"]';
                 break;
             default:
                 return 'ERROR: Unsupported standard button type';
index f82ce5d..af82844 100644 (file)
@@ -303,12 +303,22 @@ class behat_app extends behat_base {
         // Wait until the site login field appears OR the main page.
         $situation = $this->spin(
                 function($context, $args) {
-                    $input = $context->getSession()->getPage()->find('xpath', '//input[@name="url"]');
-                    if ($input) {
+                    $page = $context->getSession()->getPage();
+
+                    $element = $page->find('xpath', '//page-core-login-site//input[@name="url"]');
+                    if ($element) {
+                        // Wait for the onboarding modal to open, if any.
+                        $this->wait_for_pending_js();
+                        $element = $page->find('xpath', '//page-core-login-site-onboarding');
+                        if ($element) {
+                            $this->i_press_in_the_app('Skip');
+                        }
+
                         return 'login';
                     }
-                    $mainmenu = $context->getSession()->getPage()->find('xpath', '//page-core-mainmenu');
-                    if ($mainmenu) {
+
+                    $element = $page->find('xpath', '//page-core-mainmenu');
+                    if ($element) {
                         return 'mainpage';
                     }
                     throw new DriverException('Moodle app login URL prompt not found');
@@ -317,7 +327,7 @@ class behat_app extends behat_base {
         // If it's the login page, we automatically fill in the URL and leave it on the user/pass
         // page. If it's the main page, we just leave it there.
         if ($situation === 'login') {
-            $this->i_set_the_field_in_the_app('Site address', $CFG->wwwroot);
+            $this->i_set_the_field_in_the_app('campus.example.edu', $CFG->wwwroot);
             $this->i_press_in_the_app('Connect!');
         }
 
index 0ba8f49..fe00dc0 100644 (file)
Binary files a/lib/yui/build/moodle-core-notification-alert/moodle-core-notification-alert-debug.js and b/lib/yui/build/moodle-core-notification-alert/moodle-core-notification-alert-debug.js differ
index 250a39b..eeebc2c 100644 (file)
Binary files a/lib/yui/build/moodle-core-notification-alert/moodle-core-notification-alert-min.js and b/lib/yui/build/moodle-core-notification-alert/moodle-core-notification-alert-min.js differ
index 0ba8f49..fe00dc0 100644 (file)
Binary files a/lib/yui/build/moodle-core-notification-alert/moodle-core-notification-alert.js and b/lib/yui/build/moodle-core-notification-alert/moodle-core-notification-alert.js differ
index 2d95cfe..ffd3eb4 100644 (file)
@@ -97,17 +97,17 @@ Y.extend(ALERT, M.core.notification.info, {
          *
          * @attribute yesLabel
          * @type String
-         * @default 'Ok'
+         * @default 'OK'
          */
         yesLabel: {
             validator: Y.Lang.isString,
             setter: function(txt) {
                 if (!txt) {
-                    txt = 'Ok';
+                    txt = 'OK';
                 }
                 return txt;
             },
-            value: 'Ok'
+            value: 'OK'
         }
     }
 });
index cce3f3b..41cefd7 100644 (file)
@@ -133,7 +133,7 @@ Feature: In an assignment, teacher can annotate PDF files during grading
     And I draw on the pdf
     And I press "Save changes"
     And I should see "The changes to the grade and feedback were saved"
-    And I press "Ok"
+    And I press "OK"
     And I click on "Edit settings" "link"
     And I follow "Test assignment name"
     And I navigate to "View all submissions" in current page administration
index 1e7be2e..17c1524 100644 (file)
@@ -62,7 +62,7 @@ Feature: In a group assignment, teacher can annotate PDF files for all users
     Given I set the field "applytoall" to "0"
     And I press "Save changes"
     And I should see "The changes to the grade and feedback were saved"
-    And I press "Ok"
+    And I press "OK"
     And I click on "Edit settings" "link"
     And I log out
     And I log in as "student1"
@@ -81,7 +81,7 @@ Feature: In a group assignment, teacher can annotate PDF files for all users
   @javascript
   Scenario: Submit a PDF file as a student and annotate the PDF as a teacher and all students in the group get a copy of the annotated PDF.
     Given I press "Save changes"
-    And I click on "Ok" "button"
+    And I click on "OK" "button"
     And I am on "Course 1" course homepage
     And I log out
     And I log in as "student1"
index 1b341ee..d66eae9 100644 (file)
@@ -54,7 +54,7 @@ Feature: In an assignment, teacher can view the feedback for a previous attempt.
     And I press "Save changes"
     And I wait until the page is ready
     And I should see "The changes to the grade and feedback were saved"
-    And I press "Ok"
+    And I press "OK"
     And I follow "View a different attempt"
     And I click on "Attempt 1" "radio" in the "View a different attempt" "dialogue"
     And I press "View"
index 09a4b19..a0f260e 100644 (file)
@@ -58,7 +58,7 @@ Feature: In an assignment, teacher can submit feedback files during grading
   Scenario: A teacher can provide a feedback file when grading an assignment.
     Given I set the field "applytoall" to "0"
     And I press "Save changes"
-    And I click on "Ok" "button"
+    And I click on "OK" "button"
     And I click on "Course 1" "link" in the "[data-region=assignment-info]" "css_element"
     And I log out
     And I log in as "student1"
@@ -74,7 +74,7 @@ Feature: In an assignment, teacher can submit feedback files during grading
   @javascript
   Scenario: A teacher can provide a feedback file when grading an assignment and all students in the group will receive the file.
     Given I press "Save changes"
-    And I click on "Ok" "button"
+    And I click on "OK" "button"
     And I click on "Course 1" "link" in the "[data-region=assignment-info]" "css_element"
     And I log out
     And I log in as "student1"
index feae312..49935d7 100644 (file)
@@ -43,7 +43,7 @@ Feature: In an assignment, students start a new attempt based on their previous
     And I set the following fields to these values:
       | Allow another attempt | 1 |
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I click on "Edit settings" "link"
     And I log out
     Then I log in as "student1"
@@ -150,7 +150,7 @@ Feature: In an assignment, students start a new attempt based on their previous
     And I set the following fields to these values:
       | Allow another attempt | 1 |
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I follow "Assignment: Test assignment name"
     And I log out
     And I log in as "student4"
index fceefa0..040db93 100644 (file)
@@ -46,7 +46,7 @@ Feature: In an assignment, teachers can edit a students submission inline
       | Feedback comments | I'm the teacher feedback |
     And I upload "lib/tests/fixtures/empty.txt" file to "Feedback files" filemanager
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I click on "Edit settings" "link"
     And I follow "Test assignment name"
     And I navigate to "View all submissions" in current page administration
index 192bc69..7c4108e 100644 (file)
@@ -32,7 +32,7 @@ Feature: Check that the assignment grade can be updated correctly
     And I set the field "Grade out of 100" to "50"
     And I set the field "Notify students" to "0"
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I click on "Edit settings" "link"
     And I follow "Test assignment name"
     And I navigate to "View all submissions" in current page administration
@@ -68,7 +68,7 @@ Feature: Check that the assignment grade can be updated correctly
     And I set the field "Grade out of 100" to "50"
     And I set the field "Notify students" to "0"
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I click on "Edit settings" "link"
     And I follow "Test assignment name"
     And I navigate to "View all submissions" in current page administration
index add1bec..66e95c0 100644 (file)
@@ -46,7 +46,7 @@ Feature: In an assignment, teachers can edit feedback for a students previous su
       | Feedback comments | I'm the teacher first feedback |
       | Allow another attempt | Yes |
     And I press "Save changes"
-    And I click on "Ok" "button"
+    And I click on "OK" "button"
     And I click on "Edit settings" "link"
     And I log out
     And I log in as "student2"
@@ -66,7 +66,7 @@ Feature: In an assignment, teachers can edit feedback for a students previous su
       | Grade | 50 |
       | Feedback comments | I'm the teacher second feedback |
     And I press "Save changes"
-    And I click on "Ok" "button"
+    And I click on "OK" "button"
     And I click on "Edit settings" "link"
     And I log out
     Then I log in as "student2"
index c9933e1..56683ce 100644 (file)
@@ -36,7 +36,7 @@ Feature: In an assignment, teachers can filter displayed submissions by assigned
     And I set the field "allocatedmarker" to "Marker 1"
     And I set the field "Notify students" to "0"
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I click on "Edit settings" "link"
     And I log out
     When I log in as "teacher1"
index 29b65fc..38852e1 100644 (file)
@@ -57,7 +57,7 @@ Feature: View the grading status of an assignment
     And I set the field "Feedback comments" to "Great job! Lol, not really."
     And I set the field "Notify students" to "0"
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I click on "Edit settings" "link"
     And I follow "Test assignment name"
     And I navigate to "View all submissions" in current page administration
@@ -80,7 +80,7 @@ Feature: View the grading status of an assignment
     And I should see "1 of 1"
     And I set the field "Marking workflow state" to "Released"
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I click on "Edit settings" "link"
     And I follow "Test assignment name"
     And I navigate to "View all submissions" in current page administration
@@ -104,7 +104,7 @@ Feature: View the grading status of an assignment
     And I set the field "Marking workflow state" to "In marking"
     And I set the field "Notify students" to "0"
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I click on "Edit settings" "link"
     And I follow "Test assignment name"
     And I navigate to "View all submissions" in current page administration
@@ -154,7 +154,7 @@ Feature: View the grading status of an assignment
     And I set the field "Grade out of 100" to "50"
     And I set the field "Feedback comments" to "Great job! Lol, not really."
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I click on "Edit settings" "link"
     And I follow "Test assignment name"
     And I navigate to "View all submissions" in current page administration
@@ -188,7 +188,7 @@ Feature: View the grading status of an assignment
     And I set the field "Grade out of 100" to "99.99"
     And I set the field "Feedback comments" to "Even better job! Really."
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I click on "Edit settings" "link"
     And I follow "Test assignment name"
     And I navigate to "View all submissions" in current page administration
index e341cb4..2f8d02d 100644 (file)
@@ -189,11 +189,11 @@ Feature: Group assignment submissions
       | Grade out of 100 | 50.0 |
       | Apply grades and feedback to entire group | 1 |
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I set the following fields to these values:
       | Allow another attempt | 1 |
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     When I am on "Course 1" course homepage
     And I follow "Test assignment name"
     And I navigate to "View all submissions" in current page administration
index 9411b15..027c5c2 100644 (file)
@@ -46,7 +46,7 @@ Feature: Hide grader identities identity from students
     And I set the field "Grade out of 100" to "50"
     And I set the field "Feedback comments" to "Catch for us the foxes."
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I follow "Test assignment name"
     And I navigate to "View all submissions" in current page administration
     And I should see "Graded" in the "Student 1" "table_row"
index 2a024ba..d49ae63 100644 (file)
@@ -65,7 +65,7 @@ Feature: Outcome grading
     And I set the following fields to these values:
       | Outcome Test: | Excellent |
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I click on "Edit settings" "link"
     And I follow "Test assignment name"
     And I navigate to "View all submissions" in current page administration
@@ -113,7 +113,7 @@ Feature: Outcome grading
       | Outcome Test: | Excellent |
       | Apply grades and feedback to entire group | Yes |
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I click on "Edit settings" "link"
     And I follow "Test assignment name"
     And I navigate to "View all submissions" in current page administration
@@ -125,7 +125,7 @@ Feature: Outcome grading
       | Outcome Test: | Disappointing |
       | Apply grades and feedback to entire group | No |
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I click on "Edit settings" "link"
     And I follow "Test assignment name"
     And I navigate to "View all submissions" in current page administration
index a485ec5..f4d8792 100644 (file)
@@ -39,7 +39,7 @@ Feature: In an assignment, teachers grade multiple students on one page
     And I click on "Grade" "link" in the "Student 1" "table_row"
     And I wait until the page is ready
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I click on "Edit settings" "link"
     And I follow "Test assignment name"
     Then I should see "1" in the "Needs grading" "table_row"
@@ -113,7 +113,7 @@ Feature: In an assignment, teachers grade multiple students on one page
       | M8d skillZ! | 1337 |
       | Feedback comments | I'm the teacher first feedback |
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I click on "Edit settings" "link"
     And I follow "Test assignment name"
     And I navigate to "View all submissions" in current page administration
index ddb7609..f3a8c44 100644 (file)
@@ -31,7 +31,7 @@ Feature: Check that the assignment grade can be rescaled when the max grade is c
     And I click on "Grade" "link" in the "Student 1" "table_row"
     And I set the field "Grade out of 100" to "40"
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I follow "Edit settings"
     And I follow "Test assignment name"
     And I navigate to "View all submissions" in current page administration
index c5138d8..f7af38a 100644 (file)
@@ -51,7 +51,7 @@ Feature: Assignments correctly add feedback to the grade report when workflow an
     And I set the field "Feedback comments" to "Great job! Lol, not really."
     And I set the field "Notify students" to "0"
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I click on "Edit settings" "link"
     And I follow "Test assignment name"
     And I navigate to "View all submissions" in current page administration
@@ -63,7 +63,7 @@ Feature: Assignments correctly add feedback to the grade report when workflow an
     And I set the field "Marking workflow state" to "Ready for release"
     And I set the field "Notify students" to "0"
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I click on "Edit settings" "link"
     And I follow "Test assignment name"
     And I navigate to "View all submissions" in current page administration
@@ -71,7 +71,7 @@ Feature: Assignments correctly add feedback to the grade report when workflow an
     And I click on "Grade" "link" in the "I'm the student's first submission" "table_row"
     And I set the field "Marking workflow state" to "Released"
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I click on "Edit settings" "link"
     And I follow "Test assignment name"
     And I navigate to "View all submissions" in current page administration
@@ -91,7 +91,7 @@ Feature: Assignments correctly add feedback to the grade report when workflow an
     And I set the field "Marking workflow state" to "Ready for release"
     And I set the field "Notify students" to "0"
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I click on "Edit settings" "link"
     And I follow "Test assignment name"
     And I navigate to "View all submissions" in current page administration
@@ -101,7 +101,7 @@ Feature: Assignments correctly add feedback to the grade report when workflow an
     And I click on "Grade" "link" in the "Student 1" "table_row"
     And I set the field "Marking workflow state" to "Released"
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I click on "Edit settings" "link"
     And I follow "Test assignment name"
     And I navigate to "View all submissions" in current page administration
index d3d180c..ff96ca0 100644 (file)
@@ -58,7 +58,7 @@ Feature: In an assignment, students can comment in their submissions
       | Grade out of 100 | 50 |
       | Feedback comments | I'm the teacher feedback |
     And I press "Save changes"
-    And I press "Ok"
+    And I press "OK"
     And I click on "Edit settings" "link"
     And I follow "Test assignment name"
     And I navigate to "View all submissions" in current page administration
@@ -78,7 +78,7 @@ Feature: In an assignment, students can comment in their submissions
       | Grade out of 100 | 0 |
     And I press "Save changes"
     And I should see "The changes to the grade and feedback were saved"
-    And I press "Ok"
+    And I press "OK"
     And I set the following fields to these values:
       | Feedback comments | I'm the teacher feedback |
     And I press "Save changes"
index 473bdad..2206b60 100644 (file)
@@ -61,11 +61,11 @@ Feature: Course level forum summary report
     And I navigate to "Forum summary report" in current page administration
     And I should see "Export posts"
     And the following should exist in the "forumreport_summary_table" table:
-    # |                      | Discussions | Replies |                                    |                                   |
-      | First name / Surname | -3-         | -4-     | Earliest post                      | Most recent post                  |
-      | Student 1            | 1           | 1       | Thursday, 28 March 2019, 11:50 AM  | Thursday, 6 June 2019, 6:40 PM    |
-      | Student 2            | 0           | 0       | -                                  | -                                 |
-      | Teacher 1            | 1           | 2       | Wednesday, 27 March 2019, 12:10 PM | Wednesday, 10 July 2019, 9:30 AM  |
+    # |                      | Discussions | Replies |                                 |                                |
+      | First name / Surname | -3-         | -4-     | Earliest post                   | Most recent post               |
+      | Student 1            | 1           | 1       | Thursday, 28 March 2019, 11:50  | Thursday, 6 June 2019, 6:40    |
+      | Student 2            | 0           | 0       | -                               | -                              |
+      | Teacher 1            | 1           | 2       | Wednesday, 27 March 2019, 12:10 | Wednesday, 10 July 2019, 9:30  |
     And the following should not exist in the "forumreport_summary_table" table:
       | First name / Surname |
       | Student 3            |
@@ -77,11 +77,11 @@ Feature: Course level forum summary report
     Then I select "All forums in course" from the "Forum selected" singleselect
     And I should not see "Export posts"
     And the following should exist in the "forumreport_summary_table" table:
-    # |                      | Discussions | Replies |                                    |                                     |
-      | First name / Surname | -3-         | -4-     | Earliest post                      | Most recent post                    |
-      | Student 1            | 2           | 3       | Thursday, 25 January 2018, 4:40 PM | Saturday, 25 January 2020, 11:50 AM |
-      | Student 2            | 0           | 0       | -                                  | -                                   |
-      | Teacher 1            | 4           | 3       | Sunday, 14 January 2018, 9:00 AM   | Thursday, 26 December 2019, 9:30 AM |
+    # |                      | Discussions | Replies |                                 |                                  |
+      | First name / Surname | -3-         | -4-     | Earliest post                   | Most recent post                 |
+      | Student 1            | 2           | 3       | Thursday, 25 January 2018, 4:40 | Saturday, 25 January 2020, 11:50 |
+      | Student 2            | 0           | 0       | -                               | -                                |
+      | Teacher 1            | 4           | 3       | Sunday, 14 January 2018, 9:00   | Thursday, 26 December 2019, 9:30 |
     And the following should not exist in the "forumreport_summary_table" table:
       | First name / Surname |
       | Student 3            |
@@ -95,9 +95,9 @@ Feature: Course level forum summary report
     And I follow "forum1"
     And I navigate to "Forum summary report" in current page administration
     And the following should exist in the "forumreport_summary_table" table:
-    # |                      | Discussions | Replies |                                    |                                    |
-      | First name / Surname | -2-         | -3-     | Earliest post                      | Most recent post                   |
-      | Student 1            | 0           | 1       | Thursday, 25 January 2018, 4:40 PM | Thursday, 25 January 2018, 4:40 PM |
+    # |                      | Discussions | Replies |                                 |                                 |
+      | First name / Surname | -2-         | -3-     | Earliest post                   | Most recent post                |
+      | Student 1            | 0           | 1       | Thursday, 25 January 2018, 4:40 | Thursday, 25 January 2018, 4:40 |
     And the following should not exist in the "forumreport_summary_table" table:
       | First name / Surname |
       | Student 2            |
@@ -110,9 +110,9 @@ Feature: Course level forum summary report
     And the "Forum selected" select box should not contain "forum4"
     Then I select "All forums in course" from the "Forum selected" singleselect
     And the following should exist in the "forumreport_summary_table" table:
-    # |                      | Discussions | Replies |                                    |                                     |
-      | First name / Surname | -2-         | -3-     | Earliest post                      | Most recent post                    |
-      | Student 1            | 2           | 3       | Thursday, 25 January 2018, 4:40 PM | Saturday, 25 January 2020, 11:50 AM |
+    # |                      | Discussions | Replies |                                 |                                  |
+      | First name / Surname | -2-         | -3-     | Earliest post                   | Most recent post                 |
+      | Student 1            | 2           | 3       | Thursday, 25 January 2018, 4:40 | Saturday, 25 January 2020, 11:50 |
     And the following should not exist in the "forumreport_summary_table" table:
       | First name / Surname |
       | Student 2            |
index 07fdff1..783db9f 100644 (file)
@@ -47,9 +47,9 @@ Feature: Post date columns data available
     And I am on "Course 1" course homepage
     And I follow "forum1"
     And I navigate to "Forum summary report" in current page administration
-    Then "Teacher 1" row "Earliest post" column of "forumreport_summary_table" table should contain "Tuesday, 2 January 2018, 9:00 AM"
-    Then "Teacher 1" row "Most recent post" column of "forumreport_summary_table" table should contain "Sunday, 1 September 2019, 7:00 AM"
-    Then "Student 1" row "Earliest post" column of "forumreport_summary_table" table should contain "Wednesday, 27 March 2019, 4:00 AM"
-    Then "Student 1" row "Most recent post" column of "forumreport_summary_table" table should contain "Wednesday, 27 March 2019, 1:00 PM"
+    Then "Teacher 1" row "Earliest post" column of "forumreport_summary_table" table should contain "Tuesday, 2 January 2018, 9:00"
+    Then "Teacher 1" row "Most recent post" column of "forumreport_summary_table" table should contain "Sunday, 1 September 2019, 7:00"
+    Then "Student 1" row "Earliest post" column of "forumreport_summary_table" table should contain "Wednesday, 27 March 2019, 4:00"
+    Then "Student 1" row "Most recent post" column of "forumreport_summary_table" table should contain "Wednesday, 27 March 2019, 1:00"
     Then "Student 2" row "Earliest post" column of "forumreport_summary_table" table should contain "-"
     Then "Student 2" row "Most recent post" column of "forumreport_summary_table" table should contain "-"
index 35fa58e..83edd8f 100644 (file)
@@ -32,7 +32,7 @@ $string['answer_fail'] = 'Incorrect answer';
 $string['answer_incorrect'] = 'Your answer is incorrect';
 $string['answer_pass'] = 'Correct answer';
 $string['answer_unchecked'] = 'Answer unchecked';
-$string['answer_unknown'] = 'Unkown answer';
+$string['answer_unknown'] = 'Unknown answer';
 $string['answer_text'] = 'Answer text';
 $string['areapackage'] = 'Package file';
 $string['attempt'] = 'Attempt';
@@ -113,7 +113,7 @@ $string['result_compound'] = 'Combined partial score';
 $string['result_fill-in'] = 'Fill-in text';
 $string['result_fill-in_gap'] = 'Gap #{$a}';
 $string['result_matching'] = 'Matching choice';
-$string['result_other'] = 'Unkown interaction type';
+$string['result_other'] = 'Unknown interaction type';
 $string['result_sequencing_choice'] = 'Positions';
 $string['result_sequencing_answer'] = 'Position value';
 $string['result_sequencing_position'] = '#{$a}';
index b5d05e0..2e746f0 100644 (file)
@@ -24,12 +24,13 @@ Feature: Add H5P activity
   Scenario: Add a h5pactivity activity to a course
     When I add a "H5P" to section "1"
     And I set the following fields to these values:
-      | Name        | Awesome H5P package |
-      | Description | Description         |
+      | Name        | Awesome H5P package      |
+      | Description | H5P activity Description |
     And I upload "h5p/tests/fixtures/ipsums.h5p" file to "Package file" filemanager
     And I click on "Save and display" "button"
     And I wait until the page is ready
-    Then I switch to "h5p-player" class iframe
+    Then I should see "H5P activity Description"
+    And I switch to "h5p-player" class iframe
     And I switch to "h5p-iframe" class iframe
     And I should see "Lorum ipsum"
     And I should not see "Reuse"
index 3378138..9feac00 100644 (file)
@@ -70,6 +70,11 @@ $PAGE->set_context($context);
 echo $OUTPUT->header();
 echo $OUTPUT->heading(format_string($moduleinstance->name));
 
+$instance = $manager->get_instance();
+if (!empty($instance->intro)) {
+    echo $OUTPUT->box(format_module_intro('h5pactivity', $instance, $cm->id), 'generalbox', 'intro');
+}
+
 // Attempts review.
 if ($manager->can_view_all_attempts()) {
     $reviewurl = new moodle_url('report.php', ['a' => $cm->instance]);
index 506808b..7fc990d 100644 (file)
@@ -33,32 +33,32 @@ $string['allowedbrowserkeyssyntax'] = 'A key should be a 64-character hex string
 $string['cachedef_config'] = 'SEB config cache';
 $string['cachedef_configkey'] = 'SEB config key cache';
 $string['cachedef_quizsettings'] = 'SEB quiz settings cache';
-$string['cantdelete'] = 'Template can\'t be deleted as it has been used for one or more quizzes';
-$string['cantedit'] = 'Template can\'t be edited as it has been used for one or more quizzes';
+$string['cantdelete'] = 'The template can\'t be deleted as it has been used for one or more quizzes.';
+$string['cantedit'] = 'The template can\'t be edited as it has been used for one or more quizzes.';
 $string['clientrequiresseb'] = 'This quiz has been configured to use the Safe Exam Browser with client configuration.';
 $string['confirmtemplateremovalquestion'] = 'Are you sure you want to remove this template?';
 $string['confirmtemplateremovaltitle'] = 'Confirm template removal?';
-$string['conflictingsettings'] = 'You don\'t have permissions to update existing Safe Exam Browser settings';
+$string['conflictingsettings'] = 'You don\'t have permission to update existing Safe Exam Browser settings.';
 $string['content'] = 'Template';
 $string['description'] = 'Description';
 $string['disabledsettings'] = 'Disabled settings.';
-$string['disabledsettings_help'] = 'Safe Exam Browser quiz settings are unable to be updated if there is one or more existing quiz attempts. To re-enable settings, all quiz attempts must be deleted.';
+$string['disabledsettings_help'] = 'Safe Exam Browser quiz settings can\'t be changed if the quiz has been attempted. To change a setting, all quiz attempts must first be deleted.';
 $string['downloadsebconfig'] = 'Download SEB config file';
-$string['duplicatetemplate'] = "Template with the same name already exists";
+$string['duplicatetemplate'] = 'A template with the same name already exists.';
 $string['edittemplate'] = 'Edit template';
 $string['enabled'] = 'Enabled';
 $string['event:accessprevented'] = "Quiz access was prevented";
-$string['event:templatecreated'] = "SEB Template was created";
-$string['event:templatedeleted'] = "SEB Template was deleted";
-$string['event:templatedisabled'] = "SEB Template was disabled";
-$string['event:templateenabled'] = "SEB Template was enabled";
-$string['event:templateupdated'] = "SEB Template was updated";
+$string['event:templatecreated'] = 'SEB template was created';
+$string['event:templatedeleted'] = 'SEB template was deleted';
+$string['event:templatedisabled'] = 'SEB template was disabled';
+$string['event:templateenabled'] = 'SEB template was enabled';
+$string['event:templateupdated'] = 'SEB template was updated';
 $string['exitsebbutton'] = 'Exit Safe Exam Browser';
 $string['filemanager_sebconfigfile'] = 'Upload Safe Exam Browser config file';
 $string['filemanager_sebconfigfile_help'] = 'Please upload your own Safe Exam Browser config file for this quiz.';
 $string['filenotpresent'] = 'Please upload a SEB config file.';
 $string['fileparsefailed'] = 'The uploaded file could not be saved as a SEB config file.';
-$string['httplinkbutton'] = 'Download Configuration';
+$string['httplinkbutton'] = 'Download configuration';
 $string['invalid_browser_key'] = "Invalid SEB browser key";
 $string['invalid_config_key'] = "Invalid SEB config key";
 $string['invalidkeys'] = "The config key or browser exam keys could not be validated. Please ensure you are using the Safe Exam Browser with correct configuration file.";
@@ -70,10 +70,10 @@ $string['name'] = 'Name';
 $string['newtemplate'] = 'New template';
 $string['noconfigfilefound'] = 'No uploaded SEB config file could be found for quiz with cmid: {$a}';
 $string['noconfigfound'] = 'No SEB config could be found for quiz with cmid: {$a}';
-$string['not_seb'] = "No SEB browser is being used";
+$string['not_seb'] = 'No Safe Exam Browser is being used.';
 $string['notemplate'] = 'No template';
 $string['passwordnotset'] = 'Current settings require quizzes using the Safe Exam Browser to have a quiz password set.';
-$string['pluginname'] = 'Safe Exam Browser access rule';
+$string['pluginname'] = 'Safe Exam Browser access rules';
 $string['privacy:metadata:quizaccess_seb_quizsettings'] = 'Safe Exam Browser settings for a quiz. This includes the ID of the last user to create or modify the settings.';
 $string['privacy:metadata:quizaccess_seb_quizsettings:quizid'] = 'ID of the quiz the settings exist for.';
 $string['privacy:metadata:quizaccess_seb_quizsettings:timecreated'] = 'Unix time that the settings were created.';
@@ -87,37 +87,37 @@ $string['quizsettings'] = 'Quiz settings';
 $string['restoredfrom'] = '{$a->name} (restored via cmid {$a->cmid})';
 $string['seb'] = 'Safe Exam Browser';
 $string['seb:bypassseb'] = 'Bypass the requirement to view quiz in Safe Exam Browser.';
-$string['seb:manage_filemanager_sebconfigfile'] = 'Modify Safe Exam Browser quiz setting: Select SEB config file.';
-$string['seb:manage_seb_activateurlfiltering'] = 'Modify Safe Exam Browser quiz setting: Activate URL filtering.';
-$string['seb:manage_seb_allowedbrowserexamkeys'] = 'Modify Safe Exam Browser quiz setting: Modify Allowed Browser Exam Keys.';
-$string['seb:manage_seb_allowreloadinexam'] = 'Modify Safe Exam Browser quiz setting: Allow reload.';
-$string['seb:manage_seb_allowspellchecking'] = 'Modify Safe Exam Browser quiz setting: Enable spell checking.';
-$string['seb:manage_seb_allowuserquitseb'] = 'Modify Safe Exam Browser quiz setting: Allow quit.';
-$string['seb:manage_seb_enableaudiocontrol'] = 'Modify Safe Exam Browser quiz setting: Enable audio control.';
-$string['seb:manage_seb_expressionsallowed'] = 'Modify Safe Exam Browser quiz setting: Simple expressions allowed.';
-$string['seb:manage_seb_expressionsblocked'] = 'Modify Safe Exam Browser quiz setting: Simple expressions blocked.';
-$string['seb:manage_seb_filterembeddedcontent'] = 'Modify Safe Exam Browser quiz setting: Filter embedded content.';
-$string['seb:manage_seb_linkquitseb'] = 'Modify Safe Exam Browser quiz setting: Quit link.';
-$string['seb:manage_seb_muteonstartup'] = 'Modify Safe Exam Browser quiz setting: Mute on startup.';
-$string['seb:manage_seb_quitpassword'] = 'Modify Safe Exam Browser quiz setting: Quit password.';
-$string['seb:manage_seb_regexallowed'] = 'Modify Safe Exam Browser quiz setting: Regex expressions allowed.';
-$string['seb:manage_seb_regexblocked'] = 'Modify Safe Exam Browser quiz setting: Regex expressions blocked.';
-$string['seb:manage_seb_requiresafeexambrowser'] = 'Modify Safe Exam Browser quiz setting: Require Safe Exam Browser.';
-$string['seb:manage_seb_showkeyboardlayout'] = 'Modify Safe Exam Browser quiz setting: Show keyboard layout.';
-$string['seb:manage_seb_showreloadbutton'] = 'Modify Safe Exam Browser quiz setting: Show reload button.';
-$string['seb:manage_seb_showsebtaskbar'] = 'Modify Safe Exam Browser quiz setting: Show task bar.';
-$string['seb:manage_seb_showtime'] = 'Modify Safe Exam Browser quiz setting: Show time.';
-$string['seb:manage_seb_showwificontrol'] = 'Modify Safe Exam Browser quiz setting: Show Wi-Fi control.';
-$string['seb:manage_seb_showsebdownloadlink'] = 'Modify Safe Exam Browser quiz setting: Show download link.';
-$string['seb:manage_seb_templateid'] = 'Modify Safe Exam Browser quiz setting: Select SEB template.';
-$string['seb:manage_seb_userconfirmquit'] = 'Modify Safe Exam Browser quiz setting: Confirm on quit.';
-$string['seb:managetemplates'] = 'Manage Safe Exam Browser configuration templates.';
+$string['seb:manage_filemanager_sebconfigfile'] = 'Change SEB quiz setting: Select SEB config file';
+$string['seb:manage_seb_activateurlfiltering'] = 'Change SEB quiz setting: Activate URL filtering';
+$string['seb:manage_seb_allowedbrowserexamkeys'] = 'Change SEB quiz setting: Allowed browser exam keys';
+$string['seb:manage_seb_allowreloadinexam'] = 'Change SEB quiz setting: Allow reload';
+$string['seb:manage_seb_allowspellchecking'] = 'Change SEB quiz setting: Enable spell checking';
+$string['seb:manage_seb_allowuserquitseb'] = 'Change SEB quiz setting: Allow quit';
+$string['seb:manage_seb_enableaudiocontrol'] = 'Change SEB quiz setting: Enable audio control';
+$string['seb:manage_seb_expressionsallowed'] = 'Change SEB quiz setting: Simple expressions allowed';
+$string['seb:manage_seb_expressionsblocked'] = 'Change SEB quiz setting: Simple expressions blocked';
+$string['seb:manage_seb_filterembeddedcontent'] = 'Change SEB quiz setting: Filter embedded content';
+$string['seb:manage_seb_linkquitseb'] = 'Change SEB quiz setting: Quit link';
+$string['seb:manage_seb_muteonstartup'] = 'Change SEB quiz setting: Mute on startup';
+$string['seb:manage_seb_quitpassword'] = 'Change SEB quiz setting: Quit password';
+$string['seb:manage_seb_regexallowed'] = 'Change SEB quiz setting: Regex expressions allowed';
+$string['seb:manage_seb_regexblocked'] = 'Change SEB quiz setting: Regex expressions blocked';
+$string['seb:manage_seb_requiresafeexambrowser'] = 'Change SEB quiz setting: Require Safe Exam Browser';
+$string['seb:manage_seb_showkeyboardlayout'] = 'Change SEB quiz setting: Show keyboard layout';
+$string['seb:manage_seb_showreloadbutton'] = 'Change SEB quiz setting: Show reload button';
+$string['seb:manage_seb_showsebtaskbar'] = 'Change SEB quiz setting: Show task bar';
+$string['seb:manage_seb_showtime'] = 'Change SEB quiz setting: Show time';
+$string['seb:manage_seb_showwificontrol'] = 'Change SEB quiz setting: Show Wi-Fi control';
+$string['seb:manage_seb_showsebdownloadlink'] = 'Change SEB quiz setting: Show download link';
+$string['seb:manage_seb_templateid'] = 'Change SEB quiz setting: Select SEB template';
+$string['seb:manage_seb_userconfirmquit'] = 'Change SE quiz setting: Confirm on quit';
+$string['seb:managetemplates'] = 'Manage SEB configuration templates';
 $string['seb_activateurlfiltering'] = 'Enable URL filtering';
 $string['seb_activateurlfiltering_help'] = 'If enabled, URLs will be filtered when loading web pages. The filter set has to be defined below.';
-$string['seb_allowedbrowserexamkeys'] = 'Allowed Browser Exam Keys';
-$string['seb_allowedbrowserexamkeys_help'] = 'In this field you can enter the allowed Browser Exam Keys for versions of Safe Exam Browser that are permitted to access this quiz. If no keys are entered, then Moodle does not check Browser Exam Keys.';
+$string['seb_allowedbrowserexamkeys'] = 'Allowed browser exam keys';
+$string['seb_allowedbrowserexamkeys_help'] = 'In this field you can enter the allowed browser exam keys for versions of Safe Exam Browser that are permitted to access this quiz. If no keys are entered, then browser exam keys are not checked.';
 $string['seb_allowreloadinexam'] = 'Enable reload in exam';
-$string['seb_allowreloadinexam_help'] = 'If enabled, page reload is allowed (reload button in SEB task bar, browser tool bar, iOS side slider menu, keyboard shortcut F5/cmd+R). Offline caching might break when the user tries to reload a page without internet connection.';
+$string['seb_allowreloadinexam_help'] = 'If enabled, page reload is allowed (reload button in SEB task bar, browser tool bar, iOS side slider menu, keyboard shortcut F5/cmd+R). Note that offline caching may break if a user tries to reload a page without an internet connection.';
 $string['seb_allowspellchecking'] = 'Enable spell checking';
 $string['seb_allowspellchecking_help'] = 'If enabled, spell checking in the SEB browser is allowed.';
 $string['seb_allowuserquitseb'] = 'Enable quitting of SEB';
@@ -129,7 +129,7 @@ $string['seb_expressionsallowed_help'] = 'A text field which contains the allowe
 $string['seb_expressionsblocked'] = 'Expressions blocked';
 $string['seb_expressionsblocked_help'] = 'A text field which contains the filtering expressions for the blocked URLs. Use of the wildcard char \'\*\' is possible. Examples for expressions: \'example.com\' or \'example.com/stuff/\*\'. \'example.com\' matches \'example.com\', \'www.example.com\' and \'www.mail.example.com\'. \'example.com/stuff/\*\' matches all requests to any subdomain of \'example.com\' that have \'stuff\' as the first segment of the path.';
 $string['seb_filterembeddedcontent'] = 'Filter also embedded content';
-$string['seb_filterembeddedcontent_help'] = 'If enabled, also all embedded resources will be filtered using the filter set.';
+$string['seb_filterembeddedcontent_help'] = 'If enabled, embedded resources will also be filtered using the filter set.';
 $string['seb_help'] = 'Setup quiz to use the Safe Exam Browser.';
 $string['seb_linkquitseb'] = 'Show Exit Safe Exam Browser button, configured with this quit link';
 $string['seb_linkquitseb_help'] = 'In this field you can enter the link to quit SEB. It will be used on an "Exit Safe Exam Browser" button on the page that appears after the exam is submitted. When clicking the button or the link placed wherever you want to put it, it is possible to quit SEB without having to enter a quit password. If no link is entered, then the "Exit Safe Exam Browser" button does not appear and there is no link set to quit SEB.';
@@ -143,29 +143,29 @@ $string['seb_regexallowed_help'] = 'A text field which contains the filtering ex
 $string['seb_regexblocked'] = 'Regex blocked';
 $string['seb_regexblocked_help'] = 'A text field which contains the filtering expressions for blocked URLs in a regular expression (Regex) format.';
 $string['seb_requiresafeexambrowser'] = 'Require the use of Safe Exam Browser';
-$string['seb_requiresafeexambrowser_help'] = "If enabled, students can only attempt the quiz using the Safe Exam Browser.
+$string['seb_requiresafeexambrowser_help'] = 'If enabled, students can only attempt the quiz using the Safe Exam Browser.
 The available options are:
 
 * No
 <br/>Safe Exam Browser is not required to attempt the quiz.
 * Yes – Use an existing template
-<br/>A template for the configuration of Safe Exam Browser can be used. Templates are managed by the Moodle Administrator. Your manual settings overwrite the settings in the template.
+<br/>A template for the configuration of Safe Exam Browser can be used. Templates are managed in the site administration. Your manual settings overwrite the settings in the template.
 * Yes – Configure manually
 <br/>No template for the configuration of Safe Exam Browser will be used. You can configure Safe Exam Browser manually.
 * Yes – Upload my own config
 <br/>You can upload your own Safe Exam Browser configuration file. All manual settings and the use of templates will be disabled.
 * Yes – Use SEB client config
-<br/>No configurations of Safe Exam Browser are on Moodle side. The quiz can be attempted with any configuration of Safe Exam Browser.";
+<br/>No configurations of Safe Exam Browser are on the Moodle side. The quiz can be attempted with any configuration of Safe Exam Browser.';
 $string['seb_showkeyboardlayout'] = 'Show keyboard layout';
 $string['seb_showkeyboardlayout_help'] = 'If enabled, the current keyboard layout is shown in the SEB task bar. It allows you to switch to other keyboard layouts, which have been enabled in the operating system.';
 $string['seb_showreloadbutton'] = 'Show reload button';
-$string['seb_showreloadbutton_help'] = 'If enabled, a reload button appears in the SEB task bar. This button allows to reload the current web page.';
+$string['seb_showreloadbutton_help'] = 'If enabled, a reload button is displayed in the SEB task bar, allowing the current web page to be reloaded.';
 $string['seb_showsebtaskbar'] = 'Show SEB task bar';
-$string['seb_showsebtaskbar_help'] = 'If enabled, a task bar appears at the bottom of the SEB browser window. In case you like to show the Wi-Fi control, the reload button, the time or the keyboard layout to your students, you have to activate the task bar. The task bar is also needed when you permit third party applications, which are displayed as icons in the task bar.';
+$string['seb_showsebtaskbar_help'] = 'If enabled, a task bar appears at the bottom of the SEB browser window. The task bar is required to display items such as Wi-Fi control, reload button, time and keyboard layout.';
 $string['seb_showtime'] = 'Show time';
 $string['seb_showtime_help'] = 'If enabled, the current time is displayed in the SEB task bar.';
 $string['seb_showwificontrol'] = 'Show Wi-Fi control';
-$string['seb_showwificontrol_help'] = 'If enabled, a Wi-Fi control button appears in the SEB task bar. The button allows to reconnect to Wi-Fi networks which have previously been connected to.';
+$string['seb_showwificontrol_help'] = 'If enabled, a Wi-Fi control button appears in the SEB task bar. The button allows users to reconnect to Wi-Fi networks which have previously been connected to.';
 $string['seb_showsebdownloadlink'] = 'Show Safe Exam Browser download button';
 $string['seb_showsebdownloadlink_help'] = 'If enabled, a button for Safe Exam Browser download will be shown on the quiz start page.';
 $string['seb_templateid'] = 'Safe Exam Browser config template';
@@ -179,12 +179,12 @@ $string['seb_userconfirmquit_help'] = 'If enabled, users have to confirm quittin
 $string['sebdownloadbutton'] = 'Download Safe Exam Browser';
 $string['seblinkbutton'] = 'Launch Safe Exam Browser';
 $string['sebrequired'] = "This quiz has been configured so that students may only attempt it using the Safe Exam Browser.";
-$string['setting:autoreconfigureseb'] = 'Auto configure SEB';
+$string['setting:autoreconfigureseb'] = 'Auto-configure SEB';
 $string['setting:autoreconfigureseb_desc'] = 'If enabled, users who navigate to the quiz using the Safe Exam Browser will be automatically forced to reconfigure their Safe Exam Browser.';
-$string['setting:displayblocksbeforestart'] = 'Display blocks before quiz started';
-$string['setting:displayblocksbeforestart_desc'] = 'If enabled, blocks will be displayed before students start a quiz.';
-$string['setting:displayblockswhenfinished'] = 'Display blocks after quiz finished';
-$string['setting:displayblockswhenfinished_desc'] = 'If enabled, blocks will be displayed after students finished a quiz.';
+$string['setting:displayblocksbeforestart'] = 'Display blocks before starting quiz';
+$string['setting:displayblocksbeforestart_desc'] = 'If enabled, blocks will be displayed before a user attempts the quiz.';
+$string['setting:displayblockswhenfinished'] = 'Display blocks after finishing quiz';
+$string['setting:displayblockswhenfinished_desc'] = 'If enabled, blocks will be displayed after a user has finished their quiz attempt.';
 $string['setting:downloadlink'] = 'Safe Exam Browser download link';
 $string['setting:downloadlink_desc'] = 'URL for downloading the Safe Exam Browser application.';
 $string['setting:quizpasswordrequired'] = 'Quiz password required';
@@ -192,8 +192,8 @@ $string['setting:quizpasswordrequired_desc'] = 'If enabled, all quizzes that req
 $string['setting:showhttplink'] = 'Show http:// link';
 $string['setting:showseblink'] = 'Show seb:// link';
 $string['setting:showseblinks'] = 'Show Safe Exam Browser config links';
-$string['setting:showseblinks_desc'] = 'Decide whether to show links for the user to access the Safe Exam Browser configuration file when access to quiz is prevented. Note that seb:// links may not work for every browser.';
-$string['setting:supportedversions'] = 'Please note, that the following minimum versions of the Safe Exam Browser client are required for use of the Config Key feature: macOS - 2.1.5pre2, Windows - 3.0, iOS - 2.1.14';
+$string['setting:showseblinks_desc'] = 'Whether to show links for a user to access the Safe Exam Browser configuration file when access to the quiz is prevented. Note that seb:// links may not work in every browser.';
+$string['setting:supportedversions'] = 'Please note that the following minimum versions of the Safe Exam Browser client are required to use the config key feature: macOS - 2.1.5pre2, Windows - 3.0, iOS - 2.1.14.';
 $string['settingsfrozen'] = 'Due to there being at least one quiz attempt, the Safe Exam Browser settings can no longer be updated.';
 $string['unknown_reason'] = "Unknown reason";
 $string['used'] = 'In use';