From 3fe8dbd0278258ee3681ecb8d15569f79593656b Mon Sep 17 00:00:00 2001 From: David Woloszyn Date: Thu, 23 Nov 2023 18:13:38 +1100 Subject: [PATCH 001/134] MDL-80229 core: Add tolerance to min_get_minimum_revision --- lib/configonlylib.php | 5 ++++- lib/tests/configonlylib_test.php | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/configonlylib.php b/lib/configonlylib.php index bcdfa9bee90c7..cddb36531107e 100644 --- a/lib/configonlylib.php +++ b/lib/configonlylib.php @@ -215,6 +215,9 @@ function min_get_slash_argument($clean = true) { */ function min_get_minimum_revision(): int { static $timestamp = null; + // Days that will be deducted. + // Avoids errors when date comparisons are made at time of packaging for next release. + $tolerancedays = 2; if ($timestamp === null) { global $CFG; @@ -224,7 +227,7 @@ function min_get_minimum_revision(): int { // Parse the date components. $year = intval(substr($datestring, 0, 4)); $month = intval(substr($datestring, 4, 2)); - $day = intval(substr($datestring, 6, 2)); + $day = intval(substr($datestring, 6, 2)) - $tolerancedays; // Return converted GMT Unix timestamp. $timestamp = gmmktime(0, 0, 0, $month, $day, $year); } diff --git a/lib/tests/configonlylib_test.php b/lib/tests/configonlylib_test.php index 8a90009537690..2bd8c8ca04d4e 100644 --- a/lib/tests/configonlylib_test.php +++ b/lib/tests/configonlylib_test.php @@ -153,6 +153,8 @@ public function test_min_get_minimum_version(): void { // This is fairly hard to write a test for, but we can at least check that it returns a number // greater than the version when the feature was first introduced. $firstintroduced = 1669593600; // Equivalent to 20221128 00:00:00 GMT. + // Deduct our two day tolerance. + $firstintroduced = $firstintroduced - (DAYSECS * 2); $this->assertGreaterThanOrEqual($firstintroduced, min_get_minimum_revision()); } From e5eb66e405fe1b6ebaf9cec088b5ebbf0e645483 Mon Sep 17 00:00:00 2001 From: Francis Devine Date: Fri, 10 Nov 2023 13:27:07 +1300 Subject: [PATCH 002/134] MDL-79937 mod_lesson: Fix matching pagetype question matching The addition of text format on the output of the answer responses broke the matching later when comparing the valid answer against the sent response, as the answer response was not correspondingly formatted. I decided to use the un formatted answer response as the key, to keep it as close as identical to prior behaviour --- mod/lesson/pagetypes/matching.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/mod/lesson/pagetypes/matching.php b/mod/lesson/pagetypes/matching.php index 17c4531c917b3..930be9c588e2f 100644 --- a/mod/lesson/pagetypes/matching.php +++ b/mod/lesson/pagetypes/matching.php @@ -79,19 +79,22 @@ protected function make_answer_form($attempt=null) { $answers[$getanswer->id] = $getanswer; } + // Calculate the text for the dropdown, keyed by the non formatted version. $responses = array(); foreach ($answers as $answer) { - // get all the response + // Get all the response. if ($answer->response != null) { - $responses[] = format_text(trim($answer->response)); + $responses[trim($answer->response)] = format_text(trim($answer->response)); } } - $responseoptions = array(''=>get_string('choosedots')); + // Now shuffle the answers to randomise the order of the items in the dropdown. + $responseoptions = ['' => get_string('choosedots')]; if (!empty($responses)) { - shuffle($responses); - foreach ($responses as $response) { - $responseoptions[htmlspecialchars($response, ENT_COMPAT)] = $response; + $keys = array_keys($responses); + shuffle($keys); + foreach ($keys as $key) { + $responseoptions[$key] = $responses[$key]; } } if (isset($USER->modattempts[$this->lesson->id]) && !empty($attempt->useranswer)) { @@ -211,7 +214,6 @@ public function check_answer() { $result->noanswer = true; return $result; } - $value = htmlspecialchars_decode($value, ENT_COMPAT); $userresponse[] = $value; // Make sure the user's answer exists in question's answer if (array_key_exists($id, $answers)) { From 03454c226262c28b3c9151c72556f9160ec1588f Mon Sep 17 00:00:00 2001 From: AMOS bot Date: Sat, 23 Dec 2023 00:11:20 +0000 Subject: [PATCH 003/134] Automatically generated installer lang files --- install/lang/pt_br_kids/langconfig.php | 33 ++++++++++++++++++++++++++ install/lang/pt_br_uni/langconfig.php | 33 ++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 install/lang/pt_br_kids/langconfig.php create mode 100644 install/lang/pt_br_uni/langconfig.php diff --git a/install/lang/pt_br_kids/langconfig.php b/install/lang/pt_br_kids/langconfig.php new file mode 100644 index 0000000000000..5151effc71e86 --- /dev/null +++ b/install/lang/pt_br_kids/langconfig.php @@ -0,0 +1,33 @@ +. + +/** + * Automatically generated strings for Moodle installer + * + * Do not edit this file manually! It contains just a subset of strings + * needed during the very first steps of installation. This file was + * generated automatically by export-installer.php (which is part of AMOS + * {@link http://docs.moodle.org/dev/Languages/AMOS}) using the + * list of strings defined in /install/stringnames.txt. + * + * @package installer + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$string['parentlanguage'] = 'pt_br'; +$string['thislanguage'] = 'Português Brasil para Crianças'; diff --git a/install/lang/pt_br_uni/langconfig.php b/install/lang/pt_br_uni/langconfig.php new file mode 100644 index 0000000000000..834c07ffbacd8 --- /dev/null +++ b/install/lang/pt_br_uni/langconfig.php @@ -0,0 +1,33 @@ +. + +/** + * Automatically generated strings for Moodle installer + * + * Do not edit this file manually! It contains just a subset of strings + * needed during the very first steps of installation. This file was + * generated automatically by export-installer.php (which is part of AMOS + * {@link http://docs.moodle.org/dev/Languages/AMOS}) using the + * list of strings defined in /install/stringnames.txt. + * + * @package installer + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$string['parentlanguage'] = 'pt_br'; +$string['thislanguage'] = 'Português Brasil para Universidades'; From d6650b63bf9d49066f77af2640ae2ba25b45bdfa Mon Sep 17 00:00:00 2001 From: Angelia Dela Cruz Date: Fri, 17 Nov 2023 15:53:00 +0800 Subject: [PATCH 004/134] MDL-73639 core_grades: Behat to test Recover grades default settings --- .../behat/grade_recovery_settings.feature | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 grade/tests/behat/grade_recovery_settings.feature diff --git a/grade/tests/behat/grade_recovery_settings.feature b/grade/tests/behat/grade_recovery_settings.feature new file mode 100644 index 0000000000000..dc9949ec10d29 --- /dev/null +++ b/grade/tests/behat/grade_recovery_settings.feature @@ -0,0 +1,54 @@ +@core @core_grades @javascript +Feature: Admin can set Recover grades default setting + In order to recover grade + As an admin + I need to enable "Recover grades default" from site administration + + Background: + Given the following "courses" exist: + | fullname | shortname | + | Course 1 | C1 | + And the following "users" exist: + | username | firstname | lastname | email | + | student1 | Student | One | student1@example.com | + And the following "course enrolments" exist: + | user | course | role | + | student1 | C1 | student | + And the following "activities" exist: + | activity | course | name | + | assign | C1 | Assign 1 | + + Scenario Outline: Recover grades default setting can be changed + Given I log in as "admin" + # Set Recover grades default value + And I set the following administration settings values: + | recovergradesdefault | | + # Grade student 1 via quick grading + And I am on the "Assign 1" "assign activity" page + And I follow "View all submissions" + And I click on "Quick grading" "checkbox" + And I set the field "User grade" to "60.00" + And I press "Save all quick grading changes" + # Confirm that assigned grade was saved + And I am on the "Course 1" course page + And I navigate to "View > Grader report" in the course gradebook + And I should see "60.00" in the "Student One" "table_row" + And I navigate to course participants + And I click on "Unenrol" "icon" in the "Student One" "table_row" + And I click on "Unenrol" "button" in the "Unenrol" "dialogue" + And I press "Enrol users" + And I set the field "Select users" to "student1" + # Confirm the "Recover user's old grades if possible" checkbox state based on Recover grades default setting + When I click on "Show more..." "link" + Then the field "Recover user's old grades if possible" matches value "" + # Confirm that "Recover user's old grades if possible" checkbox state can be changed manually + And I click on "Recover user's old grades if possible" "checkbox" in the "Enrol users" "dialogue" + And I click on "Enrol users" "button" in the "Enrol users" "dialogue" + # Confirm whether re-enrolled student's grade is recovered or not based on student enrolment settings + And I navigate to "View > Grader report" in the course gradebook + And I see "60.00" in the "Student One" "table_row" + + Examples: + | recovergradesetting | oldgraderecover | gradevisibility | + | 0 | 0 | should | + | 1 | 1 | should not | From 8168155e17eafbf5459538f6ce0edb6c5b5fd270 Mon Sep 17 00:00:00 2001 From: Simey Lameze Date: Mon, 8 Jan 2024 12:43:21 +0800 Subject: [PATCH 005/134] MDL-73639 behat: improvements to the new recover grades test --- grade/tests/behat/grade_recovery_settings.feature | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/grade/tests/behat/grade_recovery_settings.feature b/grade/tests/behat/grade_recovery_settings.feature index dc9949ec10d29..7a46fc3c05422 100644 --- a/grade/tests/behat/grade_recovery_settings.feature +++ b/grade/tests/behat/grade_recovery_settings.feature @@ -1,6 +1,6 @@ @core @core_grades @javascript Feature: Admin can set Recover grades default setting - In order to recover grade + In order to recover grades As an admin I need to enable "Recover grades default" from site administration @@ -19,19 +19,16 @@ Feature: Admin can set Recover grades default setting | assign | C1 | Assign 1 | Scenario Outline: Recover grades default setting can be changed - Given I log in as "admin" - # Set Recover grades default value - And I set the following administration settings values: - | recovergradesdefault | | + Given the following config values are set as admin: + | recovergradesdefault | | # Grade student 1 via quick grading - And I am on the "Assign 1" "assign activity" page + And I am on the "Assign 1" "assign activity" page logged in as admin And I follow "View all submissions" And I click on "Quick grading" "checkbox" And I set the field "User grade" to "60.00" And I press "Save all quick grading changes" # Confirm that assigned grade was saved - And I am on the "Course 1" course page - And I navigate to "View > Grader report" in the course gradebook + And I am on the "Course 1" "grades > Grader report > View" page And I should see "60.00" in the "Student One" "table_row" And I navigate to course participants And I click on "Unenrol" "icon" in the "Student One" "table_row" @@ -45,7 +42,7 @@ Feature: Admin can set Recover grades default setting And I click on "Recover user's old grades if possible" "checkbox" in the "Enrol users" "dialogue" And I click on "Enrol users" "button" in the "Enrol users" "dialogue" # Confirm whether re-enrolled student's grade is recovered or not based on student enrolment settings - And I navigate to "View > Grader report" in the course gradebook + And I am on the "Course 1" "grades > Grader report > View" page And I see "60.00" in the "Student One" "table_row" Examples: From 6c24b8289f4c7dd1f17817633574d6192e4c851c Mon Sep 17 00:00:00 2001 From: Angelia Dela Cruz Date: Tue, 12 Dec 2023 14:19:08 +0800 Subject: [PATCH 006/134] MDL-80382 behat: Replace "Frist" typos in Behat tests to "First" --- blocks/comments/tests/behat/add_comment.feature | 2 +- blocks/comments/tests/behat/block_comment_activity.feature | 2 +- blocks/comments/tests/behat/block_comment_dashboard.feature | 2 +- completion/tests/behat/bulk_edit_activity_completion.feature | 2 +- completion/tests/behat/default_activity_completion.feature | 4 ++-- .../tests/behat/enable_completion_on_pass_grade.feature | 2 +- .../tests/behat/enable_completion_on_view_and_grade.feature | 2 +- completion/tests/behat/enable_manual_complete_mark.feature | 2 +- ...passgrade_completion_criteria_gradeitem_visibility.feature | 2 +- completion/tests/behat/restrict_activity_by_date.feature | 2 +- completion/tests/behat/restrict_activity_by_grade.feature | 2 +- completion/tests/behat/restrict_section_availability.feature | 2 +- mod/label/tests/behat/label_idnumber.feature | 2 +- mod/label/tests/behat/label_visibility.feature | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/blocks/comments/tests/behat/add_comment.feature b/blocks/comments/tests/behat/add_comment.feature index 1fef5008d0a15..a64fdcb12ad30 100644 --- a/blocks/comments/tests/behat/add_comment.feature +++ b/blocks/comments/tests/behat/add_comment.feature @@ -10,7 +10,7 @@ Feature: Add a comment to the comments block | Course 1 | C1 | 0 | And the following "users" exist: | username | firstname | lastname | email | - | teacher1 | Teacher | Frist | teacher1@example.com | + | teacher1 | Teacher | First | teacher1@example.com | | student1 | Student | First | student1@example.com | And the following "course enrolments" exist: | user | course | role | diff --git a/blocks/comments/tests/behat/block_comment_activity.feature b/blocks/comments/tests/behat/block_comment_activity.feature index 49c6dcbf37bcb..4706c9abf8878 100644 --- a/blocks/comments/tests/behat/block_comment_activity.feature +++ b/blocks/comments/tests/behat/block_comment_activity.feature @@ -10,7 +10,7 @@ Feature: Enable Block comments on an activity page and view comments | Course 1 | C1 | 0 | And the following "users" exist: | username | firstname | lastname | email | - | teacher1 | Teacher | Frist | teacher1@example.com | + | teacher1 | Teacher | First | teacher1@example.com | | student1 | Student | First | student1@example.com | And the following "course enrolments" exist: | user | course | role | diff --git a/blocks/comments/tests/behat/block_comment_dashboard.feature b/blocks/comments/tests/behat/block_comment_dashboard.feature index fcb19e7c8270d..f23ad352ddf34 100644 --- a/blocks/comments/tests/behat/block_comment_dashboard.feature +++ b/blocks/comments/tests/behat/block_comment_dashboard.feature @@ -10,7 +10,7 @@ Feature: Enable Block comments on the dashboard and view comments | Course 1 | C1 | 0 | And the following "users" exist: | username | firstname | lastname | email | - | teacher1 | Teacher | Frist | teacher1@example.com | + | teacher1 | Teacher | First | teacher1@example.com | Scenario: Add the comments block on the dashboard and add comments with Javascript disabled When I log in as "teacher1" diff --git a/completion/tests/behat/bulk_edit_activity_completion.feature b/completion/tests/behat/bulk_edit_activity_completion.feature index fadf12e8e7164..869f07ca57af8 100644 --- a/completion/tests/behat/bulk_edit_activity_completion.feature +++ b/completion/tests/behat/bulk_edit_activity_completion.feature @@ -10,7 +10,7 @@ Feature: Allow teachers to bulk edit activity completion rules in a course. | Course 1 | C1 | 0 | And the following "users" exist: | username | firstname | lastname | email | - | teacher1 | Teacher | Frist | teacher1@example.com | + | teacher1 | Teacher | First | teacher1@example.com | | student1 | Student | First | student1@example.com | And the following "course enrolments" exist: | user | course | role | diff --git a/completion/tests/behat/default_activity_completion.feature b/completion/tests/behat/default_activity_completion.feature index 92ddb9946fa84..779ec7b75ea38 100644 --- a/completion/tests/behat/default_activity_completion.feature +++ b/completion/tests/behat/default_activity_completion.feature @@ -14,7 +14,7 @@ Feature: Allow teachers to edit the default activity completion rules in a cours | Course 1 | C1 | 0 | And the following "users" exist: | username | firstname | lastname | email | - | teacher1 | Teacher | Frist | teacher1@example.com | + | teacher1 | Teacher | First | teacher1@example.com | | student1 | Student | First | student1@example.com | And the following "course enrolments" exist: | user | course | role | @@ -56,7 +56,7 @@ Feature: Allow teachers to edit the default activity completion rules in a cours | Course 1 | C1 | 0 | 1 | And the following "users" exist: | username | firstname | lastname | email | - | teacher1 | Teacher | Frist | teacher1@example.com | + | teacher1 | Teacher | First | teacher1@example.com | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | diff --git a/completion/tests/behat/enable_completion_on_pass_grade.feature b/completion/tests/behat/enable_completion_on_pass_grade.feature index cf9846663c48e..cb8b1b182aedd 100644 --- a/completion/tests/behat/enable_completion_on_pass_grade.feature +++ b/completion/tests/behat/enable_completion_on_pass_grade.feature @@ -7,7 +7,7 @@ Feature: Students will be marked as completed if they have achieved a passing gr | Course 1 | C1 | 0 | 1 | And the following "users" exist: | username | firstname | lastname | email | - | teacher1 | Teacher | Frist | teacher1@example.com | + | teacher1 | Teacher | First | teacher1@example.com | | student1 | Student | First | student1@example.com | | student2 | Student | Second | student2@example.com | And the following "course enrolments" exist: diff --git a/completion/tests/behat/enable_completion_on_view_and_grade.feature b/completion/tests/behat/enable_completion_on_view_and_grade.feature index 69e06a3f8c0a2..80a063a1bb401 100644 --- a/completion/tests/behat/enable_completion_on_view_and_grade.feature +++ b/completion/tests/behat/enable_completion_on_view_and_grade.feature @@ -8,7 +8,7 @@ Feature: Students will be marked as completed and pass/fail | Course 1 | C1 | 0 | 1 | And the following "users" exist: | username | firstname | lastname | email | - | teacher1 | Teacher | Frist | teacher1@example.com | + | teacher1 | Teacher | First | teacher1@example.com | | student1 | Student | First | student1@example.com | | student2 | Student | Second | student2@example.com | | student3 | Student | Third | student3@example.com | diff --git a/completion/tests/behat/enable_manual_complete_mark.feature b/completion/tests/behat/enable_manual_complete_mark.feature index 3884daa329d1f..2a178e1fb8991 100644 --- a/completion/tests/behat/enable_manual_complete_mark.feature +++ b/completion/tests/behat/enable_manual_complete_mark.feature @@ -11,7 +11,7 @@ Feature: Allow students to manually mark an activity as complete | Course 1 | C1 | 0 | And the following "users" exist: | username | firstname | lastname | email | - | teacher1 | Teacher | Frist | teacher1@example.com | + | teacher1 | Teacher | First | teacher1@example.com | | student1 | Student | First | student1@example.com | And the following "course enrolments" exist: | user | course | role | diff --git a/completion/tests/behat/passgrade_completion_criteria_gradeitem_visibility.feature b/completion/tests/behat/passgrade_completion_criteria_gradeitem_visibility.feature index 6fa91a783268b..7e5a26e9a645d 100644 --- a/completion/tests/behat/passgrade_completion_criteria_gradeitem_visibility.feature +++ b/completion/tests/behat/passgrade_completion_criteria_gradeitem_visibility.feature @@ -10,7 +10,7 @@ Feature: Students will be shown relevant completion state based on grade item vi | Course 1 | C1 | 0 | 1 | And the following "users" exist: | username | firstname | lastname | email | - | teacher1 | Teacher | Frist | teacher1@example.com | + | teacher1 | Teacher | First | teacher1@example.com | | student1 | Student | First | student1@example.com | | student2 | Student | Second | student2@example.com | And the following "course enrolments" exist: diff --git a/completion/tests/behat/restrict_activity_by_date.feature b/completion/tests/behat/restrict_activity_by_date.feature index a29c0b6847e70..c5ef4aff201ce 100644 --- a/completion/tests/behat/restrict_activity_by_date.feature +++ b/completion/tests/behat/restrict_activity_by_date.feature @@ -10,7 +10,7 @@ Feature: Restrict activity availability through date conditions | Course 1 | C1 | 0 | And the following "users" exist: | username | firstname | lastname | email | - | teacher1 | Teacher | Frist | teacher1@example.com | + | teacher1 | Teacher | First | teacher1@example.com | | student1 | Student | First | student1@example.com | And the following "course enrolments" exist: | user | course | role | diff --git a/completion/tests/behat/restrict_activity_by_grade.feature b/completion/tests/behat/restrict_activity_by_grade.feature index 5a85f383a9106..9f5933a65d07d 100644 --- a/completion/tests/behat/restrict_activity_by_grade.feature +++ b/completion/tests/behat/restrict_activity_by_grade.feature @@ -11,7 +11,7 @@ Feature: Restrict activity availability through grade conditions | Course 1 | C1 | 0 | And the following "users" exist: | username | firstname | lastname | email | - | teacher1 | Teacher | Frist | teacher1@example.com | + | teacher1 | Teacher | First | teacher1@example.com | | student1 | Student | First | student1@example.com | And the following "course enrolments" exist: | user | course | role | diff --git a/completion/tests/behat/restrict_section_availability.feature b/completion/tests/behat/restrict_section_availability.feature index e9fffcdeb766f..fac52c7296e9b 100644 --- a/completion/tests/behat/restrict_section_availability.feature +++ b/completion/tests/behat/restrict_section_availability.feature @@ -10,7 +10,7 @@ Feature: Restrict sections availability through completion or grade conditions | Course 1 | C1 | 0 | And the following "users" exist: | username | firstname | lastname | email | - | teacher1 | Teacher | Frist | teacher1@example.com | + | teacher1 | Teacher | First | teacher1@example.com | | student1 | Student | First | student1@example.com | And the following "course enrolments" exist: | user | course | role | diff --git a/mod/label/tests/behat/label_idnumber.feature b/mod/label/tests/behat/label_idnumber.feature index e1b0c46843feb..9d4daba1df8fc 100644 --- a/mod/label/tests/behat/label_idnumber.feature +++ b/mod/label/tests/behat/label_idnumber.feature @@ -11,7 +11,7 @@ Feature: set label idnumber | Test | C1 | 0 | And the following "users" exist: | username | firstname | lastname | email | - | teacher | Teacher | Frist | teacher1@example.com | + | teacher | Teacher | First | teacher1@example.com | | student | Student | First | student1@example.com | And the following "course enrolments" exist: | user | course | role | diff --git a/mod/label/tests/behat/label_visibility.feature b/mod/label/tests/behat/label_visibility.feature index a4ea75942d454..ae856fe9c8784 100644 --- a/mod/label/tests/behat/label_visibility.feature +++ b/mod/label/tests/behat/label_visibility.feature @@ -11,7 +11,7 @@ Feature: Check label visibility works | Test | C1 | 0 | And the following "users" exist: | username | firstname | lastname | email | - | teacher | Teacher | Frist | teacher1@example.com | + | teacher | Teacher | First | teacher1@example.com | | student | Student | First | student1@example.com | And the following "course enrolments" exist: | user | course | role | From 6483b5d68fa45e03ef9686066edf3d0641ebbd13 Mon Sep 17 00:00:00 2001 From: Simey Lameze Date: Tue, 9 Jan 2024 08:25:07 +0800 Subject: [PATCH 007/134] MDL-80340 behat: make query to fetch h5p attempts less strict --- mod/h5pactivity/lib.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mod/h5pactivity/lib.php b/mod/h5pactivity/lib.php index 91522009e4a62..6c0b876c49727 100644 --- a/mod/h5pactivity/lib.php +++ b/mod/h5pactivity/lib.php @@ -574,7 +574,7 @@ function h5pactivity_print_recent_activity($course, bool $viewfullnames, int $ti JOIN {course_modules} cm ON cm.instance = h5p.id JOIN {modules} md ON md.id = cm.module JOIN {user} u ON u.id = h5pa.userid - WHERE h5pa.timemodified > ? + WHERE h5pa.timemodified >= ? AND h5p.course = ? AND md.name = ? ORDER BY h5pa.timemodified ASC"; @@ -659,7 +659,7 @@ function h5pactivity_get_recent_mod_activity(array &$activities, int &$index, in JOIN {course_modules} cm ON cm.instance = h5p.id JOIN {modules} md ON md.id = cm.module JOIN {user} u ON u.id = h5pa.userid $groupjoin - WHERE h5pa.timemodified > :timestart + WHERE h5pa.timemodified >= :timestart AND h5p.id = :cminstance $userselect $groupselect AND cm.id = :cmid ORDER BY h5pa.timemodified ASC"; From 73cf6fb65c3b8e087c067cce123045401003e353 Mon Sep 17 00:00:00 2001 From: Sara Arjona Date: Tue, 9 Jan 2024 16:39:36 +0100 Subject: [PATCH 008/134] MDL-73639 behat: Fix the failure with the Enrol button The button at the top of the page is hidden a few seconds by the un-enrolment confirmation dialogue. Instead of waiting a few seconds or reloading the page, the button at the bottom is clicked instead. --- grade/tests/behat/grade_recovery_settings.feature | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/grade/tests/behat/grade_recovery_settings.feature b/grade/tests/behat/grade_recovery_settings.feature index 7a46fc3c05422..31f16a15f0af5 100644 --- a/grade/tests/behat/grade_recovery_settings.feature +++ b/grade/tests/behat/grade_recovery_settings.feature @@ -33,7 +33,8 @@ Feature: Admin can set Recover grades default setting And I navigate to course participants And I click on "Unenrol" "icon" in the "Student One" "table_row" And I click on "Unenrol" "button" in the "Unenrol" "dialogue" - And I press "Enrol users" + # The button at the top is hidden by the un-enrolment confirmation dialogue so the button at the bottom is clicked instead. + And I click on "Enrol users" "button" in the "div.justify-content-end .enrolusersbutton" "css_element" And I set the field "Select users" to "student1" # Confirm the "Recover user's old grades if possible" checkbox state based on Recover grades default setting When I click on "Show more..." "link" From e4eb16bd9f35223095b13d1fdda7be8f03788a79 Mon Sep 17 00:00:00 2001 From: Angelia Dela Cruz Date: Tue, 19 Dec 2023 15:08:31 +0800 Subject: [PATCH 009/134] MDL-80452 mod_h5pactivity: Behat for H5Pactivity duplication and delete --- .../duplicate_delete_h5pactivity.feature | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 mod/h5pactivity/tests/behat/duplicate_delete_h5pactivity.feature diff --git a/mod/h5pactivity/tests/behat/duplicate_delete_h5pactivity.feature b/mod/h5pactivity/tests/behat/duplicate_delete_h5pactivity.feature new file mode 100644 index 0000000000000..de55da340f857 --- /dev/null +++ b/mod/h5pactivity/tests/behat/duplicate_delete_h5pactivity.feature @@ -0,0 +1,40 @@ +@mod @mod_h5pactivity +Feature: Duplicate and delete a h5pactivity + In order to quickly create and delete h5p activities + As a teacher + I need to duplicate or delete h5pactivity inside the same course + + Background: + Given the following "courses" exist: + | fullname | shortname | + | Course 1 | C1 | + And the following "users" exist: + | username | firstname | lastname | email | + | teacher1 | Teacher | 1 | teacher1@example.com | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + + Scenario: Duplicate and delete h5p activity + Given the following "activities" exist: + | activity | course | name | packagefilepath | + | h5pactivity | C1 | H5P Activity 1 | h5p/tests/fixtures/filltheblanks.h5p | + And I am on the "H5P Activity 1" "h5pactivity activity" page logged in as teacher1 + # Initial confirmation that no error occurs when viewing h5p activity + And I should see "This content is displayed in preview mode. No attempt tracking will be stored." + And I am on "Course 1" course homepage with editing mode on + # Duplicate the h5p activity + When I duplicate "H5P Activity 1" activity + # Confirm that h5p activity was duplicated successfully + Then I should see "H5P Activity 1 (copy)" + And I am on the "H5P Activity 1 (copy)" "h5pactivity activity" page + # Confirm there are no errors when viewing duplicate h5p activity + And I should see "This content is displayed in preview mode. No attempt tracking will be stored." + And I am on the "Course 1" course page + # Delete the duplicate h5p activity + And I delete "H5P Activity 1 (copy)" activity + # Confirm duplicate was deleted successfully + And I should not see "H5P Activity 1 (copy)" + And I am on the "H5P Activity 1" "h5pactivity activity" page + # Confirm there are no errors on the original h5p activity after deleting the duplicate + And I should see "This content is displayed in preview mode. No attempt tracking will be stored." From a9cf9d5519b16cccea3d5f13cdc644d5ac7b7842 Mon Sep 17 00:00:00 2001 From: Ilya Tregubov Date: Fri, 12 Jan 2024 09:11:53 +0800 Subject: [PATCH 010/134] weekly release 4.1.8+ --- version.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.php b/version.php index 51dcee6632d75..924c61f647dc4 100644 --- a/version.php +++ b/version.php @@ -29,9 +29,9 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2022112808.00; // 20221128 = branching date YYYYMMDD - do not modify! +$version = 2022112808.01; // 20221128 = branching date YYYYMMDD - do not modify! // RR = release increments - 00 in DEV branches. // .XX = incremental changes. -$release = '4.1.8 (Build: 20231222)'; // Human-friendly version name +$release = '4.1.8+ (Build: 20240112)'; // Human-friendly version name $branch = '401'; // This version's branch. $maturity = MATURITY_STABLE; // This version's maturity level. From 4b8a001bb0e3bf0c81a6338698a90414f0baa123 Mon Sep 17 00:00:00 2001 From: "Eloy Lafuente (stronk7)" Date: Sat, 13 Jan 2024 18:13:52 +0100 Subject: [PATCH 011/134] MDL-80591 phpunit: delegate run to phpunit binary Instead of manually including composer's auto-loading stuff and then run PHPUnit\TextUI\Command::main(), now we are using the PHPUnit "binary". That way behaviour should be 100% the same than running vendor/bin/phpunit (recommended instead of util.php --run). Also, note that, for other util.php commands, we still need composer's auto-loading to happen, so we have moved it immediately after the --run command. Finally, a few more file_exists() conditions have been added to ensure that PHPUnit is properly installed. Redundant but... --- admin/tool/phpunit/cli/util.php | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/admin/tool/phpunit/cli/util.php b/admin/tool/phpunit/cli/util.php index 6bda8483c5e41..8d60ff3c95227 100644 --- a/admin/tool/phpunit/cli/util.php +++ b/admin/tool/phpunit/cli/util.php @@ -50,12 +50,10 @@ ) ); -if (file_exists(__DIR__.'/../../../../vendor/phpunit/phpunit/composer.json')) { - // Composer packages present. - require_once(__DIR__.'/../../../../vendor/autoload.php'); - -} else { - // Note: installation via PEAR is not supported any more. +// Basic check to see if phpunit is installed. +if (!file_exists(__DIR__.'/../../../../vendor/phpunit/phpunit/composer.json') || + !file_exists(__DIR__.'/../../../../vendor/bin/phpunit') || + !file_exists(__DIR__.'/../../../../vendor/autoload.php')) { phpunit_bootstrap_error(PHPUNIT_EXITCODE_PHPUNITMISSING); } @@ -74,12 +72,13 @@ } } $_SERVER['argv'] = array_values($_SERVER['argv']); - PHPUnit\TextUI\Command::main(); + require(__DIR__ . '/../../../../vendor/bin/phpunit'); exit(0); } define('PHPUNIT_UTIL', true); +require(__DIR__.'/../../../../vendor/autoload.php'); require(__DIR__ . '/../../../../lib/phpunit/bootstrap.php'); // from now on this is a regular moodle CLI_SCRIPT From 37101f0698c52d2412b409b9841e3af6195cf8df Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Mon, 15 Jan 2024 11:16:20 +0800 Subject: [PATCH 012/134] MDL-80266 mod_forum: Reset subscription caches between behat tests --- mod/forum/tests/behat/behat_mod_forum.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/mod/forum/tests/behat/behat_mod_forum.php b/mod/forum/tests/behat/behat_mod_forum.php index acbd688ad4674..8349822e0a4f4 100644 --- a/mod/forum/tests/behat/behat_mod_forum.php +++ b/mod/forum/tests/behat/behat_mod_forum.php @@ -27,7 +27,8 @@ require_once(__DIR__ . '/../../../../lib/behat/behat_base.php'); -use Behat\Gherkin\Node\TableNode as TableNode; +use Behat\Gherkin\Node\TableNode; + /** * Forum-related steps definitions. * @@ -37,6 +38,15 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class behat_mod_forum extends behat_base { + /** + * Reset forum caches between tests. + * + * @BeforeScenario @mod_forum + */ + public function reset_forum_caches(): void { + \mod_forum\subscriptions::reset_discussion_cache(); + \mod_forum\subscriptions::reset_forum_cache(); + } /** * Adds a topic to the forum specified by it's name. Useful for the Announcements and blog-style forums. From 8c7983a43f95145e2f9b92e092f136044e557575 Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Thu, 18 Jan 2024 16:45:49 +0000 Subject: [PATCH 013/134] MDL-80667 task: preserve environment when running tasks from web. For sites that rely on environment variables to set configuration, ensure they are also present when executing task process. Co-authored-by: Olivier Wenger --- lib/classes/task/manager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/classes/task/manager.php b/lib/classes/task/manager.php index e636bafc28ddd..8a84695137545 100644 --- a/lib/classes/task/manager.php +++ b/lib/classes/task/manager.php @@ -1412,7 +1412,7 @@ public static function passthru_via_mtrace(string $command) { 2 => ['pipe', 'w'], // STDERR. ]; flush(); - $process = proc_open($command, $descriptorspec, $pipes, realpath('./'), []); + $process = proc_open($command, $descriptorspec, $pipes, realpath('./')); if (is_resource($process)) { while ($s = fgets($pipes[1])) { mtrace($s, ''); From 627177855a5163c0dfa6afcbef6c3814d6b99885 Mon Sep 17 00:00:00 2001 From: Huong Nguyen Date: Fri, 19 Jan 2024 11:24:39 +0700 Subject: [PATCH 014/134] weekly release 4.1.8+ --- version.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.php b/version.php index 924c61f647dc4..f31da0af20429 100644 --- a/version.php +++ b/version.php @@ -29,9 +29,9 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2022112808.01; // 20221128 = branching date YYYYMMDD - do not modify! +$version = 2022112808.02; // 20221128 = branching date YYYYMMDD - do not modify! // RR = release increments - 00 in DEV branches. // .XX = incremental changes. -$release = '4.1.8+ (Build: 20240112)'; // Human-friendly version name +$release = '4.1.8+ (Build: 20240119)'; // Human-friendly version name $branch = '401'; // This version's branch. $maturity = MATURITY_STABLE; // This version's maturity level. From f7f40e54e62b6a9561dd1625ffcb987ca1eb7c28 Mon Sep 17 00:00:00 2001 From: Angelia Dela Cruz Date: Mon, 11 Dec 2023 15:37:26 +0800 Subject: [PATCH 015/134] MDL-80377 block: Behat confirming folder file in recent activity block --- .../tests/behat/recent_activity.feature | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 mod/folder/tests/behat/recent_activity.feature diff --git a/mod/folder/tests/behat/recent_activity.feature b/mod/folder/tests/behat/recent_activity.feature new file mode 100644 index 0000000000000..158461a8d89a6 --- /dev/null +++ b/mod/folder/tests/behat/recent_activity.feature @@ -0,0 +1,40 @@ +@mod @mod_folder @block @block_recent_activity +Feature: Files added in folder activity are visible in the recent activity block + In order to view and download folder activity from recent activity block + As a teacher + I should be able to create folder activity with contents + + Background: + Given the following "courses" exist: + | fullname | shortname | + | Course 1 | C1 | + And the following "blocks" exist: + | blockname | contextlevel | reference | pagetypepattern | defaultregion | + | recent_activity | Course | C1 | course-view-* | side-pre | + And the following "activities" exist: + | activity | course | name | + | folder | C1 | Folder 1 | + + @_file_upload @javascript + Scenario: Files added in folder activity are visible in recent activity block + Given I am on the "Folder 1" "folder activity" page logged in as admin + And I click on "Edit" "button" + # Upload different file types in folder resource + And I upload "lib/tests/fixtures/empty.txt" file to "Files" filemanager + And I upload "lib/tests/fixtures/gd-logo.png" file to "Files" filemanager + And I press "Save changes" + # Confirm folder activity and files within the folder are visible in Recent activity block + When I am on the "Course 1" course page + Then I should see "Folder 1" in the "Recent activity" "block" + And I should see "empty.txt" in the "Recent activity" "block" + And I should see "gd-logo.png" in the "Recent activity" "block" + And I click on "Full report of recent activity..." "link" + # Confirm files within folder activity are visible in the full report + And "Folder 1" "link" should exist + And "empty.txt" "link" should exist + And "gd-logo.png" "link" should exist + And "//img[@alt='empty.txt']" "xpath_element" should exist + And "//img[contains(@src, 'preview=tinyicon')]" "xpath_element" should exist + # Confirm files are downloadable + And following "empty.txt" should download between "1" and "3000" bytes + And following "gd-logo.png" should download between "1" and "3000" bytes From 66e8ce9976bae772047314987804485139e63b88 Mon Sep 17 00:00:00 2001 From: Ferran Recio Date: Wed, 13 Dec 2023 09:52:14 +0100 Subject: [PATCH 016/134] MDL-79029 behat: new add activity steps --- course/tests/behat/behat_course.php | 116 ++++++++++++++++++++++++++-- course/upgrade.txt | 4 + 2 files changed, 115 insertions(+), 5 deletions(-) diff --git a/course/tests/behat/behat_course.php b/course/tests/behat/behat_course.php index 9a21a979e12bc..8639f2a381282 100644 --- a/course/tests/behat/behat_course.php +++ b/course/tests/behat/behat_course.php @@ -202,7 +202,7 @@ public function i_add_to_section($activity, $section) { if ($section) { // Section 1 represents the contents on the frontpage. $sectionxpath = "//body[@id='page-site-index']" . - "/descendant::div[contains(concat(' ',normalize-space(@class),' '),' sitetopic ')]"; + "/descendant::div[contains(concat(' ',normalize-space(@class),' '),' sitetopic ')]"; } else { // Section 0 represents "Site main menu" block. $sectionxpath = "//*[contains(concat(' ',normalize-space(@class),' '),' block_site_main_menu ')]"; @@ -224,10 +224,116 @@ public function i_add_to_section($activity, $section) { // Clicks the selected activity if it exists. $activityliteral = behat_context_helper::escape(ucfirst($activity)); $activityxpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' modchooser ')]" . - "/descendant::div[contains(concat(' ', normalize-space(@class), ' '), ' optioninfo ')]" . - "/descendant::div[contains(concat(' ', normalize-space(@class), ' '), ' optionname ')]" . - "[normalize-space(.)=$activityliteral]" . - "/parent::a"; + "/descendant::div[contains(concat(' ', normalize-space(@class), ' '), ' optioninfo ')]" . + "/descendant::div[contains(concat(' ', normalize-space(@class), ' '), ' optionname ')]" . + "[normalize-space(.)=$activityliteral]" . + "/parent::a"; + + $this->execute('behat_general::i_click_on', [$activityxpath, 'xpath']); + } + + /** + * Adds the selected activity/resource filling the form data with the specified field/value pairs. + * + * Sections 0 and 1 are also allowed on frontpage. + * + * @Given I add a :activity activity to course :coursefullname section :sectionnum and I fill the form with: + * @Given I add an :activity activity to course :coursefullname section :sectionnum and I fill the form with: + * @param string $activity The activity name + * @param string $coursefullname The course full name of the course. + * @param int $section The section number + * @param TableNode $data The activity field/value data + */ + public function i_add_to_course_section_and_i_fill_the_form_with($activity, $coursefullname, $section, TableNode $data) { + + // Add activity to section. + $this->execute( + "behat_course::i_add_to_course_section", + [$this->escape($activity), $this->escape($coursefullname), $this->escape($section)] + ); + + // Wait to be redirected. + $this->execute('behat_general::wait_until_the_page_is_ready'); + + // Set form fields. + $this->execute("behat_forms::i_set_the_following_fields_to_these_values", $data); + + // Save course settings. + $this->execute("behat_forms::press_button", get_string('savechangesandreturntocourse')); + } + + /** + * Open a add activity form page. + * + * @Given I add a :activity activity to course :coursefullname section :sectionnum + * @Given I add an :activity activity to course :coursefullname section :sectionnum + * @throws coding_exception + * @param string $activity The activity name. + * @param string $coursefullname The course full name of the course. + * @param string $sectionnum The section number. + */ + public function i_add_to_course_section(string $activity, string $coursefullname, string $sectionnum): void { + $addurl = new moodle_url('/course/modedit.php', [ + 'add' => $activity, + 'course' => $this->get_course_id($coursefullname), + 'section' => intval($sectionnum), + ]); + $this->execute('behat_general::i_visit', [$addurl]); + } + + /** + * Opens the activity chooser and opens the activity/resource link form page. + * + * Sections 0 and 1 are also allowed on frontpage. + * + * This step require javascript enabled and it is used mainly to click activities or resources by name, + * not by plugin name. Use the standard behat_course::i_add_to_course_section step instead unless the + * plugin create extra entries into the activity chooser (like LTI). + * + * @Given I add a :activityname to section :sectionnum using the activity chooser + * @Given I add an :activityname to section :sectionnum using the activity chooser + * @throws ElementNotFoundException Thrown by behat_base::find + * @param string $activityname + * @param int $sectionnum + */ + public function i_add_to_section_using_the_activity_chooser($activityname, $sectionnum) { + + $this->require_javascript('Please use the \'the following "activity" exists:\' data generator instead.'); + + if ($this->getSession()->getPage()->find('css', 'body#page-site-index') && (int) $sectionnum <= 1) { + // We are on the frontpage. + if ($sectionnum) { + // Section 1 represents the contents on the frontpage. + $sectionxpath = "//body[@id='page-site-index']" . + "/descendant::div[contains(concat(' ',normalize-space(@class),' '),' sitetopic ')]"; + } else { + // Section 0 represents "Site main menu" block. + $sectionxpath = "//*[contains(concat(' ',normalize-space(@class),' '),' block_site_main_menu ')]"; + } + } else { + // We are inside the course. + $sectionxpath = "//li[@id='section-" . $sectionnum . "']"; + } + + // Clicks add activity or resource section link. + $sectionnode = $this->find('xpath', $sectionxpath); + $this->execute( + 'behat_general::i_click_on_in_the', + [ + "//button[@data-action='open-chooser' and not(@data-beforemod)]", + 'xpath', + $sectionnode, + 'NodeElement', + ] + ); + + // Clicks the selected activity if it exists. + $activityliteral = behat_context_helper::escape(ucfirst($activityname)); + $activityxpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' modchooser ')]" . + "/descendant::div[contains(concat(' ', normalize-space(@class), ' '), ' optioninfo ')]" . + "/descendant::div[contains(concat(' ', normalize-space(@class), ' '), ' optionname ')]" . + "[normalize-space(.)=$activityliteral]" . + "/parent::a"; $this->execute('behat_general::i_click_on', [$activityxpath, 'xpath']); } diff --git a/course/upgrade.txt b/course/upgrade.txt index c066c42c27967..198a1c5854ee3 100644 --- a/course/upgrade.txt +++ b/course/upgrade.txt @@ -2,6 +2,10 @@ This files describes API changes in /course/*, information provided here is intended especially for developers. === 4.1.7 === +* New behat steps backported to stable versions: + - I add a :activityname to section :sectionnum using the activity chooser + - I add a :activitypluginname activity to course :coursefullname section :sectionnum + - I add a :activitypluginname activity to course :coursefullname section :sectionnum and I fill the form with: * set_coursemodule_visible() has a new $rebuildcache parameter. If this is being called multiple times in the same request, consider passing `false` for this parameter and rebuilding the cache once after all the course modules have been updated. See course_update_section() for an example. From 5661849a021b34038ce41959fc948e5e652cfedc Mon Sep 17 00:00:00 2001 From: Ferran Recio Date: Thu, 18 Jan 2024 10:47:12 +0100 Subject: [PATCH 017/134] MDL-79029 behat: optimize behat tests Replace some add activity to section steps to the new add activity to course section so they are faster and do not require javascript. --- .../tests/behat/add_filetypes.feature | 3 +-- course/tests/behat/add_activities.feature | 20 ++++++++----------- .../videojs/tests/behat/modules.feature | 2 +- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/admin/tool/filetypes/tests/behat/add_filetypes.feature b/admin/tool/filetypes/tests/behat/add_filetypes.feature index 90e8d21adcc8d..de8d5550b770f 100644 --- a/admin/tool/filetypes/tests/behat/add_filetypes.feature +++ b/admin/tool/filetypes/tests/behat/add_filetypes.feature @@ -120,8 +120,7 @@ Feature: Add customised file types | Custom description | Froggy file | And I press "Save changes" # Create a resource activity and add it to a course - And I am on "Course 1" course homepage with editing mode on - When I add a "File" to section "1" + When I add a resource activity to course "Course 1" section "1" And I set the following fields to these values: | Name | An example of customised file type | | Description | File description | diff --git a/course/tests/behat/add_activities.feature b/course/tests/behat/add_activities.feature index 47d76d7ec0cc5..11e4c4af02af3 100644 --- a/course/tests/behat/add_activities.feature +++ b/course/tests/behat/add_activities.feature @@ -11,9 +11,8 @@ Feature: Add activities to courses @javascript Scenario: Add an activity to a course - Given I am on the "Course 1" Course page logged in as admin - And I am on "Course 1" course homepage with editing mode on - When I add a "Database" to section "3" and I fill the form with: + Given I log in as "admin" + When I add a data activity to course "Course 1" section "3" and I fill the form with: | Name | Test name | | Description | Test database description | | ID number | TESTNAME | @@ -32,9 +31,8 @@ Feature: Add activities to courses @javascript Scenario: Add an activity supplying only the name - Given I am on the "Course 1" Course page logged in as admin - And I am on "Course 1" course homepage with editing mode on - When I add a "Database" to section "3" and I fill the form with: + Given I log in as "admin" + When I add a data activity to course "Course 1" section "3" and I fill the form with: | Name | Test name | Then I should see "Test name" @@ -42,9 +40,8 @@ Feature: Add activities to courses Scenario: Set activity description to required then add an activity supplying only the name Given the following config values are set as admin: | requiremodintro | 1 | - And I am on the "Course 1" Course page logged in as admin - And I am on "Course 1" course homepage with editing mode on - And I add a "Database" to section "3" and I fill the form with: + And I log in as "admin" + And I add a data activity to course "Course 1" section "3" and I fill the form with: | Name | Test name | Then I should see "Required" @@ -53,9 +50,8 @@ Feature: Add activities to courses Given the following "user preferences" exist: | user | preference | value | | admin | htmleditor | textarea | - And I am logged in as admin - And I am on "Course 1" course homepage with editing mode on - When I add a "Database" to section "3" + And I log in as "admin" + When I add a data activity to course "Course 1" section "3" Then the field "Description format" matches value "0" @javascript diff --git a/media/player/videojs/tests/behat/modules.feature b/media/player/videojs/tests/behat/modules.feature index 90da7466d2004..e02bcd63d25d3 100644 --- a/media/player/videojs/tests/behat/modules.feature +++ b/media/player/videojs/tests/behat/modules.feature @@ -24,7 +24,7 @@ Feature: Embed videos without the media filter @javascript Scenario: Add a video as a File resource. Make sure media filters work - When I add a "File" to section "1" + When I add a "File" to section "1" using the activity chooser And I set the following fields to these values: | Name | Video File | | Description | Example of a video file | From a86228f547cdc078227a7c6acfd5baf84310b7a6 Mon Sep 17 00:00:00 2001 From: Angelia Dela Cruz Date: Thu, 7 Dec 2023 13:15:05 +0800 Subject: [PATCH 018/134] MDL-80339 mod_lesson: Behat to confirm grade visibility in lesson --- .../behat/lesson_informations_at_end.feature | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/mod/lesson/tests/behat/lesson_informations_at_end.feature b/mod/lesson/tests/behat/lesson_informations_at_end.feature index 63bcbee46fa8a..5f3c12bf83c18 100644 --- a/mod/lesson/tests/behat/lesson_informations_at_end.feature +++ b/mod/lesson/tests/behat/lesson_informations_at_end.feature @@ -91,3 +91,22 @@ Feature: In a lesson activity, if custom scoring is not enabled, student should Then I should see "Congratulations - end of lesson reached" And I should see "Your score is 1 (out of 1)." And I should see "Your current grade is Excellent" + + Scenario: Verify lesson summary with grade type set to none + Given I am on the "Test lesson name" "lesson activity editing" page logged in as teacher1 + # Since by default the grade type is point, change it to None. + And I set the field "grade[modgrade_type]" to "None" + And I press "Save and return to course" + # Answer the question incorrectly. + When I am on the "Test lesson name" "lesson activity" page logged in as student1 + And I press "Next page" + And I set the following fields to these values: + | Your answer | 1 | + And I press "Submit" + And I press "Continue" + # Confirm the information displayed at the end of lesson when grade type is set to None. + Then I should see "Congratulations - end of lesson reached" + And I should see "Number of questions answered: 1" + And I should see "Number of correct answers: 0" + And I should see "Your score is 0 (out of 1)." + And I should not see "Your current grade is 0.0 out of 75" From 603c794190ce5c655f1f3b7a1d1428167e78358e Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Tue, 23 Jan 2024 09:14:27 +0000 Subject: [PATCH 019/134] MDL-80512 tool_task: absolute path to required library file. --- admin/tool/task/schedule_task.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/admin/tool/task/schedule_task.php b/admin/tool/task/schedule_task.php index 84097ba0571c7..3e8a59475dc1c 100644 --- a/admin/tool/task/schedule_task.php +++ b/admin/tool/task/schedule_task.php @@ -87,7 +87,8 @@ // Prepare to handle output via mtrace. echo html_writer::start_tag('pre'); -require('lib.php'); + +require_once("{$CFG->dirroot}/{$CFG->admin}/tool/task/lib.php"); $CFG->mtrace_wrapper = 'tool_task_mtrace_wrapper'; // Run the specified task (this will output an error if it doesn't exist). From 35a2a34a7d9c53c6bb0a2e30290ff2ebb2e778d2 Mon Sep 17 00:00:00 2001 From: Angelia Dela Cruz Date: Fri, 3 Nov 2023 11:56:22 +0800 Subject: [PATCH 020/134] MDL-79940 mod_workshop: Behat to confirm review button for reviewers --- .../workshop_assessment_navigation.feature | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 mod/workshop/tests/behat/workshop_assessment_navigation.feature diff --git a/mod/workshop/tests/behat/workshop_assessment_navigation.feature b/mod/workshop/tests/behat/workshop_assessment_navigation.feature new file mode 100644 index 0000000000000..baa31487e66da --- /dev/null +++ b/mod/workshop/tests/behat/workshop_assessment_navigation.feature @@ -0,0 +1,67 @@ +@mod @mod_workshop +Feature: Workshop assessment navigation display for reviewers + As a reviewer + I need to be able to see the "Save and show next" button during assessment + + Background: + Given the following "users" exist: + | username | firstname | lastname | email | + | teacher1 | Teacher | 1 | teacher1@example.com | + | student1 | Student | 1 | student1@example.com | + | student2 | Student | 2 | student2@example.com | + | student3 | Student | 3 | student3@example.com | + And the following "courses" exist: + | fullname | shortname | + | Course 1 | C1 | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + | student1 | C1 | student | + | student2 | C1 | student | + | student3 | C1 | student | + And the following "activities" exist: + | activity | name | course | submissiontypetext | + | workshop | Workshop 1 | C1 | 2 | + + Scenario: Reviewers can navigate between submissions using save and show next button + Given I am on the "Course 1" course page logged in as teacher1 + And I edit assessment form in workshop "Workshop 1" as: + | id_description__idx_0_editor | Aspect 1 | + | id_description__idx_1_editor | Aspect 2 | + And I change phase in workshop "Workshop 1" to "Submission phase" + # Add a submission for students to be assessed. + And I am on the "Workshop 1" "workshop activity" page logged in as student2 + And I add a submission in workshop "Workshop 1" as: + | Title | Student 2 submission | + | Submission content | Submission content | + And I am on the "Workshop 1" "workshop activity" page logged in as student3 + And I add a submission in workshop "Workshop 1" as: + | Title | Student 3 submission | + | Submission content | Submission content | + # Allocate student1 as reviewer for other student submissions. + And I am on the "Workshop 1" "workshop activity" page logged in as teacher1 + And I allocate submissions in workshop "Workshop 1" as: + | Participant | Reviewer | + | Student 2 | Student 1 | + | Student 3 | Student 1 | + And I change phase in workshop "Workshop 1" to "Assessment phase" + When I am on the "Workshop 1" "workshop activity" page logged in as student1 + And I press "Assess" + # Confirm student1 can see "Save and show next" button while assessing the first submission. + Then "Save and show next" "button" should exist + And I set the following fields to these values: + | Grade for Aspect 1 | 6 | + | Grade for Aspect 2 | 7 | + | Feedback for the author | Keep it up | + And I press "Save and show next" + # Confirm student1 can't see "Save and show next" button while assessing the last submission. + And "Save and show next" "button" should not exist + And "Save and close" "button" should exist + And I set the following fields to these values: + | Grade for Aspect 1 | 7 | + | Grade for Aspect 2 | 6 | + | Feedback for the author | Keep it up | + And I press "Save and close" + # Confirm that the corresponding buttons are not displayed after pressing "Save and close". + And "Save and show next" "button" should not exist + And "Save and close" "button" should not exist From ca7b24f6d0c0c754b726327c0e970c0db6bc5983 Mon Sep 17 00:00:00 2001 From: Simey Lameze Date: Thu, 18 Jan 2024 15:35:47 +0800 Subject: [PATCH 021/134] MDL-80656 behat: remove unnecessary activity duplication steps --- .../behat/lesson_informations_at_end.feature | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/mod/lesson/tests/behat/lesson_informations_at_end.feature b/mod/lesson/tests/behat/lesson_informations_at_end.feature index 63bcbee46fa8a..913f2c8461cd3 100644 --- a/mod/lesson/tests/behat/lesson_informations_at_end.feature +++ b/mod/lesson/tests/behat/lesson_informations_at_end.feature @@ -34,19 +34,9 @@ Feature: In a lesson activity, if custom scoring is not enabled, student should | Maximum grade | 75 | | Custom scoring | No | And I press "Save and display" - And I am on "Course 1" course homepage with editing mode on - And I duplicate "Test lesson name" activity - And I wait until section "1" is available - And I am on the "Test lesson name (copy)" "lesson activity editing" page - And I set the field "Name" to "Test lesson name 2" - And I set the field "grade[modgrade_type]" to "Scale" - And I set the field "Scale" to "Test Scale" - And I press "Save and return to course" - And I log out - And I log in as "student1" Scenario: Informations at end of lesson if custom scoring not enabled - Given I am on the "Test lesson name" "lesson activity" page + Given I am on the "Test lesson name" "lesson activity" page logged in as student1 And I should see "First page contents" When I press "Next page" And I should see "1 + 1?" @@ -65,7 +55,7 @@ Feature: In a lesson activity, if custom scoring is not enabled, student should Given the following "language customisations" exist: | component | stringid | value | | core_langconfig | decsep | # | - And I am on the "Test lesson name" "lesson activity" page + And I am on the "Test lesson name" "lesson activity" page logged in as student1 And I should see "First page contents" When I press "Next page" And I should see "1 + 1?" @@ -81,8 +71,12 @@ Feature: In a lesson activity, if custom scoring is not enabled, student should And I should see "Your current grade is 0#0 out of 75" Scenario: Current grade is displayed at end of lesson when grade type is set to scale - Given I am on the "Test lesson name 2" "lesson activity" page - When I press "Next page" + Given I am on the "Test lesson name" "lesson activity editing" page logged in as teacher1 + And I set the field "grade[modgrade_type]" to "Scale" + And I set the field "Scale" to "Test Scale" + And I press "Save and return to course" + When I am on the "Test lesson name" "lesson activity" page logged in as student1 + And I press "Next page" And I should see "1 + 1?" And I set the following fields to these values: | Your answer | 2 | From 3a779663aa4e9a0a1e0dc082a4916692892e70e3 Mon Sep 17 00:00:00 2001 From: Angelia Dela Cruz Date: Thu, 24 Aug 2023 16:59:44 +0800 Subject: [PATCH 022/134] MDL-79159 mod_lesson: Behat coverage for lesson question max attempts --- .../lesson_question_max_attempts.feature | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 mod/lesson/tests/behat/lesson_question_max_attempts.feature diff --git a/mod/lesson/tests/behat/lesson_question_max_attempts.feature b/mod/lesson/tests/behat/lesson_question_max_attempts.feature new file mode 100644 index 0000000000000..01683094fc087 --- /dev/null +++ b/mod/lesson/tests/behat/lesson_question_max_attempts.feature @@ -0,0 +1,58 @@ +@mod @mod_lesson +Feature: Set the maximum number of attempts for lesson activity question + In order to limit the number of attempts a student can take for lesson activity question + As a teacher + I should be able to set the maximum number of attempts for a lesson activity question + + Background: + Given the following "users" exist: + | username | firstname | lastname | email | + | teacher1 | Teacher | 1 | teacher1@example.com | + | student1 | Student | 1 | student1@example.com | + And the following "courses" exist: + | fullname | shortname | + | Course 1 | C1 | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + | student1 | C1 | student | + + Scenario: Lesson activity question maximum number of attempts can be set + Given the following "activities" exist: + | activity | name | course | modattempts | review | maxattempts | feedback | + | lesson | Test lesson name | C1 | 0 | 1 | 2 | 1 | + And the following "mod_lesson > pages" exist: + | lesson | qtype | title | content | + | Test lesson name | truefalse | Question 1 | Dolphins are mammals. | + | Test lesson name | truefalse | Question 2 | Trees are plants. | + And the following "mod_lesson > answers" exist: + | page | answer | response | jumpto | score | + | Question 1 | True | Right | Next page | 1 | + | Question 1 | False | Wrong | This page | 0 | + | Question 2 | True | Right | Next page | 1 | + | Question 2 | False | Wrong | This page | 0 | + And I am on the "Test lesson name" "lesson activity" page logged in as student1 + # Answer lesson activity question 1 incorrectly. + When I set the following fields to these values: + | False | 1 | + And I press "Submit" + # Confirm you can still re-attempt the question 1. + Then I should see "You have 1 attempt(s) remaining" + And I press "Yes, I'd like to try again" + # Answer question 1 incorrectly again. + And I set the following fields to these values: + | False | 1 | + And I press "Submit" + # Confirm you can't re-attempt the question 1 anymore. + And I should not see "Yes, I'd like to try again" + And I press "Continue" + # Answer question 2 correctly. + And I set the following fields to these values: + | True | 1 | + And I press "Submit" + And I should not see "Yes, I'd like to try again" + # Complete attempt. + And I press "Continue" + And I am on the "Test lesson name" "lesson activity" page + # Confirm you can't see the question anymore. + And I should see "You are not allowed to retake this lesson." From 45f674e8cb0f5eb5d86bab50ee6033f1284af54d Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Thu, 18 Jan 2024 18:08:37 +0000 Subject: [PATCH 023/134] MDL-80653 h5p: handle content type creation errors in test. See also same in c7d08f85. --- h5p/tests/h5p_core_test.php | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/h5p/tests/h5p_core_test.php b/h5p/tests/h5p_core_test.php index 355be8ca80db4..55cf3e5c69986 100644 --- a/h5p/tests/h5p_core_test.php +++ b/h5p/tests/h5p_core_test.php @@ -14,27 +14,20 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * Testing the H5P core methods. - * - * @package core_h5p - * @category test - * @copyright 2019 Victor Deniz - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - namespace core_h5p; use core_h5p\local\library\autoloader; -defined('MOODLE_INTERNAL') || die(); +use invalid_response_exception; /** - * Test class covering the H5PFileStorage interface implementation. + * Testing the H5P core methods. * * @package core_h5p + * @category test * @copyright 2019 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core_h5p\core * * @runTestsInSeparateProcesses */ @@ -67,10 +60,14 @@ public function test_fetch_content_type(): void { $this->resetAfterTest(true); // Get info of latest content types versions. - $contenttypes = $this->core->get_latest_content_types()->contentTypes; + $response = $this->core->get_latest_content_types(); + if (!empty($response->error)) { + throw new invalid_response_exception($response->error); + } + // We are installing the first content type with tutorial and example fields (or the first one if none has them). - $librarydata = $contenttypes[0]; - foreach ($contenttypes as $contentype) { + $librarydata = $response->contentTypes[0]; + foreach ($response->contentTypes as $contenttype) { if (isset($contenttype->tutorial) && isset($contenttype->example)) { $librarydata = $contenttype; break; From 560f4d895bd1215be80646b7a0adf91a19ff9626 Mon Sep 17 00:00:00 2001 From: Sara Arjona Date: Thu, 25 Jan 2024 16:48:39 +0100 Subject: [PATCH 024/134] weekly release 4.1.8+ --- version.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.php b/version.php index f31da0af20429..9668534a091f2 100644 --- a/version.php +++ b/version.php @@ -29,9 +29,9 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2022112808.02; // 20221128 = branching date YYYYMMDD - do not modify! +$version = 2022112808.03; // 20221128 = branching date YYYYMMDD - do not modify! // RR = release increments - 00 in DEV branches. // .XX = incremental changes. -$release = '4.1.8+ (Build: 20240119)'; // Human-friendly version name +$release = '4.1.8+ (Build: 20240125)'; // Human-friendly version name $branch = '401'; // This version's branch. $maturity = MATURITY_STABLE; // This version's maturity level. From 80083244b67369ddcbfb9f2f11647ae5d753bc57 Mon Sep 17 00:00:00 2001 From: AMOS bot Date: Sat, 27 Jan 2024 00:11:24 +0000 Subject: [PATCH 025/134] Automatically generated installer lang files --- install/lang/es_ar/admin.php | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 install/lang/es_ar/admin.php diff --git a/install/lang/es_ar/admin.php b/install/lang/es_ar/admin.php new file mode 100644 index 0000000000000..20f25fa71ce37 --- /dev/null +++ b/install/lang/es_ar/admin.php @@ -0,0 +1,35 @@ +. + +/** + * Automatically generated strings for Moodle installer + * + * Do not edit this file manually! It contains just a subset of strings + * needed during the very first steps of installation. This file was + * generated automatically by export-installer.php (which is part of AMOS + * {@link http://docs.moodle.org/dev/Languages/AMOS}) using the + * list of strings defined in /install/stringnames.txt. + * + * @package installer + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$string['clianswerno'] = 'n'; +$string['cliansweryes'] = 's'; +$string['cliincorrectvalueerror'] = 'Error, valor incorrecto "{$a->value}" para "{$a->option}"'; +$string['cliincorrectvalueretry'] = 'Valor incorrecto, por favor reintentar'; From 2b864ceec35b512b1cc0ac9c5feee84e9e766d3a Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Mon, 29 Jan 2024 20:19:05 +0800 Subject: [PATCH 026/134] MDL-79003 core: Bump NodeJS to lts/iron (NodeJS 20) --- .nvmrc | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.nvmrc b/.nvmrc index 53d838af2152f..9de2256827aef 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -lts/gallium +lts/iron diff --git a/package.json b/package.json index 045ccf34cda59..7fbba3abeee24 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,6 @@ "xpath": "0.0.32" }, "engines": { - "node": ">=16.14.0 <17" + "node": ">=20.11.0 <21" } } From 8092eb95c4aa6fc676f42b83474aa13c0897668a Mon Sep 17 00:00:00 2001 From: Ruslan Kabalin Date: Tue, 22 Mar 2022 15:36:39 +0000 Subject: [PATCH 027/134] MDL-79003 eslint: Upgrade eslint and babel This commit is a backport of MDL-74301 which should have been backported at the time. This patch includes changes: * Upgrade "@babel/core" to latest point release * Remove plugins already included into current "@babel/preset-env": - "@babel/plugin-proposal-class-properties" - "@babel/plugin-proposal-json-strings" - "@babel/plugin-syntax-dynamic-import" - "@babel/plugin-syntax-import-meta" * Upgrade "eslint" to latest version * Replace "eslint-plugin-babel" (depreacted in 2019) with "@babel/eslint-parser" and "@babel/eslint-plugin" --- .grunt/tasks/javascript.js | 6 +- npm-shrinkwrap.json | 911 +++++++++++++++++++++---------------- package.json | 9 +- 3 files changed, 532 insertions(+), 394 deletions(-) diff --git a/.grunt/tasks/javascript.js b/.grunt/tasks/javascript.js index 19ece75d5b2d1..34b57bc755b95 100644 --- a/.grunt/tasks/javascript.js +++ b/.grunt/tasks/javascript.js @@ -151,11 +151,7 @@ module.exports = grunt => { // // It also adds the Moodle plugin name to the AMD module definition // so that it can be imported as expected in other modules. - path.resolve('.grunt/babel-plugin-add-module-to-define.js'), - '@babel/plugin-syntax-dynamic-import', - '@babel/plugin-syntax-import-meta', - ['@babel/plugin-proposal-class-properties', {'loose': false}], - '@babel/plugin-proposal-json-strings' + path.resolve('.grunt/babel-plugin-add-module-to-define.js') ], presets: [ ['@babel/preset-env', { diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 7b7506eb4430a..574d3c45fe6cd 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -8,22 +8,17 @@ "devDependencies": { "@babel/core": "7.17.5", "@babel/eslint-parser": "^7.21.3", - "@babel/plugin-proposal-class-properties": "7.16.7", - "@babel/plugin-proposal-json-strings": "7.16.7", - "@babel/plugin-syntax-dynamic-import": "7.8.3", - "@babel/plugin-syntax-import-meta": "7.10.4", + "@babel/eslint-plugin": "7.19.1", "@babel/preset-env": "7.16.11", "@xmldom/xmldom": "^0.8.7", "ajv": "8.10.0", "async": "3.2.3", - "babel-eslint": "10.1.0", "babel-plugin-system-import-transformer": "^4.0.0", "babel-plugin-transform-es2015-modules-amd-lazy": "2.0.1", "babel-preset-minify": "0.5.1", "cross-env": "^7.0.3", "docdash": "^1.2.0", - "eslint": "8.9.0", - "eslint-plugin-babel": "5.3.1", + "eslint": "8.41.0", "eslint-plugin-jsdoc": "^37.9.4", "eslint-plugin-promise": "6.0.0", "fb-watchman": "2.0.1", @@ -165,6 +160,22 @@ "semver": "bin/semver.js" } }, + "node_modules/@babel/eslint-plugin": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/eslint-plugin/-/eslint-plugin-7.19.1.tgz", + "integrity": "sha512-ElGPkQPapKMa3zVqXHkZYzuL7I5LbRw9UWBUArgWsdWDDb9XcACqOpBib5tRPA9XvbVZYrFUkoQPbiJ4BFvu4w==", + "dev": true, + "dependencies": { + "eslint-rule-composer": "^0.3.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || >=14.0.0" + }, + "peerDependencies": { + "@babel/eslint-parser": ">=7.11.0", + "eslint": ">=7.5.0" + } + }, "node_modules/@babel/generator": { "version": "7.17.3", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz", @@ -904,18 +915,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-json-strings": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", @@ -1720,24 +1719,51 @@ "node": "^12 || ^14 || ^16 || ^17" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", + "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.1.0.tgz", - "integrity": "sha512-C1DfL7XX4nPqGd6jcP01W9pVM1HYCuUkFk1432D7F0v3JSlUIeOYn9oCoi3eoLZ+iwBSb29BMFxxny0YrrEZqg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", + "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.3.1", - "globals": "^13.9.0", - "ignore": "^4.0.6", + "espree": "^9.5.2", + "globals": "^13.19.0", + "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/@eslint/eslintrc/node_modules/ajv": { @@ -1763,9 +1789,9 @@ "dev": true }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.12.1", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz", - "integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==", + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -1777,15 +1803,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, "node_modules/@eslint/eslintrc/node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -1798,6 +1815,18 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@eslint/eslintrc/node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -1810,20 +1839,54 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@eslint/js": { + "version": "8.41.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.41.0.tgz", + "integrity": "sha512-LxcyMGxwmTh2lY9FwHPGWOHmYFCZvbrFCBZL4FzSSsxsRPuhrYUg/49/0KDfW8tnIEaEHtfmn6+NPN+1DqaNmA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/@humanwhocodes/config-array": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.3.tgz", - "integrity": "sha512-3xSMlXHh03hCcCmFc0rbKp3Ivt2PFEJnQUJDDMTJQ2wkECZWdq4GePs2ctc5H8zV+cHPaq8k2vU8mrQjA6iHdQ==", + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", - "minimatch": "^3.0.4" + "minimatch": "^3.0.5" }, "engines": { "node": ">=10.10.0" } }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@humanwhocodes/object-schema": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", @@ -1865,12 +1928,12 @@ } }, "node_modules/@nodelib/fs.scandir": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", - "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, "dependencies": { - "@nodelib/fs.stat": "2.0.3", + "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" }, "engines": { @@ -1878,21 +1941,21 @@ } }, "node_modules/@nodelib/fs.stat": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", - "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, "engines": { "node": ">= 8" } }, "node_modules/@nodelib/fs.walk": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", - "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, "dependencies": { - "@nodelib/fs.scandir": "2.1.3", + "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" }, "engines": { @@ -2167,9 +2230,9 @@ "dev": true }, "node_modules/acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -2414,27 +2477,6 @@ "url": "https://tidelift.com/funding/github/npm/autoprefixer" } }, - "node_modules/babel-eslint": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", - "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", - "deprecated": "babel-eslint is now @babel/eslint-parser. This package will no longer receive updates.", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.7.0", - "@babel/traverse": "^7.7.0", - "@babel/types": "^7.7.0", - "eslint-visitor-keys": "^1.0.0", - "resolve": "^1.12.0" - }, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "eslint": ">= 4.12.1" - } - }, "node_modules/babel-helper-evaluate-path": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/babel-helper-evaluate-path/-/babel-helper-evaluate-path-0.5.0.tgz", @@ -4448,46 +4490,50 @@ } }, "node_modules/eslint": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.9.0.tgz", - "integrity": "sha512-PB09IGwv4F4b0/atrbcMFboF/giawbBLVC7fyDamk5Wtey4Jh2K+rYaBhCAbUyEI4QzB1ly09Uglc9iCtFaG2Q==", - "dev": true, - "dependencies": { - "@eslint/eslintrc": "^1.1.0", - "@humanwhocodes/config-array": "^0.9.2", + "version": "8.41.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.41.0.tgz", + "integrity": "sha512-WQDQpzGBOP5IrXPo4Hc0814r4/v2rrIsB0rhT7jtunIalgg6gYXWhRMOejVO8yH21T/FGaxjmFjBMNqcIlmH1Q==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.3", + "@eslint/js": "8.41.0", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.1", - "esquery": "^1.4.0", + "eslint-scope": "^7.2.0", + "eslint-visitor-keys": "^3.4.1", + "espree": "^9.5.2", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.6.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", "ignore": "^5.2.0", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.1", - "regexpp": "^3.2.0", "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" + "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" @@ -4499,21 +4545,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-plugin-babel": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-babel/-/eslint-plugin-babel-5.3.1.tgz", - "integrity": "sha512-VsQEr6NH3dj664+EyxJwO4FCYm/00JhYb3Sk3ft8o+fpKuIfQ9TaW6uVUfvwMXHcf/lsnRIoyFPsLMyiWCSL/g==", - "dev": true, - "dependencies": { - "eslint-rule-composer": "^0.3.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": ">=4.0.0" - } - }, "node_modules/eslint-plugin-jsdoc": { "version": "37.9.4", "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-37.9.4.tgz", @@ -4592,40 +4623,16 @@ "node": ">=8.0.0" } }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "node_modules/eslint-visitor-keys": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", - "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", - "dev": true, - "engines": { - "node": ">=4" + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint/node_modules/ajv": { @@ -4726,9 +4733,9 @@ } }, "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", @@ -4736,15 +4743,9 @@ }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint/node_modules/estraverse": { @@ -4800,9 +4801,9 @@ } }, "node_modules/eslint/node_modules/globals": { - "version": "13.12.1", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz", - "integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==", + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -4848,6 +4849,18 @@ "node": ">= 0.8.0" } }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint/node_modules/optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -4971,26 +4984,20 @@ } }, "node_modules/espree": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.1.tgz", - "integrity": "sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==", + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", + "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", "dev": true, "dependencies": { - "acorn": "^8.7.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^3.3.0" + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/esprima": { @@ -5007,9 +5014,9 @@ } }, "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, "dependencies": { "estraverse": "^5.1.0" @@ -5362,6 +5369,79 @@ "node": ">=6" } }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-up/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-up/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-up/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-up/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/find-versions": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-3.2.0.tgz", @@ -5499,32 +5579,12 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, "node_modules/gaze": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", @@ -6121,6 +6181,12 @@ "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", "dev": true }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, "node_modules/gray-matter": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-3.1.1.tgz", @@ -7362,6 +7428,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -9470,6 +9545,15 @@ "integrity": "sha1-hZB92GBaoGq7PdKV1QuyuPpN0Rc=", "dev": true }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -9948,6 +10032,26 @@ "node": ">=0.10.0" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/quick-lru": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", @@ -10149,18 +10253,6 @@ "@babel/runtime": "^7.8.4" } }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, "node_modules/regexpu-core": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.0.1.tgz", @@ -10393,10 +10485,27 @@ } }, "node_modules/run-parallel": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", - "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==", - "dev": true + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } }, "node_modules/safe-buffer": { "version": "5.1.2", @@ -11365,15 +11474,6 @@ "node": ">=8" } }, - "node_modules/stylelint/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/stylelint/node_modules/read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", @@ -12563,6 +12663,15 @@ } } }, + "@babel/eslint-plugin": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/eslint-plugin/-/eslint-plugin-7.19.1.tgz", + "integrity": "sha512-ElGPkQPapKMa3zVqXHkZYzuL7I5LbRw9UWBUArgWsdWDDb9XcACqOpBib5tRPA9XvbVZYrFUkoQPbiJ4BFvu4w==", + "dev": true, + "requires": { + "eslint-rule-composer": "^0.3.0" + } + }, "@babel/generator": { "version": "7.17.3", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz", @@ -13084,15 +13193,6 @@ "@babel/helper-plugin-utils": "^7.8.3" } }, - "@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, "@babel/plugin-syntax-json-strings": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", @@ -13650,20 +13750,35 @@ "jsdoc-type-pratt-parser": "~2.2.3" } }, + "@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^3.3.0" + } + }, + "@eslint-community/regexpp": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", + "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "dev": true + }, "@eslint/eslintrc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.1.0.tgz", - "integrity": "sha512-C1DfL7XX4nPqGd6jcP01W9pVM1HYCuUkFk1432D7F0v3JSlUIeOYn9oCoi3eoLZ+iwBSb29BMFxxny0YrrEZqg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", + "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.3.1", - "globals": "^13.9.0", - "ignore": "^4.0.6", + "espree": "^9.5.2", + "globals": "^13.19.0", + "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, "dependencies": { @@ -13686,20 +13801,14 @@ "dev": true }, "globals": { - "version": "13.12.1", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz", - "integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==", + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", "dev": true, "requires": { "type-fest": "^0.20.2" } }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, "js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -13709,6 +13818,15 @@ "argparse": "^2.0.1" } }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -13717,17 +13835,40 @@ } } }, + "@eslint/js": { + "version": "8.41.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.41.0.tgz", + "integrity": "sha512-LxcyMGxwmTh2lY9FwHPGWOHmYFCZvbrFCBZL4FzSSsxsRPuhrYUg/49/0KDfW8tnIEaEHtfmn6+NPN+1DqaNmA==", + "dev": true + }, "@humanwhocodes/config-array": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.3.tgz", - "integrity": "sha512-3xSMlXHh03hCcCmFc0rbKp3Ivt2PFEJnQUJDDMTJQ2wkECZWdq4GePs2ctc5H8zV+cHPaq8k2vU8mrQjA6iHdQ==", + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", "dev": true, "requires": { "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", - "minimatch": "^3.0.4" + "minimatch": "^3.0.5" + }, + "dependencies": { + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } } }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, "@humanwhocodes/object-schema": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", @@ -13766,28 +13907,28 @@ } }, "@nodelib/fs.scandir": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", - "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, "requires": { - "@nodelib/fs.stat": "2.0.3", + "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "@nodelib/fs.stat": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", - "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true }, "@nodelib/fs.walk": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", - "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, "requires": { - "@nodelib/fs.scandir": "2.1.3", + "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, @@ -14026,9 +14167,9 @@ "dev": true }, "acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", "dev": true }, "acorn-jsx": { @@ -14198,20 +14339,6 @@ "postcss-value-parser": "^4.0.3" } }, - "babel-eslint": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", - "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.7.0", - "@babel/traverse": "^7.7.0", - "@babel/types": "^7.7.0", - "eslint-visitor-keys": "^1.0.0", - "resolve": "^1.12.0" - } - }, "babel-helper-evaluate-path": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/babel-helper-evaluate-path/-/babel-helper-evaluate-path-0.5.0.tgz", @@ -15859,46 +15986,50 @@ } }, "eslint": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.9.0.tgz", - "integrity": "sha512-PB09IGwv4F4b0/atrbcMFboF/giawbBLVC7fyDamk5Wtey4Jh2K+rYaBhCAbUyEI4QzB1ly09Uglc9iCtFaG2Q==", - "dev": true, - "requires": { - "@eslint/eslintrc": "^1.1.0", - "@humanwhocodes/config-array": "^0.9.2", + "version": "8.41.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.41.0.tgz", + "integrity": "sha512-WQDQpzGBOP5IrXPo4Hc0814r4/v2rrIsB0rhT7jtunIalgg6gYXWhRMOejVO8yH21T/FGaxjmFjBMNqcIlmH1Q==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.3", + "@eslint/js": "8.41.0", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.1", - "esquery": "^1.4.0", + "eslint-scope": "^7.2.0", + "eslint-visitor-keys": "^3.4.1", + "espree": "^9.5.2", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.6.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", "ignore": "^5.2.0", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.1", - "regexpp": "^3.2.0", "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" + "text-table": "^0.2.0" }, "dependencies": { "ajv": { @@ -15971,21 +16102,15 @@ "dev": true }, "eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", "dev": true, "requires": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, - "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true - }, "estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", @@ -16027,9 +16152,9 @@ } }, "globals": { - "version": "13.12.1", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz", - "integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==", + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -16060,6 +16185,15 @@ "type-check": "~0.4.0" } }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -16145,15 +16279,6 @@ } } }, - "eslint-plugin-babel": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-babel/-/eslint-plugin-babel-5.3.1.tgz", - "integrity": "sha512-VsQEr6NH3dj664+EyxJwO4FCYm/00JhYb3Sk3ft8o+fpKuIfQ9TaW6uVUfvwMXHcf/lsnRIoyFPsLMyiWCSL/g==", - "dev": true, - "requires": { - "eslint-rule-composer": "^0.3.0" - } - }, "eslint-plugin-jsdoc": { "version": "37.9.4", "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-37.9.4.tgz", @@ -16211,46 +16336,21 @@ "estraverse": "^4.1.1" } }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - } - } - }, "eslint-visitor-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", - "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", "dev": true }, "espree": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.1.tgz", - "integrity": "sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==", + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", + "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", "dev": true, "requires": { - "acorn": "^8.7.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^3.3.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true - } + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" } }, "esprima": { @@ -16260,9 +16360,9 @@ "dev": true }, "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, "requires": { "estraverse": "^5.1.0" @@ -16550,6 +16650,51 @@ } } }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "dependencies": { + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } + }, "find-versions": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-3.2.0.tgz", @@ -16662,25 +16807,12 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, "gaze": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", @@ -17151,6 +17283,12 @@ "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", "dev": true }, + "graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, "gray-matter": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-3.1.1.tgz", @@ -18057,6 +18195,12 @@ "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", "dev": true }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, "is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -19698,6 +19842,12 @@ "integrity": "sha1-hZB92GBaoGq7PdKV1QuyuPpN0Rc=", "dev": true }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -20070,6 +20220,12 @@ "strict-uri-encode": "^1.0.0" } }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, "quick-lru": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", @@ -20238,12 +20394,6 @@ "@babel/runtime": "^7.8.4" } }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, "regexpu-core": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.0.1.tgz", @@ -20427,10 +20577,13 @@ } }, "run-parallel": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", - "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==", - "dev": true + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } }, "safe-buffer": { "version": "5.1.2", @@ -21186,12 +21339,6 @@ "lines-and-columns": "^1.1.6" } }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, "read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", diff --git a/package.json b/package.json index 7fbba3abeee24..865432994c5dd 100644 --- a/package.json +++ b/package.json @@ -5,22 +5,17 @@ "devDependencies": { "@babel/core": "7.17.5", "@babel/eslint-parser": "^7.21.3", - "@babel/plugin-proposal-class-properties": "7.16.7", - "@babel/plugin-proposal-json-strings": "7.16.7", - "@babel/plugin-syntax-dynamic-import": "7.8.3", - "@babel/plugin-syntax-import-meta": "7.10.4", + "@babel/eslint-plugin": "7.19.1", "@babel/preset-env": "7.16.11", "@xmldom/xmldom": "^0.8.7", "ajv": "8.10.0", "async": "3.2.3", - "babel-eslint": "10.1.0", "babel-plugin-system-import-transformer": "^4.0.0", "babel-plugin-transform-es2015-modules-amd-lazy": "2.0.1", "babel-preset-minify": "0.5.1", "cross-env": "^7.0.3", "docdash": "^1.2.0", - "eslint": "8.9.0", - "eslint-plugin-babel": "5.3.1", + "eslint": "8.41.0", "eslint-plugin-jsdoc": "^37.9.4", "eslint-plugin-promise": "6.0.0", "fb-watchman": "2.0.1", From 692d3090faf9acf22fca21e1d28265fd4f2efd98 Mon Sep 17 00:00:00 2001 From: Ruslan Kabalin Date: Wed, 23 Mar 2022 11:40:32 +0000 Subject: [PATCH 028/134] MDL-79003 eslint: Change configuration and use 'eslint:recommended' This commit is a backport of MDL-74301 which should have been backported at the time. --- .eslintrc | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/.eslintrc b/.eslintrc index 754c8be4f5a03..b39c110b1bea8 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,8 +1,11 @@ { 'plugins': [ - 'babel', + '@babel', 'promise', - 'jsdoc', + 'jsdoc' + ], + 'extends': [ + 'eslint:recommended' ], 'env': { 'browser': true, @@ -195,7 +198,7 @@ "no-restricted-properties": ['warn', { 'object': 'M', 'property': 'str', - 'message': 'Use AMD module "core/str" or M.util.get_string()' + 'message': 'Use "core/str" module or M.util.get_string()' }], }, overrides: [ @@ -247,15 +250,13 @@ 'semi': 'off', 'no-unused-expressions': 'off', // Enable all of the babel version of these rules. - 'babel/new-cap': ['warn', { 'properties': false }], + '@babel/new-cap': ['warn', { 'properties': false }], // Not using this rule for the time being because it isn't // compatible with jQuery and ES6. - 'babel/no-invalid-this': 'off', - 'babel/object-curly-spacing': 'warn', - // This is off in the original style int. - 'babel/quotes': 'off', - 'babel/semi': 'error', - 'babel/no-unused-expressions': 'error', + '@babel/no-invalid-this': 'off', + '@babel/object-curly-spacing': 'warn', + '@babel/semi': 'error', + '@babel/no-unused-expressions': 'error', // === Promises === // We have Promise now that we're using ES6. 'promise/no-native': 'off', @@ -305,7 +306,7 @@ }, parserOptions: { 'sourceType': 'module', - 'requireConfigFile': false, + 'requireConfigFile': false } } ] From 846a8b6568c45efc4bcb0ac16dd7d827382a2843 Mon Sep 17 00:00:00 2001 From: Ruslan Kabalin Date: Wed, 24 May 2023 22:07:05 +0100 Subject: [PATCH 029/134] MDL-79003 eslint: Address issues reported by eslint This commit is a backport of MDL-74301 which should have been backported at the time. --- .../local/courseeditor/fileuploader.min.js | 3 + .../courseeditor/fileuploader.min.js.map | 1 + .../amd/build/local/courseindex/cm.min.js | 2 +- .../amd/build/local/courseindex/cm.min.js.map | 2 +- .../src/local/courseeditor/fileuploader.js | 549 ++++++++++++++++++ course/format/amd/src/local/courseindex/cm.js | 8 +- lib/amd/build/sortable_list.min.js.map | 2 +- lib/amd/src/sortable_list.js | 1 + .../amd/build/plugin.min.js.map | 2 +- .../accessibilitychecker/amd/src/plugin.js | 1 + .../autosave/amd/build/plugin.min.js.map | 2 +- .../tiny/plugins/autosave/amd/src/plugin.js | 1 + .../equation/amd/build/plugin.min.js.map | 2 +- .../tiny/plugins/equation/amd/src/plugin.js | 1 + .../plugins/h5p/amd/build/plugin.min.js.map | 2 +- lib/editor/tiny/plugins/h5p/amd/src/plugin.js | 1 + .../plugins/link/amd/build/plugin.min.js.map | 2 +- .../tiny/plugins/link/amd/src/plugin.js | 1 + .../plugins/media/amd/build/plugin.min.js.map | 2 +- .../tiny/plugins/media/amd/src/plugin.js | 1 + .../recordrtc/amd/build/plugin.min.js.map | 2 +- .../tiny/plugins/recordrtc/amd/src/plugin.js | 1 + ...dle-assignfeedback_editpdf-editor-debug.js | 1 + .../moodle-assignfeedback_editpdf-editor.js | 1 + .../editpdf/yui/src/editor/js/editor.js | 1 + 25 files changed, 576 insertions(+), 16 deletions(-) create mode 100644 course/format/amd/build/local/courseeditor/fileuploader.min.js create mode 100644 course/format/amd/build/local/courseeditor/fileuploader.min.js.map create mode 100644 course/format/amd/src/local/courseeditor/fileuploader.js diff --git a/course/format/amd/build/local/courseeditor/fileuploader.min.js b/course/format/amd/build/local/courseeditor/fileuploader.min.js new file mode 100644 index 0000000000000..308e86a85c1a9 --- /dev/null +++ b/course/format/amd/build/local/courseeditor/fileuploader.min.js @@ -0,0 +1,3 @@ +define("core_courseformat/local/courseeditor/fileuploader",["exports","core/config","core/modal_factory","core/modal_events","core/templates","core/normalise","core/prefetch","core/str","core_courseformat/courseeditor","core/process_monitor","core/utils"],(function(_exports,_config,_modal_factory,_modal_events,_templates,_normalise,_prefetch,_str,_courseeditor,_process_monitor,_utils){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.uploadFilesToCourse=void 0,_config=_interopRequireDefault(_config),_modal_factory=_interopRequireDefault(_modal_factory),_modal_events=_interopRequireDefault(_modal_events),_templates=_interopRequireDefault(_templates);const UPLOADURL=_config.default.wwwroot+"/course/dndupload.php";let uploadQueue=null,handlerManagers={},courseUpdates=new Map,errors=null;(0,_prefetch.prefetchStrings)("moodle",["addresourceoractivity","upload"]),(0,_prefetch.prefetchStrings)("core_error",["dndmaxbytes","dndread","dndupload","dndunkownfile"]);class FileUploader{constructor(courseId,sectionId,sectionNum,fileInfo,handler){this.courseId=courseId,this.sectionId=sectionId,this.sectionNum=sectionNum,this.fileInfo=fileInfo,this.handler=handler}execute(process){const fileInfo=this.fileInfo,xhr=this._createXhrRequest(process),formData=this._createUploadFormData(),reader=new FileReader;reader.onload=function(){xhr.open("POST",UPLOADURL,!0),xhr.send(formData)},reader.onerror=function(){process.setError(errors.dndread)},fileInfo.size>0?reader.readAsText(fileInfo.slice(0,5)):reader.readAsText(fileInfo)}getExecutionFunction(){return this.execute.bind(this)}_createXhrRequest(process){const xhr=new XMLHttpRequest;return xhr.upload.addEventListener("progress",(event=>{if(event.lengthComputable){const percent=Math.round(100*event.loaded/event.total);process.setPercentage(percent)}}),!1),xhr.onreadystatechange=()=>{if(1==xhr.readyState&&process.setPercentage(1),4==xhr.readyState)if(200==xhr.status){var result=JSON.parse(xhr.responseText);result&&0==result.error?this._finishProcess(process):process.setError(result.error)}else process.setError(errors.dndupload)},xhr}_createUploadFormData(){const formData=new FormData;try{formData.append("repo_upload_file",this.fileInfo)}catch(error){throw Error(error.dndread)}return formData.append("sesskey",_config.default.sesskey),formData.append("course",this.courseId),formData.append("section",this.sectionNum),formData.append("module",this.handler.module),formData.append("type","Files"),formData}_finishProcess(process){!function(courseId,sectionId){let refresh=courseUpdates.get(courseId);refresh||(refresh=new Set);refresh.add(sectionId),courseUpdates.set(courseId,refresh),refreshCourseEditors()}(this.courseId,this.sectionId),process.setPercentage(100),process.finish()}}class HandlerManager{constructor(courseId){var _this$courseEditor$ge,_this$courseEditor$ge2;if(_defineProperty(this,"lastHandlers",{}),_defineProperty(this,"allHandlers",null),this.courseId=courseId,this.lastUploadId=0,this.courseEditor=(0,_courseeditor.getCourseEditor)(courseId),!this.courseEditor)throw Error("Unkown course editor");this.maxbytes=null!==(_this$courseEditor$ge=null===(_this$courseEditor$ge2=this.courseEditor.get("course"))||void 0===_this$courseEditor$ge2?void 0:_this$courseEditor$ge2.maxbytes)&&void 0!==_this$courseEditor$ge?_this$courseEditor$ge:0}async loadHandlers(){this.allHandlers=await this.courseEditor.getFileHandlersPromise()}getFileExtension(fileInfo){let extension="";const dotpos=fileInfo.name.lastIndexOf(".");return-1!=dotpos&&(extension=fileInfo.name.substring(dotpos+1,fileInfo.name.length).toLowerCase()),extension}validateFile(fileInfo){if(-1!==this.maxbytes&&fileInfo.size>this.maxbytes)throw Error(errors.dndmaxbytes)}filterHandlers(fileInfo){const extension=this.getFileExtension(fileInfo);return this.allHandlers.filter((handler=>"*"==handler.extension||handler.extension==extension))}async getFileHandler(fileInfo){const fileHandlers=this.filterHandlers(fileInfo);if(0==fileHandlers.length)throw Error(errors.dndunkownfile);let fileHandler=null;return fileHandler=1==fileHandlers.length?fileHandlers[0]:await this.askHandlerToUser(fileHandlers,fileInfo),fileHandler}async askHandlerToUser(fileHandlers,fileInfo){var _this$lastHandlers$ex;const extension=this.getFileExtension(fileInfo),modalParams={title:(0,_str.get_string)("addresourceoractivity","moodle"),body:_templates.default.render("core_courseformat/fileuploader",this.getModalData(fileHandlers,fileInfo,null!==(_this$lastHandlers$ex=this.lastHandlers[extension])&&void 0!==_this$lastHandlers$ex?_this$lastHandlers$ex:null)),type:_modal_factory.default.types.SAVE_CANCEL,saveButtonText:(0,_str.get_string)("upload","moodle")},modal=await this.modalBodyRenderedPromise(modalParams),selectedHandler=await this.modalUserAnswerPromise(modal,fileHandlers);return null===selectedHandler?null:(this.lastHandlers[extension]=selectedHandler.module,selectedHandler)}getModalData(fileHandlers,fileInfo,defaultModule){const data={filename:fileInfo.name,uploadid:++this.lastUploadId,handlers:[]};let hasDefault=!1;if(fileHandlers.forEach(((handler,index)=>{const isDefault=defaultModule==handler.module;data.handlers.push({...handler,selected:isDefault,labelid:"fileuploader_".concat(data.uploadid),value:index}),hasDefault=hasDefault||isDefault})),!hasDefault&&data.handlers.length>0){const lastHandler=data.handlers.pop();lastHandler.selected=!0,data.handlers.push(lastHandler)}return data}modalUserAnswerPromise(modal,fileHandlers){const modalBody=(0,_normalise.getFirst)(modal.getBody());return new Promise(((resolve,reject)=>{modal.getRoot().on(_modal_events.default.save,(event=>{const index=modalBody.querySelector("input:checked").value;event.preventDefault(),modal.destroy(),fileHandlers[index]||reject("Invalid handler selected"),resolve(fileHandlers[index])})),modal.getRoot().on(_modal_events.default.cancel,(()=>{resolve(null)}))}))}modalBodyRenderedPromise(modalParams){return new Promise(((resolve,reject)=>{_modal_factory.default.create(modalParams).then((modal=>{modal.setRemoveOnClose(!0),modal.getRoot().on(_modal_events.default.bodyRendered,(()=>{resolve(modal)})),void 0!==modalParams.saveButtonText&&modal.setSaveButtonText(modalParams.saveButtonText),modal.show()})).catch((()=>{reject("Cannot load modal content")}))}))}}const refreshCourseEditors=(0,_utils.debounce)((()=>{const refreshes=courseUpdates;courseUpdates=new Map,refreshes.forEach(((sectionIds,courseId)=>{const courseEditor=(0,_courseeditor.getCourseEditor)(courseId);courseEditor&&courseEditor.dispatch("sectionState",[...sectionIds])}))}),500);const queueFileUpload=async function(courseId,sectionId,sectionNum,fileInfo,handlerManager){let handler;uploadQueue=await _process_monitor.processMonitor.createProcessQueue();try{handlerManager.validateFile(fileInfo),handler=await handlerManager.getFileHandler(fileInfo)}catch(error){return void uploadQueue.addError(fileInfo.name,error.message)}if(!handler)return;const fileProcessor=new FileUploader(courseId,sectionId,sectionNum,fileInfo,handler);uploadQueue.addPending(fileInfo.name,fileProcessor.getExecutionFunction())};_exports.uploadFilesToCourse=async function(courseId,sectionId,sectionNum,files){const handlerManager=await async function(courseId){if(void 0!==handlerManagers[courseId])return handlerManagers[courseId];const handlerManager=new HandlerManager(courseId);return await handlerManager.loadHandlers(),handlerManagers[courseId]=handlerManager,handlerManagers[courseId]}(courseId);await async function(courseId){var _courseEditor$get$max,_courseEditor$get;if(null!==errors)return;const maxbytestext=null!==(_courseEditor$get$max=null===(_courseEditor$get=(0,_courseeditor.getCourseEditor)(courseId).get("course"))||void 0===_courseEditor$get?void 0:_courseEditor$get.maxbytestext)&&void 0!==_courseEditor$get$max?_courseEditor$get$max:"0";errors={};const allStrings=[{key:"dndmaxbytes",component:"core_error",param:{size:maxbytestext}},{key:"dndread",component:"core_error"},{key:"dndupload",component:"core_error"},{key:"dndunkownfile",component:"core_error"}];window.console.log(allStrings);const loadedStrings=await(0,_str.get_strings)(allStrings);allStrings.forEach(((_ref,index)=>{let{key:key}=_ref;errors[key]=loadedStrings[index]}))}(courseId);for(let index=0;index.\n\n/**\n * The course file uploader.\n *\n * This module is used to upload files directly into the course.\n *\n * @module core_courseformat/local/courseeditor/fileuploader\n * @copyright 2022 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * @typedef {Object} Handler\n * @property {String} extension the handled extension or * for any\n * @property {String} message the handler message\n * @property {String} module the module name\n */\n\nimport Config from 'core/config';\nimport ModalFactory from 'core/modal_factory';\nimport ModalEvents from 'core/modal_events';\nimport Templates from 'core/templates';\nimport {getFirst} from 'core/normalise';\nimport {prefetchStrings} from 'core/prefetch';\nimport {get_string as getString, get_strings as getStrings} from 'core/str';\nimport {getCourseEditor} from 'core_courseformat/courseeditor';\nimport {processMonitor} from 'core/process_monitor';\nimport {debounce} from 'core/utils';\n\n// Uploading url.\nconst UPLOADURL = Config.wwwroot + '/course/dndupload.php';\nconst DEBOUNCETIMER = 500;\nconst USERCANIGNOREFILESIZELIMITS = -1;\n\n/** @var {ProcessQueue} uploadQueue the internal uploadQueue instance. */\nlet uploadQueue = null;\n/** @var {Object} handlerManagers the courseId indexed loaded handler managers. */\nlet handlerManagers = {};\n/** @var {Map} courseUpdates the pending course sections updates. */\nlet courseUpdates = new Map();\n/** @var {Object} errors the error messages. */\nlet errors = null;\n\n// Load global strings.\nprefetchStrings('moodle', ['addresourceoractivity', 'upload']);\nprefetchStrings('core_error', ['dndmaxbytes', 'dndread', 'dndupload', 'dndunkownfile']);\n\n/**\n * Class to upload a file into the course.\n * @private\n */\nclass FileUploader {\n /**\n * Class constructor.\n *\n * @param {number} courseId the course id\n * @param {number} sectionId the section id\n * @param {number} sectionNum the section number\n * @param {File} fileInfo the file information object\n * @param {Handler} handler the file selected file handler\n */\n constructor(courseId, sectionId, sectionNum, fileInfo, handler) {\n this.courseId = courseId;\n this.sectionId = sectionId;\n this.sectionNum = sectionNum;\n this.fileInfo = fileInfo;\n this.handler = handler;\n }\n\n /**\n * Execute the file upload and update the state in the given process.\n *\n * @param {LoadingProcess} process the process to store the upload result\n */\n execute(process) {\n const fileInfo = this.fileInfo;\n const xhr = this._createXhrRequest(process);\n const formData = this._createUploadFormData();\n\n // Try reading the file to check it is not a folder, before sending it to the server.\n const reader = new FileReader();\n reader.onload = function() {\n // File was read OK - send it to the server.\n xhr.open(\"POST\", UPLOADURL, true);\n xhr.send(formData);\n };\n reader.onerror = function() {\n // Unable to read the file (it is probably a folder) - display an error message.\n process.setError(errors.dndread);\n };\n if (fileInfo.size > 0) {\n // If this is a non-empty file, try reading the first few bytes.\n // This will trigger reader.onerror() for folders and reader.onload() for ordinary, readable files.\n reader.readAsText(fileInfo.slice(0, 5));\n } else {\n // If you call slice() on a 0-byte folder, before calling readAsText, then Firefox triggers reader.onload(),\n // instead of reader.onerror().\n // So, for 0-byte files, just call readAsText on the whole file (and it will trigger load/error functions as expected).\n reader.readAsText(fileInfo);\n }\n }\n\n /**\n * Returns the bind version of execute function.\n *\n * This method is used to queue the process into a ProcessQueue instance.\n *\n * @returns {Function} the bind function to execute the process\n */\n getExecutionFunction() {\n return this.execute.bind(this);\n }\n\n /**\n * Generate a upload XHR file request.\n *\n * @param {LoadingProcess} process the current process\n * @return {XMLHttpRequest} the XHR request\n */\n _createXhrRequest(process) {\n const xhr = new XMLHttpRequest();\n // Update the progress bar as the file is uploaded.\n xhr.upload.addEventListener(\n 'progress',\n (event) => {\n if (event.lengthComputable) {\n const percent = Math.round((event.loaded * 100) / event.total);\n process.setPercentage(percent);\n }\n },\n false\n );\n // Wait for the AJAX call to complete.\n xhr.onreadystatechange = () => {\n if (xhr.readyState == 1) {\n // Add a 1% just to indicate that it is uploading.\n process.setPercentage(1);\n }\n // State 4 is DONE. Otherwise the connection is still ongoing.\n if (xhr.readyState != 4) {\n return;\n }\n if (xhr.status == 200) {\n var result = JSON.parse(xhr.responseText);\n if (result && result.error == 0) {\n // All OK.\n this._finishProcess(process);\n } else {\n process.setError(result.error);\n }\n } else {\n process.setError(errors.dndupload);\n }\n };\n return xhr;\n }\n\n /**\n * Upload a file into the course.\n *\n * @return {FormData|null} the new form data object\n */\n _createUploadFormData() {\n const formData = new FormData();\n try {\n formData.append('repo_upload_file', this.fileInfo);\n } catch (error) {\n throw Error(error.dndread);\n }\n formData.append('sesskey', Config.sesskey);\n formData.append('course', this.courseId);\n formData.append('section', this.sectionNum);\n formData.append('module', this.handler.module);\n formData.append('type', 'Files');\n return formData;\n }\n\n /**\n * Finishes the current process.\n * @param {LoadingProcess} process the process\n */\n _finishProcess(process) {\n addRefreshSection(this.courseId, this.sectionId);\n process.setPercentage(100);\n process.finish();\n }\n}\n\n/**\n * The file handler manager class.\n *\n * @private\n */\nclass HandlerManager {\n\n /** @var {Object} lastHandlers the last handlers selected per each file extension. */\n lastHandlers = {};\n\n /** @var {Handler[]|null} allHandlers all the available handlers. */\n allHandlers = null;\n\n /**\n * Class constructor.\n *\n * @param {Number} courseId\n */\n constructor(courseId) {\n this.courseId = courseId;\n this.lastUploadId = 0;\n this.courseEditor = getCourseEditor(courseId);\n if (!this.courseEditor) {\n throw Error('Unkown course editor');\n }\n this.maxbytes = this.courseEditor.get('course')?.maxbytes ?? 0;\n }\n\n /**\n * Load the course file handlers.\n */\n async loadHandlers() {\n this.allHandlers = await this.courseEditor.getFileHandlersPromise();\n }\n\n /**\n * Extract the file extension from a fileInfo.\n *\n * @param {File} fileInfo\n * @returns {String} the file extension or an empty string.\n */\n getFileExtension(fileInfo) {\n let extension = '';\n const dotpos = fileInfo.name.lastIndexOf('.');\n if (dotpos != -1) {\n extension = fileInfo.name.substring(dotpos + 1, fileInfo.name.length).toLowerCase();\n }\n return extension;\n }\n\n /**\n * Check if the file is valid.\n *\n * @param {File} fileInfo the file info\n */\n validateFile(fileInfo) {\n if (this.maxbytes !== USERCANIGNOREFILESIZELIMITS && fileInfo.size > this.maxbytes) {\n throw Error(errors.dndmaxbytes);\n }\n }\n\n /**\n * Get the file handlers of an specific file.\n *\n * @param {File} fileInfo the file indo\n * @return {Array} Array of handlers\n */\n filterHandlers(fileInfo) {\n const extension = this.getFileExtension(fileInfo);\n return this.allHandlers.filter(handler => handler.extension == '*' || handler.extension == extension);\n }\n\n /**\n * Get the Handler to upload a specific file.\n *\n * It will ask the used if more than one handler is available.\n *\n * @param {File} fileInfo the file info\n * @returns {Promise} the selected handler or null if the user cancel\n */\n async getFileHandler(fileInfo) {\n const fileHandlers = this.filterHandlers(fileInfo);\n if (fileHandlers.length == 0) {\n throw Error(errors.dndunkownfile);\n }\n let fileHandler = null;\n if (fileHandlers.length == 1) {\n fileHandler = fileHandlers[0];\n } else {\n fileHandler = await this.askHandlerToUser(fileHandlers, fileInfo);\n }\n return fileHandler;\n }\n\n /**\n * Ask the user to select a specific handler.\n *\n * @param {Handler[]} fileHandlers\n * @param {File} fileInfo the file info\n * @return {Promise} the selected handler\n */\n async askHandlerToUser(fileHandlers, fileInfo) {\n const extension = this.getFileExtension(fileInfo);\n // Build the modal parameters from the event data.\n const modalParams = {\n title: getString('addresourceoractivity', 'moodle'),\n body: Templates.render(\n 'core_courseformat/fileuploader',\n this.getModalData(\n fileHandlers,\n fileInfo,\n this.lastHandlers[extension] ?? null\n )\n ),\n type: ModalFactory.types.SAVE_CANCEL,\n saveButtonText: getString('upload', 'moodle'),\n };\n // Create the modal.\n const modal = await this.modalBodyRenderedPromise(modalParams);\n const selectedHandler = await this.modalUserAnswerPromise(modal, fileHandlers);\n // Cancel action.\n if (selectedHandler === null) {\n return null;\n }\n // Save last selected handler.\n this.lastHandlers[extension] = selectedHandler.module;\n return selectedHandler;\n }\n\n /**\n * Generated the modal template data.\n *\n * @param {Handler[]} fileHandlers\n * @param {File} fileInfo the file info\n * @param {String|null} defaultModule the default module if any\n * @return {Object} the modal template data.\n */\n getModalData(fileHandlers, fileInfo, defaultModule) {\n const data = {\n filename: fileInfo.name,\n uploadid: ++this.lastUploadId,\n handlers: [],\n };\n let hasDefault = false;\n fileHandlers.forEach((handler, index) => {\n const isDefault = (defaultModule == handler.module);\n data.handlers.push({\n ...handler,\n selected: isDefault,\n labelid: `fileuploader_${data.uploadid}`,\n value: index,\n });\n hasDefault = hasDefault || isDefault;\n });\n if (!hasDefault && data.handlers.length > 0) {\n const lastHandler = data.handlers.pop();\n lastHandler.selected = true;\n data.handlers.push(lastHandler);\n }\n return data;\n }\n\n /**\n * Get the user handler choice.\n *\n * Wait for the user answer in the modal and resolve with the selected index.\n *\n * @param {Modal} modal the modal instance\n * @param {Handler[]} fileHandlers the availabvle file handlers\n * @return {Promise} with the option selected by the user.\n */\n modalUserAnswerPromise(modal, fileHandlers) {\n const modalBody = getFirst(modal.getBody());\n return new Promise((resolve, reject) => {\n modal.getRoot().on(\n ModalEvents.save,\n event => {\n // Get the selected option.\n const index = modalBody.querySelector('input:checked').value;\n event.preventDefault();\n modal.destroy();\n if (!fileHandlers[index]) {\n reject('Invalid handler selected');\n }\n resolve(fileHandlers[index]);\n\n }\n );\n modal.getRoot().on(\n ModalEvents.cancel,\n () => {\n resolve(null);\n }\n );\n });\n }\n\n /**\n * Create a new modal and return a Promise to the body rendered.\n *\n * @param {Object} modalParams the modal params\n * @returns {Promise} the modal body rendered promise\n */\n modalBodyRenderedPromise(modalParams) {\n return new Promise((resolve, reject) => {\n ModalFactory.create(modalParams).then((modal) => {\n modal.setRemoveOnClose(true);\n // Handle body loading event.\n modal.getRoot().on(ModalEvents.bodyRendered, () => {\n resolve(modal);\n });\n // Configure some extra modal params.\n if (modalParams.saveButtonText !== undefined) {\n modal.setSaveButtonText(modalParams.saveButtonText);\n }\n modal.show();\n return;\n }).catch(() => {\n reject(`Cannot load modal content`);\n });\n });\n }\n}\n\n/**\n * Add a section to refresh.\n *\n * @param {number} courseId the course id\n * @param {number} sectionId the seciton id\n */\nfunction addRefreshSection(courseId, sectionId) {\n let refresh = courseUpdates.get(courseId);\n if (!refresh) {\n refresh = new Set();\n }\n refresh.add(sectionId);\n courseUpdates.set(courseId, refresh);\n refreshCourseEditors();\n}\n\n/**\n * Debounced processing all pending course refreshes.\n * @private\n */\nconst refreshCourseEditors = debounce(\n () => {\n const refreshes = courseUpdates;\n courseUpdates = new Map();\n refreshes.forEach((sectionIds, courseId) => {\n const courseEditor = getCourseEditor(courseId);\n if (!courseEditor) {\n return;\n }\n courseEditor.dispatch('sectionState', [...sectionIds]);\n });\n },\n DEBOUNCETIMER\n);\n\n/**\n * Load and return the course handler manager instance.\n *\n * @param {Number} courseId the course Id to load\n * @returns {Promise} promise of the the loaded handleManager\n */\nasync function loadCourseHandlerManager(courseId) {\n if (handlerManagers[courseId] !== undefined) {\n return handlerManagers[courseId];\n }\n const handlerManager = new HandlerManager(courseId);\n await handlerManager.loadHandlers();\n handlerManagers[courseId] = handlerManager;\n return handlerManagers[courseId];\n}\n\n/**\n * Load all the erros messages at once in the module \"errors\" variable.\n * @param {Number} courseId the course id\n */\nasync function loadErrorStrings(courseId) {\n if (errors !== null) {\n return;\n }\n const courseEditor = getCourseEditor(courseId);\n const maxbytestext = courseEditor.get('course')?.maxbytestext ?? '0';\n\n errors = {};\n const allStrings = [\n {key: 'dndmaxbytes', component: 'core_error', param: {size: maxbytestext}},\n {key: 'dndread', component: 'core_error'},\n {key: 'dndupload', component: 'core_error'},\n {key: 'dndunkownfile', component: 'core_error'},\n ];\n window.console.log(allStrings);\n const loadedStrings = await getStrings(allStrings);\n allStrings.forEach(({key}, index) => {\n errors[key] = loadedStrings[index];\n });\n}\n\n/**\n * Start a batch file uploading into the course.\n *\n * @private\n * @param {number} courseId the course id.\n * @param {number} sectionId the section id.\n * @param {number} sectionNum the section number.\n * @param {File} fileInfo the file information object\n * @param {HandlerManager} handlerManager the course handler manager\n */\nconst queueFileUpload = async function(courseId, sectionId, sectionNum, fileInfo, handlerManager) {\n let handler;\n uploadQueue = await processMonitor.createProcessQueue();\n try {\n handlerManager.validateFile(fileInfo);\n handler = await handlerManager.getFileHandler(fileInfo);\n } catch (error) {\n uploadQueue.addError(fileInfo.name, error.message);\n return;\n }\n // If we don't have a handler means the user cancel the upload.\n if (!handler) {\n return;\n }\n const fileProcessor = new FileUploader(courseId, sectionId, sectionNum, fileInfo, handler);\n uploadQueue.addPending(fileInfo.name, fileProcessor.getExecutionFunction());\n};\n\n/**\n * Upload a file to the course.\n *\n * This method will show any necesary modal to handle the request.\n *\n * @param {number} courseId the course id\n * @param {number} sectionId the section id\n * @param {number} sectionNum the section number\n * @param {Array} files and array of files\n */\nexport const uploadFilesToCourse = async function(courseId, sectionId, sectionNum, files) {\n // Get the course handlers.\n const handlerManager = await loadCourseHandlerManager(courseId);\n await loadErrorStrings(courseId);\n for (let index = 0; index < files.length; index++) {\n const fileInfo = files[index];\n await queueFileUpload(courseId, sectionId, sectionNum, fileInfo, handlerManager);\n }\n};\n"],"names":["UPLOADURL","Config","wwwroot","uploadQueue","handlerManagers","courseUpdates","Map","errors","FileUploader","constructor","courseId","sectionId","sectionNum","fileInfo","handler","execute","process","this","xhr","_createXhrRequest","formData","_createUploadFormData","reader","FileReader","onload","open","send","onerror","setError","dndread","size","readAsText","slice","getExecutionFunction","bind","XMLHttpRequest","upload","addEventListener","event","lengthComputable","percent","Math","round","loaded","total","setPercentage","onreadystatechange","readyState","status","result","JSON","parse","responseText","error","_finishProcess","dndupload","FormData","append","Error","sesskey","module","refresh","get","Set","add","set","refreshCourseEditors","addRefreshSection","finish","HandlerManager","lastUploadId","courseEditor","maxbytes","_this$courseEditor$ge2","allHandlers","getFileHandlersPromise","getFileExtension","extension","dotpos","name","lastIndexOf","substring","length","toLowerCase","validateFile","dndmaxbytes","filterHandlers","filter","fileHandlers","dndunkownfile","fileHandler","askHandlerToUser","modalParams","title","body","Templates","render","getModalData","lastHandlers","type","ModalFactory","types","SAVE_CANCEL","saveButtonText","modal","modalBodyRenderedPromise","selectedHandler","modalUserAnswerPromise","defaultModule","data","filename","uploadid","handlers","hasDefault","forEach","index","isDefault","push","selected","labelid","value","lastHandler","pop","modalBody","getBody","Promise","resolve","reject","getRoot","on","ModalEvents","save","querySelector","preventDefault","destroy","cancel","create","then","setRemoveOnClose","bodyRendered","undefined","setSaveButtonText","show","catch","refreshes","sectionIds","dispatch","queueFileUpload","async","handlerManager","processMonitor","createProcessQueue","getFileHandler","addError","message","fileProcessor","addPending","files","loadHandlers","loadCourseHandlerManager","maxbytestext","_courseEditor$get","allStrings","key","component","param","window","console","log","loadedStrings","loadErrorStrings"],"mappings":"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"} \ No newline at end of file diff --git a/course/format/amd/build/local/courseindex/cm.min.js b/course/format/amd/build/local/courseindex/cm.min.js index af1d673aba2b1..5064e50eb4445 100644 --- a/course/format/amd/build/local/courseindex/cm.min.js +++ b/course/format/amd/build/local/courseindex/cm.min.js @@ -8,6 +8,6 @@ define("core_courseformat/local/courseindex/cm",["exports","core_courseformat/lo * @class core_courseformat/local/courseindex/cm * @copyright 2021 Ferran Recio * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_dndcmitem=_interopRequireDefault(_dndcmitem),_templates=_interopRequireDefault(_templates),_prefetch=_interopRequireDefault(_prefetch),_config=_interopRequireDefault(_config);_prefetch.default.prefetchTemplate("core_courseformat/local/courseindex/cmcompletion");class Component extends _dndcmitem.default{create(){this.name="courseindex_cm",this.selectors={CM_NAME:"[data-for='cm_name']",CM_COMPLETION:"[data-for='cm_completion']"},this.classes={CMHIDDEN:"dimmed",LOCKED:"editinprogress",RESTRICTIONS:"restrictions",PAGEITEM:"pageitem",INDENTED:"indented"},this.id=this.element.dataset.id}static init(target,selectors){return new Component({element:document.getElementById(target),selectors:selectors})}stateReady(state){this.configDragDrop(this.id);const cm=state.cm.get(this.id),course=state.course;this._refreshCompletion({state:state,element:cm});const anchor=new URL(window.location.href).hash.replace("#","");(window.location.href==cm.url||window.location.href.includes(course.baseurl)&&anchor==cm.anchor)&&(this.reactive.dispatch("setPageItem","cm",this.id),this.element.scrollIntoView({block:"center"})),_config.default.contextid!=_config.default.courseContextId&&_config.default.contextInstanceId==this.id&&(this.reactive.dispatch("setPageItem","cm",this.id,!0),this.element.scrollIntoView({block:"center"})),cm.uservisible||this.addEventListener(this.getElement(this.selectors.CM_NAME),"click",this._activityAnchor)}getWatchers(){return[{watch:"cm[".concat(this.id,"]:deleted"),handler:this.remove},{watch:"cm[".concat(this.id,"]:updated"),handler:this._refreshCm},{watch:"cm[".concat(this.id,"].completionstate:updated"),handler:this._refreshCompletion},{watch:"course.pageItem:updated",handler:this._refreshPageItem}]}_refreshCm(_ref){var _element$dragging,_element$locked,_element$hascmrestric;let{element:element}=_ref;this.element.classList.toggle(this.classes.CMHIDDEN,!element.visible),this.getElement(this.selectors.CM_NAME).innerHTML=element.name,this.element.classList.toggle(this.classes.DRAGGING,null!==(_element$dragging=element.dragging)&&void 0!==_element$dragging&&_element$dragging),this.element.classList.toggle(this.classes.LOCKED,null!==(_element$locked=element.locked)&&void 0!==_element$locked&&_element$locked),this.element.classList.toggle(this.classes.RESTRICTIONS,null!==(_element$hascmrestric=element.hascmrestrictions)&&void 0!==_element$hascmrestric&&_element$hascmrestric),this.element.classList.toggle(this.classes.INDENTED,element.indent),this.locked=element.locked}_refreshPageItem(_ref2){let{element:element}=_ref2;if(!element.pageItem)return;const isPageId="cm"==element.pageItem.type&&element.pageItem.id==this.id;this.element.classList.toggle(this.classes.PAGEITEM,isPageId),isPageId&&!this.reactive.isEditing&&this.element.scrollIntoView({block:"nearest"})}async _refreshCompletion(_ref3){let{state:state,element:element}=_ref3;if(this.reactive.isEditing||!element.istrackeduser)return;const completionElement=this.getElement(this.selectors.CM_COMPLETION);if(completionElement.dataset.value==element.completionstate)return;const data=this.reactive.getExporter().cmCompletion(state,element);try{const{html:html,js:js}=await _templates.default.renderForPromise("core_courseformat/local/courseindex/cmcompletion",data);_templates.default.replaceNode(completionElement,html,js)}catch(error){throw error}}_activityAnchor(event){const cm=this.reactive.get("cm",this.id);if(document.getElementById(cm.anchor))return void setTimeout((()=>{this.reactive.dispatch("setPageItem","cm",cm.id)}),50);const course=this.reactive.get("course"),section=this.reactive.get("section",cm.sectionid);if(!section)return;const url="".concat(course.baseurl,"§ion=").concat(section.number,"#").concat(cm.anchor);event.preventDefault(),window.location=url}}return _exports.default=Component,_exports.default})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_dndcmitem=_interopRequireDefault(_dndcmitem),_templates=_interopRequireDefault(_templates),_prefetch=_interopRequireDefault(_prefetch),_config=_interopRequireDefault(_config);_prefetch.default.prefetchTemplate("core_courseformat/local/courseindex/cmcompletion");class Component extends _dndcmitem.default{create(){this.name="courseindex_cm",this.selectors={CM_NAME:"[data-for='cm_name']",CM_COMPLETION:"[data-for='cm_completion']"},this.classes={CMHIDDEN:"dimmed",LOCKED:"editinprogress",RESTRICTIONS:"restrictions",PAGEITEM:"pageitem",INDENTED:"indented"},this.id=this.element.dataset.id}static init(target,selectors){return new Component({element:document.getElementById(target),selectors:selectors})}stateReady(state){this.configDragDrop(this.id);const cm=state.cm.get(this.id),course=state.course;this._refreshCompletion({state:state,element:cm});const anchor=new URL(window.location.href).hash.replace("#","");(window.location.href==cm.url||window.location.href.includes(course.baseurl)&&anchor==cm.anchor)&&(this.reactive.dispatch("setPageItem","cm",this.id),this.element.scrollIntoView({block:"center"})),_config.default.contextid!=_config.default.courseContextId&&_config.default.contextInstanceId==this.id&&(this.reactive.dispatch("setPageItem","cm",this.id,!0),this.element.scrollIntoView({block:"center"})),cm.uservisible||this.addEventListener(this.getElement(this.selectors.CM_NAME),"click",this._activityAnchor)}getWatchers(){return[{watch:"cm[".concat(this.id,"]:deleted"),handler:this.remove},{watch:"cm[".concat(this.id,"]:updated"),handler:this._refreshCm},{watch:"cm[".concat(this.id,"].completionstate:updated"),handler:this._refreshCompletion},{watch:"course.pageItem:updated",handler:this._refreshPageItem}]}_refreshCm(_ref){var _element$dragging,_element$locked,_element$hascmrestric;let{element:element}=_ref;this.element.classList.toggle(this.classes.CMHIDDEN,!element.visible),this.getElement(this.selectors.CM_NAME).innerHTML=element.name,this.element.classList.toggle(this.classes.DRAGGING,null!==(_element$dragging=element.dragging)&&void 0!==_element$dragging&&_element$dragging),this.element.classList.toggle(this.classes.LOCKED,null!==(_element$locked=element.locked)&&void 0!==_element$locked&&_element$locked),this.element.classList.toggle(this.classes.RESTRICTIONS,null!==(_element$hascmrestric=element.hascmrestrictions)&&void 0!==_element$hascmrestric&&_element$hascmrestric),this.element.classList.toggle(this.classes.INDENTED,element.indent),this.locked=element.locked}_refreshPageItem(_ref2){let{element:element}=_ref2;if(!element.pageItem)return;const isPageId="cm"==element.pageItem.type&&element.pageItem.id==this.id;this.element.classList.toggle(this.classes.PAGEITEM,isPageId),isPageId&&!this.reactive.isEditing&&this.element.scrollIntoView({block:"nearest"})}async _refreshCompletion(_ref3){let{state:state,element:element}=_ref3;if(this.reactive.isEditing||!element.istrackeduser)return;const completionElement=this.getElement(this.selectors.CM_COMPLETION);if(completionElement.dataset.value==element.completionstate)return;const data=this.reactive.getExporter().cmCompletion(state,element),{html:html,js:js}=await _templates.default.renderForPromise("core_courseformat/local/courseindex/cmcompletion",data);_templates.default.replaceNode(completionElement,html,js)}_activityAnchor(event){const cm=this.reactive.get("cm",this.id);if(document.getElementById(cm.anchor))return void setTimeout((()=>{this.reactive.dispatch("setPageItem","cm",cm.id)}),50);const course=this.reactive.get("course"),section=this.reactive.get("section",cm.sectionid);if(!section)return;const url="".concat(course.baseurl,"§ion=").concat(section.number,"#").concat(cm.anchor);event.preventDefault(),window.location=url}}return _exports.default=Component,_exports.default})); //# sourceMappingURL=cm.min.js.map \ No newline at end of file diff --git a/course/format/amd/build/local/courseindex/cm.min.js.map b/course/format/amd/build/local/courseindex/cm.min.js.map index 1aa0b7832f0f4..196a505893cc3 100644 --- a/course/format/amd/build/local/courseindex/cm.min.js.map +++ b/course/format/amd/build/local/courseindex/cm.min.js.map @@ -1 +1 @@ -{"version":3,"file":"cm.min.js","sources":["../../../src/local/courseindex/cm.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course index cm component.\n *\n * This component is used to control specific course modules interactions like drag and drop.\n *\n * @module core_courseformat/local/courseindex/cm\n * @class core_courseformat/local/courseindex/cm\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport DndCmItem from 'core_courseformat/local/courseeditor/dndcmitem';\nimport Templates from 'core/templates';\nimport Prefetch from 'core/prefetch';\nimport Config from 'core/config';\n\n// Prefetch the completion icons template.\nconst completionTemplate = 'core_courseformat/local/courseindex/cmcompletion';\nPrefetch.prefetchTemplate(completionTemplate);\n\nexport default class Component extends DndCmItem {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'courseindex_cm';\n // Default query selectors.\n this.selectors = {\n CM_NAME: `[data-for='cm_name']`,\n CM_COMPLETION: `[data-for='cm_completion']`,\n };\n // Default classes to toggle on refresh.\n this.classes = {\n CMHIDDEN: 'dimmed',\n LOCKED: 'editinprogress',\n RESTRICTIONS: 'restrictions',\n PAGEITEM: 'pageitem',\n INDENTED: 'indented',\n };\n // We need our id to watch specific events.\n this.id = this.element.dataset.id;\n }\n\n /**\n * Static method to create a component instance form the mustache template.\n *\n * @param {element|string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @return {Component}\n */\n static init(target, selectors) {\n return new Component({\n element: document.getElementById(target),\n selectors,\n });\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the course state.\n */\n stateReady(state) {\n this.configDragDrop(this.id);\n const cm = state.cm.get(this.id);\n const course = state.course;\n // Refresh completion icon.\n this._refreshCompletion({\n state,\n element: cm,\n });\n const url = new URL(window.location.href);\n const anchor = url.hash.replace('#', '');\n // Check if the current url is the cm url.\n if (window.location.href == cm.url\n || (window.location.href.includes(course.baseurl) && anchor == cm.anchor)\n ) {\n this.reactive.dispatch('setPageItem', 'cm', this.id);\n this.element.scrollIntoView({block: \"center\"});\n }\n // Check if this we are displaying this activity page.\n if (Config.contextid != Config.courseContextId && Config.contextInstanceId == this.id) {\n this.reactive.dispatch('setPageItem', 'cm', this.id, true);\n this.element.scrollIntoView({block: \"center\"});\n }\n // Add anchor logic if the element is not user visible.\n if (!cm.uservisible) {\n this.addEventListener(\n this.getElement(this.selectors.CM_NAME),\n 'click',\n this._activityAnchor,\n );\n }\n }\n\n /**\n * Component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n {watch: `cm[${this.id}]:deleted`, handler: this.remove},\n {watch: `cm[${this.id}]:updated`, handler: this._refreshCm},\n {watch: `cm[${this.id}].completionstate:updated`, handler: this._refreshCompletion},\n {watch: `course.pageItem:updated`, handler: this._refreshPageItem},\n ];\n }\n\n /**\n * Update a course index cm using the state information.\n *\n * @param {object} param\n * @param {Object} param.element details the update details.\n */\n _refreshCm({element}) {\n // Update classes.\n this.element.classList.toggle(this.classes.CMHIDDEN, !element.visible);\n this.getElement(this.selectors.CM_NAME).innerHTML = element.name;\n this.element.classList.toggle(this.classes.DRAGGING, element.dragging ?? false);\n this.element.classList.toggle(this.classes.LOCKED, element.locked ?? false);\n this.element.classList.toggle(this.classes.RESTRICTIONS, element.hascmrestrictions ?? false);\n this.element.classList.toggle(this.classes.INDENTED, element.indent);\n this.locked = element.locked;\n }\n\n /**\n * Handle a page item update.\n *\n * @param {Object} details the update details\n * @param {Object} details.element the course state data.\n */\n _refreshPageItem({element}) {\n if (!element.pageItem) {\n return;\n }\n const isPageId = (element.pageItem.type == 'cm' && element.pageItem.id == this.id);\n this.element.classList.toggle(this.classes.PAGEITEM, isPageId);\n if (isPageId && !this.reactive.isEditing) {\n this.element.scrollIntoView({block: \"nearest\"});\n }\n }\n\n /**\n * Update the activity completion icon.\n *\n * @param {Object} details the update details\n * @param {Object} details.state the state data\n * @param {Object} details.element the element data\n */\n async _refreshCompletion({state, element}) {\n // No completion icons are displayed in edit mode.\n if (this.reactive.isEditing || !element.istrackeduser) {\n return;\n }\n // Check if the completion value has changed.\n const completionElement = this.getElement(this.selectors.CM_COMPLETION);\n if (completionElement.dataset.value == element.completionstate) {\n return;\n }\n\n // Collect section information from the state.\n const exporter = this.reactive.getExporter();\n const data = exporter.cmCompletion(state, element);\n\n try {\n const {html, js} = await Templates.renderForPromise(completionTemplate, data);\n Templates.replaceNode(completionElement, html, js);\n } catch (error) {\n throw error;\n }\n }\n\n /**\n * The activity anchor event.\n *\n * @param {Event} event\n */\n _activityAnchor(event) {\n const cm = this.reactive.get('cm', this.id);\n // If the user cannot access the element but the element is present in the page\n // the new url should be an anchor link.\n const element = document.getElementById(cm.anchor);\n if (element) {\n // Marc the element as page item once the event is handled.\n setTimeout(() => {\n this.reactive.dispatch('setPageItem', 'cm', cm.id);\n }, 50);\n return;\n }\n // If the element is not present in the page we need to go to the specific section.\n const course = this.reactive.get('course');\n const section = this.reactive.get('section', cm.sectionid);\n if (!section) {\n return;\n }\n const url = `${course.baseurl}§ion=${section.number}#${cm.anchor}`;\n event.preventDefault();\n window.location = url;\n }\n}\n"],"names":["prefetchTemplate","Component","DndCmItem","create","name","selectors","CM_NAME","CM_COMPLETION","classes","CMHIDDEN","LOCKED","RESTRICTIONS","PAGEITEM","INDENTED","id","this","element","dataset","target","document","getElementById","stateReady","state","configDragDrop","cm","get","course","_refreshCompletion","anchor","URL","window","location","href","hash","replace","url","includes","baseurl","reactive","dispatch","scrollIntoView","block","Config","contextid","courseContextId","contextInstanceId","uservisible","addEventListener","getElement","_activityAnchor","getWatchers","watch","handler","remove","_refreshCm","_refreshPageItem","classList","toggle","visible","innerHTML","DRAGGING","dragging","locked","hascmrestrictions","indent","pageItem","isPageId","type","isEditing","istrackeduser","completionElement","value","completionstate","data","getExporter","cmCompletion","html","js","Templates","renderForPromise","replaceNode","error","event","setTimeout","section","sectionid","number","preventDefault"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"cm.min.js","sources":["../../../src/local/courseindex/cm.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course index cm component.\n *\n * This component is used to control specific course modules interactions like drag and drop.\n *\n * @module core_courseformat/local/courseindex/cm\n * @class core_courseformat/local/courseindex/cm\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport DndCmItem from 'core_courseformat/local/courseeditor/dndcmitem';\nimport Templates from 'core/templates';\nimport Prefetch from 'core/prefetch';\nimport Config from 'core/config';\n\n// Prefetch the completion icons template.\nconst completionTemplate = 'core_courseformat/local/courseindex/cmcompletion';\nPrefetch.prefetchTemplate(completionTemplate);\n\nexport default class Component extends DndCmItem {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'courseindex_cm';\n // Default query selectors.\n this.selectors = {\n CM_NAME: `[data-for='cm_name']`,\n CM_COMPLETION: `[data-for='cm_completion']`,\n };\n // Default classes to toggle on refresh.\n this.classes = {\n CMHIDDEN: 'dimmed',\n LOCKED: 'editinprogress',\n RESTRICTIONS: 'restrictions',\n PAGEITEM: 'pageitem',\n INDENTED: 'indented',\n };\n // We need our id to watch specific events.\n this.id = this.element.dataset.id;\n }\n\n /**\n * Static method to create a component instance form the mustache template.\n *\n * @param {element|string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @return {Component}\n */\n static init(target, selectors) {\n return new Component({\n element: document.getElementById(target),\n selectors,\n });\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the course state.\n */\n stateReady(state) {\n this.configDragDrop(this.id);\n const cm = state.cm.get(this.id);\n const course = state.course;\n // Refresh completion icon.\n this._refreshCompletion({\n state,\n element: cm,\n });\n const url = new URL(window.location.href);\n const anchor = url.hash.replace('#', '');\n // Check if the current url is the cm url.\n if (window.location.href == cm.url\n || (window.location.href.includes(course.baseurl) && anchor == cm.anchor)\n ) {\n this.reactive.dispatch('setPageItem', 'cm', this.id);\n this.element.scrollIntoView({block: \"center\"});\n }\n // Check if this we are displaying this activity page.\n if (Config.contextid != Config.courseContextId && Config.contextInstanceId == this.id) {\n this.reactive.dispatch('setPageItem', 'cm', this.id, true);\n this.element.scrollIntoView({block: \"center\"});\n }\n // Add anchor logic if the element is not user visible.\n if (!cm.uservisible) {\n this.addEventListener(\n this.getElement(this.selectors.CM_NAME),\n 'click',\n this._activityAnchor,\n );\n }\n }\n\n /**\n * Component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n {watch: `cm[${this.id}]:deleted`, handler: this.remove},\n {watch: `cm[${this.id}]:updated`, handler: this._refreshCm},\n {watch: `cm[${this.id}].completionstate:updated`, handler: this._refreshCompletion},\n {watch: `course.pageItem:updated`, handler: this._refreshPageItem},\n ];\n }\n\n /**\n * Update a course index cm using the state information.\n *\n * @param {object} param\n * @param {Object} param.element details the update details.\n */\n _refreshCm({element}) {\n // Update classes.\n this.element.classList.toggle(this.classes.CMHIDDEN, !element.visible);\n this.getElement(this.selectors.CM_NAME).innerHTML = element.name;\n this.element.classList.toggle(this.classes.DRAGGING, element.dragging ?? false);\n this.element.classList.toggle(this.classes.LOCKED, element.locked ?? false);\n this.element.classList.toggle(this.classes.RESTRICTIONS, element.hascmrestrictions ?? false);\n this.element.classList.toggle(this.classes.INDENTED, element.indent);\n this.locked = element.locked;\n }\n\n /**\n * Handle a page item update.\n *\n * @param {Object} details the update details\n * @param {Object} details.element the course state data.\n */\n _refreshPageItem({element}) {\n if (!element.pageItem) {\n return;\n }\n const isPageId = (element.pageItem.type == 'cm' && element.pageItem.id == this.id);\n this.element.classList.toggle(this.classes.PAGEITEM, isPageId);\n if (isPageId && !this.reactive.isEditing) {\n this.element.scrollIntoView({block: \"nearest\"});\n }\n }\n\n /**\n * Update the activity completion icon.\n *\n * @param {Object} details the update details\n * @param {Object} details.state the state data\n * @param {Object} details.element the element data\n */\n async _refreshCompletion({state, element}) {\n // No completion icons are displayed in edit mode.\n if (this.reactive.isEditing || !element.istrackeduser) {\n return;\n }\n // Check if the completion value has changed.\n const completionElement = this.getElement(this.selectors.CM_COMPLETION);\n if (completionElement.dataset.value == element.completionstate) {\n return;\n }\n\n // Collect section information from the state.\n const exporter = this.reactive.getExporter();\n const data = exporter.cmCompletion(state, element);\n\n const {html, js} = await Templates.renderForPromise(completionTemplate, data);\n Templates.replaceNode(completionElement, html, js);\n }\n\n /**\n * The activity anchor event.\n *\n * @param {Event} event\n */\n _activityAnchor(event) {\n const cm = this.reactive.get('cm', this.id);\n // If the user cannot access the element but the element is present in the page\n // the new url should be an anchor link.\n const element = document.getElementById(cm.anchor);\n if (element) {\n // Marc the element as page item once the event is handled.\n setTimeout(() => {\n this.reactive.dispatch('setPageItem', 'cm', cm.id);\n }, 50);\n return;\n }\n // If the element is not present in the page we need to go to the specific section.\n const course = this.reactive.get('course');\n const section = this.reactive.get('section', cm.sectionid);\n if (!section) {\n return;\n }\n const url = `${course.baseurl}§ion=${section.number}#${cm.anchor}`;\n event.preventDefault();\n window.location = url;\n }\n}\n"],"names":["prefetchTemplate","Component","DndCmItem","create","name","selectors","CM_NAME","CM_COMPLETION","classes","CMHIDDEN","LOCKED","RESTRICTIONS","PAGEITEM","INDENTED","id","this","element","dataset","target","document","getElementById","stateReady","state","configDragDrop","cm","get","course","_refreshCompletion","anchor","URL","window","location","href","hash","replace","url","includes","baseurl","reactive","dispatch","scrollIntoView","block","Config","contextid","courseContextId","contextInstanceId","uservisible","addEventListener","getElement","_activityAnchor","getWatchers","watch","handler","remove","_refreshCm","_refreshPageItem","classList","toggle","visible","innerHTML","DRAGGING","dragging","locked","hascmrestrictions","indent","pageItem","isPageId","type","isEditing","istrackeduser","completionElement","value","completionstate","data","getExporter","cmCompletion","html","js","Templates","renderForPromise","replaceNode","event","setTimeout","section","sectionid","number","preventDefault"],"mappings":"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"} \ No newline at end of file diff --git a/course/format/amd/src/local/courseeditor/fileuploader.js b/course/format/amd/src/local/courseeditor/fileuploader.js new file mode 100644 index 0000000000000..278e2dd4e49fc --- /dev/null +++ b/course/format/amd/src/local/courseeditor/fileuploader.js @@ -0,0 +1,549 @@ +// 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 . + +/** + * The course file uploader. + * + * This module is used to upload files directly into the course. + * + * @module core_courseformat/local/courseeditor/fileuploader + * @copyright 2022 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +/** + * @typedef {Object} Handler + * @property {String} extension the handled extension or * for any + * @property {String} message the handler message + * @property {String} module the module name + */ + +import Config from 'core/config'; +import ModalFactory from 'core/modal_factory'; +import ModalEvents from 'core/modal_events'; +import Templates from 'core/templates'; +import {getFirst} from 'core/normalise'; +import {prefetchStrings} from 'core/prefetch'; +import {get_string as getString, get_strings as getStrings} from 'core/str'; +import {getCourseEditor} from 'core_courseformat/courseeditor'; +import {processMonitor} from 'core/process_monitor'; +import {debounce} from 'core/utils'; + +// Uploading url. +const UPLOADURL = Config.wwwroot + '/course/dndupload.php'; +const DEBOUNCETIMER = 500; +const USERCANIGNOREFILESIZELIMITS = -1; + +/** @var {ProcessQueue} uploadQueue the internal uploadQueue instance. */ +let uploadQueue = null; +/** @var {Object} handlerManagers the courseId indexed loaded handler managers. */ +let handlerManagers = {}; +/** @var {Map} courseUpdates the pending course sections updates. */ +let courseUpdates = new Map(); +/** @var {Object} errors the error messages. */ +let errors = null; + +// Load global strings. +prefetchStrings('moodle', ['addresourceoractivity', 'upload']); +prefetchStrings('core_error', ['dndmaxbytes', 'dndread', 'dndupload', 'dndunkownfile']); + +/** + * Class to upload a file into the course. + * @private + */ +class FileUploader { + /** + * Class constructor. + * + * @param {number} courseId the course id + * @param {number} sectionId the section id + * @param {number} sectionNum the section number + * @param {File} fileInfo the file information object + * @param {Handler} handler the file selected file handler + */ + constructor(courseId, sectionId, sectionNum, fileInfo, handler) { + this.courseId = courseId; + this.sectionId = sectionId; + this.sectionNum = sectionNum; + this.fileInfo = fileInfo; + this.handler = handler; + } + + /** + * Execute the file upload and update the state in the given process. + * + * @param {LoadingProcess} process the process to store the upload result + */ + execute(process) { + const fileInfo = this.fileInfo; + const xhr = this._createXhrRequest(process); + const formData = this._createUploadFormData(); + + // Try reading the file to check it is not a folder, before sending it to the server. + const reader = new FileReader(); + reader.onload = function() { + // File was read OK - send it to the server. + xhr.open("POST", UPLOADURL, true); + xhr.send(formData); + }; + reader.onerror = function() { + // Unable to read the file (it is probably a folder) - display an error message. + process.setError(errors.dndread); + }; + if (fileInfo.size > 0) { + // If this is a non-empty file, try reading the first few bytes. + // This will trigger reader.onerror() for folders and reader.onload() for ordinary, readable files. + reader.readAsText(fileInfo.slice(0, 5)); + } else { + // If you call slice() on a 0-byte folder, before calling readAsText, then Firefox triggers reader.onload(), + // instead of reader.onerror(). + // So, for 0-byte files, just call readAsText on the whole file (and it will trigger load/error functions as expected). + reader.readAsText(fileInfo); + } + } + + /** + * Returns the bind version of execute function. + * + * This method is used to queue the process into a ProcessQueue instance. + * + * @returns {Function} the bind function to execute the process + */ + getExecutionFunction() { + return this.execute.bind(this); + } + + /** + * Generate a upload XHR file request. + * + * @param {LoadingProcess} process the current process + * @return {XMLHttpRequest} the XHR request + */ + _createXhrRequest(process) { + const xhr = new XMLHttpRequest(); + // Update the progress bar as the file is uploaded. + xhr.upload.addEventListener( + 'progress', + (event) => { + if (event.lengthComputable) { + const percent = Math.round((event.loaded * 100) / event.total); + process.setPercentage(percent); + } + }, + false + ); + // Wait for the AJAX call to complete. + xhr.onreadystatechange = () => { + if (xhr.readyState == 1) { + // Add a 1% just to indicate that it is uploading. + process.setPercentage(1); + } + // State 4 is DONE. Otherwise the connection is still ongoing. + if (xhr.readyState != 4) { + return; + } + if (xhr.status == 200) { + var result = JSON.parse(xhr.responseText); + if (result && result.error == 0) { + // All OK. + this._finishProcess(process); + } else { + process.setError(result.error); + } + } else { + process.setError(errors.dndupload); + } + }; + return xhr; + } + + /** + * Upload a file into the course. + * + * @return {FormData|null} the new form data object + */ + _createUploadFormData() { + const formData = new FormData(); + try { + formData.append('repo_upload_file', this.fileInfo); + } catch (error) { + throw Error(error.dndread); + } + formData.append('sesskey', Config.sesskey); + formData.append('course', this.courseId); + formData.append('section', this.sectionNum); + formData.append('module', this.handler.module); + formData.append('type', 'Files'); + return formData; + } + + /** + * Finishes the current process. + * @param {LoadingProcess} process the process + */ + _finishProcess(process) { + addRefreshSection(this.courseId, this.sectionId); + process.setPercentage(100); + process.finish(); + } +} + +/** + * The file handler manager class. + * + * @private + */ +class HandlerManager { + + /** @var {Object} lastHandlers the last handlers selected per each file extension. */ + lastHandlers = {}; + + /** @var {Handler[]|null} allHandlers all the available handlers. */ + allHandlers = null; + + /** + * Class constructor. + * + * @param {Number} courseId + */ + constructor(courseId) { + this.courseId = courseId; + this.lastUploadId = 0; + this.courseEditor = getCourseEditor(courseId); + if (!this.courseEditor) { + throw Error('Unkown course editor'); + } + this.maxbytes = this.courseEditor.get('course')?.maxbytes ?? 0; + } + + /** + * Load the course file handlers. + */ + async loadHandlers() { + this.allHandlers = await this.courseEditor.getFileHandlersPromise(); + } + + /** + * Extract the file extension from a fileInfo. + * + * @param {File} fileInfo + * @returns {String} the file extension or an empty string. + */ + getFileExtension(fileInfo) { + let extension = ''; + const dotpos = fileInfo.name.lastIndexOf('.'); + if (dotpos != -1) { + extension = fileInfo.name.substring(dotpos + 1, fileInfo.name.length).toLowerCase(); + } + return extension; + } + + /** + * Check if the file is valid. + * + * @param {File} fileInfo the file info + */ + validateFile(fileInfo) { + if (this.maxbytes !== USERCANIGNOREFILESIZELIMITS && fileInfo.size > this.maxbytes) { + throw Error(errors.dndmaxbytes); + } + } + + /** + * Get the file handlers of an specific file. + * + * @param {File} fileInfo the file indo + * @return {Array} Array of handlers + */ + filterHandlers(fileInfo) { + const extension = this.getFileExtension(fileInfo); + return this.allHandlers.filter(handler => handler.extension == '*' || handler.extension == extension); + } + + /** + * Get the Handler to upload a specific file. + * + * It will ask the used if more than one handler is available. + * + * @param {File} fileInfo the file info + * @returns {Promise} the selected handler or null if the user cancel + */ + async getFileHandler(fileInfo) { + const fileHandlers = this.filterHandlers(fileInfo); + if (fileHandlers.length == 0) { + throw Error(errors.dndunkownfile); + } + let fileHandler = null; + if (fileHandlers.length == 1) { + fileHandler = fileHandlers[0]; + } else { + fileHandler = await this.askHandlerToUser(fileHandlers, fileInfo); + } + return fileHandler; + } + + /** + * Ask the user to select a specific handler. + * + * @param {Handler[]} fileHandlers + * @param {File} fileInfo the file info + * @return {Promise} the selected handler + */ + async askHandlerToUser(fileHandlers, fileInfo) { + const extension = this.getFileExtension(fileInfo); + // Build the modal parameters from the event data. + const modalParams = { + title: getString('addresourceoractivity', 'moodle'), + body: Templates.render( + 'core_courseformat/fileuploader', + this.getModalData( + fileHandlers, + fileInfo, + this.lastHandlers[extension] ?? null + ) + ), + type: ModalFactory.types.SAVE_CANCEL, + saveButtonText: getString('upload', 'moodle'), + }; + // Create the modal. + const modal = await this.modalBodyRenderedPromise(modalParams); + const selectedHandler = await this.modalUserAnswerPromise(modal, fileHandlers); + // Cancel action. + if (selectedHandler === null) { + return null; + } + // Save last selected handler. + this.lastHandlers[extension] = selectedHandler.module; + return selectedHandler; + } + + /** + * Generated the modal template data. + * + * @param {Handler[]} fileHandlers + * @param {File} fileInfo the file info + * @param {String|null} defaultModule the default module if any + * @return {Object} the modal template data. + */ + getModalData(fileHandlers, fileInfo, defaultModule) { + const data = { + filename: fileInfo.name, + uploadid: ++this.lastUploadId, + handlers: [], + }; + let hasDefault = false; + fileHandlers.forEach((handler, index) => { + const isDefault = (defaultModule == handler.module); + data.handlers.push({ + ...handler, + selected: isDefault, + labelid: `fileuploader_${data.uploadid}`, + value: index, + }); + hasDefault = hasDefault || isDefault; + }); + if (!hasDefault && data.handlers.length > 0) { + const lastHandler = data.handlers.pop(); + lastHandler.selected = true; + data.handlers.push(lastHandler); + } + return data; + } + + /** + * Get the user handler choice. + * + * Wait for the user answer in the modal and resolve with the selected index. + * + * @param {Modal} modal the modal instance + * @param {Handler[]} fileHandlers the availabvle file handlers + * @return {Promise} with the option selected by the user. + */ + modalUserAnswerPromise(modal, fileHandlers) { + const modalBody = getFirst(modal.getBody()); + return new Promise((resolve, reject) => { + modal.getRoot().on( + ModalEvents.save, + event => { + // Get the selected option. + const index = modalBody.querySelector('input:checked').value; + event.preventDefault(); + modal.destroy(); + if (!fileHandlers[index]) { + reject('Invalid handler selected'); + } + resolve(fileHandlers[index]); + + } + ); + modal.getRoot().on( + ModalEvents.cancel, + () => { + resolve(null); + } + ); + }); + } + + /** + * Create a new modal and return a Promise to the body rendered. + * + * @param {Object} modalParams the modal params + * @returns {Promise} the modal body rendered promise + */ + modalBodyRenderedPromise(modalParams) { + return new Promise((resolve, reject) => { + ModalFactory.create(modalParams).then((modal) => { + modal.setRemoveOnClose(true); + // Handle body loading event. + modal.getRoot().on(ModalEvents.bodyRendered, () => { + resolve(modal); + }); + // Configure some extra modal params. + if (modalParams.saveButtonText !== undefined) { + modal.setSaveButtonText(modalParams.saveButtonText); + } + modal.show(); + return; + }).catch(() => { + reject(`Cannot load modal content`); + }); + }); + } +} + +/** + * Add a section to refresh. + * + * @param {number} courseId the course id + * @param {number} sectionId the seciton id + */ +function addRefreshSection(courseId, sectionId) { + let refresh = courseUpdates.get(courseId); + if (!refresh) { + refresh = new Set(); + } + refresh.add(sectionId); + courseUpdates.set(courseId, refresh); + refreshCourseEditors(); +} + +/** + * Debounced processing all pending course refreshes. + * @private + */ +const refreshCourseEditors = debounce( + () => { + const refreshes = courseUpdates; + courseUpdates = new Map(); + refreshes.forEach((sectionIds, courseId) => { + const courseEditor = getCourseEditor(courseId); + if (!courseEditor) { + return; + } + courseEditor.dispatch('sectionState', [...sectionIds]); + }); + }, + DEBOUNCETIMER +); + +/** + * Load and return the course handler manager instance. + * + * @param {Number} courseId the course Id to load + * @returns {Promise} promise of the the loaded handleManager + */ +async function loadCourseHandlerManager(courseId) { + if (handlerManagers[courseId] !== undefined) { + return handlerManagers[courseId]; + } + const handlerManager = new HandlerManager(courseId); + await handlerManager.loadHandlers(); + handlerManagers[courseId] = handlerManager; + return handlerManagers[courseId]; +} + +/** + * Load all the erros messages at once in the module "errors" variable. + * @param {Number} courseId the course id + */ +async function loadErrorStrings(courseId) { + if (errors !== null) { + return; + } + const courseEditor = getCourseEditor(courseId); + const maxbytestext = courseEditor.get('course')?.maxbytestext ?? '0'; + + errors = {}; + const allStrings = [ + {key: 'dndmaxbytes', component: 'core_error', param: {size: maxbytestext}}, + {key: 'dndread', component: 'core_error'}, + {key: 'dndupload', component: 'core_error'}, + {key: 'dndunkownfile', component: 'core_error'}, + ]; + window.console.log(allStrings); + const loadedStrings = await getStrings(allStrings); + allStrings.forEach(({key}, index) => { + errors[key] = loadedStrings[index]; + }); +} + +/** + * Start a batch file uploading into the course. + * + * @private + * @param {number} courseId the course id. + * @param {number} sectionId the section id. + * @param {number} sectionNum the section number. + * @param {File} fileInfo the file information object + * @param {HandlerManager} handlerManager the course handler manager + */ +const queueFileUpload = async function(courseId, sectionId, sectionNum, fileInfo, handlerManager) { + let handler; + uploadQueue = await processMonitor.createProcessQueue(); + try { + handlerManager.validateFile(fileInfo); + handler = await handlerManager.getFileHandler(fileInfo); + } catch (error) { + uploadQueue.addError(fileInfo.name, error.message); + return; + } + // If we don't have a handler means the user cancel the upload. + if (!handler) { + return; + } + const fileProcessor = new FileUploader(courseId, sectionId, sectionNum, fileInfo, handler); + uploadQueue.addPending(fileInfo.name, fileProcessor.getExecutionFunction()); +}; + +/** + * Upload a file to the course. + * + * This method will show any necesary modal to handle the request. + * + * @param {number} courseId the course id + * @param {number} sectionId the section id + * @param {number} sectionNum the section number + * @param {Array} files and array of files + */ +export const uploadFilesToCourse = async function(courseId, sectionId, sectionNum, files) { + // Get the course handlers. + const handlerManager = await loadCourseHandlerManager(courseId); + await loadErrorStrings(courseId); + for (let index = 0; index < files.length; index++) { + const fileInfo = files[index]; + await queueFileUpload(courseId, sectionId, sectionNum, fileInfo, handlerManager); + } +}; diff --git a/course/format/amd/src/local/courseindex/cm.js b/course/format/amd/src/local/courseindex/cm.js index 80e16607c277d..01418990fe4cd 100644 --- a/course/format/amd/src/local/courseindex/cm.js +++ b/course/format/amd/src/local/courseindex/cm.js @@ -180,12 +180,8 @@ export default class Component extends DndCmItem { const exporter = this.reactive.getExporter(); const data = exporter.cmCompletion(state, element); - try { - const {html, js} = await Templates.renderForPromise(completionTemplate, data); - Templates.replaceNode(completionElement, html, js); - } catch (error) { - throw error; - } + const {html, js} = await Templates.renderForPromise(completionTemplate, data); + Templates.replaceNode(completionElement, html, js); } /** diff --git a/lib/amd/build/sortable_list.min.js.map b/lib/amd/build/sortable_list.min.js.map index b55723897969d..5b24c2e46a1a8 100644 --- a/lib/amd/build/sortable_list.min.js.map +++ b/lib/amd/build/sortable_list.min.js.map @@ -1 +1 @@ -{"version":3,"file":"sortable_list.min.js","sources":["../src/sortable_list.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * A javascript module to handle list items drag and drop\n *\n * Example of usage:\n *\n * Create a list (for example `