From a10f75360ba97462eeb1207bee021ebb7ccaadaf Mon Sep 17 00:00:00 2001 From: Miri Lipson Date: Mon, 23 Jan 2023 17:26:45 +0200 Subject: [PATCH 01/61] MDL-75047 icons: Define the orientation of the question mark in right-to-left languages. --- lang/en/langconfig.php | 1 + lib/classes/output/icon_system_fontawesome.php | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lang/en/langconfig.php b/lang/en/langconfig.php index e34c4019b709e..71d0561021e58 100644 --- a/lang/en/langconfig.php +++ b/lang/en/langconfig.php @@ -64,6 +64,7 @@ $string['strftimetime24'] = '%H:%M'; $string['thisdirection'] = 'ltr'; $string['thisdirectionvertical'] = 'btt'; +$string['thisicondirection'] = 'standard'; $string['thislanguage'] = 'English'; $string['thislanguageint'] = 'English'; $string['thousandssep'] = ','; diff --git a/lib/classes/output/icon_system_fontawesome.php b/lib/classes/output/icon_system_fontawesome.php index e3aa76c0250c4..7532cd04acd29 100644 --- a/lib/classes/output/icon_system_fontawesome.php +++ b/lib/classes/output/icon_system_fontawesome.php @@ -497,8 +497,9 @@ public function render_pix_icon(renderer_base $output, pix_icon $icon) { $data['aria-hidden'] = $icon->attributes['aria-hidden']; } - // Flipping help icon direction in right-to-left languages. - if (right_to_left() && $data['key'] == "fa-question-circle text-info") { + // Define the orientation of the question mark in right-to-left languages. + $mirror = get_string('thisicondirection', 'langconfig'); + if ($mirror == 'mirror' && (strpos($data['key'], 'fa-question') !== false)) { $data['extraclasses'] = "fa-flip-horizontal"; } From 315e8dd32738b7faf24cc0b65843f7f05f4fc869 Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Mon, 26 Jun 2023 13:31:52 +0100 Subject: [PATCH 02/61] MDL-78564 mod_assign: ensure submission statement is present. Don't require acceptance of statement, if no such statements exists, during assignment submission. --- mod/assign/locallib.php | 10 +++++++++- mod/assign/tests/behat/submission_statement.feature | 13 +++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/mod/assign/locallib.php b/mod/assign/locallib.php index 9ad5098e0e2d5..132616430a8fc 100644 --- a/mod/assign/locallib.php +++ b/mod/assign/locallib.php @@ -6785,7 +6785,15 @@ public function submit_for_grading($data, $notices) { return false; } - if ($instance->requiresubmissionstatement && empty($data->submissionstatement) && $USER->id == $userid) { + $adminconfig = $this->get_admin_config(); + + $submissionstatement = ''; + if ($instance->requiresubmissionstatement) { + $submissionstatement = $this->get_submissionstatement($adminconfig, $instance, $this->context); + } + + if (!empty($submissionstatement) && $instance->requiresubmissionstatement + && empty($data->submissionstatement) && $USER->id == $userid) { return false; } diff --git a/mod/assign/tests/behat/submission_statement.feature b/mod/assign/tests/behat/submission_statement.feature index 1098133fdbf6c..ddce1a10c2f42 100644 --- a/mod/assign/tests/behat/submission_statement.feature +++ b/mod/assign/tests/behat/submission_statement.feature @@ -39,6 +39,19 @@ Feature: In an assignment, teacher can require submission statements And I press "Continue" And I should see "Submitted for grading" in the "Submission status" "table_row" + Scenario: Student is not required to accept assignment submission statement when non exists + Given the following config values are set as admin: + | config | value | plugin | + | submissionstatement | | assign | + And I am on the "Test assign" "assign activity" page logged in as student1 + When I press "Add submission" + And I set the field "Online text" to "My submission text." + And I press "Save changes" + And I should see "Draft (not submitted)" in the "Submission status" "table_row" + And I press "Submit assignment" + And I press "Continue" + Then I should see "Submitted for grading" in the "Submission status" "table_row" + Scenario: Student is not required to accept assignment submission statement Given I am on the "Test assign" "assign activity editing" page logged in as teacher1 And I set the following fields to these values: From 24b8efe27a77e18b1a1596a8dcd450018c61be2c Mon Sep 17 00:00:00 2001 From: Lars Bonczek Date: Thu, 6 Jul 2023 18:51:55 +0200 Subject: [PATCH 03/61] MDL-78618 core_course: use UNION in get_nearest_editable_subcategory --- course/classes/category.php | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/course/classes/category.php b/course/classes/category.php index 29b1bed852462..1ad2cdae5e7a9 100644 --- a/course/classes/category.php +++ b/course/classes/category.php @@ -3206,24 +3206,27 @@ public static function get_nearest_editable_subcategory(core_course_category $pa $fields = array_keys(array_filter(self::$coursecatfields)); $ctxselect = context_helper::get_preload_record_columns_sql('ctx'); $rs = $DB->get_recordset_sql(" - SELECT cc.". join(',cc.', $fields). ", $ctxselect - FROM {course_categories} cc - JOIN {context} ctx ON cc.id = ctx.instanceid AND ctx.contextlevel = :contextcoursecat - LEFT JOIN {role_assignments} ra ON ra.contextid = ctx.id - LEFT JOIN {role_capabilities} rc ON rc.contextid = ctx.id - LEFT JOIN {role_assignments} rc_ra ON rc_ra.roleid = rc.roleid - LEFT JOIN {context} rc_ra_ctx ON rc_ra_ctx.id = rc_ra.contextid - WHERE ctx.path LIKE :parentpath - AND ( - ra.userid = :userid1 - OR ( - rc_ra.userid = :userid2 - AND (ctx.path = rc_ra_ctx.path OR ctx.path LIKE " . $DB->sql_concat("rc_ra_ctx.path", "'/%'") . ") - ) - ) + SELECT cc.". join(',cc.', $fields). ", $ctxselect + FROM {course_categories} cc + JOIN {context} ctx ON cc.id = ctx.instanceid AND ctx.contextlevel = :contextcoursecat1 + JOIN {role_assignments} ra ON ra.contextid = ctx.id + WHERE ctx.path LIKE :parentpath1 + AND ra.userid = :userid1 + UNION + SELECT cc.". join(',cc.', $fields). ", $ctxselect + FROM {course_categories} cc + JOIN {context} ctx ON cc.id = ctx.instanceid AND ctx.contextlevel = :contextcoursecat2 + JOIN {role_capabilities} rc ON rc.contextid = ctx.id + JOIN {role_assignments} rc_ra ON rc_ra.roleid = rc.roleid + JOIN {context} rc_ra_ctx ON rc_ra_ctx.id = rc_ra.contextid + WHERE ctx.path LIKE :parentpath2 + AND rc_ra.userid = :userid2 + AND (ctx.path = rc_ra_ctx.path OR ctx.path LIKE " . $DB->sql_concat("rc_ra_ctx.path", "'/%'") . ") ", [ - 'contextcoursecat' => CONTEXT_COURSECAT, - 'parentpath' => $parentcat->get_context()->path . '/%', + 'contextcoursecat1' => CONTEXT_COURSECAT, + 'contextcoursecat2' => CONTEXT_COURSECAT, + 'parentpath1' => $parentcat->get_context()->path . '/%', + 'parentpath2' => $parentcat->get_context()->path . '/%', 'userid1' => $USER->id, 'userid2' => $USER->id ]); From 03a7d208f90c673dff7eb55bf6b4e8c0d94f3898 Mon Sep 17 00:00:00 2001 From: Miri Lipson Date: Mon, 10 Jul 2023 11:36:01 +0300 Subject: [PATCH 04/61] MDL-75115 theme_boost: Fix Bootstrap tooltips in RTL mode --- theme/boost/scss/moodle/bootstrap-rtl.scss | 31 ++++++++++++++++++---- theme/boost/style/moodle.css | 12 ++++++++- theme/classic/style/moodle.css | 12 ++++++++- 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/theme/boost/scss/moodle/bootstrap-rtl.scss b/theme/boost/scss/moodle/bootstrap-rtl.scss index da2a1e1452506..ede468e9c7a07 100644 --- a/theme/boost/scss/moodle/bootstrap-rtl.scss +++ b/theme/boost/scss/moodle/bootstrap-rtl.scss @@ -13,10 +13,31 @@ } } -.dir-rtl .custom-select { - background-position: 0.75rem center; -} +.dir-rtl { + .custom-select { + background-position: 0.75rem center; + } + + .custom-switch .custom-control-input:checked ~ .custom-control-label::after { + transform: translateX(-($custom-switch-width - $custom-control-indicator-size)); + } -.dir-rtl .custom-switch .custom-control-input:checked ~ .custom-control-label::after { - transform: translateX(-($custom-switch-width - $custom-control-indicator-size)); + .tooltip { + &.bs-tooltip-left, + &.bs-tooltip-right { + .arrow { + transform: rotate(180deg); + } + } + + &.bs-tooltip-left .arrow { + left: auto; + right: 0; + } + + &.bs-tooltip-right .arrow { + left: 0; + right: auto; + } + } } diff --git a/theme/boost/style/moodle.css b/theme/boost/style/moodle.css index 4c5456b877864..32ffee115e4cf 100644 --- a/theme/boost/style/moodle.css +++ b/theme/boost/style/moodle.css @@ -12713,10 +12713,20 @@ a.text-dark:hover, a.text-dark:focus { .dir-rtl .custom-select { background-position: 0.75rem center; } - .dir-rtl .custom-switch .custom-control-input:checked ~ .custom-control-label::after { transform: translateX(-0.9375rem); } +.dir-rtl .tooltip.bs-tooltip-left .arrow, .dir-rtl .tooltip.bs-tooltip-auto[x-placement^=left] .arrow, .dir-rtl .tooltip.bs-tooltip-right .arrow, .dir-rtl .tooltip.bs-tooltip-auto[x-placement^=right] .arrow { + transform: rotate(180deg); +} +.dir-rtl .tooltip.bs-tooltip-left .arrow, .dir-rtl .tooltip.bs-tooltip-auto[x-placement^=left] .arrow { + left: auto; + right: 0; +} +.dir-rtl .tooltip.bs-tooltip-right .arrow, .dir-rtl .tooltip.bs-tooltip-auto[x-placement^=right] .arrow { + left: 0; + right: auto; +} /** * Moodle variables diff --git a/theme/classic/style/moodle.css b/theme/classic/style/moodle.css index fdf5ff15b853a..d0d24ab67d048 100644 --- a/theme/classic/style/moodle.css +++ b/theme/classic/style/moodle.css @@ -12713,10 +12713,20 @@ a.text-dark:hover, a.text-dark:focus { .dir-rtl .custom-select { background-position: 0.75rem center; } - .dir-rtl .custom-switch .custom-control-input:checked ~ .custom-control-label::after { transform: translateX(-0.75rem); } +.dir-rtl .tooltip.bs-tooltip-left .arrow, .dir-rtl .tooltip.bs-tooltip-auto[x-placement^=left] .arrow, .dir-rtl .tooltip.bs-tooltip-right .arrow, .dir-rtl .tooltip.bs-tooltip-auto[x-placement^=right] .arrow { + transform: rotate(180deg); +} +.dir-rtl .tooltip.bs-tooltip-left .arrow, .dir-rtl .tooltip.bs-tooltip-auto[x-placement^=left] .arrow { + left: auto; + right: 0; +} +.dir-rtl .tooltip.bs-tooltip-right .arrow, .dir-rtl .tooltip.bs-tooltip-auto[x-placement^=right] .arrow { + left: 0; + right: auto; +} /** * Moodle variables From 020259726134f76581fe1a013c87f0b389b7ecd3 Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Wed, 12 Jul 2023 14:03:59 +0100 Subject: [PATCH 05/61] MDL-77645 course: ensure active enrolments are considered for drawer. Otherwise the enrolment index page tries to load the course index drawer, which doesn't exist at this point and triggers exceptions in the editor module. Co-authored-by: Tim Volckmann --- course/lib.php | 2 +- course/tests/behat/course_browsing.feature | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/course/lib.php b/course/lib.php index 50602113e769e..f01de15b6c752 100644 --- a/course/lib.php +++ b/course/lib.php @@ -3751,7 +3751,7 @@ function core_course_drawer(): string { } // Show course index to users can access the course only. - if (!can_access_course($PAGE->course)) { + if (!can_access_course($PAGE->course, null, '', true)) { return ''; } diff --git a/course/tests/behat/course_browsing.feature b/course/tests/behat/course_browsing.feature index 2cacc8c866159..a8b375bd31db1 100644 --- a/course/tests/behat/course_browsing.feature +++ b/course/tests/behat/course_browsing.feature @@ -102,3 +102,11 @@ Feature: Restricting access to course lists And I follow "Biology Y1" And I should see "You cannot enrol yourself in this course." And I log out + + @javascript + Scenario: Browse courses as a user who has a disabled enrolment in them + Given the following "course enrolments" exist: + | user | course | role | status | + | usere | ENG1 | student | 1 | + When I am on the "ENG1" course page logged in as usere + Then I should see "You cannot enrol yourself in this course." From 962a3f866c01d7b3a8ac721e153abf17f2869df0 Mon Sep 17 00:00:00 2001 From: Rajneel Totaram Date: Thu, 13 Jul 2023 13:12:38 +1200 Subject: [PATCH 06/61] MDL-78704 course: Render unescaped HTML for activity dates --- course/templates/activity_date.mustache | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/course/templates/activity_date.mustache b/course/templates/activity_date.mustache index a2013d6a20d52..9a8aabadd5c09 100644 --- a/course/templates/activity_date.mustache +++ b/course/templates/activity_date.mustache @@ -26,5 +26,5 @@ } }}
- {{label}} {{datestring}} + {{label}} {{{datestring}}}
From f25ad012c54f8659301f75134be4bee02a715bc1 Mon Sep 17 00:00:00 2001 From: Petr Skoda Date: Tue, 20 Jun 2023 09:30:46 +0200 Subject: [PATCH 07/61] MDL-78525 core: fix word and character counting --- lib/moodlelib.php | 16 +++++++++-- lib/tests/moodlelib_test.php | 35 ++++++++++++++++++++--- mod/forum/classes/local/entities/post.php | 4 +-- mod/forum/post.php | 7 ++--- 4 files changed, 50 insertions(+), 12 deletions(-) diff --git a/lib/moodlelib.php b/lib/moodlelib.php index d979952162968..d00252caba914 100644 --- a/lib/moodlelib.php +++ b/lib/moodlelib.php @@ -8381,9 +8381,10 @@ function moodle_setlocale($locale='') { * * @category string * @param string $string The text to be searched for words. May be HTML. + * @param int|null $format * @return int The count of words in the specified string */ -function count_words($string) { +function count_words($string, $format = null) { // Before stripping tags, add a space after the close tag of anything that is not obviously inline. // Also, br is a special case because it definitely delimits a word, but has no close tag. $string = preg_replace('~ @@ -8400,6 +8401,11 @@ function count_words($string) {
| # Special cases that are not close tags. ) ~x', '$1 ', $string); // Add a space after the close tag. + if ($format !== null && $format != FORMAT_PLAIN) { + // Match the usual text cleaning before display. + // Ideally we should apply multilang filter only here, other filters might add extra text. + $string = format_text($string, $format, ['filter' => false, 'noclean' => false, 'para' => false]); + } // Now remove HTML tags. $string = strip_tags($string); // Decode HTML entities. @@ -8421,9 +8427,15 @@ function count_words($string) { * * @category string * @param string $string The text to be searched for letters. May be HTML. + * @param int|null $format * @return int The count of letters in the specified text. */ -function count_letters($string) { +function count_letters($string, $format = null) { + if ($format !== null && $format != FORMAT_PLAIN) { + // Match the usual text cleaning before display. + // Ideally we should apply multilang filter only here, other filters might add extra text. + $string = format_text($string, $format, ['filter' => false, 'noclean' => false, 'para' => false]); + } $string = strip_tags($string); // Tags are out now. $string = html_entity_decode($string, ENT_COMPAT); $string = preg_replace('/[[:space:]]*/', '', $string); // Whitespace are out now. diff --git a/lib/tests/moodlelib_test.php b/lib/tests/moodlelib_test.php index 591b4e35ab9d1..bc8ac172a8e39 100644 --- a/lib/tests/moodlelib_test.php +++ b/lib/tests/moodlelib_test.php @@ -3958,9 +3958,11 @@ public function test_username_load_fields_from_object() { * @dataProvider count_words_testcases * @param int $expectedcount number of words in $string. * @param string $string the test string to count the words of. + * @param int|null $format */ - public function test_count_words(int $expectedcount, string $string): void { - $this->assertEquals($expectedcount, count_words($string)); + public function test_count_words(int $expectedcount, string $string, $format = null): void { + $this->assertEquals($expectedcount, count_words($string, $format), + "'$string' with format '$format' does not match count $expectedcount"); } /** @@ -3969,6 +3971,13 @@ public function test_count_words(int $expectedcount, string $string): void { * @return array of test cases. */ public function count_words_testcases(): array { + // Copy-pasting example from MDL-64240. + $copypasted = <<Snoot is booped

+ + Boop the Snoot. +EOT; + // The counts here should match MS Word and Libre Office. return [ [0, ''], @@ -4005,6 +4014,16 @@ public function count_words_testcases(): array { [1, "SO42-"], [6, '4+4=8 i.e. O(1) a,b,c,d I’m black&blue_really'], [1, 'ab'], + [1, 'ab', FORMAT_PLAIN], + [1, 'ab', FORMAT_HTML], + [1, 'ab', FORMAT_MOODLE], + [1, 'ab', FORMAT_MARKDOWN], + [1, 'aa pokus'], + [2, 'aa pokus', FORMAT_HTML], + [6, $copypasted], + [6, $copypasted, FORMAT_PLAIN], + [3, $copypasted, FORMAT_HTML], + [3, $copypasted, FORMAT_MOODLE], ]; } @@ -4014,9 +4033,11 @@ public function count_words_testcases(): array { * @dataProvider count_letters_testcases * @param int $expectedcount number of characters in $string. * @param string $string the test string to count the letters of. + * @param int|null $format */ - public function test_count_letters(int $expectedcount, string $string): void { - $this->assertEquals($expectedcount, count_letters($string)); + public function test_count_letters(int $expectedcount, string $string, $format = null): void { + $this->assertEquals($expectedcount, count_letters($string, $format), + "'$string' with format '$format' does not match count $expectedcount"); } /** @@ -4030,6 +4051,12 @@ public function count_letters_testcases(): array { [1, 'x'], [1, '&'], [4, '

frog

'], + [4, '

frog

', FORMAT_PLAIN], + [4, '

frog

', FORMAT_MOODLE], + [4, '

frog

', FORMAT_HTML], + [4, '

frog

', FORMAT_MARKDOWN], + [2, 'aa pokus'], + [7, 'aa pokus', FORMAT_HTML], ]; } diff --git a/mod/forum/classes/local/entities/post.php b/mod/forum/classes/local/entities/post.php index 90cd0fc6ba888..119e1d02cd220 100644 --- a/mod/forum/classes/local/entities/post.php +++ b/mod/forum/classes/local/entities/post.php @@ -350,8 +350,8 @@ public function get_charcount() : ?int { */ public static function add_message_counts(\stdClass $record) : void { if (!empty($record->message)) { - $record->wordcount = count_words($record->message); - $record->charcount = count_letters($record->message); + $record->wordcount = count_words($record->message, $record->messageformat); + $record->charcount = count_letters($record->message, $record->messageformat); } } } diff --git a/mod/forum/post.php b/mod/forum/post.php index a3f2925cc0a70..9ea0f2aef3144 100644 --- a/mod/forum/post.php +++ b/mod/forum/post.php @@ -799,10 +799,9 @@ // WARNING: the $fromform->message array has been overwritten, do not use it anymore! $fromform->messagetrust = trusttext_trusted($modcontext); - // Clean message text, unless markdown which should be saved as it is, otherwise editing messes things up. - if ($fromform->messageformat != FORMAT_MARKDOWN) { - $fromform = trusttext_pre_edit($fromform, 'message', $modcontext); - } + // Do not clean text here, text cleaning can be done only after conversion to HTML. + // Word counting now uses text formatting, there is no need to abuse trusttext_pre_edit() here. + if ($fromform->edit) { // Updating a post. unset($fromform->groupid); From f6583c57c8bdeef1a05787a14c52b05bcc63702f Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Tue, 13 Jun 2023 14:52:08 +0100 Subject: [PATCH 08/61] MDL-78460 mod_forum: correctly render timed post label information. --- mod/forum/templates/discussion_list.mustache | 2 +- mod/forum/templates/discussion_times.mustache | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/mod/forum/templates/discussion_list.mustache b/mod/forum/templates/discussion_list.mustache index 1c533b7dde599..13e5309dbc93a 100644 --- a/mod/forum/templates/discussion_list.mustache +++ b/mod/forum/templates/discussion_list.mustache @@ -222,7 +222,7 @@ diff --git a/mod/forum/templates/discussion_times.mustache b/mod/forum/templates/discussion_times.mustache index e35828d151318..2b0c35723e887 100644 --- a/mod/forum/templates/discussion_times.mustache +++ b/mod/forum/templates/discussion_times.mustache @@ -17,7 +17,8 @@ {{! @template mod_forum/discussion_times - Template to display the discussion times. + Template to display the discussion times. Note that this template is used only as a partial from within discussion_list, which + requires the language strings be passed through the cleanstr helper Classes required for JS: * none @@ -40,20 +41,20 @@
    {{#start}}
  • - {{#str}} displaystartdate, mod_forum, {{#userdate}}{{.}}, {{#str}}strftimerecentfull {{/str}}{{/userdate}} {{/str}} + {{#cleanstr}} displaystartdate, mod_forum, {{#userdate}}{{.}}, {{#str}}strftimerecentfull {{/str}}{{/userdate}} {{/cleanstr}}
  • {{/start}} {{#end}}
  • - {{#str}} displayenddate, mod_forum, {{#userdate}}{{.}}, {{#str}}strftimerecentfull {{/str}}{{/userdate}} {{/str}} + {{#cleanstr}} displayenddate, mod_forum, {{#userdate}}{{.}}, {{#str}}strftimerecentfull {{/str}}{{/userdate}} {{/cleanstr}}
  • {{/end}}
  • {{#visible}} - {{#str}} timedvisible, mod_forum {{/str}} + {{#cleanstr}} timedvisible, mod_forum {{/cleanstr}} {{/visible}} {{^visible}} - {{#str}} timedhidden, mod_forum {{/str}} + {{#cleanstr}} timedhidden, mod_forum {{/cleanstr}} {{/visible}}
From 587ea4d3f348211d4e77699bac89b950fb2ae7ac Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Fri, 14 Jul 2023 15:07:15 +0100 Subject: [PATCH 09/61] MDL-78716 course: fix category idnumber updating logic. Where only the case of the idnumber field was being updated, we triggered an exception because checking for an exising idnumber was not excluding the current category (i.e. the one being updated). --- course/classes/category.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/course/classes/category.php b/course/classes/category.php index 29b1bed852462..293340b3a8ccc 100644 --- a/course/classes/category.php +++ b/course/classes/category.php @@ -596,7 +596,11 @@ public function update($data, $editoroptions = null) { if (core_text::strlen($data->idnumber) > 100) { throw new moodle_exception('idnumbertoolong'); } - if (strval($data->idnumber) !== '' && $DB->record_exists('course_categories', array('idnumber' => $data->idnumber))) { + + // Ensure there are no other categories with the same idnumber. + if (strval($data->idnumber) !== '' && + $DB->record_exists_select('course_categories', 'idnumber = ? AND id != ?', [$data->idnumber, $this->id])) { + throw new moodle_exception('categoryidnumbertaken'); } $newcategory->idnumber = $data->idnumber; From 0c7a684fccdc52a63caf2b408cb0204f15d344ff Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Fri, 14 Jul 2023 17:12:51 +0100 Subject: [PATCH 10/61] MDL-78715 payment: prevent action menus flowing under container. --- payment/accounts.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/payment/accounts.php b/payment/accounts.php index a612cf84a4264..bfccc651dfe21 100644 --- a/payment/accounts.php +++ b/payment/accounts.php @@ -60,6 +60,7 @@ $menu = new action_menu(); $menu->set_menu_trigger(get_string('edit')); + $menu->set_boundary('window'); if ($canmanage) { $menu->add(new action_menu_link_secondary($account->get_edit_url(), null, get_string('edit'))); if (!$account->get('archived')) { @@ -85,7 +86,7 @@ echo html_writer::div($text, 'pb-2'); } -echo html_writer::table($table); +echo html_writer::div(html_writer::table($table), 'position-relative'); $PAGE->requires->event_handler('[data-action=delete]', 'click', 'M.util.show_confirm_dialog', array('message' => get_string('accountdeleteconfirm', 'payment'))); From ba6143d1acfbf11c9c45b1cc39684deb6fdbef19 Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Fri, 14 Jul 2023 16:25:07 +0100 Subject: [PATCH 11/61] MDL-78703 behat: fix HTTP GET method capitalization on redirect. --- lib/tests/behat/behat_general.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tests/behat/behat_general.php b/lib/tests/behat/behat_general.php index dbce60b922d49..bb66b5305d74f 100644 --- a/lib/tests/behat/behat_general.php +++ b/lib/tests/behat/behat_general.php @@ -148,7 +148,7 @@ public function i_wait_to_be_redirected() { } else if (!empty($url)) { // We redirect directly as we can not wait for an automatic redirection. - $this->getSession()->getDriver()->getClient()->request('get', $url); + $this->getSession()->getDriver()->getClient()->request('GET', $url); } else { // Reload the page if no URL was provided. From ad5e75ddf117613968a63f40a89d8283051c2da6 Mon Sep 17 00:00:00 2001 From: Leon Stringer Date: Wed, 11 Jan 2023 11:32:23 +0000 Subject: [PATCH 12/61] MDL-76854 tool_monitor: subscription_deleted ctx Fix course context for subscription_deleted event when deleting rules with multiple subscriptions. --- admin/tool/monitor/classes/subscription_manager.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/admin/tool/monitor/classes/subscription_manager.php b/admin/tool/monitor/classes/subscription_manager.php index df2d3b367701e..9415f01b77e56 100644 --- a/admin/tool/monitor/classes/subscription_manager.php +++ b/admin/tool/monitor/classes/subscription_manager.php @@ -194,10 +194,9 @@ public static function remove_all_subscriptions_for_rule($ruleid, $coursecontext if (!is_null($coursecontext)) { $context = $coursecontext; $courseid = $subscription->courseid; - } else if (!empty($subscription->courseid) && ($coursecontext = + } else if (!empty($subscription->courseid) && ($context = \context_course::instance($subscription->courseid, IGNORE_MISSING))) { $courseid = $subscription->courseid; - $context = $coursecontext; } else { $courseid = 0; $context = \context_system::instance(); From 36918dfbfc571aec069bf02806183731b93535ff Mon Sep 17 00:00:00 2001 From: AMOS bot Date: Wed, 19 Jul 2023 00:11:00 +0000 Subject: [PATCH 13/61] Automatically generated installer lang files --- install/lang/lv/admin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/lang/lv/admin.php b/install/lang/lv/admin.php index be457af940f75..83514a8450907 100644 --- a/install/lang/lv/admin.php +++ b/install/lang/lv/admin.php @@ -38,7 +38,7 @@ $string['cliunknowoption'] = 'Neatpazītas opcijas: {$a} Lūdzu, izmantojiet opciju --help.'; -$string['cliyesnoprompt'] = 'ierakstiet y (nozīmē jā) vai n (nozīmē nē)'; +$string['cliyesnoprompt'] = 'ierakstiet j (nozīmē jā) vai n (nozīmē nē)'; $string['environmentrequireinstall'] = 'nepieciešams instalēt/iespējot'; $string['environmentrequireversion'] = 'nepieciešama versija {$a->needed}, bet jūs izmantojat versiju {$a->current}'; $string['upgradekeyset'] = 'Jaunināšanas atslēga (lai to neiestatītu, atstājiet tukšu)'; From 792530cfe67fc7ba52c96ff7ad8c80f931878bd4 Mon Sep 17 00:00:00 2001 From: Huong Nguyen Date: Thu, 20 Jul 2023 10:54:57 +0700 Subject: [PATCH 14/61] MDL-78525 core: Update upgrade.txt for count_words and count_letters --- lib/upgrade.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/upgrade.txt b/lib/upgrade.txt index 34bcec72732ba..bbb13e03f079b 100644 --- a/lib/upgrade.txt +++ b/lib/upgrade.txt @@ -11,6 +11,7 @@ information provided here is intended especially for developers. even if we define the settings in config.php. * New \core\output\activity_header::get_heading_level() method to get the heading level for a given heading level depending whether the page displays a heading for the activity (usually a h2 heading containing the activity name). +* count_words() and count_letters() have a new optional parameter called $format to format the text before doing the counting. === 4.1.4 === * Added a new parameter in address_in_subnet to give us the ability to check for 0.0.0.0 or not. From 9cb8b3fa5077ce81b917ea794536da93ac4a844a Mon Sep 17 00:00:00 2001 From: Jun Pataleta Date: Thu, 20 Jul 2023 14:22:01 +0800 Subject: [PATCH 15/61] MDL-78770 atto_recordrtc: Use proper window.console.warn() function --- .../moodle-atto_recordrtc-recording-debug.js | 2 +- .../moodle-atto_recordrtc-recording-min.js | 2 +- .../moodle-atto_recordrtc-recording.js | 2 +- .../atto/plugins/recordrtc/yui/src/recording/js/commonmodule.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/editor/atto/plugins/recordrtc/yui/build/moodle-atto_recordrtc-recording/moodle-atto_recordrtc-recording-debug.js b/lib/editor/atto/plugins/recordrtc/yui/build/moodle-atto_recordrtc-recording/moodle-atto_recordrtc-recording-debug.js index 9b2eafc39341e..691a99c55da3b 100644 --- a/lib/editor/atto/plugins/recordrtc/yui/build/moodle-atto_recordrtc-recording/moodle-atto_recordrtc-recording-debug.js +++ b/lib/editor/atto/plugins/recordrtc/yui/build/moodle-atto_recordrtc-recording/moodle-atto_recordrtc-recording-debug.js @@ -204,7 +204,7 @@ M.atto_recordrtc.commonmodule = { } } - window.console.warning('Unknown file type for MediaRecorder API'); + window.console.warn('Unknown file type for MediaRecorder API'); return ''; }, diff --git a/lib/editor/atto/plugins/recordrtc/yui/build/moodle-atto_recordrtc-recording/moodle-atto_recordrtc-recording-min.js b/lib/editor/atto/plugins/recordrtc/yui/build/moodle-atto_recordrtc-recording/moodle-atto_recordrtc-recording-min.js index 02a03763d5ee2..27b3cf0fc92e1 100644 --- a/lib/editor/atto/plugins/recordrtc/yui/build/moodle-atto_recordrtc-recording/moodle-atto_recordrtc-recording-min.js +++ b/lib/editor/atto/plugins/recordrtc/yui/build/moodle-atto_recordrtc-recording/moodle-atto_recordrtc-recording-min.js @@ -1,2 +1,2 @@ -YUI.add("moodle-atto_recordrtc-recording",function(r,t){var l,n,e;M.atto_recordrtc=M.atto_recordrtc||{},l=M.atto_recordrtc.commonmodule,n=M.atto_recordrtc.abstractmodule,M.atto_recordrtc.commonmodule={editorScope:null,alertWarning:null,alertDanger:null,player:null,playerDOM:null,startStopBtn:null,uploadBtn:null,countdownSeconds:null,countdownTicker:null,recType:null,stream:null,mediaRecorder:null,chunks:null,blobSize:null,maxUploadSize:null,capture_user_media:function(t,e,o){window.navigator.mediaDevices.getUserMedia(t).then(e)["catch"](o)},handle_data_available:function(t){l.chunks.push(t.data),l.blobSize+=t.data.size,l.blobSize>=l.maxUploadSize&&(window.localStorage.getItem("alerted")?window.localStorage.removeItem("alerted"):(window.localStorage.setItem("alerted","true"),l.startStopBtn.simulate("click"),n.show_alert("nearingmaxsize")),l.chunks.pop())},handle_stop:function(){var t=new window.Blob(l.chunks,{type:l.mediaRecorder.mimeType});l.player.set("srcObject",null),l.player.set("src",window.URL.createObjectURL(t)),l.player.set("muted",!1),l.player.set("controls",!0),l.player.ancestor().ancestor().removeClass("hide"),l.uploadBtn.ancestor().ancestor().removeClass("hide"),l.uploadBtn.set("textContent",M.util.get_string("attachrecording","atto_recordrtc")),l.uploadBtn.set("disabled",!1),l.editorScope.getDialogue().centered(),l.uploadBtn.on("click",function(){0===l.chunks.length?n.show_alert("norecordingfound"):(l.uploadBtn.set("disabled",!0),l.upload_to_server(l.recType,function(t,e){"ended"===t?(l.uploadBtn.set("disabled",!1),l.insert_annotation(l.recType,e)):"upload-failed"===t?(l.uploadBtn.set("disabled",!1),l.uploadBtn.set("textContent",M.util.get_string("uploadfailed","atto_recordrtc")+" "+e)):"upload-failed-404"===t?(l.uploadBtn.set("disabled",!1),l.uploadBtn.set("textContent",M.util.get_string("uploadfailed404","atto_recordrtc"))):"upload-aborted"===t?(l.uploadBtn.set("disabled",!1),l.uploadBtn.set("textContent",M.util.get_string("uploadaborted","atto_recordrtc")+" "+e)):l.uploadBtn.set("textContent",t)}))})},start_recording:function(t,e){var o=n.select_rec_options(t);l.mediaRecorder=new window.MediaRecorder(e,o),l.mediaRecorder.ondataavailable=l.handle_data_available,l.mediaRecorder.onstop=l.handle_stop,l.mediaRecorder.start(1e3),l.player.set("muted",!0),l.countdownSeconds="audio"===t?l.editorScope.get("audiotimelimit"):"video"===t?l.editorScope.get("videotimelimit"):l.editorScope.get("defaulttimelimit"),l.countdownSeconds++,e=M.util.get_string("stoprecording","atto_recordrtc"),l.startStopBtn.setHTML(e+=' (:)'),l.set_time(),l.countdownTicker=window.setInterval(l.set_time,1e3),l.startStopBtn.set("disabled",!1)},stop_recording:function(t){var e,o;for(l.mediaRecorder.stop(),e=t.getTracks(),o=0;o":"")},insert_annotation:function(t,e){t=l.create_annotation(t,e);t?l.editorScope.setLink(l.editorScope,t):l.uploadBtn.set("textContent",M.util.get_string("attachrecording","atto_recordrtc"))}},M.atto_recordrtc=M.atto_recordrtc||{},l=M.atto_recordrtc.commonmodule,n=M.atto_recordrtc.abstractmodule,M.atto_recordrtc.compatcheckmodule={check_has_gum:function(){navigator.mediaDevices&&window.MediaRecorder||n.show_alert("nowebrtc",function(){l.editorScope.closeDialogue(l.editorScope)})},check_secure:function(){"https:"===window.location.protocol||-1!==window.location.host.indexOf("localhost")||l.alertDanger.ancestor().ancestor().removeClass("hide")}},M.atto_recordrtc=M.atto_recordrtc||{},l=M.atto_recordrtc.commonmodule,n=M.atto_recordrtc.abstractmodule,M.atto_recordrtc.abstractmodule={show_alert:function(e,o){r.use("moodle-core-notification-alert",function(){var t=new M.core.alert({ +YUI.add("moodle-atto_recordrtc-recording",function(r,t){var l,n,e;M.atto_recordrtc=M.atto_recordrtc||{},l=M.atto_recordrtc.commonmodule,n=M.atto_recordrtc.abstractmodule,M.atto_recordrtc.commonmodule={editorScope:null,alertWarning:null,alertDanger:null,player:null,playerDOM:null,startStopBtn:null,uploadBtn:null,countdownSeconds:null,countdownTicker:null,recType:null,stream:null,mediaRecorder:null,chunks:null,blobSize:null,maxUploadSize:null,capture_user_media:function(t,e,o){window.navigator.mediaDevices.getUserMedia(t).then(e)["catch"](o)},handle_data_available:function(t){l.chunks.push(t.data),l.blobSize+=t.data.size,l.blobSize>=l.maxUploadSize&&(window.localStorage.getItem("alerted")?window.localStorage.removeItem("alerted"):(window.localStorage.setItem("alerted","true"),l.startStopBtn.simulate("click"),n.show_alert("nearingmaxsize")),l.chunks.pop())},handle_stop:function(){var t=new window.Blob(l.chunks,{type:l.mediaRecorder.mimeType});l.player.set("srcObject",null),l.player.set("src",window.URL.createObjectURL(t)),l.player.set("muted",!1),l.player.set("controls",!0),l.player.ancestor().ancestor().removeClass("hide"),l.uploadBtn.ancestor().ancestor().removeClass("hide"),l.uploadBtn.set("textContent",M.util.get_string("attachrecording","atto_recordrtc")),l.uploadBtn.set("disabled",!1),l.editorScope.getDialogue().centered(),l.uploadBtn.on("click",function(){0===l.chunks.length?n.show_alert("norecordingfound"):(l.uploadBtn.set("disabled",!0),l.upload_to_server(l.recType,function(t,e){"ended"===t?(l.uploadBtn.set("disabled",!1),l.insert_annotation(l.recType,e)):"upload-failed"===t?(l.uploadBtn.set("disabled",!1),l.uploadBtn.set("textContent",M.util.get_string("uploadfailed","atto_recordrtc")+" "+e)):"upload-failed-404"===t?(l.uploadBtn.set("disabled",!1),l.uploadBtn.set("textContent",M.util.get_string("uploadfailed404","atto_recordrtc"))):"upload-aborted"===t?(l.uploadBtn.set("disabled",!1),l.uploadBtn.set("textContent",M.util.get_string("uploadaborted","atto_recordrtc")+" "+e)):l.uploadBtn.set("textContent",t)}))})},start_recording:function(t,e){var o=n.select_rec_options(t);l.mediaRecorder=new window.MediaRecorder(e,o),l.mediaRecorder.ondataavailable=l.handle_data_available,l.mediaRecorder.onstop=l.handle_stop,l.mediaRecorder.start(1e3),l.player.set("muted",!0),l.countdownSeconds="audio"===t?l.editorScope.get("audiotimelimit"):"video"===t?l.editorScope.get("videotimelimit"):l.editorScope.get("defaulttimelimit"),l.countdownSeconds++,e=M.util.get_string("stoprecording","atto_recordrtc"),l.startStopBtn.setHTML(e+=' (:)'),l.set_time(),l.countdownTicker=window.setInterval(l.set_time,1e3),l.startStopBtn.set("disabled",!1)},stop_recording:function(t){var e,o;for(l.mediaRecorder.stop(),e=t.getTracks(),o=0;o":"")},insert_annotation:function(t,e){t=l.create_annotation(t,e);t?l.editorScope.setLink(l.editorScope,t):l.uploadBtn.set("textContent",M.util.get_string("attachrecording","atto_recordrtc"))}},M.atto_recordrtc=M.atto_recordrtc||{},l=M.atto_recordrtc.commonmodule,n=M.atto_recordrtc.abstractmodule,M.atto_recordrtc.compatcheckmodule={check_has_gum:function(){navigator.mediaDevices&&window.MediaRecorder||n.show_alert("nowebrtc",function(){l.editorScope.closeDialogue(l.editorScope)})},check_secure:function(){"https:"===window.location.protocol||-1!==window.location.host.indexOf("localhost")||l.alertDanger.ancestor().ancestor().removeClass("hide")}},M.atto_recordrtc=M.atto_recordrtc||{},l=M.atto_recordrtc.commonmodule,n=M.atto_recordrtc.abstractmodule,M.atto_recordrtc.abstractmodule={show_alert:function(e,o){r.use("moodle-core-notification-alert",function(){var t=new M.core.alert({ title:M.util.get_string(e+"_title","atto_recordrtc"),message:M.util.get_string(e,"atto_recordrtc")});o&&t.after("complete",o)})},handle_gum_errors:function(t,e){var o=M.util.get_string("recordingfailed","atto_recordrtc"),r=function(){e.onMediaStopped(o)},t="gum"+t.name.replace("Error","").toLowerCase();"gumsecurity"!=t?n.show_alert(t,r):n.show_alert(t,function(){l.editorScope.closeDialogue(l.editorScope)})},select_rec_options:function(t){var t="audio"===t?(e=["audio/ogg;codecs=opus","audio/mp4;codecs=opus","audio/mp4;codecs=wav","audio/mp4;codecs=mp3"],{audioBitsPerSecond:window.parseInt(l.editorScope.get("audiobitrate"))}):(e=["video/webm;codecs=vp9,opus","video/webm;codecs=vp8,opus","video/mp4;codecs=h264,opus","video/mp4;codecs=h264,wav","video/mp4;codecs=v9,opus"],{audioBitsPerSecond:window.parseInt(l.editorScope.get("audiobitrate")),videoBitsPerSecond:window.parseInt(l.editorScope.get("videobitrate"))}),e=e.reduce(function(t,e){return t.push(e),t.push(e.replace("=",":")),t},[]).filter(function(t){return window.MediaRecorder.isTypeSupported(t)});return 0!==e.length&&(t.mimeType=e[0]),t}},M.atto_recordrtc=M.atto_recordrtc||{},l=M.atto_recordrtc.commonmodule,n=M.atto_recordrtc.abstractmodule,e=M.atto_recordrtc.compatcheckmodule,M.atto_recordrtc.audiomodule={init:function(t){l.editorScope=t,l.alertWarning=r.one("div#alert-warning"),l.alertDanger=r.one("div#alert-danger"),l.player=r.one("audio#player"),l.playerDOM=document.querySelector("audio#player"),l.startStopBtn=r.one("button#start-stop"),l.uploadBtn=r.one("button#upload"),l.recType="audio",l.maxUploadSize=t.get("maxrecsize"),e.check_has_gum(),e.check_secure(),l.startStopBtn.on("click",function(){var e;l.startStopBtn.set("disabled",!0),l.startStopBtn.get("textContent")===M.util.get_string("startrecording","atto_recordrtc")||l.startStopBtn.get("textContent")===M.util.get_string("recordagain","atto_recordrtc")||l.startStopBtn.get("textContent")===M.util.get_string("recordingfailed","atto_recordrtc")?(l.player.ancestor().ancestor().addClass("hide"),l.uploadBtn.ancestor().ancestor().addClass("hide"),l.startStopBtn.replaceClass("btn-outline-danger","btn-danger"),l.chunks=[],l.blobSize=0,l.uploadBtn.detach("click"),e={onMediaCaptured:function(t){l.stream=t,l.start_recording(l.recType,l.stream)},onMediaStopped:function(t){l.startStopBtn.set("textContent",t),l.startStopBtn.set("disabled",!1),l.startStopBtn.replaceClass("btn-danger","btn-outline-danger")},onMediaCapturingFailed:function(t){n.handle_gum_errors(t,e)}},M.atto_recordrtc.audiomodule.capture_audio(e)):(window.clearInterval(l.countdownTicker),window.setTimeout(function(){l.startStopBtn.set("disabled",!1)},1e3),l.stop_recording(l.stream),l.startStopBtn.set("textContent",M.util.get_string("recordagain","atto_recordrtc")),l.startStopBtn.replaceClass("btn-danger","btn-outline-danger")),l.editorScope.getDialogue().centered()})},capture_audio:function(e){l.capture_user_media({audio:!0},function(t){l.playerDOM.srcObject=t,e.onMediaCaptured(t)},function(t){e.onMediaCapturingFailed(t)})}},M.atto_recordrtc=M.atto_recordrtc||{},l=M.atto_recordrtc.commonmodule,n=M.atto_recordrtc.abstractmodule,e=M.atto_recordrtc.compatcheckmodule,M.atto_recordrtc.videomodule={init:function(t){l.editorScope=t,l.alertWarning=r.one("div#alert-warning"),l.alertDanger=r.one("div#alert-danger"),l.player=r.one("video#player"),l.playerDOM=document.querySelector("video#player"),l.startStopBtn=r.one("button#start-stop"),l.uploadBtn=r.one("button#upload"),l.recType="video",l.maxUploadSize=t.get("maxrecsize"),e.check_has_gum(),e.check_secure(),l.startStopBtn.on("click",function(){var e;l.startStopBtn.set("disabled",!0),l.startStopBtn.get("textContent")===M.util.get_string("startrecording","atto_recordrtc")||l.startStopBtn.get("textContent")===M.util.get_string("recordagain","atto_recordrtc")||l.startStopBtn.get("textContent")===M.util.get_string("recordingfailed","atto_recordrtc")?(l.uploadBtn.ancestor().ancestor().addClass("hide"),l.startStopBtn.replaceClass("btn-outline-danger","btn-danger"),l.chunks=[],l.blobSize=0,l.uploadBtn.detach("click"),e={onMediaCaptured:function(t){l.stream=t,l.start_recording(l.recType,l.stream)},onMediaStopped:function(t){l.startStopBtn.set("textContent",t),l.startStopBtn.set("disabled",!1),l.startStopBtn.replaceClass("btn-danger","btn-outline-danger")},onMediaCapturingFailed:function(t){n.handle_gum_errors(t,e)}},l.player.ancestor().ancestor().removeClass("hide"),l.player.set("controls",!1),M.atto_recordrtc.videomodule.capture_audio_video(e)):(window.clearInterval(l.countdownTicker),window.setTimeout(function(){l.startStopBtn.set("disabled",!1)},1e3),l.stop_recording(l.stream),l.startStopBtn.set("textContent",M.util.get_string("recordagain","atto_recordrtc")),l.startStopBtn.replaceClass("btn-danger","btn-outline-danger")),l.editorScope.getDialogue().centered()})},capture_audio_video:function(e){l.capture_user_media({audio:!0,video:{width:{ideal:640},height:{ideal:480}}},function(t){l.playerDOM.srcObject=t,l.playerDOM.play(),e.onMediaCaptured(t)},function(t){e.onMediaCapturingFailed(t)})}}},"@VERSION@",{requires:["moodle-atto_recordrtc-button"]}); \ No newline at end of file diff --git a/lib/editor/atto/plugins/recordrtc/yui/build/moodle-atto_recordrtc-recording/moodle-atto_recordrtc-recording.js b/lib/editor/atto/plugins/recordrtc/yui/build/moodle-atto_recordrtc-recording/moodle-atto_recordrtc-recording.js index 9b2eafc39341e..691a99c55da3b 100644 --- a/lib/editor/atto/plugins/recordrtc/yui/build/moodle-atto_recordrtc-recording/moodle-atto_recordrtc-recording.js +++ b/lib/editor/atto/plugins/recordrtc/yui/build/moodle-atto_recordrtc-recording/moodle-atto_recordrtc-recording.js @@ -204,7 +204,7 @@ M.atto_recordrtc.commonmodule = { } } - window.console.warning('Unknown file type for MediaRecorder API'); + window.console.warn('Unknown file type for MediaRecorder API'); return ''; }, diff --git a/lib/editor/atto/plugins/recordrtc/yui/src/recording/js/commonmodule.js b/lib/editor/atto/plugins/recordrtc/yui/src/recording/js/commonmodule.js index bed2e4147407d..73a82fb144a9b 100644 --- a/lib/editor/atto/plugins/recordrtc/yui/src/recording/js/commonmodule.js +++ b/lib/editor/atto/plugins/recordrtc/yui/src/recording/js/commonmodule.js @@ -202,7 +202,7 @@ M.atto_recordrtc.commonmodule = { } } - window.console.warning('Unknown file type for MediaRecorder API'); + window.console.warn('Unknown file type for MediaRecorder API'); return ''; }, From 61d3f1345a1c012968491304f45b1fda74c3199e Mon Sep 17 00:00:00 2001 From: Jun Pataleta Date: Thu, 20 Jul 2023 14:22:14 +0800 Subject: [PATCH 16/61] MDL-78770 tiny_recordrtc: Use proper window.console.warn() function --- .../tiny/plugins/recordrtc/amd/build/audio_recorder.min.js | 2 +- .../tiny/plugins/recordrtc/amd/build/audio_recorder.min.js.map | 2 +- .../tiny/plugins/recordrtc/amd/build/video_recorder.min.js | 2 +- .../tiny/plugins/recordrtc/amd/build/video_recorder.min.js.map | 2 +- lib/editor/tiny/plugins/recordrtc/amd/src/audio_recorder.js | 2 +- lib/editor/tiny/plugins/recordrtc/amd/src/video_recorder.js | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/editor/tiny/plugins/recordrtc/amd/build/audio_recorder.min.js b/lib/editor/tiny/plugins/recordrtc/amd/build/audio_recorder.min.js index e91f936f03ddf..c5fe0ce043358 100644 --- a/lib/editor/tiny/plugins/recordrtc/amd/build/audio_recorder.min.js +++ b/lib/editor/tiny/plugins/recordrtc/amd/build/audio_recorder.min.js @@ -1,3 +1,3 @@ -define("tiny_recordrtc/audio_recorder",["exports","./base_recorder","./modal","core/modal_registry","tiny_recordrtc/common"],(function(_exports,_base_recorder,_modal,_modal_registry,_common){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.default=void 0,_base_recorder=_interopRequireDefault(_base_recorder),_modal=_interopRequireDefault(_modal),_modal_registry=_interopRequireDefault(_modal_registry);class Audio extends _base_recorder.default{configurePlayer(){return this.modalRoot.querySelector("audio")}getSupportedTypes(){return["audio/ogg;codecs=opus","audio/mp4;codecs=opus","audio/mp4;codecs=wav","audio/mp4;codecs=mp3"]}getRecordingOptions(){return{audioBitsPerSecond:parseInt(this.config.audiobitrate)}}getMediaConstraints(){return{audio:!0}}getRecordingType(){return"audio"}getTimeLimit(){return this.config.audiotimelimit}getEmbedTemplateName(){return"tiny_recordrtc/embed_audio"}getFileName(prefix){return"".concat(prefix,"-audio.").concat(this.getFileExtension())}getFileExtension(){return window.MediaRecorder.isTypeSupported("audio/ogg")?"ogg":window.MediaRecorder.isTypeSupported("audio/mp4")?"mp4":(window.console.warning("Unknown file type for MediaRecorder API"),"")}static getModalClass(){var _class;const modalType="".concat(_common.component,"/audio_recorder"),registration=_modal_registry.default.get(modalType);if(registration)return registration.module;const AudioModal=(_defineProperty(_class=class extends _modal.default{},"TYPE",modalType),_defineProperty(_class,"TEMPLATE","".concat(_common.component,"/audio_recorder")),_class);return _modal_registry.default.register(AudioModal.TYPE,AudioModal,AudioModal.TEMPLATE),AudioModal}}return _exports.default=Audio,_exports.default})); +define("tiny_recordrtc/audio_recorder",["exports","./base_recorder","./modal","core/modal_registry","tiny_recordrtc/common"],(function(_exports,_base_recorder,_modal,_modal_registry,_common){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.default=void 0,_base_recorder=_interopRequireDefault(_base_recorder),_modal=_interopRequireDefault(_modal),_modal_registry=_interopRequireDefault(_modal_registry);class Audio extends _base_recorder.default{configurePlayer(){return this.modalRoot.querySelector("audio")}getSupportedTypes(){return["audio/ogg;codecs=opus","audio/mp4;codecs=opus","audio/mp4;codecs=wav","audio/mp4;codecs=mp3"]}getRecordingOptions(){return{audioBitsPerSecond:parseInt(this.config.audiobitrate)}}getMediaConstraints(){return{audio:!0}}getRecordingType(){return"audio"}getTimeLimit(){return this.config.audiotimelimit}getEmbedTemplateName(){return"tiny_recordrtc/embed_audio"}getFileName(prefix){return"".concat(prefix,"-audio.").concat(this.getFileExtension())}getFileExtension(){return window.MediaRecorder.isTypeSupported("audio/ogg")?"ogg":window.MediaRecorder.isTypeSupported("audio/mp4")?"mp4":(window.console.warn("Unknown file type for MediaRecorder API"),"")}static getModalClass(){var _class;const modalType="".concat(_common.component,"/audio_recorder"),registration=_modal_registry.default.get(modalType);if(registration)return registration.module;const AudioModal=(_defineProperty(_class=class extends _modal.default{},"TYPE",modalType),_defineProperty(_class,"TEMPLATE","".concat(_common.component,"/audio_recorder")),_class);return _modal_registry.default.register(AudioModal.TYPE,AudioModal,AudioModal.TEMPLATE),AudioModal}}return _exports.default=Audio,_exports.default})); //# sourceMappingURL=audio_recorder.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/plugins/recordrtc/amd/build/audio_recorder.min.js.map b/lib/editor/tiny/plugins/recordrtc/amd/build/audio_recorder.min.js.map index 195370aa07263..18d39e0256b13 100644 --- a/lib/editor/tiny/plugins/recordrtc/amd/build/audio_recorder.min.js.map +++ b/lib/editor/tiny/plugins/recordrtc/amd/build/audio_recorder.min.js.map @@ -1 +1 @@ -{"version":3,"file":"audio_recorder.min.js","sources":["../src/audio_recorder.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 * Tiny Record RTC - audio recorder configuration.\n *\n * @module tiny_recordrtc/audio\n * @copyright 2022 Stevani Andolo \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport BaseClass from './base_recorder';\nimport Modal from './modal';\nimport ModalRegistry from 'core/modal_registry';\nimport {component} from 'tiny_recordrtc/common';\n\nexport default class Audio extends BaseClass {\n configurePlayer() {\n return this.modalRoot.querySelector('audio');\n }\n\n getSupportedTypes() {\n return [\n // Firefox supports webm and ogg but Chrome only supports ogg.\n // So we use ogg to maximize the compatibility.\n 'audio/ogg;codecs=opus',\n\n // Safari supports mp4.\n 'audio/mp4;codecs=opus',\n 'audio/mp4;codecs=wav',\n 'audio/mp4;codecs=mp3',\n ];\n }\n\n getRecordingOptions() {\n return {\n audioBitsPerSecond: parseInt(this.config.audiobitrate),\n };\n }\n\n getMediaConstraints() {\n return {\n audio: true,\n };\n }\n\n getRecordingType() {\n return 'audio';\n }\n\n getTimeLimit() {\n return this.config.audiotimelimit;\n }\n\n getEmbedTemplateName() {\n return 'tiny_recordrtc/embed_audio';\n }\n\n getFileName(prefix) {\n return `${prefix}-audio.${this.getFileExtension()}`;\n }\n\n getFileExtension() {\n if (window.MediaRecorder.isTypeSupported('audio/ogg')) {\n return 'ogg';\n } else if (window.MediaRecorder.isTypeSupported('audio/mp4')) {\n return 'mp4';\n }\n\n window.console.warning(`Unknown file type for MediaRecorder API`);\n return '';\n }\n\n static getModalClass() {\n const modalType = `${component}/audio_recorder`;\n const registration = ModalRegistry.get(modalType);\n if (registration) {\n return registration.module;\n }\n\n const AudioModal = class extends Modal {\n static TYPE = modalType;\n static TEMPLATE = `${component}/audio_recorder`;\n };\n\n ModalRegistry.register(AudioModal.TYPE, AudioModal, AudioModal.TEMPLATE);\n return AudioModal;\n }\n}\n"],"names":["Audio","BaseClass","configurePlayer","this","modalRoot","querySelector","getSupportedTypes","getRecordingOptions","audioBitsPerSecond","parseInt","config","audiobitrate","getMediaConstraints","audio","getRecordingType","getTimeLimit","audiotimelimit","getEmbedTemplateName","getFileName","prefix","getFileExtension","window","MediaRecorder","isTypeSupported","console","warning","modalType","component","registration","ModalRegistry","get","module","AudioModal","Modal","register","TYPE","TEMPLATE"],"mappings":"+pBA4BqBA,cAAcC,uBAC/BC,yBACWC,KAAKC,UAAUC,cAAc,SAGxCC,0BACW,CAGH,wBAGA,wBACA,uBACA,wBAIRC,4BACW,CACHC,mBAAoBC,SAASN,KAAKO,OAAOC,eAIjDC,4BACW,CACHC,OAAO,GAIfC,yBACW,QAGXC,sBACWZ,KAAKO,OAAOM,eAGvBC,6BACW,6BAGXC,YAAYC,wBACEA,yBAAgBhB,KAAKiB,oBAGnCA,0BACQC,OAAOC,cAAcC,gBAAgB,aAC9B,MACAF,OAAOC,cAAcC,gBAAgB,aACrC,OAGXF,OAAOG,QAAQC,mDACR,4CAIDC,oBAAeC,qCACfC,aAAeC,wBAAcC,IAAIJ,cACnCE,oBACOA,aAAaG,aAGlBC,mCAAa,cAAcC,wBACfP,uDACOC,6EAGXO,SAASF,WAAWG,KAAMH,WAAYA,WAAWI,UACxDJ"} \ No newline at end of file +{"version":3,"file":"audio_recorder.min.js","sources":["../src/audio_recorder.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 * Tiny Record RTC - audio recorder configuration.\n *\n * @module tiny_recordrtc/audio\n * @copyright 2022 Stevani Andolo \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport BaseClass from './base_recorder';\nimport Modal from './modal';\nimport ModalRegistry from 'core/modal_registry';\nimport {component} from 'tiny_recordrtc/common';\n\nexport default class Audio extends BaseClass {\n configurePlayer() {\n return this.modalRoot.querySelector('audio');\n }\n\n getSupportedTypes() {\n return [\n // Firefox supports webm and ogg but Chrome only supports ogg.\n // So we use ogg to maximize the compatibility.\n 'audio/ogg;codecs=opus',\n\n // Safari supports mp4.\n 'audio/mp4;codecs=opus',\n 'audio/mp4;codecs=wav',\n 'audio/mp4;codecs=mp3',\n ];\n }\n\n getRecordingOptions() {\n return {\n audioBitsPerSecond: parseInt(this.config.audiobitrate),\n };\n }\n\n getMediaConstraints() {\n return {\n audio: true,\n };\n }\n\n getRecordingType() {\n return 'audio';\n }\n\n getTimeLimit() {\n return this.config.audiotimelimit;\n }\n\n getEmbedTemplateName() {\n return 'tiny_recordrtc/embed_audio';\n }\n\n getFileName(prefix) {\n return `${prefix}-audio.${this.getFileExtension()}`;\n }\n\n getFileExtension() {\n if (window.MediaRecorder.isTypeSupported('audio/ogg')) {\n return 'ogg';\n } else if (window.MediaRecorder.isTypeSupported('audio/mp4')) {\n return 'mp4';\n }\n\n window.console.warn(`Unknown file type for MediaRecorder API`);\n return '';\n }\n\n static getModalClass() {\n const modalType = `${component}/audio_recorder`;\n const registration = ModalRegistry.get(modalType);\n if (registration) {\n return registration.module;\n }\n\n const AudioModal = class extends Modal {\n static TYPE = modalType;\n static TEMPLATE = `${component}/audio_recorder`;\n };\n\n ModalRegistry.register(AudioModal.TYPE, AudioModal, AudioModal.TEMPLATE);\n return AudioModal;\n }\n}\n"],"names":["Audio","BaseClass","configurePlayer","this","modalRoot","querySelector","getSupportedTypes","getRecordingOptions","audioBitsPerSecond","parseInt","config","audiobitrate","getMediaConstraints","audio","getRecordingType","getTimeLimit","audiotimelimit","getEmbedTemplateName","getFileName","prefix","getFileExtension","window","MediaRecorder","isTypeSupported","console","warn","modalType","component","registration","ModalRegistry","get","module","AudioModal","Modal","register","TYPE","TEMPLATE"],"mappings":"+pBA4BqBA,cAAcC,uBAC/BC,yBACWC,KAAKC,UAAUC,cAAc,SAGxCC,0BACW,CAGH,wBAGA,wBACA,uBACA,wBAIRC,4BACW,CACHC,mBAAoBC,SAASN,KAAKO,OAAOC,eAIjDC,4BACW,CACHC,OAAO,GAIfC,yBACW,QAGXC,sBACWZ,KAAKO,OAAOM,eAGvBC,6BACW,6BAGXC,YAAYC,wBACEA,yBAAgBhB,KAAKiB,oBAGnCA,0BACQC,OAAOC,cAAcC,gBAAgB,aAC9B,MACAF,OAAOC,cAAcC,gBAAgB,aACrC,OAGXF,OAAOG,QAAQC,gDACR,4CAIDC,oBAAeC,qCACfC,aAAeC,wBAAcC,IAAIJ,cACnCE,oBACOA,aAAaG,aAGlBC,mCAAa,cAAcC,wBACfP,uDACOC,6EAGXO,SAASF,WAAWG,KAAMH,WAAYA,WAAWI,UACxDJ"} \ No newline at end of file diff --git a/lib/editor/tiny/plugins/recordrtc/amd/build/video_recorder.min.js b/lib/editor/tiny/plugins/recordrtc/amd/build/video_recorder.min.js index 3231c44749e6d..1a9d67c323efc 100644 --- a/lib/editor/tiny/plugins/recordrtc/amd/build/video_recorder.min.js +++ b/lib/editor/tiny/plugins/recordrtc/amd/build/video_recorder.min.js @@ -1,3 +1,3 @@ -define("tiny_recordrtc/video_recorder",["exports","./base_recorder","tiny_recordrtc/modal","core/modal_registry","tiny_recordrtc/common"],(function(_exports,_base_recorder,_modal,_modal_registry,_common){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.default=void 0,_base_recorder=_interopRequireDefault(_base_recorder),_modal=_interopRequireDefault(_modal),_modal_registry=_interopRequireDefault(_modal_registry);class Video extends _base_recorder.default{configurePlayer(){return this.modalRoot.querySelector("video")}getSupportedTypes(){return["video/webm;codecs=vp9,opus","video/webm;codecs=vp8,opus","video/mp4;codecs=h264,opus","video/mp4;codecs=h264,wav","video/mp4;codecs=v9,opus"]}getRecordingOptions(){return{audioBitsPerSecond:parseInt(this.config.audiobitrate),videoBitsPerSecond:parseInt(this.config.videobitrate)}}getMediaConstraints(){return{audio:!0,video:{width:{ideal:640},height:{ideal:480}}}}playOnCapture(){return!0}getRecordingType(){return"video"}getTimeLimit(){return this.config.videotimelimit}getEmbedTemplateName(){return"tiny_recordrtc/embed_video"}getFileName(prefix){return"".concat(prefix,"-video.").concat(this.getFileExtension())}getFileExtension(){return window.MediaRecorder.isTypeSupported("audio/webm")?"webm":window.MediaRecorder.isTypeSupported("audio/mp4")?"mp4":(window.console.warning("Unknown file type for MediaRecorder API"),"")}static getModalClass(){var _class;const modalType="".concat(_common.component,"/video_recorder"),registration=_modal_registry.default.get(modalType);if(registration)return registration.module;const VideoModal=(_defineProperty(_class=class extends _modal.default{},"TYPE",modalType),_defineProperty(_class,"TEMPLATE","".concat(_common.component,"/video_recorder")),_class);return _modal_registry.default.register(VideoModal.TYPE,VideoModal,VideoModal.TEMPLATE),VideoModal}}return _exports.default=Video,_exports.default})); +define("tiny_recordrtc/video_recorder",["exports","./base_recorder","tiny_recordrtc/modal","core/modal_registry","tiny_recordrtc/common"],(function(_exports,_base_recorder,_modal,_modal_registry,_common){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.default=void 0,_base_recorder=_interopRequireDefault(_base_recorder),_modal=_interopRequireDefault(_modal),_modal_registry=_interopRequireDefault(_modal_registry);class Video extends _base_recorder.default{configurePlayer(){return this.modalRoot.querySelector("video")}getSupportedTypes(){return["video/webm;codecs=vp9,opus","video/webm;codecs=vp8,opus","video/mp4;codecs=h264,opus","video/mp4;codecs=h264,wav","video/mp4;codecs=v9,opus"]}getRecordingOptions(){return{audioBitsPerSecond:parseInt(this.config.audiobitrate),videoBitsPerSecond:parseInt(this.config.videobitrate)}}getMediaConstraints(){return{audio:!0,video:{width:{ideal:640},height:{ideal:480}}}}playOnCapture(){return!0}getRecordingType(){return"video"}getTimeLimit(){return this.config.videotimelimit}getEmbedTemplateName(){return"tiny_recordrtc/embed_video"}getFileName(prefix){return"".concat(prefix,"-video.").concat(this.getFileExtension())}getFileExtension(){return window.MediaRecorder.isTypeSupported("audio/webm")?"webm":window.MediaRecorder.isTypeSupported("audio/mp4")?"mp4":(window.console.warn("Unknown file type for MediaRecorder API"),"")}static getModalClass(){var _class;const modalType="".concat(_common.component,"/video_recorder"),registration=_modal_registry.default.get(modalType);if(registration)return registration.module;const VideoModal=(_defineProperty(_class=class extends _modal.default{},"TYPE",modalType),_defineProperty(_class,"TEMPLATE","".concat(_common.component,"/video_recorder")),_class);return _modal_registry.default.register(VideoModal.TYPE,VideoModal,VideoModal.TEMPLATE),VideoModal}}return _exports.default=Video,_exports.default})); //# sourceMappingURL=video_recorder.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/plugins/recordrtc/amd/build/video_recorder.min.js.map b/lib/editor/tiny/plugins/recordrtc/amd/build/video_recorder.min.js.map index 208febb1098dd..13d15efacd6a1 100644 --- a/lib/editor/tiny/plugins/recordrtc/amd/build/video_recorder.min.js.map +++ b/lib/editor/tiny/plugins/recordrtc/amd/build/video_recorder.min.js.map @@ -1 +1 @@ -{"version":3,"file":"video_recorder.min.js","sources":["../src/video_recorder.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 * Tiny Record RTC - Video recorder configuration.\n *\n * @module tiny_recordrtc/video\n * @copyright 2022 Stevani Andolo \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport BaseClass from './base_recorder';\nimport Modal from 'tiny_recordrtc/modal';\nimport ModalRegistry from 'core/modal_registry';\nimport {component} from 'tiny_recordrtc/common';\n\nexport default class Video extends BaseClass {\n configurePlayer() {\n return this.modalRoot.querySelector('video');\n }\n\n getSupportedTypes() {\n return [\n // Support webm as a preference.\n // This container supports both vp9, and vp8.\n // It does not support AVC1/h264 at all.\n // It is supported by Chromium, and Firefox browsers, but not Safari.\n 'video/webm;codecs=vp9,opus',\n 'video/webm;codecs=vp8,opus',\n\n // Fall back to mp4 if webm is not available.\n // The mp4 container supports v9, and h264 but neither of these are supported for recording on other\n // browsers.\n // In addition to this, we can record in v9, but VideoJS does not support an mp4 containern with v9 codec\n // for playback. We leave it as a final option as a just-in-case.\n 'video/mp4;codecs=h264,opus',\n 'video/mp4;codecs=h264,wav',\n 'video/mp4;codecs=v9,opus',\n ];\n\n }\n\n getRecordingOptions() {\n return {\n audioBitsPerSecond: parseInt(this.config.audiobitrate),\n videoBitsPerSecond: parseInt(this.config.videobitrate)\n };\n }\n\n getMediaConstraints() {\n return {\n audio: true,\n video: {\n width: {\n ideal: 640,\n },\n height: {\n ideal: 480,\n },\n },\n };\n }\n\n playOnCapture() {\n // Play the recording back on capture.\n return true;\n }\n\n getRecordingType() {\n return 'video';\n }\n\n getTimeLimit() {\n return this.config.videotimelimit;\n }\n\n getEmbedTemplateName() {\n return 'tiny_recordrtc/embed_video';\n }\n\n getFileName(prefix) {\n return `${prefix}-video.${this.getFileExtension()}`;\n }\n\n getFileExtension() {\n if (window.MediaRecorder.isTypeSupported('audio/webm')) {\n return 'webm';\n } else if (window.MediaRecorder.isTypeSupported('audio/mp4')) {\n return 'mp4';\n }\n\n window.console.warning(`Unknown file type for MediaRecorder API`);\n return '';\n }\n\n static getModalClass() {\n const modalType = `${component}/video_recorder`;\n const registration = ModalRegistry.get(modalType);\n if (registration) {\n return registration.module;\n }\n\n const VideoModal = class extends Modal {\n static TYPE = modalType;\n static TEMPLATE = `${component}/video_recorder`;\n };\n\n ModalRegistry.register(VideoModal.TYPE, VideoModal, VideoModal.TEMPLATE);\n return VideoModal;\n }\n}\n"],"names":["Video","BaseClass","configurePlayer","this","modalRoot","querySelector","getSupportedTypes","getRecordingOptions","audioBitsPerSecond","parseInt","config","audiobitrate","videoBitsPerSecond","videobitrate","getMediaConstraints","audio","video","width","ideal","height","playOnCapture","getRecordingType","getTimeLimit","videotimelimit","getEmbedTemplateName","getFileName","prefix","getFileExtension","window","MediaRecorder","isTypeSupported","console","warning","modalType","component","registration","ModalRegistry","get","module","VideoModal","Modal","register","TYPE","TEMPLATE"],"mappings":"4qBA4BqBA,cAAcC,uBAC/BC,yBACWC,KAAKC,UAAUC,cAAc,SAGxCC,0BACW,CAKH,6BACA,6BAOA,6BACA,4BACA,4BAKRC,4BACW,CACHC,mBAAoBC,SAASN,KAAKO,OAAOC,cACzCC,mBAAoBH,SAASN,KAAKO,OAAOG,eAIjDC,4BACW,CACHC,OAAO,EACPC,MAAO,CACHC,MAAO,CACHC,MAAO,KAEXC,OAAQ,CACJD,MAAO,OAMvBE,uBAEW,EAGXC,yBACW,QAGXC,sBACWnB,KAAKO,OAAOa,eAGvBC,6BACW,6BAGXC,YAAYC,wBACEA,yBAAgBvB,KAAKwB,oBAGnCA,0BACQC,OAAOC,cAAcC,gBAAgB,cAC9B,OACAF,OAAOC,cAAcC,gBAAgB,aACrC,OAGXF,OAAOG,QAAQC,mDACR,4CAIDC,oBAAeC,qCACfC,aAAeC,wBAAcC,IAAIJ,cACnCE,oBACOA,aAAaG,aAGlBC,mCAAa,cAAcC,wBACfP,uDACOC,6EAGXO,SAASF,WAAWG,KAAMH,WAAYA,WAAWI,UACxDJ"} \ No newline at end of file +{"version":3,"file":"video_recorder.min.js","sources":["../src/video_recorder.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 * Tiny Record RTC - Video recorder configuration.\n *\n * @module tiny_recordrtc/video\n * @copyright 2022 Stevani Andolo \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport BaseClass from './base_recorder';\nimport Modal from 'tiny_recordrtc/modal';\nimport ModalRegistry from 'core/modal_registry';\nimport {component} from 'tiny_recordrtc/common';\n\nexport default class Video extends BaseClass {\n configurePlayer() {\n return this.modalRoot.querySelector('video');\n }\n\n getSupportedTypes() {\n return [\n // Support webm as a preference.\n // This container supports both vp9, and vp8.\n // It does not support AVC1/h264 at all.\n // It is supported by Chromium, and Firefox browsers, but not Safari.\n 'video/webm;codecs=vp9,opus',\n 'video/webm;codecs=vp8,opus',\n\n // Fall back to mp4 if webm is not available.\n // The mp4 container supports v9, and h264 but neither of these are supported for recording on other\n // browsers.\n // In addition to this, we can record in v9, but VideoJS does not support an mp4 containern with v9 codec\n // for playback. We leave it as a final option as a just-in-case.\n 'video/mp4;codecs=h264,opus',\n 'video/mp4;codecs=h264,wav',\n 'video/mp4;codecs=v9,opus',\n ];\n\n }\n\n getRecordingOptions() {\n return {\n audioBitsPerSecond: parseInt(this.config.audiobitrate),\n videoBitsPerSecond: parseInt(this.config.videobitrate)\n };\n }\n\n getMediaConstraints() {\n return {\n audio: true,\n video: {\n width: {\n ideal: 640,\n },\n height: {\n ideal: 480,\n },\n },\n };\n }\n\n playOnCapture() {\n // Play the recording back on capture.\n return true;\n }\n\n getRecordingType() {\n return 'video';\n }\n\n getTimeLimit() {\n return this.config.videotimelimit;\n }\n\n getEmbedTemplateName() {\n return 'tiny_recordrtc/embed_video';\n }\n\n getFileName(prefix) {\n return `${prefix}-video.${this.getFileExtension()}`;\n }\n\n getFileExtension() {\n if (window.MediaRecorder.isTypeSupported('audio/webm')) {\n return 'webm';\n } else if (window.MediaRecorder.isTypeSupported('audio/mp4')) {\n return 'mp4';\n }\n\n window.console.warn(`Unknown file type for MediaRecorder API`);\n return '';\n }\n\n static getModalClass() {\n const modalType = `${component}/video_recorder`;\n const registration = ModalRegistry.get(modalType);\n if (registration) {\n return registration.module;\n }\n\n const VideoModal = class extends Modal {\n static TYPE = modalType;\n static TEMPLATE = `${component}/video_recorder`;\n };\n\n ModalRegistry.register(VideoModal.TYPE, VideoModal, VideoModal.TEMPLATE);\n return VideoModal;\n }\n}\n"],"names":["Video","BaseClass","configurePlayer","this","modalRoot","querySelector","getSupportedTypes","getRecordingOptions","audioBitsPerSecond","parseInt","config","audiobitrate","videoBitsPerSecond","videobitrate","getMediaConstraints","audio","video","width","ideal","height","playOnCapture","getRecordingType","getTimeLimit","videotimelimit","getEmbedTemplateName","getFileName","prefix","getFileExtension","window","MediaRecorder","isTypeSupported","console","warn","modalType","component","registration","ModalRegistry","get","module","VideoModal","Modal","register","TYPE","TEMPLATE"],"mappings":"4qBA4BqBA,cAAcC,uBAC/BC,yBACWC,KAAKC,UAAUC,cAAc,SAGxCC,0BACW,CAKH,6BACA,6BAOA,6BACA,4BACA,4BAKRC,4BACW,CACHC,mBAAoBC,SAASN,KAAKO,OAAOC,cACzCC,mBAAoBH,SAASN,KAAKO,OAAOG,eAIjDC,4BACW,CACHC,OAAO,EACPC,MAAO,CACHC,MAAO,CACHC,MAAO,KAEXC,OAAQ,CACJD,MAAO,OAMvBE,uBAEW,EAGXC,yBACW,QAGXC,sBACWnB,KAAKO,OAAOa,eAGvBC,6BACW,6BAGXC,YAAYC,wBACEA,yBAAgBvB,KAAKwB,oBAGnCA,0BACQC,OAAOC,cAAcC,gBAAgB,cAC9B,OACAF,OAAOC,cAAcC,gBAAgB,aACrC,OAGXF,OAAOG,QAAQC,gDACR,4CAIDC,oBAAeC,qCACfC,aAAeC,wBAAcC,IAAIJ,cACnCE,oBACOA,aAAaG,aAGlBC,mCAAa,cAAcC,wBACfP,uDACOC,6EAGXO,SAASF,WAAWG,KAAMH,WAAYA,WAAWI,UACxDJ"} \ No newline at end of file diff --git a/lib/editor/tiny/plugins/recordrtc/amd/src/audio_recorder.js b/lib/editor/tiny/plugins/recordrtc/amd/src/audio_recorder.js index 22edcfbfa11aa..e73905b12edbf 100644 --- a/lib/editor/tiny/plugins/recordrtc/amd/src/audio_recorder.js +++ b/lib/editor/tiny/plugins/recordrtc/amd/src/audio_recorder.js @@ -79,7 +79,7 @@ export default class Audio extends BaseClass { return 'mp4'; } - window.console.warning(`Unknown file type for MediaRecorder API`); + window.console.warn(`Unknown file type for MediaRecorder API`); return ''; } diff --git a/lib/editor/tiny/plugins/recordrtc/amd/src/video_recorder.js b/lib/editor/tiny/plugins/recordrtc/amd/src/video_recorder.js index 870facecf5f81..510ee4986b201 100644 --- a/lib/editor/tiny/plugins/recordrtc/amd/src/video_recorder.js +++ b/lib/editor/tiny/plugins/recordrtc/amd/src/video_recorder.js @@ -101,7 +101,7 @@ export default class Video extends BaseClass { return 'mp4'; } - window.console.warning(`Unknown file type for MediaRecorder API`); + window.console.warn(`Unknown file type for MediaRecorder API`); return ''; } From 5f35d838d1ee43b3376151da5c3251de66b7b492 Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Tue, 18 Jul 2023 12:42:01 +0100 Subject: [PATCH 17/61] MDL-78742 tool_uploadcourse: configure show activity dates value. --- admin/tool/uploadcourse/classes/course.php | 2 +- admin/tool/uploadcourse/classes/step2_form.php | 4 ++++ admin/tool/uploadcourse/cli/uploadcourse.php | 1 + admin/tool/uploadcourse/tests/course_test.php | 8 ++++++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/admin/tool/uploadcourse/classes/course.php b/admin/tool/uploadcourse/classes/course.php index dc648df82160d..ac1e78bc45d26 100644 --- a/admin/tool/uploadcourse/classes/course.php +++ b/admin/tool/uploadcourse/classes/course.php @@ -101,7 +101,7 @@ class tool_uploadcourse_course { /** @var array fields allowed as course data. */ static protected $validfields = array('fullname', 'shortname', 'idnumber', 'category', 'visible', 'startdate', 'enddate', 'summary', 'format', 'theme', 'lang', 'newsitems', 'showgrades', 'showreports', 'legacyfiles', 'maxbytes', - 'groupmode', 'groupmodeforce', 'enablecompletion', 'downloadcontent'); + 'groupmode', 'groupmodeforce', 'enablecompletion', 'downloadcontent', 'showactivitydates'); /** @var array fields required on course creation. */ static protected $mandatoryfields = array('fullname', 'category'); diff --git a/admin/tool/uploadcourse/classes/step2_form.php b/admin/tool/uploadcourse/classes/step2_form.php index 4cd4f27c2bea3..ffd6ba06f7996 100644 --- a/admin/tool/uploadcourse/classes/step2_form.php +++ b/admin/tool/uploadcourse/classes/step2_form.php @@ -192,6 +192,10 @@ public function definition () { $mform->addHelpButton('defaults[enablecompletion]', 'enablecompletion', 'completion'); } + $mform->addElement('selectyesno', 'defaults[showactivitydates]', get_string('showactivitydates')); + $mform->addHelpButton('defaults[showactivitydates]', 'showactivitydates'); + $mform->setDefault('defaults[showactivitydates]', $courseconfig->showactivitydates); + // Add custom fields to the form. $handler = \core_course\customfield\course_handler::create(); $handler->instance_form_definition($mform, 0, 'defaultvaluescustomfieldcategory', 'tool_uploadcourse'); diff --git a/admin/tool/uploadcourse/cli/uploadcourse.php b/admin/tool/uploadcourse/cli/uploadcourse.php index 5068670d8b2c9..2b50730ca50ef 100644 --- a/admin/tool/uploadcourse/cli/uploadcourse.php +++ b/admin/tool/uploadcourse/cli/uploadcourse.php @@ -166,6 +166,7 @@ $defaults['visible'] = $courseconfig->visible; $defaults['lang'] = $courseconfig->lang; $defaults['enablecompletion'] = $courseconfig->enablecompletion; +$defaults['showactivitydates'] = $courseconfig->showactivitydates; // Course template. if (isset($options['templatecourse'])) { diff --git a/admin/tool/uploadcourse/tests/course_test.php b/admin/tool/uploadcourse/tests/course_test.php index d664621904c01..fbd749de352dc 100644 --- a/admin/tool/uploadcourse/tests/course_test.php +++ b/admin/tool/uploadcourse/tests/course_test.php @@ -425,6 +425,7 @@ public function test_data_saved() { 'groupmode' => '2', 'groupmodeforce' => '1', 'enablecompletion' => '1', + 'showactivitydates' => '1', 'tags' => 'Cat, Dog', 'role_teacher' => 'Knight', @@ -478,6 +479,7 @@ public function test_data_saved() { $this->assertEquals($data['groupmode'], $course->groupmode); $this->assertEquals($data['groupmodeforce'], $course->groupmodeforce); $this->assertEquals($data['enablecompletion'], $course->enablecompletion); + $this->assertEquals($data['showactivitydates'], $course->showactivitydates); $this->assertEquals($data['tags'], join(', ', \core_tag_tag::get_item_tags_array('core', 'course', $course->id))); // Roles. @@ -530,6 +532,7 @@ public function test_data_saved() { 'groupmode' => '1', 'groupmodeforce' => '0', 'enablecompletion' => '0', + 'showactivitydates' => '0', 'role_teacher' => 'Teacher', 'role_manager' => 'Manager', @@ -583,6 +586,7 @@ public function test_data_saved() { $this->assertEquals($data['groupmode'], $course->groupmode); $this->assertEquals($data['groupmodeforce'], $course->groupmodeforce); $this->assertEquals($data['enablecompletion'], $course->enablecompletion); + $this->assertEquals($data['showactivitydates'], $course->showactivitydates); // Roles. $roleids = array(); @@ -644,6 +648,7 @@ public function test_default_data_saved() { 'groupmode' => '2', 'groupmodeforce' => '1', 'enablecompletion' => '1', + 'showactivitydates' => '1', ); $this->assertFalse($DB->record_exists('course', array('shortname' => 'c1'))); @@ -673,6 +678,7 @@ public function test_default_data_saved() { $this->assertEquals($defaultdata['groupmode'], $course->groupmode); $this->assertEquals($defaultdata['groupmodeforce'], $course->groupmodeforce); $this->assertEquals($defaultdata['enablecompletion'], $course->enablecompletion); + $this->assertEquals($defaultdata['showactivitydates'], $course->showactivitydates); // Update. $cat = $this->getDataGenerator()->create_category(); @@ -701,6 +707,7 @@ public function test_default_data_saved() { 'groupmode' => '1', 'groupmodeforce' => '0', 'enablecompletion' => '0', + 'showactivitydates' => '0', ); $this->assertTrue($DB->record_exists('course', array('shortname' => 'c1'))); @@ -730,6 +737,7 @@ public function test_default_data_saved() { $this->assertEquals($defaultdata['groupmode'], $course->groupmode); $this->assertEquals($defaultdata['groupmodeforce'], $course->groupmodeforce); $this->assertEquals($defaultdata['enablecompletion'], $course->enablecompletion); + $this->assertEquals($defaultdata['showactivitydates'], $course->showactivitydates); } public function test_rename() { From 533ce567437c9a86fb3286d58deee07ca1cebcaa Mon Sep 17 00:00:00 2001 From: Huong Nguyen Date: Fri, 21 Jul 2023 20:56:32 +0700 Subject: [PATCH 18/61] weekly release 4.1.4+ --- version.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.php b/version.php index e6d38433ef09b..78d539aafa5af 100644 --- a/version.php +++ b/version.php @@ -29,9 +29,9 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2022112804.06; // 20221128 = branching date YYYYMMDD - do not modify! +$version = 2022112804.07; // 20221128 = branching date YYYYMMDD - do not modify! // RR = release increments - 00 in DEV branches. // .XX = incremental changes. -$release = '4.1.4+ (Build: 20230714)'; // Human-friendly version name +$release = '4.1.4+ (Build: 20230721)'; // Human-friendly version name $branch = '401'; // This version's branch. $maturity = MATURITY_STABLE; // This version's maturity level. From 28aaa93df94945adcc5e11b4c92ef9b0bc7f5ca8 Mon Sep 17 00:00:00 2001 From: Mark Webster Date: Fri, 16 Jun 2023 13:07:19 +0100 Subject: [PATCH 19/61] MDL-78492 dml: Better support for auroramysql Aurora MySQL does not support row format COMPRESSED and falls back to COMPACT if you try to use it, cuasing column size too large errors if you try to use it with utf8mb4 collation. --- lib/dml/mysqli_native_moodle_database.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/dml/mysqli_native_moodle_database.php b/lib/dml/mysqli_native_moodle_database.php index 3f718772f8db5..8d5228ea5d6a3 100644 --- a/lib/dml/mysqli_native_moodle_database.php +++ b/lib/dml/mysqli_native_moodle_database.php @@ -396,6 +396,10 @@ public function is_compressed_row_format_supported($cached = true) { } else if ($this->get_row_format() !== 'Barracuda') { $this->compressedrowformatsupported = false; + } else if ($this->get_dbtype() === 'auroramysql') { + // Aurora MySQL doesn't support COMPRESSED and falls back to COMPACT if you try to use it. + $this->compressedrowformatsupported = false; + } else { // All the tests passed, we can safely use ROW_FORMAT=Compressed in sql statements. $this->compressedrowformatsupported = true; From dcd72611759f87f18e96eed4f82d7d0877ee2468 Mon Sep 17 00:00:00 2001 From: Ferran Recio Date: Fri, 21 Jul 2023 10:17:11 +0200 Subject: [PATCH 20/61] MDL-78782 behat: optimize with editing mode step The current I am on course homepage with editing mode loads twice the course page. This patch prevents this from happening by going directly to editmode.php. --- editmode.php | 7 ++++++- lib/tests/behat/behat_navigation.php | 28 +++++++++++++--------------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/editmode.php b/editmode.php index 4d6f452aa4786..8ea0cac991db5 100644 --- a/editmode.php +++ b/editmode.php @@ -28,7 +28,12 @@ $contextid = required_param('context', PARAM_INT); $pageurl = required_param('pageurl', PARAM_LOCALURL); -require_sesskey(); +// Behat requires JS to get the session. To allow goutte +// to enable edit mode sesskey validation is skipped for behat scripts. +if (!defined('BEHAT_SITE_RUNNING')) { + require_sesskey(); +} + require_login(); $context = \context_helper::instance_by_id($contextid); diff --git a/lib/tests/behat/behat_navigation.php b/lib/tests/behat/behat_navigation.php index 8f4b805458cad..446ed06032f83 100644 --- a/lib/tests/behat/behat_navigation.php +++ b/lib/tests/behat/behat_navigation.php @@ -941,22 +941,20 @@ public function i_am_on_course_homepage_with_editing_mode_on($coursefullname) { * @param string $onoroff Whehter to switch editing on, or off. */ public function i_am_on_course_homepage_with_editing_mode_set_to(string $coursefullname, string $onoroff): void { - $courseid = $this->get_course_id($coursefullname); - $url = new moodle_url('/course/view.php', ['id' => $courseid]); - - // Visit the course page. - $this->execute('behat_general::i_visit', [$url]); - - switch ($onoroff) { - case 'on': - $this->execute('behat_navigation::i_turn_editing_mode_on'); - break; - case 'off': - $this->execute('behat_navigation::i_turn_editing_mode_off'); - break; - default: - throw new \coding_exception("Unknown editing mode '{$onoroff}'. Accepted values are 'on' and 'off'"); + if ($onoroff !== 'on' && $onoroff !== 'off') { + throw new coding_exception("Unknown editing mode '{$onoroff}'. Accepted values are 'on' and 'off'"); } + + $courseid = $this->get_course_id($coursefullname); + $context = context_course::instance($courseid); + $courseurl = new moodle_url('/course/view.php', ['id' => $courseid]); + + $editmodeurl = new moodle_url('/editmode.php', [ + 'context' => $context->id, + 'pageurl' => $courseurl->out(true), + 'setmode' => ($onoroff === 'on' ? 1 : 0), + ]); + $this->execute('behat_general::i_visit', [$editmodeurl]); } /** From 13553c25da0d37e202812e2e5371c59695445a1f Mon Sep 17 00:00:00 2001 From: Ilya Tregubov Date: Fri, 28 Jul 2023 10:44:15 +0800 Subject: [PATCH 21/61] weekly release 4.1.4+ --- version.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.php b/version.php index 78d539aafa5af..0f6bcf0f8a1ca 100644 --- a/version.php +++ b/version.php @@ -29,9 +29,9 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2022112804.07; // 20221128 = branching date YYYYMMDD - do not modify! +$version = 2022112804.08; // 20221128 = branching date YYYYMMDD - do not modify! // RR = release increments - 00 in DEV branches. // .XX = incremental changes. -$release = '4.1.4+ (Build: 20230721)'; // Human-friendly version name +$release = '4.1.4+ (Build: 20230728)'; // Human-friendly version name $branch = '401'; // This version's branch. $maturity = MATURITY_STABLE; // This version's maturity level. From 3295c4bc1db619881f7bc0bf3a541b809bb25e9e Mon Sep 17 00:00:00 2001 From: Marcus Boon Date: Mon, 17 Jul 2023 14:14:03 +1000 Subject: [PATCH 22/61] MDL-78632 filetypes: Introduce yaml file type --- lib/classes/filetypes.php | 3 +++ lib/db/upgrade.php | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/lib/classes/filetypes.php b/lib/classes/filetypes.php index cea82ed1973fa..f4d09c6bd150d 100644 --- a/lib/classes/filetypes.php +++ b/lib/classes/filetypes.php @@ -313,6 +313,9 @@ protected static function get_default_types() { 'xml' => array('type' => 'application/xml', 'icon' => 'markup'), 'xsl' => array('type' => 'text/xml', 'icon' => 'markup'), + 'yaml' => array('type' => 'application/yaml', 'icon' => 'markup'), + 'yml' => array('type' => 'application/yaml', 'icon' => 'markup'), + 'zip' => array('type' => 'application/zip', 'icon' => 'archive', 'groups' => array('archive'), 'string' => 'archive') ); } diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php index e8bfaf584a33a..0405aa1d0a64b 100644 --- a/lib/db/upgrade.php +++ b/lib/db/upgrade.php @@ -3064,5 +3064,27 @@ function xmldb_main_upgrade($oldversion) { upgrade_main_savepoint(true, 2022112803.03); } + if ($oldversion < 2022112804.08) { + // Upgrade yaml mime type for existing yaml and yml files. + $filetypes = array( + '%.yaml' => 'application/yaml', + '%.yml' => 'application/yaml,' + ); + + $select = $DB->sql_like('filename', '?', false); + foreach ($filetypes as $extension => $mimetype) { + $DB->set_field_select( + 'files', + 'mimetype', + $mimetype, + $select, + array($extension) + ); + } + + // Main savepoint reached. + upgrade_main_savepoint(true, 2022112804.08); + } + return true; } From e953e71c54b52ce11f700034c4ed9f2737a0668c Mon Sep 17 00:00:00 2001 From: Jun Pataleta Date: Thu, 22 Jun 2023 14:02:22 +0800 Subject: [PATCH 23/61] MDL-78550 grade: Fix HTML validator errors * Remove invalid "text" attribute in elements * Use proper id value for grade category checkboxes. --- grade/edit/tree/lib.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/grade/edit/tree/lib.php b/grade/edit/tree/lib.php index d49b451fe527c..1e27fa5dff931 100644 --- a/grade/edit/tree/lib.php +++ b/grade/edit/tree/lib.php @@ -749,13 +749,11 @@ public abstract function get_header_cell(); public function get_category_cell($category, $levelclass, $params) { $cell = clone($this->categorycell); $cell->attributes['class'] .= ' ' . $levelclass; - $cell->attributes['text'] = ''; return $cell; } public function get_item_cell($item, $params) { $cell = clone($this->itemcell); - $cell->attributes['text'] = ''; if (isset($params['level'])) { $level = $params['level'] + (($item->itemtype == 'category' || $item->itemtype == 'course') ? 0 : 1); $cell->attributes['class'] .= ' level' . $level; @@ -1029,7 +1027,7 @@ public function get_category_cell($category, $levelclass, $params) { } // Build the master checkbox. $mastercheckbox = new \core\output\checkbox_toggleall($togglegroup, true, [ - 'id' => $togglegroup, + 'id' => 'select_category_' . $category->id, 'name' => $togglegroup, 'value' => 1, 'classes' => 'itemselect ignoredirty', From b98fb9032c3d015a5a469f6a4533d5c9ec856c91 Mon Sep 17 00:00:00 2001 From: Jun Pataleta Date: Thu, 22 Jun 2023 14:19:51 +0800 Subject: [PATCH 24/61] MDL-78550 grade: Avoid rendering an empty

heading --- grade/lib.php | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/grade/lib.php b/grade/lib.php index f069d54da72ee..94e5d799397d2 100644 --- a/grade/lib.php +++ b/grade/lib.php @@ -970,15 +970,13 @@ function print_grade_page_head(int $courseid, string $active_type, ?string $acti $output = ''; // Add a help dialogue box if provided. - if (isset($headerhelpidentifier)) { + if (isset($headerhelpidentifier) && !empty($heading)) { $output = $OUTPUT->heading_with_help($heading, $headerhelpidentifier, $headerhelpcomponent); - } else { - if (isset($user)) { - $renderer = $PAGE->get_renderer('core_grades'); - $output = $OUTPUT->heading($renderer->user_heading($user, $courseid)); - } else { - $output = $OUTPUT->heading($heading); - } + } else if (isset($user)) { + $renderer = $PAGE->get_renderer('core_grades'); + $output = $OUTPUT->heading($renderer->user_heading($user, $courseid)); + } else if (!empty($heading)) { + $output = $OUTPUT->heading($heading); } if ($return) { From 27f453d6e92bc4ae2b4c3124805e001bfa757b1e Mon Sep 17 00:00:00 2001 From: Jun Pataleta Date: Thu, 13 Jul 2023 12:34:15 +0800 Subject: [PATCH 25/61] MDL-78550 grade: Hide empty fillers from the accessibility tree * And remove the title attribute for the filler as it is unnecessary. --- grade/edit/tree/lib.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/grade/edit/tree/lib.php b/grade/edit/tree/lib.php index 1e27fa5dff931..9a82049c0a7bc 100644 --- a/grade/edit/tree/lib.php +++ b/grade/edit/tree/lib.php @@ -312,8 +312,8 @@ public function build_html_tree($element, $totals, $parents, $level, &$row_count $headercell = new html_table_cell(); $headercell->header = true; $headercell->scope = 'row'; - $headercell->attributes['title'] = $object->stripped_name; $headercell->attributes['class'] = 'cell column-rowspan rowspan ' . $levelclass; + $headercell->attributes['aria-hidden'] = 'true'; $headercell->rowspan = $row_count + 1; $row->cells[] = $headercell; @@ -338,6 +338,7 @@ public function build_html_tree($element, $totals, $parents, $level, &$row_count $endcell = new html_table_cell(); $endcell->colspan = (19 - $level); $endcell->attributes['class'] = 'emptyrow colspan ' . $levelclass; + $endcell->attributes['aria-hidden'] = 'true'; $returnrows[] = new html_table_row(array($endcell)); From ebe27cf615337dd88cbb13cd3ec617273d7b7378 Mon Sep 17 00:00:00 2001 From: Jun Pataleta Date: Fri, 28 Jul 2023 17:32:10 +0800 Subject: [PATCH 26/61] MDL-78550 core: Ensure spacing between more menu item attributes --- lib/templates/moremenu_children.mustache | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/templates/moremenu_children.mustache b/lib/templates/moremenu_children.mustache index bed6e9d8cd91f..cc7d5b566737b 100644 --- a/lib/templates/moremenu_children.mustache +++ b/lib/templates/moremenu_children.mustache @@ -43,7 +43,7 @@ {{#children}} {{^divider}} {{#is_action_link}} - {{{text}}} @@ -86,7 +86,7 @@ {{/istablist}} {{^istablist}} {{#is_action_link}} - + {{{text}}} {{#action_link_actions}} From affda87228a6a8cd3f6570ea6f988c728f8c4ad9 Mon Sep 17 00:00:00 2001 From: Michael Aherne Date: Wed, 17 May 2023 15:25:49 +0100 Subject: [PATCH 27/61] MDL-78235 userprofile: Check for defaultdata with isset(). --- user/profile/lib.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user/profile/lib.php b/user/profile/lib.php index b6a2cc21812a4..86058b71c7759 100644 --- a/user/profile/lib.php +++ b/user/profile/lib.php @@ -258,7 +258,7 @@ public function edit_validate_field($usernew) { * @param MoodleQuickForm $mform instance of the moodleform class */ public function edit_field_set_default($mform) { - if (!empty($this->field->defaultdata)) { + if (isset($this->field->defaultdata)) { $mform->setDefault($this->inputname, $this->field->defaultdata); } } From 2ba3b8b27e8a938d2a700af9e401dfde21af6782 Mon Sep 17 00:00:00 2001 From: Tomo Tsuyuki Date: Tue, 18 Jul 2023 11:52:26 +1000 Subject: [PATCH 28/61] MDL-77378 qtype_multianswer: Fix division by zero errors --- question/type/multianswer/question.php | 9 ++++ question/type/multianswer/tests/helper.php | 46 ++++++++++++++++++- .../type/multianswer/tests/question_test.php | 19 ++++++++ 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/question/type/multianswer/question.php b/question/type/multianswer/question.php index 62036c263a47b..a52eec83b05a0 100644 --- a/question/type/multianswer/question.php +++ b/question/type/multianswer/question.php @@ -147,6 +147,9 @@ public function get_min_fraction() { $fractionmax += $subq->defaultmark; $fractionsum += $subq->defaultmark * $subq->get_min_fraction(); } + if (empty($fractionsum)) { + return 0; + } return $fractionsum / (!empty($this->subquestions) ? $fractionmax : 1); } @@ -157,6 +160,9 @@ public function get_max_fraction() { $fractionmax += $subq->defaultmark; $fractionsum += $subq->defaultmark * $subq->get_max_fraction(); } + if (empty($fractionsum)) { + return 1; + } return $fractionsum / (!empty($this->subquestions) ? $fractionmax : 1); } @@ -299,6 +305,9 @@ public function grade_response(array $response) { $overallstate = $this->combine_states($overallstate, $newstate); } } + if (empty($fractionmax)) { + return array(null, $overallstate ?? question_state::$finished); + } return array($fractionsum / $fractionmax, $overallstate); } diff --git a/question/type/multianswer/tests/helper.php b/question/type/multianswer/tests/helper.php index 9c6e2d2595ddd..6469b2a808d64 100644 --- a/question/type/multianswer/tests/helper.php +++ b/question/type/multianswer/tests/helper.php @@ -37,7 +37,7 @@ */ class qtype_multianswer_test_helper extends question_test_helper { public function get_test_questions() { - return array('twosubq', 'fourmc', 'numericalzero', 'dollarsigns', 'multiple'); + return array('twosubq', 'fourmc', 'numericalzero', 'dollarsigns', 'multiple', 'zeroweight'); } /** @@ -486,4 +486,48 @@ public function make_multianswer_question_multiple() { return $q; } + /** + * Makes a multianswer question with zero weight. + * This is used for testing the MDL-77378 bug. + * @return qtype_multianswer_question + */ + public function make_multianswer_question_zeroweight() { + question_bank::load_question_definition_classes('multianswer'); + $q = new qtype_multianswer_question(); + test_question_maker::initialise_a_question($q); + $q->name = 'Zero weight'; + $q->questiontext = + 'Optional question: {#1}.'; + $q->generalfeedback = ''; + $q->qtype = question_bank::get_qtype('multianswer'); + $q->textfragments = array( + 'Optional question: ', + '.', + ); + $q->places = array('1' => '1'); + + // Shortanswer subquestion. + question_bank::load_question_definition_classes('shortanswer'); + $sa = new qtype_shortanswer_question(); + test_question_maker::initialise_a_question($sa); + $sa->name = 'Zero weight'; + $sa->questiontext = '{0:SHORTANSWER:~%0%Input box~%100%*}'; + $sa->questiontextformat = FORMAT_HTML; + $sa->generalfeedback = ''; + $sa->generalfeedbackformat = FORMAT_HTML; + $sa->usecase = true; + $sa->answers = array( + 13 => new question_answer(13, 'Input box', 0.0, '', FORMAT_HTML), + 14 => new question_answer(14, '*', 1.0, '', FORMAT_HTML), + ); + $sa->qtype = question_bank::get_qtype('shortanswer'); + $sa->defaultmark = 0; + + $q->subquestions = array( + 1 => $sa, + ); + + return $q; + } + } diff --git a/question/type/multianswer/tests/question_test.php b/question/type/multianswer/tests/question_test.php index 407d8b97487ce..5d0beed7a8f0d 100644 --- a/question/type/multianswer/tests/question_test.php +++ b/question/type/multianswer/tests/question_test.php @@ -350,4 +350,23 @@ public function test_update_attempt_state_date_from_old_version_ok() { $newquestion->update_attempt_state_data_for_new_version($oldstep, $question)); } + /** + * Test functions work with zero weight. + * This is used for testing the MDL-77378 bug. + */ + public function test_zeroweight() { + $this->resetAfterTest(); + /** @var \qtype_multianswer_question $question */ + $question = \test_question_maker::make_question('multianswer', 'zeroweight'); + $question->start_attempt(new question_attempt_step(), 1); + + $this->assertEquals([null, question_state::$gradedright], $question->grade_response( + ['sub1_answer' => 'Something'])); + $this->assertEquals([null, question_state::$gradedwrong], $question->grade_response( + ['sub1_answer' => 'Input box'])); + + $this->assertEquals(1, $question->get_max_fraction()); + $this->assertEquals(0, $question->get_min_fraction()); + } + } From 8c8cb002c0d8b70225b2cf890d3ee67347f3f8c9 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Tue, 1 Aug 2023 09:29:34 +0800 Subject: [PATCH 29/61] MDL-78632 core: Update upgrade versions --- lib/db/upgrade.php | 10 +++++----- version.php | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php index 0405aa1d0a64b..38767ba12b864 100644 --- a/lib/db/upgrade.php +++ b/lib/db/upgrade.php @@ -3064,12 +3064,12 @@ function xmldb_main_upgrade($oldversion) { upgrade_main_savepoint(true, 2022112803.03); } - if ($oldversion < 2022112804.08) { + if ($oldversion < 2022112804.09) { // Upgrade yaml mime type for existing yaml and yml files. - $filetypes = array( + $filetypes = [ '%.yaml' => 'application/yaml', '%.yml' => 'application/yaml,' - ); + ]; $select = $DB->sql_like('filename', '?', false); foreach ($filetypes as $extension => $mimetype) { @@ -3078,12 +3078,12 @@ function xmldb_main_upgrade($oldversion) { 'mimetype', $mimetype, $select, - array($extension) + [$extension] ); } // Main savepoint reached. - upgrade_main_savepoint(true, 2022112804.08); + upgrade_main_savepoint(true, 2022112804.09); } return true; diff --git a/version.php b/version.php index 0f6bcf0f8a1ca..e388d9281f502 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2022112804.08; // 20221128 = branching date YYYYMMDD - do not modify! +$version = 2022112804.09; // 20221128 = branching date YYYYMMDD - do not modify! // RR = release increments - 00 in DEV branches. // .XX = incremental changes. $release = '4.1.4+ (Build: 20230728)'; // Human-friendly version name From 73a08e3ccda1ce14bf125553990f1a4d0973afea Mon Sep 17 00:00:00 2001 From: Angelia Dela Cruz Date: Mon, 19 Jun 2023 16:46:29 +0800 Subject: [PATCH 30/61] MDL-78498 Behat: Use data generators for forum posts and replies This commit does few things: * Replace manual forum posts and replies to use data generator. * It also changes the code to handle social forums. * Other behat clean-ups and optimization. Co-authored-by: Simey Lameze --- .../tests/behat/protecteddelete.feature | 38 ++++-------- .../social_adjust_discussion_count.feature | 11 ++-- .../graderescale_for_forum_pointscale.feature | 6 +- .../tests/behat/modgrade_validation.feature | 6 +- .../tests/behat/private_replies.feature | 25 ++++---- mod/forum/tests/behat/add_forum.feature | 24 ++++---- mod/forum/tests/behat/advanced_search.feature | 6 +- mod/forum/tests/behat/discussion_lock.feature | 17 +++--- .../tests/behat/discussion_navigation.feature | 55 +++++++++--------- .../behat/discussion_subscriptions.feature | 58 +++++++++---------- .../tests/behat/edit_post_student.feature | 7 ++- mod/forum/tests/behat/edit_tags.feature | 7 ++- .../tests/behat/favourite_discussion.feature | 26 ++++----- .../behat/forum_activity_breadcrumbs.feature | 23 ++++---- .../tests/behat/forum_subscriptions.feature | 14 ++--- mod/forum/tests/behat/guest_users.feature | 26 ++++----- .../behat/h5p_inline_editing_content.feature | 9 ++- mod/forum/tests/behat/move_discussion.feature | 12 ++-- mod/forum/tests/behat/my_forum_posts.feature | 12 ++-- .../behat/post_to_multiple_groups.feature | 4 +- .../behat/posts_ordering_general.feature | 13 +++-- mod/forum/tests/behat/private_replies.feature | 6 +- mod/forum/tests/behat/recent_activity.feature | 56 ++++++++---------- ...ate_group_single_group_discussions.feature | 12 ++-- .../tests/behat/timed_discussions.feature | 21 ++----- 25 files changed, 227 insertions(+), 267 deletions(-) diff --git a/admin/tool/dataprivacy/tests/behat/protecteddelete.feature b/admin/tool/dataprivacy/tests/behat/protecteddelete.feature index b4e36dde72d80..978da609881fe 100644 --- a/admin/tool/dataprivacy/tests/behat/protecteddelete.feature +++ b/admin/tool/dataprivacy/tests/behat/protecteddelete.feature @@ -30,6 +30,12 @@ Feature: Protected data should not be deleted | Site purpose | PT1H | 0 | | prot | P1D | 1 | | unprot | P1D | 0 | + And the following "mod_forum > discussions" exist: + | user | forum | name | message | + | u1 | forump1 | Discussion subject | Test post in forump1 | + | u1 | forumu1 | Discussion subject | Test post in forumu1 | + | u1 | forump2 | Discussion subject | Test post in forump2 | + | u1 | forumu2 | Discussion subject | Test post in forumu2 | And I set the category and purpose for the "forump1" "forum" in course "C1" to "CAT" and "prot" And I set the category and purpose for the "forump2" "forum" in course "C2" to "CAT" and "prot" And I set the category and purpose for the "forumu1" "forum" in course "C1" to "CAT" and "unprot" @@ -38,47 +44,25 @@ Feature: Protected data should not be deleted @javascripta Scenario: Unexpired and protected data is not removed - Given I log in as "u1" - And I am on "C1" course homepage - And I add a new discussion to "forump1" forum with: - | Subject | Discussion subject | - | Message | Test post in forump1 | - And I am on "C1" course homepage - And I add a new discussion to "forumu1" forum with: - | Subject | Discussion subject | - | Message | Test post in forumu1 | - And I am on "C2" course homepage - And I add a new discussion to "forump2" forum with: - | Subject | Discussion subject | - | Message | Test post in forump2 | - And I am on "C2" course homepage - And I add a new discussion to "forumu2" forum with: - | Subject | Discussion subject | - | Message | Test post in forumu2 | - And I log out - And I log in as "admin" + Given I log in as "admin" And I create a dataprivacy "delete" request for "u1" And I approve a dataprivacy "delete" request for "u1" And I run all adhoc tasks And I navigate to "Users > Privacy and policies > Data requests" in site administration And I should see "Deleted" in the "u1" "table_row" - And I am on "C1" course homepage - And I follow "forump1" + And I am on the "forump1" "forum activity" page And I follow "Discussion subject" Then I should not see "Test post in forump1" - When I am on "C1" course homepage - And I follow "forumu1" + When I am on the "forumu1" "forum activity" page And I follow "Discussion subject" Then I should not see "Test post in forumu1" - And I am on "C2" course homepage - And I follow "forump2" + And I am on the "forump2" "forum activity" page And I follow "Discussion subject" Then I should see "Test post in forump2" - When I am on "C2" course homepage - And I follow "forumu2" + When I am on the "forumu2" "forum activity" page And I follow "Discussion subject" Then I should not see "Test post in forumu2" diff --git a/course/format/social/tests/behat/social_adjust_discussion_count.feature b/course/format/social/tests/behat/social_adjust_discussion_count.feature index 2fec3514751ba..07d11ae0182e4 100644 --- a/course/format/social/tests/behat/social_adjust_discussion_count.feature +++ b/course/format/social/tests/behat/social_adjust_discussion_count.feature @@ -105,11 +105,10 @@ Feature: Change number of discussions displayed | Message | This is forum post one | And I press "Post to forum" And I wait to be redirected - And I am on "Course 1" course homepage + And I am on the "C1" "course editing" page logged in as teacher1 Scenario: When number of discussions is decreased fewer discussions appear - Given I navigate to "Settings" in current page administration - And I set the following fields to these values: + Given I set the following fields to these values: | numdiscussions | 5 | When I press "Save and display" Then I should see "This is forum post one" @@ -117,8 +116,7 @@ Feature: Change number of discussions displayed And I should not see "This is forum post six" Scenario: When number of discussions is decreased to less than 1 only 1 discussion should appear - Given I navigate to "Settings" in current page administration - And I set the following fields to these values: + Given I set the following fields to these values: | numdiscussions | -1 | When I press "Save and display" Then I should see "This is forum post one" @@ -126,8 +124,7 @@ Feature: Change number of discussions displayed And I should not see "This is forum post ten" Scenario: When number of discussions is increased more discussions appear - Given I navigate to "Settings" in current page administration - And I set the following fields to these values: + Given I set the following fields to these values: | numdiscussions | 9 | When I press "Save and display" Then I should see "This is forum post one" diff --git a/lib/form/tests/behat/graderescale_for_forum_pointscale.feature b/lib/form/tests/behat/graderescale_for_forum_pointscale.feature index 62a7c3f4d9fd3..7e213d937f778 100644 --- a/lib/form/tests/behat/graderescale_for_forum_pointscale.feature +++ b/lib/form/tests/behat/graderescale_for_forum_pointscale.feature @@ -21,9 +21,9 @@ Feature: Using the forum activities which support point scale | activity | forum | | name | Test forum name | | idnumber | forum1 | - And the following forum discussions exist in course "Course 1": - | user | forum | name | message | - | student1 | Test forum name | Discussion subject | Test post in forum 1 | + And the following "mod_forum > discussions" exist: + | user | forum | name | message | + | student1 | forum1 | Discussion subject | Test post in forum 1 | @javascript Scenario: Forum rescale grade should not be possible when users are graded diff --git a/lib/form/tests/behat/modgrade_validation.feature b/lib/form/tests/behat/modgrade_validation.feature index 96b3245b395f5..41f36ca4a3542 100644 --- a/lib/form/tests/behat/modgrade_validation.feature +++ b/lib/form/tests/behat/modgrade_validation.feature @@ -24,9 +24,9 @@ Feature: Using the activity grade form element | activity | course | section | name | intro | idnumber | type | groupmode | | assign | C1 | 1 | Test assignment name | Test assignment description | | | | | forum | C1 | 1 | Test forum name | | forum1 | general | 0 | - And the following forum discussions exist in course "Course 1": - | user | forum | name | message | - | student1 | Test forum name | Discussion subject | Discussion message | + And the following "mod_forum > discussions" exist: + | user | forum | name | message | + | student1 | forum1 | Discussion subject | Discussion message | @javascript Scenario: Being able to change the grade type, scale and maximum grade when there are no grades diff --git a/mod/forum/report/summary/tests/behat/private_replies.feature b/mod/forum/report/summary/tests/behat/private_replies.feature index 8979ca4ee1352..c166db4d014e7 100644 --- a/mod/forum/report/summary/tests/behat/private_replies.feature +++ b/mod/forum/report/summary/tests/behat/private_replies.feature @@ -23,20 +23,17 @@ Feature: Include private replies in the summary report And the following "activities" exist: | activity | name | course | idnumber | | forum | forum1 | C1 | forum1 | - And the following forum discussions exist in course "Course 1": - | user | forum | name | message | - | teacher1 | forum1 | discussion1 | t1 earliest | - | teacher1 | forum1 | discussion2 | t1 between | - | student1 | forum1 | discussion3 | s1 latest | - And the following forum replies exist in course "Course 1": - | user | forum | discussion | subject | message | - | teacher1 | forum1 | discussion1 | t1 between | t1 between | - | teacher1 | forum1 | discussion2 | t1 latest | t1 latest | - | student1 | forum1 | discussion1 | s1 earliest | s1 earliest | - And I am on the "Course 1" course page logged in as teacher1 - And I reply "s1 earliest" post from "discussion1" forum with: - | Message | This is a private reply | - | Reply privately | 1 | + And the following "mod_forum > discussions" exist: + | user | forum | name | message | + | teacher1 | forum1 | discussion1 | t1 earliest | + | teacher1 | forum1 | discussion2 | t1 between | + | teacher1 | forum1 | discussion3 | s1 latest | + And the following "mod_forum > posts" exist: + | user | parentsubject | subject | message | privatereplyto | + | teacher1 | discussion1 | t1 between | t1 between | | + | teacher1 | discussion2 | t1 latest | t1 latest | | + | student1 | discussion1 | s1 earliest | s1 earliest | | + | teacher1 | s1 earliest | This is a private reply | This is a private reply | 1 | Scenario: Private replies are counted for Teacher When I am on the forum1 "forum activity" page logged in as teacher2 diff --git a/mod/forum/tests/behat/add_forum.feature b/mod/forum/tests/behat/add_forum.feature index 187cbd1dc661e..e88dd30c7e3eb 100644 --- a/mod/forum/tests/behat/add_forum.feature +++ b/mod/forum/tests/behat/add_forum.feature @@ -17,16 +17,20 @@ Feature: Add forum activities and discussions | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | - And the following "activity" exists: - | activity | forum | - | course | C1 | - | name | Test forum name | - | type | general | - And the following forum discussions exist in course "Course 1": - | user | forum | name | message | attachments | - | teacher1 | Test forum name | Forum post 1 | this is the body | | - | student1 | Test forum name | Post with attachment | this is the body | empty.txt | - And I log in as "student1" + And I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + And I add a "Forum" to section "1" and I fill the form with: + | Forum name | Test forum name | + | Forum type | Standard forum for general use | + | Description | Test forum description | + And I add a new discussion to "Test forum name" forum with: + | Subject | Forum post 1 | + | Message | This is the body | + And I am on the "Course 1" course page logged in as student1 + When I add a new discussion to "Test forum name" forum with: + | Subject | Post with attachment | + | Message | This is the body | + | Attachment | lib/tests/fixtures/empty.txt | And I reply "Forum post 1" post from "Test forum name" forum with: | Subject | Reply with attachment | | Message | This is the body | diff --git a/mod/forum/tests/behat/advanced_search.feature b/mod/forum/tests/behat/advanced_search.feature index 3b032263b9afb..45f3bb5c405c8 100644 --- a/mod/forum/tests/behat/advanced_search.feature +++ b/mod/forum/tests/behat/advanced_search.feature @@ -117,8 +117,7 @@ Feature: The forum search allows users to perform advanced searches for forum po @javascript Scenario: Perform an advanced search on starred discussions without text Given I am on the "Announcements" "forum activity" page logged in as student1 - And I click on "Your subjective" action menu - And I follow "Star this discussion" + And I click on "Star this discussion" "link" in the "Your subjective" "table_row" And I press "Search" And I should see "Please enter search terms into one or more of the following fields" And I set the field "starredonly" to "1" @@ -129,8 +128,7 @@ Feature: The forum search allows users to perform advanced searches for forum po @javascript Scenario: Perform an advanced search on starred discussions with text Given I am on the "Announcements" "forum activity" page logged in as student1 - And I click on "Your subjective" action menu - And I follow "Star this discussion" + And I click on "Star this discussion" "link" in the "Your subjective" "table_row" And I press "Search" And I should see "Please enter search terms into one or more of the following fields" And I set the field "words" to "message" diff --git a/mod/forum/tests/behat/discussion_lock.feature b/mod/forum/tests/behat/discussion_lock.feature index 5adf1c9d8374e..92d9090590386 100644 --- a/mod/forum/tests/behat/discussion_lock.feature +++ b/mod/forum/tests/behat/discussion_lock.feature @@ -15,14 +15,15 @@ Feature: As a teacher, you can manually lock individual discussions when viewing | course | C1 | | activity | forum | | name | Test forum name | - And the following forum discussions exist in course "Course 1": - | user | forum | name | message | - | admin | Test forum name | Discussion 1 | Discussion contents 1, first message | - | admin | Test forum name | Discussion 2 | Discussion contents 2, first message | - And the following forum replies exist in course "Course 1": - | user | forum | discussion | subject | message | - | admin | Test forum name | Discussion 1 | Reply 1 to discussion 1 | Discussion contents 1, second message | - | admin | Test forum name | Discussion 2 | Reply 1 to discussion 2 | Discussion contents 2, second message | + | idnumber | forum1 | + And the following "mod_forum > discussions" exist: + | user | forum | name | message | + | admin | forum1 | Discussion 1 | Discussion contents 1, first message | + | admin | forum1 | Discussion 2 | Discussion contents 2, first message | + And the following "mod_forum > posts" exist: + | user | parentsubject | subject | message | + | admin | Discussion 1 | Reply 1 to discussion 1 | Discussion contents 1, second message | + | admin | Discussion 2 | Reply 1 to discussion 2 | Discussion contents 2, second message | Scenario: Lock a discussion and view Given I am on the "Course 1" course page logged in as admin diff --git a/mod/forum/tests/behat/discussion_navigation.feature b/mod/forum/tests/behat/discussion_navigation.feature index 60759e8f11cb1..4caa84a708139 100644 --- a/mod/forum/tests/behat/discussion_navigation.feature +++ b/mod/forum/tests/behat/discussion_navigation.feature @@ -33,19 +33,12 @@ Feature: A user can navigate to previous and next discussions Given the following "activities" exist: | activity | name | course | idnumber | groupmode | | forum | Test forum name | C1 | forum | 0 | + And the following "mod_forum > discussions" exist: + | user | forum | name | message | + | teacher1 | forum | Discussion 1 | Test post message | + | teacher1 | forum | Discussion 2 | Test post message | + | teacher1 | forum | Discussion 3 | Test post message | And I am on the "Test forum name" "forum activity" page logged in as teacher1 - And I add a new discussion to "Test forum name" forum with: - | Subject | Discussion 1 | - | Message | Test post message | - And I wait "1" seconds - And I add a new discussion to "Test forum name" forum with: - | Subject | Discussion 2 | - | Message | Test post message | - And I wait "1" seconds - And I add a new discussion to "Test forum name" forum with: - | Subject | Discussion 3 | - | Message | Test post message | - And I wait "1" seconds When I follow "Discussion 3" Then I should not see "Discussion 1" And I should see "Discussion 2" @@ -72,14 +65,16 @@ Feature: A user can navigate to previous and next discussions Given the following "activities" exist: | activity | name | course | idnumber | groupmode | | forum | Test forum name | C1 | forum | 2 | - And the following forum discussions exist in course "Course 1": - | user | forum | name | message | group | - | teacher1 | Test forum name | Discussion 1 Group 0 | Test post message | | - | teacher1 | Test forum name | Discussion 2 Group 0 | Test post message | | - | teacher1 | Test forum name | Discussion 1 Group 1 | Test post message | G1 | - | teacher1 | Test forum name | Discussion 2 Group 1 | Test post message | G1 | - | teacher1 | Test forum name | Discussion 1 Group 2 | Test post message | G2 | - | teacher1 | Test forum name | Discussion 2 Group 2 | Test post message | G2 | + And the following "mod_forum > discussions" exist: + | user | forum | name | message | + | student1 | forum | Discussion 1 Group 0 | Test post message | + | student1 | forum | Discussion 2 Group 0 | Test post message | + And the following "mod_forum > discussions" exist: + | user | forum | name | message | group | + | student1 | forum | Discussion 1 Group 1 | Test post message | G1 | + | student1 | forum | Discussion 2 Group 1 | Test post message | G1 | + | student1 | forum | Discussion 1 Group 2 | Test post message | G2 | + | student1 | forum | Discussion 2 Group 2 | Test post message | G2 | When I am on the "Test forum name" "forum activity" page logged in as student1 And I select "All participants" from the "Visible groups" singleselect And I follow "Discussion 1 Group 0" @@ -99,7 +94,7 @@ Feature: A user can navigate to previous and next discussions And I follow "Test forum name" And I select "Group 1" from the "Visible groups" singleselect And I follow "Discussion 1 Group 1" - Then I should see "Discussion 2 Group 0" + And I should see "Discussion 2 Group 0" And I should see "Discussion 2 Group 1" And I follow "Discussion 2 Group 1" And I should see "Discussion 1 Group 1" @@ -109,14 +104,16 @@ Feature: A user can navigate to previous and next discussions Given the following "activities" exist: | activity | name | course | idnumber | groupmode | | forum | Test forum name | C1 | forum | 1 | - And the following forum discussions exist in course "Course 1": - | user | forum | name | message | group | - | teacher1 | Test forum name | Discussion 1 Group 0 | Test post message | | - | teacher1 | Test forum name | Discussion 2 Group 0 | Test post message | | - | teacher1 | Test forum name | Discussion 1 Group 1 | Test post message | G1 | - | teacher1 | Test forum name | Discussion 2 Group 1 | Test post message | G1 | - | teacher1 | Test forum name | Discussion 1 Group 2 | Test post message | G2 | - | teacher1 | Test forum name | Discussion 2 Group 2 | Test post message | G2 | + And the following "mod_forum > discussions" exist: + | user | forum | name | message | + | student1 | forum | Discussion 1 Group 0 | Test post message | + | student1 | forum | Discussion 2 Group 0 | Test post message | + And the following "mod_forum > discussions" exist: + | user | forum | name | message | group | + | student1 | forum | Discussion 1 Group 1 | Test post message | G1 | + | student1 | forum | Discussion 2 Group 1 | Test post message | G1 | + | student1 | forum | Discussion 1 Group 2 | Test post message | G2 | + | student1 | forum | Discussion 2 Group 2 | Test post message | G2 | When I am on the "Test forum name" "forum activity" page logged in as student1 And I follow "Discussion 1 Group 1" Then I should see "Discussion 2 Group 0" diff --git a/mod/forum/tests/behat/discussion_subscriptions.feature b/mod/forum/tests/behat/discussion_subscriptions.feature index e708d8af71bcb..198d2495c6ef8 100644 --- a/mod/forum/tests/behat/discussion_subscriptions.feature +++ b/mod/forum/tests/behat/discussion_subscriptions.feature @@ -24,10 +24,10 @@ Feature: A user can control their own subscription preferences for a discussion | name | Test forum name | | type | general | | forcesubscribe | 0 | - And the following forum discussions exist in course "Course 1": - | user | forum | name | message | - | admin | Test forum name | Test post subject one | Test post message one | - | admin | Test forum name | Test post subject two | Test post message two | + And the following "mod_forum > discussions" exist: + | user | forum | name | message | + | admin | forum1 | Test post subject one | Test post message one | + | admin | forum1 | Test post subject two | Test post message two | When I am on the "Test forum name" "forum activity" page logged in as student1 Then I can subscribe to this forum And "Subscribe to this discussion" "checkbox" should exist in the "Test post subject one" "table_row" @@ -64,10 +64,10 @@ Feature: A user can control their own subscription preferences for a discussion | name | Test forum name | | type | general | | forcesubscribe | 2 | - And the following forum discussions exist in course "Course 1": - | user | forum | name | message | - | admin | Test forum name | Test post subject one | Test post message one | - | admin | Test forum name | Test post subject two | Test post message two | + And the following "mod_forum > discussions" exist: + | user | forum | name | message | + | admin | forum1 | Test post subject one | Test post message one | + | admin | forum1 | Test post subject two | Test post message two | When I am on the "Test forum name" "forum activity" page logged in as student1 Then I can unsubscribe from this forum And "Unsubscribe from this discussion" "checkbox" should exist in the "Test post subject one" "table_row" @@ -104,10 +104,10 @@ Feature: A user can control their own subscription preferences for a discussion | name | Test forum name | | type | general | | forcesubscribe | 0 | - And the following forum discussions exist in course "Course 1": - | user | forum | name | message | - | admin | Test forum name | Test post subject one | Test post message one | - | admin | Test forum name | Test post subject two | Test post message two | + And the following "mod_forum > discussions" exist: + | user | forum | name | message | + | admin | forum1 | Test post subject one | Test post message one | + | admin | forum1 | Test post subject two | Test post message two | And I am on the "Test forum name" "forum activity" page logged in as student1 And I can subscribe to this forum And "Subscribe to this discussion" "checkbox" should exist in the "Test post subject one" "table_row" @@ -138,10 +138,10 @@ Feature: A user can control their own subscription preferences for a discussion | name | Test forum name | | type | general | | forcesubscribe | 0 | - And the following forum discussions exist in course "Course 1": - | user | forum | name | message | - | admin | Test forum name | Test post subject one | Test post message one | - | admin | Test forum name | Test post subject two | Test post message two | + And the following "mod_forum > discussions" exist: + | user | forum | name | message | + | admin | forum1 | Test post subject one | Test post message one | + | admin | forum1 | Test post subject two | Test post message two | When I am on the "Test forum name" "forum activity" page logged in as student1 And I should see "Subscribe to forum" And I reply "Test post subject one" post from "Test forum name" forum with: @@ -171,10 +171,10 @@ Feature: A user can control their own subscription preferences for a discussion | name | Test forum name | | type | general | | forcesubscribe | 2 | - And the following forum discussions exist in course "Course 1": - | user | forum | name | message | - | admin | Test forum name | Test post subject one | Test post message one | - | admin | Test forum name | Test post subject two | Test post message two | + And the following "mod_forum > discussions" exist: + | user | forum | name | message | + | admin | forum1 | Test post subject one | Test post message one | + | admin | forum1 | Test post subject two | Test post message two | # added for this scenario And the following "users" exist: | username | firstname | lastname | email | @@ -207,9 +207,9 @@ Feature: A user can control their own subscription preferences for a discussion Given the following "activities" exist: | activity | name | course | section | idnumber | type | | forum | Test forum name | Acceptance test site | 1 | forum1 | general | - And the following forum discussions exist in course "Acceptance test site": - | user | forum | name | message | - | admin | Test forum name | Test post subject one | Test post message one | + And the following "mod_forum > discussions" exist: + | user | forum | name | message | + | admin | forum1 | Test post subject one | Test post message one | When I am on the "Test forum name" "forum activity" page logged in as guest Then "Subscribe to this discussion" "checkbox" should not exist in the "Test post subject one" "table_row" And "Unsubscribe from this discussion" "checkbox" should not exist in the "Test post subject one" "table_row" @@ -221,9 +221,9 @@ Feature: A user can control their own subscription preferences for a discussion Given the following "activities" exist: | activity | name | course | section | idnumber | type | | forum | Test forum name | Acceptance test site | 1 | forum1 | general | - And the following forum discussions exist in course "Acceptance test site": - | user | forum | name | message | - | admin | Test forum name | Test post subject one | Test post message one | + And the following "mod_forum > discussions" exist: + | user | forum | name | message | + | admin | forum1 | Test post subject one | Test post message one | When I am on site homepage And I follow "Test forum name" Then "Subscribe to this discussion" "checkbox" should not exist in the "Test post subject one" "table_row" @@ -240,9 +240,9 @@ Feature: A user can control their own subscription preferences for a discussion | name | Test forum name | | type | general | | forcesubscribe | 0 | - And the following forum discussions exist in course "Course 1": - | user | forum | name | message | - | admin | Test forum name | Test post subject one | Test post message one | + And the following "mod_forum > discussions" exist: + | user | forum | name | message | + | admin | forum1 | Test post subject one | Test post message one | When I am on the "Test forum name" "forum activity" page logged in as student1 Then "Subscribe to forum" "link" should exist And I follow "Test post subject one" diff --git a/mod/forum/tests/behat/edit_post_student.feature b/mod/forum/tests/behat/edit_post_student.feature index 1e866bc520e0d..af0cdddb1e9f7 100644 --- a/mod/forum/tests/behat/edit_post_student.feature +++ b/mod/forum/tests/behat/edit_post_student.feature @@ -18,9 +18,10 @@ Feature: Students can edit or delete their forum posts within a set time limit | activity | name | course | idnumber | | forum | Test forum name | C1 | forum | And I am on the "Course 1" course page logged in as student1 - And I add a new discussion to "Test forum name" forum with: - | Subject | Forum post subject | - | Message | This is the body | + And the following "mod_forum > discussions" exist: + | user | forum | name | message | + | student1 | forum | Forum post subject | This is the body | + And I am on the "Test forum name" "forum activity" page Scenario: Edit forum post Given I follow "Forum post subject" diff --git a/mod/forum/tests/behat/edit_tags.feature b/mod/forum/tests/behat/edit_tags.feature index 94dc1b4d1288f..83a6d60d5a06b 100644 --- a/mod/forum/tests/behat/edit_tags.feature +++ b/mod/forum/tests/behat/edit_tags.feature @@ -20,9 +20,10 @@ Feature: Edited forum posts handle tags correctly | course | C1 | | activity | forum | | name | Test forum name | - And the following forum discussions exist in course "Course 1": - | user | forum | name | message | - | teacher1 | Test forum name | Teacher post subject | Teacher post message | + | idnumber | forum1 | + And the following "mod_forum > discussions" exist: + | user | forum | name | message | + | teacher1 | forum1 | Teacher post subject | Teacher post message | @javascript Scenario: Forum post edition of custom tags works as expected diff --git a/mod/forum/tests/behat/favourite_discussion.feature b/mod/forum/tests/behat/favourite_discussion.feature index eaf3822f404d2..b1743cf955567 100644 --- a/mod/forum/tests/behat/favourite_discussion.feature +++ b/mod/forum/tests/behat/favourite_discussion.feature @@ -15,19 +15,17 @@ Feature: A student can favourite a discussion via the forum settings menu | course | C1 | | activity | forum | | name | Test forum name | - And the following forum discussions exist in course "Course 1": - | user | forum | name | message | - | admin | Test forum name | Discussion 1 | Discussion contents 1, first message | - And the following forum replies exist in course "Course 1": - | user | forum | discussion | name | message | - | admin | Test forum name | Discussion 1 | Reply 1 to discussion 1 | Discussion contents 1, second message | + | idnumber | forum1 | + And the following "mod_forum > discussions" exist: + | user | forum | name | message | + | admin | forum1 | Discussion 1 | Discussion contents 1, first message | + And the following "mod_forum > posts" exist: + | user | parentsubject | subject | message | + | admin | Discussion 1 | Reply 1 to discussion 1 | Discussion contents 1, second message | + | student1 | Discussion 1 | Reply 2 to discussion 1 | Discussion contents 1, third message | Scenario: Student can favourite a discussion from within an individual discussion - Given I am on the "Course 1" Course page logged in as student1 - And I reply "Discussion 1" post from "Test forum name" forum with: - | Subject | Reply 2 to discussion 1 | - | Message | Discussion contents 1, third message | - And I wait until the page is ready + Given I am on the "Test forum name" "forum activity" page logged in as student1 When I open the action menu in "[data-container='discussion-tools']" "css_element" And I click on "[title='Star this discussion']" "css_element" And I wait "3" seconds @@ -35,11 +33,7 @@ Feature: A student can favourite a discussion via the forum settings menu And I click on "[title='Unstar this discussion']" "css_element" Scenario: Student can favourite a discussion from the discussion list - Given I am on the "Course 1" Course page logged in as student1 - And I reply "Discussion 1" post from "Test forum name" forum with: - | Subject | Reply 2 to discussion 1 | - | Message | Discussion contents 1, third message | - When I am on the "Test forum name" "forum activity" page + When I am on the "Test forum name" "forum activity" page logged in as student1 And I click on "Discussion 1" action menu And I click on "[title='Star this discussion']" "css_element" And I click on "Discussion 1" action menu diff --git a/mod/forum/tests/behat/forum_activity_breadcrumbs.feature b/mod/forum/tests/behat/forum_activity_breadcrumbs.feature index ca01b8ce90878..f84c1076364a0 100644 --- a/mod/forum/tests/behat/forum_activity_breadcrumbs.feature +++ b/mod/forum/tests/behat/forum_activity_breadcrumbs.feature @@ -12,9 +12,10 @@ Feature: A teacher or admin can view breadcrumbs in the reply, delete, split, ed | user | course | role | | teacher | C1 | editingteacher | And the following "activity" exists: - | course | C1 | - | activity | forum | - | name | Test forum name | + | course | C1 | + | activity | forum | + | name | Test forum name | + | idnumber | forum1 | Scenario: A teacher views add discussion topics advanced page Given I am on the "Test forum name" "forum activity" page logged in as teacher @@ -25,14 +26,14 @@ Feature: A teacher or admin can view breadcrumbs in the reply, delete, split, ed And I should see "Test forum name" in the ".breadcrumb" "css_element" Scenario: A teacher adds posts and then verifies the breadcrumbs in the links - Given I am on the "Course 1" course page logged in as teacher - And I add a new discussion to "Test forum name" forum with: - | Subject | Test post subject one | - | Message | Test post message one | - And I reply "Test post subject one" post from "Test forum name" forum with: - | Subject | Reply 1 to discussion 1 | - | Message | Discussion contents 1, second message | - And I should see "Settings" in the "//div[@class='ml-auto dropdown']" "xpath_element" + Given the following "mod_forum > discussions" exist: + | user | forum | name | message | + | teacher | forum1 | Test post subject one | Test post message one | + And the following "mod_forum > posts" exist: + | user | parentsubject | subject | message | + | teacher | Test post subject one | Reply 1 to discussion 1 | Discussion contents 1, second message | + And I am on the "Test forum name" "forum activity" page logged in as teacher + And I follow "Test post subject one" When I follow "Edit" Then I should see "Edit discussion topic" And I should see "Edit discussion topic" in the ".breadcrumb" "css_element" diff --git a/mod/forum/tests/behat/forum_subscriptions.feature b/mod/forum/tests/behat/forum_subscriptions.feature index d6d1d84727d29..8af07c1a34207 100644 --- a/mod/forum/tests/behat/forum_subscriptions.feature +++ b/mod/forum/tests/behat/forum_subscriptions.feature @@ -20,11 +20,10 @@ Feature: A user can control their own subscription preferences for a forum And the following "mod_forum > discussions" exist: | forum | course | user | name | message | | forum1 | C1 | admin | Test post subject | Test post message | - And I log in as "admin" + And I am on the "Test forum name" "forum activity editing" page logged in as admin Scenario: A disallowed subscription forum cannot be subscribed to - Given I am on the "Test forum name" "forum activity editing" page - And I set the following fields to these values: + Given I set the following fields to these values: | Subscription mode | Subscription disabled | And I press "Save and return to course" When I am on the "Test forum name" "forum activity" page logged in as student1 @@ -34,8 +33,7 @@ Feature: A user can control their own subscription preferences for a forum And "You are not subscribed to this discussion. Click to subscribe." "link" should not exist in the "Test post subject" "table_row" Scenario: A forced subscription forum cannot be subscribed to - Given I am on the "Test forum name" "forum activity editing" page - And I set the following fields to these values: + Given I set the following fields to these values: | Subscription mode | Forced subscription | And I press "Save and return to course" When I am on the "Test forum name" "forum activity" page logged in as student1 @@ -45,8 +43,7 @@ Feature: A user can control their own subscription preferences for a forum And "You are not subscribed to this discussion. Click to subscribe." "link" should not exist in the "Test post subject" "table_row" Scenario: An optional forum can be subscribed to - Given I am on the "Test forum name" "forum activity editing" page - And I set the following fields to these values: + Given I set the following fields to these values: | Subscription mode | Optional subscription | And I press "Save and return to course" When I am on the "Test forum name" "forum activity" page logged in as student1 @@ -58,8 +55,7 @@ Feature: A user can control their own subscription preferences for a forum And I should not see "Subscribe to forum" Scenario: An Automatic forum can be unsubscribed from - Given I am on the "Test forum name" "forum activity editing" page - And I set the following fields to these values: + Given I set the following fields to these values: | Subscription mode | Auto subscription | And I press "Save and return to course" When I am on the "Test forum name" "forum activity" page logged in as student1 diff --git a/mod/forum/tests/behat/guest_users.feature b/mod/forum/tests/behat/guest_users.feature index aba0defa27653..88cc39e25fd97 100644 --- a/mod/forum/tests/behat/guest_users.feature +++ b/mod/forum/tests/behat/guest_users.feature @@ -23,9 +23,9 @@ Feature: Guest and not logged users could see the option to add new post or repl Given the following "activities" exist: | activity | name | course | idnumber | type | | forum | Forum | C1 | forum | | - And I add a new discussion to "Forum" forum with: - | Subject | Forum discussion 1 | - | Message | How awesome is this forum discussion? | + And the following "mod_forum > discussions" exist: + | user | forum | name | message | + | teacher | forum | Forum discussion 1 | How awesome is this forum discussion? | And I log out And I am on "Course 1" course homepage When I press "Access as a guest" @@ -56,10 +56,10 @@ Feature: Guest and not logged users could see the option to add new post or repl Scenario: As a not enrolled guest I don't see the option to add a new discussion in a blog type forum Given the following "activities" exist: | activity | name | course | idnumber | type | - | forum | Forum | C1 | forum | blog | - And I add a new discussion to "Forum" forum with: - | Subject | Forum discussion 1 | - | Message | How awesome is this forum discussion? | + | forum | Forum | C1 | forum | blog | + And the following "mod_forum > discussions" exist: + | user | forum | name | message | + | teacher | forum | Forum discussion 1 | How awesome is this forum discussion? | And I log out And I am on "Course 1" course homepage When I press "Access as a guest" @@ -74,9 +74,9 @@ Feature: Guest and not logged users could see the option to add new post or repl And the following "activities" exist: | activity | name | course | idnumber | type | | forum | Forum | C1 | forum | | - And I add a new discussion to "Forum" forum with: - | Subject | Forum discussion 1 | - | Message | How awesome is this forum discussion? | + And the following "mod_forum > discussions" exist: + | user | forum | name | message | + | teacher | forum | Forum discussion 1 | How awesome is this forum discussion? | And I log out And I am on "Course 1" course homepage When I press "Access as a guest" @@ -125,9 +125,9 @@ Feature: Guest and not logged users could see the option to add new post or repl And the following "activities" exist: | activity | name | course | idnumber | type | | forum | Forum | C1 | forum | blog | - And I add a new discussion to "Forum" forum with: - | Subject | Forum discussion 1 | - | Message | How awesome is this forum discussion? | + And the following "mod_forum > discussions" exist: + | user | forum | name | message | + | teacher | forum | Forum discussion 1 | How awesome is this forum discussion? | And I log out And I am on "Course 1" course homepage When I press "Access as a guest" diff --git a/mod/forum/tests/behat/h5p_inline_editing_content.feature b/mod/forum/tests/behat/h5p_inline_editing_content.feature index 92868c0448170..69728bc332813 100644 --- a/mod/forum/tests/behat/h5p_inline_editing_content.feature +++ b/mod/forum/tests/behat/h5p_inline_editing_content.feature @@ -73,9 +73,13 @@ Feature: Inline editing H5P content in mod_forum @javascript @repository_contentbank Scenario: Edit H5P content from a forum post + Given the following "mod_forum > discussions" exist: + | user | forum | name | message | + | admin | 1 | Forum post by admin | Forum post by admin | Given I am on the "ForumName1" "forum activity" page logged in as admin + And I follow "Forum post by admin" + And I click on "Edit" "link" # Add H5P content to a forum post as admin. - And I click on "Add discussion topic" "link" And I set the following fields to these values: | Subject | Forum post by admin | And I click on "Insert H5P" "button" in the "#fitem_id_message" "css_element" @@ -84,8 +88,7 @@ Feature: Inline editing H5P content in mod_forum And I click on "Greeting card" "file" in repository content area And I click on "Select this file" "button" And I click on "Insert H5P" "button" in the "Insert H5P" "dialogue" - And I press "Post to forum" - And I follow "Forum post by admin" + And I press "Save changes" And I switch to "h5p-iframe" class iframe And I switch to "h5p-iframe" class iframe And I should see "Hello world!" diff --git a/mod/forum/tests/behat/move_discussion.feature b/mod/forum/tests/behat/move_discussion.feature index 9a823319d8aa4..d773716fc2d77 100644 --- a/mod/forum/tests/behat/move_discussion.feature +++ b/mod/forum/tests/behat/move_discussion.feature @@ -19,12 +19,12 @@ Feature: A teacher can move discussions between forums Scenario: A teacher can move discussions Given the following "activities" exist: - | activity | name | course | idnumber | groupmode | - | forum | Test forum 1 | C1 | forum | 0 | - | forum | Test forum 2 | C1 | forum | 0 | - And the following forum discussions exist in course "Course 1": - | user | forum | name | message | - | student1 | Test forum 1 | Discussion 1 | Test post message | + | activity | name | course | idnumber | groupmode | + | forum | Test forum 1 | C1 | forum1 | 0 | + | forum | Test forum 2 | C1 | forum2 | 0 | + And the following "mod_forum > discussions" exist: + | user | forum | name | message | + | student1 | forum1 | Discussion 1 | Test post message | And I am on the "Test forum 1" "forum activity" page logged in as teacher1 And I follow "Discussion 1" When I set the field "jump" to "Test forum 2" diff --git a/mod/forum/tests/behat/my_forum_posts.feature b/mod/forum/tests/behat/my_forum_posts.feature index 35a1d0afe7f9c..27cbdcc3b94e9 100644 --- a/mod/forum/tests/behat/my_forum_posts.feature +++ b/mod/forum/tests/behat/my_forum_posts.feature @@ -17,12 +17,12 @@ Feature: A user can view their posts and discussions And the following "activities" exist: | activity | name | course | idnumber | groupmode | | forum | Test forum name | C1 | forum | 0 | - And the following forum discussions exist in course "Course 1": - | user | forum | name | message | - | student1 | Test forum name | Forum discussion 1 | How awesome is this forum discussion? | - And the following forum replies exist in course "Course 1": - | user | forum | discussion | message | - | student1 | Test forum name | Forum discussion 1 | Actually, I've seen better. | + And the following "mod_forum > discussions" exist: + | user | forum | name | message | + | student1 | forum | Forum discussion 1 | How awesome is this forum discussion? | + And the following "mod_forum > posts" exist: + | user | parentsubject | subject | message | + | student1 | Forum discussion 1 | Actually, I've seen better. | Actually, I've seen better. | And I log in as "student1" When I follow "Profile" in the user menu And I follow "Forum posts" diff --git a/mod/forum/tests/behat/post_to_multiple_groups.feature b/mod/forum/tests/behat/post_to_multiple_groups.feature index e5da6205963d2..b191a6f4b6628 100644 --- a/mod/forum/tests/behat/post_to_multiple_groups.feature +++ b/mod/forum/tests/behat/post_to_multiple_groups.feature @@ -79,12 +79,12 @@ Feature: A user with access to multiple groups should be able to post a copy of Then I should see "Discussion 1" And I am on the "Visible group forum" "forum activity" page logged in as student2 And I should see "Discussion 1" - And I am on the "Visible group forum" "forum activity" page logged in as student2 + And I am on the "Visible group forum" "forum activity" page logged in as student3 And I should see "Discussion 1" Scenario: Teacher is unable to post a copy of a message to all groups in a no group forum Given I am on the "No group forum" "forum activity" page logged in as teacher1 - And I click on "Add discussion topic" "link" + When I click on "Add discussion topic" "link" Then I should not see "Post a copy to all groups" Scenario: Posts to all groups that have groupings should only display within the grouping and not to other groups diff --git a/mod/forum/tests/behat/posts_ordering_general.feature b/mod/forum/tests/behat/posts_ordering_general.feature index a577be71de4f1..7bf0bf286ac34 100644 --- a/mod/forum/tests/behat/posts_ordering_general.feature +++ b/mod/forum/tests/behat/posts_ordering_general.feature @@ -20,14 +20,15 @@ Feature: New discussions and discussions with recently added replies are display | course | C1 | | activity | forum | | name | Course general forum | + | idnumber | forum1 | # # Add three posts into the blog. # - And the following forum discussions exist in course "Course 1": - | user | forum | name | message | timemodified | - | student1 | Course general forum | Forum post 1 | This is the first post | ##now +1 second## | - | student1 | Course general forum | Forum post 2 | This is the second post | ##now +2 second## | - | student1 | Course general forum | Forum post 3 | This is the third post | ##now +3 second## | + And the following "mod_forum > discussions" exist: + | user | forum | name | message | timemodified | + | student1 | forum1 | Forum post 1 | This is the first post | ##now +1 second## | + | student1 | forum1 | Forum post 2 | This is the second post | ##now +2 second## | + | student1 | forum1 | Forum post 3 | This is the third post | ##now +3 second## | # # We need javascript/wait to prevent creation of the posts in the same second. The threads @@ -51,7 +52,7 @@ Feature: New discussions and discussions with recently added replies are display # And I am on the "Course general forum" "forum activity" page logged in as teacher1 And I follow "Forum post 1" - And I reply "Forum post 1" post from "Course general forum" forum with: + When I reply "Forum post 1" post from "Course general forum" forum with: | Message | Reply to the first post | And I am on the "Course general forum" "forum activity" page # diff --git a/mod/forum/tests/behat/private_replies.feature b/mod/forum/tests/behat/private_replies.feature index 9abe1d0d4ec32..ec64e3632bcf2 100644 --- a/mod/forum/tests/behat/private_replies.feature +++ b/mod/forum/tests/behat/private_replies.feature @@ -23,9 +23,9 @@ Feature: Forum posts can be replied to in private | teacher2 | C1 | editingteacher | | student1 | C1 | student | | student2 | C1 | student | - And the following forum discussions exist in course "Science 101": - | user | forum | name | message | - | student1 | Study discussions | Answers to the homework | Here are the answers to last night's homework. | + And the following "mod_forum > discussions" exist: + | user | forum | name | message | + | student1 | forum | Answers to the homework | Here are the answers to last night's homework. | And I am on the "Science 101" course page logged in as teacher1 And I reply "Answers to the homework" post from "Study discussions" forum with: | Message | How about you and I have a meeting after class about plagiarism? | diff --git a/mod/forum/tests/behat/recent_activity.feature b/mod/forum/tests/behat/recent_activity.feature index 86574b8e6c2db..8769e57af1894 100644 --- a/mod/forum/tests/behat/recent_activity.feature +++ b/mod/forum/tests/behat/recent_activity.feature @@ -41,33 +41,30 @@ Feature: Users can see the relevant recent forum posts from the recent activity And I log in as "teacher1" Scenario: Recent forum activity with separate group discussion - Given I add a new discussion to "Separate groups forum" forum with: - | Subject | Group 1 separate discussion | - | Message | Group 1 members only | - | Group | Group 1 | + Given the following "mod_forum > discussions" exist: + | user | forum | name | message | group | + | teacher1 | forum1 | Group 1 separate discussion | Group 1 members only | G1 | When I am on the "Course 1" course page logged in as student1 Then I should see "Group 1 separate discussion" in the "Recent activity" "block" And I am on the "Course 1" course page logged in as student2 And I should not see "Group 1 separate discussion" in the "Recent activity" "block" Scenario: Recent forum activity with visible groups discussion - Given I add a new discussion to "Visible groups forum" forum with: - | Subject | Group 1 visible discussion | - | Message | Not just for group 1 members | - | Group | Group 1 | + Given the following "mod_forum > discussions" exist: + | user | forum | name | message | group | + | teacher1 | forum2 | Group 1 visible discussion | Not just for group 1 members | G1 | When I am on the "Course 1" course page logged in as student1 Then I should see "Group 1 visible discussion" in the "Recent activity" "block" And I am on the "Course 1" course page logged in as student2 And I should see "Group 1 visible discussion" in the "Recent activity" "block" Scenario: Recent forum activity with recent post as a private reply - Given I add a new discussion to "Standard forum" forum with: - | Subject | Standard forum discussion | - | Message | Discuss anything under the sun here! | - And I reply "Standard forum discussion" post from "Standard forum" forum with: - | Subject | Teacher's private reply | - | Message | This is a private reply | - | Reply privately | 1 | + Given the following "mod_forum > discussions" exist: + | user | forum | name | message | + | teacher1 | forum3 | Standard forum discussion | Discuss anything under the sun here! | + And the following "mod_forum > posts" exist: + | user | parentsubject | subject | message | privatereplyto | + | teacher1 | Standard forum discussion | Teacher's private reply | This is a private reply | 1 | And I am on "Course 1" course homepage And I should see "Standard forum discussion" in the "Recent activity" "block" And I should see "Teacher's private reply" in the "Recent activity" "block" @@ -76,23 +73,22 @@ Feature: Users can see the relevant recent forum posts from the recent activity But I should not see "Teacher's private reply" in the "Recent activity" "block" Scenario: Recent forum activity with recent post in a hidden forum - Given the following forum discussions exist in course "Course 1": - | user | forum | name | message | - | teacher1 | Hidden forum | Hidden discussion | Should be hidden! | + Given the following "mod_forum > discussions" exist: + | user | forum | name | message | + | teacher1 | forum4 | Hidden discussion | Should be hidden! | And I am on "Course 1" course homepage And I should see "Hidden discussion" in the "Recent activity" "block" When I am on the "Course 1" course page logged in as student1 Then I should not see "Hidden discussion" in the "Recent activity" "block" Scenario: Recent forum activity with question and answer forum - Given I add a new question to "Q&A forum" forum with: - | Subject | The egg vs the chicken | - | Message | Which came first? The egg or the chicken? | + Given the following "mod_forum > discussions" exist: + | user | forum | name | message | + | teacher1 | forum5 | The egg vs the chicken | Which came first? The egg or the chicken? | + And the following "mod_forum > posts" exist: + | user | parentsubject | subject | message | + | student1 | The egg vs the chicken | Student 1's answer | The egg! | And I am on the "Course 1" course page logged in as student1 - And I reply "The egg vs the chicken" post from "Q&A forum" forum with: - | Subject | Student 1's answer | - | Message | The egg! | - And I am on "Course 1" course homepage And I should see "The egg vs the chicken" in the "Recent activity" "block" And I should see "Student 1's answer" in the "Recent activity" "block" And the following config values are set as admin: @@ -109,13 +105,9 @@ Feature: Users can see the relevant recent forum posts from the recent activity And I should see "Student 2's answer" in the "Recent activity" "block" Scenario: Recent forum activity with timed discussion - Given I add a new discussion to "Standard forum" forum with: - | Subject | Timed discussion | - | Message | Discuss anything under the sun here... no more!!! | - | timeend[enabled] | 1 | - | timeend[year] | 2020 | - | timeend[month] | 1 | - | timeend[day] | 1 | + Given the following "mod_forum > discussions" exist: + | user | forum | name | message | timeend | + | teacher1 | forum3 | Timed discussion | Discuss anything under the sun here... no more!!! | ##1 Jan 2020 08:00## | And I am on "Course 1" course homepage And I should see "Timed discussion" in the "Recent activity" "block" When I am on the "Course 1" course page logged in as student1 diff --git a/mod/forum/tests/behat/separate_group_single_group_discussions.feature b/mod/forum/tests/behat/separate_group_single_group_discussions.feature index 6ddf9a37cce9a..5232ad3cea7f5 100644 --- a/mod/forum/tests/behat/separate_group_single_group_discussions.feature +++ b/mod/forum/tests/behat/separate_group_single_group_discussions.feature @@ -39,9 +39,9 @@ Feature: Posting to groups in a separate group discussion when restricted to gro | G1 | G1G2 | | G2 | G2G1 | And the following "activities" exist: - | activity | course | idnumber | name | type | groupmode | grouping | - | forum | C1 | 00001 | Multiple groups forum | general | 1 | G1 | - | forum | C1 | 00001 | Single groups forum | general | 1 | G2 | + | activity | course | idnumber | name | intro | type | section | groupmode | grouping | + | forum | C1 | 00001 | Multiple groups forum | Standard forum description | general | 1 | 1 | G1 | + | forum | C1 | 00001 | Single groups forum | Standard forum description | general | 1 | 1 | G2 | Scenario: Teacher with accessallgroups can post in all groups Given I am on the "Multiple groups forum" "forum activity" page logged in as teacher1 @@ -50,7 +50,8 @@ Feature: Posting to groups in a separate group discussion when restricted to gro Then the "Group" select box should contain "All participants" And the "Group" select box should contain "G1G1" And the "Group" select box should contain "G1G2" - And I am on the "Single groups forum" "forum activity" page + And I am on "Course 1" course homepage + And I follow "Single groups forum" And I click on "Add discussion topic" "link" And I click on "Advanced" "button" And the "Group" select box should contain "All participants" @@ -64,7 +65,8 @@ Feature: Posting to groups in a separate group discussion when restricted to gro Then the "Group" select box should not contain "All participants" And the "Group" select box should contain "G1G1" And the "Group" select box should contain "G1G2" - And I am on the "Single groups forum" "forum activity" page + And I am on "Course 1" course homepage + And I follow "Single groups forum" And I click on "Add discussion topic" "link" And I click on "Advanced" "button" And I should see "G2G1" diff --git a/mod/forum/tests/behat/timed_discussions.feature b/mod/forum/tests/behat/timed_discussions.feature index 15cb6bd991d68..54d7af870b658 100644 --- a/mod/forum/tests/behat/timed_discussions.feature +++ b/mod/forum/tests/behat/timed_discussions.feature @@ -17,23 +17,14 @@ Feature: Users can choose to set start and end time for display of their discuss And the following "activities" exist: | activity | name | course | idnumber | type | | forum | Test forum name | C1 | forump1 | general | - And I log in as "admin" + And the following "mod_forum > discussions" exist: + | user | forum | name | message | timeend | timestart | + | admin | forump1 | Discussion 1 | Discussion contents 1, first message | | | + | admin | forump1 | Discussion 2 timed not visible | Discussion contents 2, first message | ##1 Jan 2014 08:00## | | + | admin | forump1 | Discussion 3 timed visible now | Discussion contents 3, first message | | 1 | And the following config values are set as admin: | forum_enabletimedposts | 1 | - And I am on "Course 1" course homepage - And I add a new discussion to "Test forum name" forum with: - | Subject | Discussion 1 | - | Message | Discussion contents 1, first message | - And I add a new discussion to "Test forum name" forum with: - | Subject | Discussion 2 timed not visible | - | Message | Discussion contents 2, first message | - | timeend[enabled] | 1 | - | timeend[year] | 2014 | - And I add a new discussion to "Test forum name" forum with: - | Subject | Discussion 3 timed visible now | - | Message | Discussion contents 3, first message | - | timestart[enabled] | 1 | - And I am on the "Test forum name" "forum activity" page + And I am on the "Test forum name" "forum activity" page logged in as admin And I should see "Discussion 2 timed" And I should see "Discussion 3 timed" And "[data-region=timed-label]" "css_element" should exist From 33d829716e22aa98b47b39e91b477972b66deb7a Mon Sep 17 00:00:00 2001 From: Simey Lameze Date: Tue, 18 Jul 2023 16:35:30 +0800 Subject: [PATCH 31/61] MDL-78498 behat: add support for private replies This commit adds support for private replies in the 'the following forum replies exist in course' custom step. --- mod/forum/tests/behat/behat_mod_forum.php | 5 +++++ mod/forum/tests/behat/private_replies.feature | 7 +++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/mod/forum/tests/behat/behat_mod_forum.php b/mod/forum/tests/behat/behat_mod_forum.php index 9173933426c10..acbd688ad4674 100644 --- a/mod/forum/tests/behat/behat_mod_forum.php +++ b/mod/forum/tests/behat/behat_mod_forum.php @@ -250,6 +250,11 @@ public function the_following_forum_replies_exist(string $coursename, TableNode $cm = get_coursemodule_from_instance('forum', $replyinfo['forum']); } + // Get the user id of the user to whom the reply is private. + if (!empty($replyinfo['privatereplyto'])) { + $replyinfo['privatereplyto'] = $this->get_user_id($replyinfo['privatereplyto']); + } + // Create the reply post. $reply = $forumgenerator->create_post($replyinfo); diff --git a/mod/forum/tests/behat/private_replies.feature b/mod/forum/tests/behat/private_replies.feature index ec64e3632bcf2..a647363115de5 100644 --- a/mod/forum/tests/behat/private_replies.feature +++ b/mod/forum/tests/behat/private_replies.feature @@ -26,10 +26,9 @@ Feature: Forum posts can be replied to in private And the following "mod_forum > discussions" exist: | user | forum | name | message | | student1 | forum | Answers to the homework | Here are the answers to last night's homework. | - And I am on the "Science 101" course page logged in as teacher1 - And I reply "Answers to the homework" post from "Study discussions" forum with: - | Message | How about you and I have a meeting after class about plagiarism? | - | Reply privately | 1 | + And the following forum replies exist in course "Science 101": + | user | forum | discussion | message | privatereplyto | + | teacher1 | Study discussions | Answers to the homework |How about you and I have a meeting after class about plagiarism?| student1 | Scenario: As a teacher I can see my own response Given I am on the "Study discussions" "forum activity" page logged in as teacher1 From 1b4ee84aee712d78bbb84c0e698ae89c2c5c6001 Mon Sep 17 00:00:00 2001 From: danghieu1407 Date: Tue, 1 Aug 2023 15:41:38 +0700 Subject: [PATCH 32/61] MDL-78866 question: question_created trigger pre-saving extra fields --- question/format.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/question/format.php b/question/format.php index 620fc55f41182..db8ffec063150 100644 --- a/question/format.php +++ b/question/format.php @@ -474,9 +474,6 @@ public function importprocess() { $questionversion->status = \core_question\local\bank\question_version_status::QUESTION_STATUS_READY; $questionversion->id = $DB->insert_record('question_versions', $questionversion); - $event = \core\event\question_created::create_from_question_instance($question, $this->importcontext); - $event->trigger(); - if (isset($question->questiontextitemid)) { $question->questiontext = file_save_draft_area_files($question->questiontextitemid, $this->importcontext->id, 'question', 'questiontext', $question->id, @@ -504,6 +501,8 @@ public function importprocess() { // Now to save all the answers and type-specific options $result = question_bank::get_qtype($question->qtype)->save_question_options($question); + $event = \core\event\question_created::create_from_question_instance($question, $this->importcontext); + $event->trigger(); if (core_tag_tag::is_enabled('core_question', 'question')) { // Is the current context we're importing in a course context? From 58836d7143865d4a387b33dfc65e97bf2a8e4863 Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Wed, 2 Aug 2023 02:12:56 +0100 Subject: [PATCH 33/61] MDL-78872 grade: fix external class execute method visibility. --- grade/classes/external/get_groups_for_search_widget.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/grade/classes/external/get_groups_for_search_widget.php b/grade/classes/external/get_groups_for_search_widget.php index 7ca8230ec63cf..300cb4627751c 100644 --- a/grade/classes/external/get_groups_for_search_widget.php +++ b/grade/classes/external/get_groups_for_search_widget.php @@ -63,12 +63,8 @@ public static function execute_parameters(): external_function_parameters { * @param int $courseid * @param string $actionbaseurl The base URL for the group action. * @return array Groups and warnings to pass back to the calling widget. - * @throws coding_exception - * @throws invalid_parameter_exception - * @throws moodle_exception - * @throws restricted_context_exception */ - protected static function execute(int $courseid, string $actionbaseurl): array { + public static function execute(int $courseid, string $actionbaseurl): array { global $DB, $USER, $COURSE; $params = self::validate_parameters( From d25c0c5448d4f6d150c2c3467766b1a652a30d8f Mon Sep 17 00:00:00 2001 From: Jun Pataleta Date: Thu, 3 Aug 2023 11:08:40 +0800 Subject: [PATCH 34/61] MDL-78498 mod_forum: Prevent race condition in forum recent activity * Using the generator to create the forum discussions, the timecreated of a discussion can be set before the user's last course access which will prevent forum_print_recent_activity() from including this generated discussion in the recent activity results. To work around this, generate the forum discussion 1 second after the current time to make sure that the user's last course access will always be before the discussion's creation time. * This patch also includes some optimisations by: - Removing the unnecessary @javascript tag for the `Time limit expires` scenario - Bringing the discussion generation to each scenario to allow each scenario to customise the data passed to the generator like for the `Time limit expires` scenario. - Navigating directly to the forum instance. --- .../tests/behat/edit_post_student.feature | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/mod/forum/tests/behat/edit_post_student.feature b/mod/forum/tests/behat/edit_post_student.feature index af0cdddb1e9f7..0a92b9f03186c 100644 --- a/mod/forum/tests/behat/edit_post_student.feature +++ b/mod/forum/tests/behat/edit_post_student.feature @@ -17,14 +17,13 @@ Feature: Students can edit or delete their forum posts within a set time limit And the following "activities" exist: | activity | name | course | idnumber | | forum | Test forum name | C1 | forum | - And I am on the "Course 1" course page logged in as student1 - And the following "mod_forum > discussions" exist: - | user | forum | name | message | - | student1 | forum | Forum post subject | This is the body | - And I am on the "Test forum name" "forum activity" page Scenario: Edit forum post - Given I follow "Forum post subject" + Given the following "mod_forum > discussions" exist: + | user | forum | name | message | + | student1 | forum | Forum post subject | This is the body | + And I am on the "Test forum name" "forum activity" page logged in as "student1" + And I follow "Forum post subject" And I follow "Edit" When I set the following fields to these values: | Subject | Edited post subject | @@ -35,18 +34,25 @@ Feature: Students can edit or delete their forum posts within a set time limit And I should see "Edited post body" Scenario: Delete forum post - Given I follow "Forum post subject" + Given the following "mod_forum > discussions" exist: + | user | forum | name | message | + | student1 | forum | Forum post subject | This is the body | + And I am on the "Test forum name" "forum activity" page logged in as "student1" + And I follow "Forum post subject" When I follow "Delete" And I press "Continue" Then I should not see "Forum post subject" - @javascript @block_recent_activity + @block_recent_activity Scenario: Time limit expires Given the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | recent_activity | Course | C1 | course-view-* | side-pre | And the following config values are set as admin: | maxeditingtime | 1 | + And the following "mod_forum > discussions" exist: + | user | forum | name | message | timemodified | + | student1 | forum | Forum post subject | This is the body | ##now +1 second## | And I am on the "Course 1" course page logged in as student1 And I should see "New forum posts:" in the "Recent activity" "block" And I should see "Forum post subject" in the "Recent activity" "block" From f8691f2e281b6df4368000f88f2e900afb9aaf25 Mon Sep 17 00:00:00 2001 From: meirzamoodle Date: Wed, 2 Aug 2023 12:46:53 +0700 Subject: [PATCH 35/61] MDL-77817 editor_tiny: Fix menu and dialogue window position MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The boost theme makes the TinyMCE editor rendered in a scrollable container, scrolling the editor’s container will cause TinyMCE UI elements to be detached from the anchor. Therefore, to keep the tinyMCE menu in the correct position, adjustments must be made on the page drawers style. When using TinyMCE editor in Safari browsers, a problem may occur where the dialogue windows (such as modal dialogs) overlap with page drawers due to a specific behavior in Safari's rendering. This function addresses the issue by adjusting the CSS overflow property of the page drawers, ensuring they do not obscure the dialogue windows. --- lib/editor/tiny/amd/build/editor.min.js | 2 +- lib/editor/tiny/amd/build/editor.min.js.map | 2 +- lib/editor/tiny/amd/src/editor.js | 66 +++++++++++++++++++++ 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/lib/editor/tiny/amd/build/editor.min.js b/lib/editor/tiny/amd/build/editor.min.js index f08b2b15e8e65..ef48a86e97449 100644 --- a/lib/editor/tiny/amd/build/editor.min.js +++ b/lib/editor/tiny/amd/build/editor.min.js @@ -1,3 +1,3 @@ -define("editor_tiny/editor",["exports","jquery","core/pending","./defaults","./loader","./options","./utils"],(function(_exports,_jquery,_pending,_defaults,_loader,Options,_utils){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.setupForTarget=_exports.setupForElementId=_exports.getInstanceForElementId=_exports.getInstanceForElement=_exports.getAllInstances=_exports.configureDefaultEditor=void 0,_jquery=_interopRequireDefault(_jquery),_pending=_interopRequireDefault(_pending),Options=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(Options);var _systemImportTransformerGlobalIdentifier="undefined"!=typeof window?window:"undefined"!=typeof self?self:"undefined"!=typeof global?global:{};function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}const instanceMap=new Map;let defaultOptions={};const importPluginList=async pluginList=>{const pluginHandlers=await Promise.all(pluginList.map((pluginPath=>-1===pluginPath.indexOf("/")?Promise.resolve(pluginPath):"function"==typeof _systemImportTransformerGlobalIdentifier.define&&_systemImportTransformerGlobalIdentifier.define.amd?new Promise((function(resolve,reject){_systemImportTransformerGlobalIdentifier.require([pluginPath],resolve,reject)})):"undefined"!=typeof module&&module.exports&&"undefined"!=typeof require||"undefined"!=typeof module&&module.component&&_systemImportTransformerGlobalIdentifier.require&&"component"===_systemImportTransformerGlobalIdentifier.require.loader?Promise.resolve(require(pluginPath)):Promise.resolve(_systemImportTransformerGlobalIdentifier[pluginPath])))),pluginNames=pluginHandlers.map((pluginConfig=>"string"==typeof pluginConfig?pluginConfig:Array.isArray(pluginConfig)?pluginConfig[0]:null)).filter((value=>value));return{pluginNames:pluginNames,pluginConfig:pluginHandlers.map((pluginConfig=>Array.isArray(pluginConfig)?pluginConfig[1]:null)).filter((value=>value))}};_exports.getAllInstances=()=>new Map(instanceMap.entries());_exports.getInstanceForElementId=elementId=>getInstanceForElement(document.getElementById(elementId));const getInstanceForElement=element=>{const instance=instanceMap.get(element);if(!instance||!instance.removed)return instance;instanceMap.remove(element)};_exports.getInstanceForElement=getInstanceForElement;_exports.setupForElementId=_ref=>{let{elementId:elementId,options:options}=_ref;const target=document.getElementById(elementId);return setupForTarget(target,options)};(async()=>{const lang=document.querySelector("html").lang,[tinyMCE,langData]=await Promise.all([(0,_loader.getTinyMCE)(),(language=lang,fetch("".concat(M.cfg.wwwroot,"/lib/editor/tiny/lang.php/").concat(M.cfg.langrev,"/").concat(language)).then((response=>response.json())))]);var language;tinyMCE.addI18n(lang,langData)})();const getPlugins=function(){let{plugins:plugins=null}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return plugins||(defaultOptions.plugins?defaultOptions.plugins:{})},getStandardConfig=(target,tinyMCE,options,plugins)=>{const lang=document.querySelector("html").lang,config=Object.assign({},(0,_defaults.getDefaultConfiguration)(),{base_url:_loader.baseUrl,target:target,min_height:175,height:target.clientHeight||"calc(".concat(target.rows," * ").concat(window.getComputedStyle(target).lineHeight||"22px",")"),language:lang,content_css:[options.css],convert_urls:!1,a11y_advanced_options:!0,extended_valid_elements:"script[*]",quickbars_insert_toolbar:"",block_formats:"Paragraph=p; Heading 3=h3; Heading 4=h4; Heading 5=h5; Heading 6=h6;",plugins:[...plugins],skin:"oxide",promotion:!1,branding:options.branding,table_header_type:"sectionCells",setup:editor=>{Options.register(editor,options),editor.on("PreInit",(function(){this.contentWindow=this.iframeElement.contentWindow})),editor.on("init",(function(){(0,_utils.removeSubmenuItem)(editor,"align","tiny:justify")})),editor.on("PostRender",(function(){options.nestedmenu&&(editor=>{const container=editor.getContainer(),menuContainer=document.querySelector("body > .tox.tox-tinymce-aux");container.parentNode.appendChild(menuContainer)})(editor)}))}});return config.toolbar=(0,_utils.addToolbarSection)(config.toolbar,"content","formatting",!0),config.toolbar=(0,_utils.addToolbarButton)(config.toolbar,"content","link"),config.toolbar=(0,_utils.addToolbarSection)(config.toolbar,"directionality","alignment",!0),config.toolbar=(0,_utils.addToolbarButtons)(config.toolbar,"directionality",["ltr","rtl"]),config.toolbar=(0,_utils.removeToolbarButton)(config.toolbar,"alignment","alignjustify"),config},getEditorConfiguration=(target,tinyMCE,options,pluginValues)=>{const{pluginNames:pluginNames,pluginConfig:pluginConfig}=pluginValues,instanceConfig=getStandardConfig(target,0,options,pluginNames);return instanceConfig.menu.file&&(instanceConfig.menu.file.items=""),instanceConfig.menu.format&&(instanceConfig.menu.format.items=instanceConfig.menu.format.items.replace(/forecolor ?/,"").replace(/backcolor ?/,"").replace(/fontfamily ?/,"").replace(/fontsize ?/,"").replace(/styles ?/,"").replaceAll(/\| *\|/g,"|")),instanceConfig.quickbars_selection_toolbar=instanceConfig.quickbars_selection_toolbar.replace("h2 h3","h3 h4 h5 h6"),pluginConfig.filter((pluginConfig=>"function"==typeof pluginConfig.configure)).forEach((pluginConfig=>{const pluginInstanceOverride=pluginConfig.configure(instanceConfig,options);Object.assign(instanceConfig,pluginInstanceOverride)})),Object.assign(instanceConfig,Options.getInitialPluginConfiguration(options)),instanceConfig},setupForTarget=async function(target){let options=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const instance=getInstanceForElement(target);if(instance)return Promise.resolve(instance);const pendingPromise=new _pending.default("editor_tiny/editor:setupForTarget"),plugins=getPlugins(options),[tinyMCE,pluginValues]=await Promise.all([(0,_loader.getTinyMCE)(),importPluginList(Object.keys(plugins))]);tinyMCE.get().filter((editor=>!editor.getElement().isConnected)).forEach((editor=>{editor.remove()}));const existingEditor=tinyMCE.EditorManager.get(target.id);if(existingEditor){if(existingEditor.getElement()===target)return pendingPromise.resolve(),Promise.resolve(existingEditor);throw pendingPromise.resolve(),new Error("TinyMCE instance already exists for different target with same ID")}const instanceConfig=getEditorConfiguration(target,0,options,pluginValues),[editor]=await tinyMCE.init(instanceConfig);return instanceMap.set(target,editor),editor.on("remove",(_ref2=>{let{target:target}=_ref2;instanceMap.delete(target.targetElm)})),target.form&&(0,_jquery.default)(target.form).on("submit",(()=>{editor.save()})),editor.on("blur",(()=>{editor.save()})),pendingPromise.resolve(),editor};_exports.setupForTarget=setupForTarget;_exports.configureDefaultEditor=function(){let options=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};defaultOptions=options}})); +define("editor_tiny/editor",["exports","jquery","core/pending","./defaults","./loader","./options","./utils"],(function(_exports,_jquery,_pending,_defaults,_loader,Options,_utils){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.setupForTarget=_exports.setupForElementId=_exports.getInstanceForElementId=_exports.getInstanceForElement=_exports.getAllInstances=_exports.configureDefaultEditor=void 0,_jquery=_interopRequireDefault(_jquery),_pending=_interopRequireDefault(_pending),Options=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(Options);var _systemImportTransformerGlobalIdentifier="undefined"!=typeof window?window:"undefined"!=typeof self?self:"undefined"!=typeof global?global:{};function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}const instanceMap=new Map;let defaultOptions={};const importPluginList=async pluginList=>{const pluginHandlers=await Promise.all(pluginList.map((pluginPath=>-1===pluginPath.indexOf("/")?Promise.resolve(pluginPath):"function"==typeof _systemImportTransformerGlobalIdentifier.define&&_systemImportTransformerGlobalIdentifier.define.amd?new Promise((function(resolve,reject){_systemImportTransformerGlobalIdentifier.require([pluginPath],resolve,reject)})):"undefined"!=typeof module&&module.exports&&"undefined"!=typeof require||"undefined"!=typeof module&&module.component&&_systemImportTransformerGlobalIdentifier.require&&"component"===_systemImportTransformerGlobalIdentifier.require.loader?Promise.resolve(require(pluginPath)):Promise.resolve(_systemImportTransformerGlobalIdentifier[pluginPath])))),pluginNames=pluginHandlers.map((pluginConfig=>"string"==typeof pluginConfig?pluginConfig:Array.isArray(pluginConfig)?pluginConfig[0]:null)).filter((value=>value));return{pluginNames:pluginNames,pluginConfig:pluginHandlers.map((pluginConfig=>Array.isArray(pluginConfig)?pluginConfig[1]:null)).filter((value=>value))}};_exports.getAllInstances=()=>new Map(instanceMap.entries());_exports.getInstanceForElementId=elementId=>getInstanceForElement(document.getElementById(elementId));const getInstanceForElement=element=>{const instance=instanceMap.get(element);if(!instance||!instance.removed)return instance;instanceMap.remove(element)};_exports.getInstanceForElement=getInstanceForElement;_exports.setupForElementId=_ref=>{let{elementId:elementId,options:options}=_ref;const target=document.getElementById(elementId);return setupForTarget(target,options)};(async()=>{const lang=document.querySelector("html").lang,[tinyMCE,langData]=await Promise.all([(0,_loader.getTinyMCE)(),(language=lang,fetch("".concat(M.cfg.wwwroot,"/lib/editor/tiny/lang.php/").concat(M.cfg.langrev,"/").concat(language)).then((response=>response.json())))]);var language;tinyMCE.addI18n(lang,langData)})();const getPlugins=function(){let{plugins:plugins=null}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return plugins||(defaultOptions.plugins?defaultOptions.plugins:{})},getStandardConfig=(target,tinyMCE,options,plugins)=>{const lang=document.querySelector("html").lang,config=Object.assign({},(0,_defaults.getDefaultConfiguration)(),{base_url:_loader.baseUrl,target:target,min_height:175,height:target.clientHeight||"calc(".concat(target.rows," * ").concat(window.getComputedStyle(target).lineHeight||"22px",")"),language:lang,content_css:[options.css],convert_urls:!1,a11y_advanced_options:!0,extended_valid_elements:"script[*]",quickbars_insert_toolbar:"",block_formats:"Paragraph=p; Heading 3=h3; Heading 4=h4; Heading 5=h5; Heading 6=h6;",plugins:[...plugins],skin:"oxide",promotion:!1,branding:options.branding,table_header_type:"sectionCells",setup:editor=>{Options.register(editor,options),editor.on("PreInit",(function(){this.contentWindow=this.iframeElement.contentWindow})),editor.on("init",(function(){(0,_utils.removeSubmenuItem)(editor,"align","tiny:justify")})),editor.on("PostRender",(function(){options.nestedmenu&&(editor=>{const container=editor.getContainer(),menuContainer=document.querySelector("body > .tox.tox-tinymce-aux");container.parentNode.appendChild(menuContainer)})(editor)})),editor.on("FullscreenStateChanged",(function(e){(params=>{params.open&&(document.querySelector(".tox-fullscreen").style.overflow="unset");const pageWithDrawers=document.querySelector("#page.drawers");pageWithDrawers&&(pageWithDrawers.style.overflow=params.open?"unset":"")})({open:e.state})})),editor.on("OpenWindow CloseWindow",(function(e){(params=>{if(params.browser.isSafari()&&!params.fsplugin.isFullscreen()){const pageWithDrawers=document.querySelector("#page.drawers");pageWithDrawers&&(pageWithDrawers.style.overflow=params.open?"unset":"")}})({browser:tinyMCE.Env.browser,fsplugin:editor.plugins.fullscreen,open:"openwindow"==e.type})}))}});return config.toolbar=(0,_utils.addToolbarSection)(config.toolbar,"content","formatting",!0),config.toolbar=(0,_utils.addToolbarButton)(config.toolbar,"content","link"),config.toolbar=(0,_utils.addToolbarSection)(config.toolbar,"directionality","alignment",!0),config.toolbar=(0,_utils.addToolbarButtons)(config.toolbar,"directionality",["ltr","rtl"]),config.toolbar=(0,_utils.removeToolbarButton)(config.toolbar,"alignment","alignjustify"),config},getEditorConfiguration=(target,tinyMCE,options,pluginValues)=>{const{pluginNames:pluginNames,pluginConfig:pluginConfig}=pluginValues,instanceConfig=getStandardConfig(target,tinyMCE,options,pluginNames);return instanceConfig.menu.file&&(instanceConfig.menu.file.items=""),instanceConfig.menu.format&&(instanceConfig.menu.format.items=instanceConfig.menu.format.items.replace(/forecolor ?/,"").replace(/backcolor ?/,"").replace(/fontfamily ?/,"").replace(/fontsize ?/,"").replace(/styles ?/,"").replaceAll(/\| *\|/g,"|")),instanceConfig.quickbars_selection_toolbar=instanceConfig.quickbars_selection_toolbar.replace("h2 h3","h3 h4 h5 h6"),pluginConfig.filter((pluginConfig=>"function"==typeof pluginConfig.configure)).forEach((pluginConfig=>{const pluginInstanceOverride=pluginConfig.configure(instanceConfig,options);Object.assign(instanceConfig,pluginInstanceOverride)})),Object.assign(instanceConfig,Options.getInitialPluginConfiguration(options)),instanceConfig},setupForTarget=async function(target){let options=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const instance=getInstanceForElement(target);if(instance)return Promise.resolve(instance);const pendingPromise=new _pending.default("editor_tiny/editor:setupForTarget"),plugins=getPlugins(options),[tinyMCE,pluginValues]=await Promise.all([(0,_loader.getTinyMCE)(),importPluginList(Object.keys(plugins))]);tinyMCE.get().filter((editor=>!editor.getElement().isConnected)).forEach((editor=>{editor.remove()}));const existingEditor=tinyMCE.EditorManager.get(target.id);if(existingEditor){if(existingEditor.getElement()===target)return pendingPromise.resolve(),Promise.resolve(existingEditor);throw pendingPromise.resolve(),new Error("TinyMCE instance already exists for different target with same ID")}const instanceConfig=getEditorConfiguration(target,tinyMCE,options,pluginValues),[editor]=await tinyMCE.init(instanceConfig);return instanceMap.set(target,editor),editor.on("remove",(_ref2=>{let{target:target}=_ref2;instanceMap.delete(target.targetElm)})),target.form&&(0,_jquery.default)(target.form).on("submit",(()=>{editor.save()})),editor.on("blur",(()=>{editor.save()})),pendingPromise.resolve(),editor};_exports.setupForTarget=setupForTarget;_exports.configureDefaultEditor=function(){let options=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};defaultOptions=options}})); //# sourceMappingURL=editor.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/amd/build/editor.min.js.map b/lib/editor/tiny/amd/build/editor.min.js.map index 477fd52d43015..498a1913c5c6e 100644 --- a/lib/editor/tiny/amd/build/editor.min.js.map +++ b/lib/editor/tiny/amd/build/editor.min.js.map @@ -1 +1 @@ -{"version":3,"file":"editor.min.js","sources":["../src/editor.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 * TinyMCE Editor Manager.\n *\n * @module editor_tiny/editor\n * @copyright 2022 Andrew Lyons \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport jQuery from 'jquery';\nimport Pending from 'core/pending';\nimport {getDefaultConfiguration} from './defaults';\nimport {getTinyMCE, baseUrl} from './loader';\nimport * as Options from './options';\nimport {addToolbarButton, addToolbarButtons, addToolbarSection,\n removeToolbarButton, removeSubmenuItem} from './utils';\n\n/**\n * Storage for the TinyMCE instances on the page.\n * @type {Map}\n */\nconst instanceMap = new Map();\n\n/**\n * The default editor configuration.\n * @type {Object}\n */\nlet defaultOptions = {};\n\n/**\n * Require the modules for the named set of TinyMCE plugins.\n *\n * @param {string[]} pluginList The list of plugins\n * @return {Promise[]} A matching set of Promises relating to the requested plugins\n */\nconst importPluginList = async(pluginList) => {\n // Fetch all of the plugins from the list of plugins.\n // If a plugin contains a '/' then it is assumed to be a Moodle AMD module to import.\n const pluginHandlers = await Promise.all(pluginList.map(pluginPath => {\n if (pluginPath.indexOf('/') === -1) {\n // A standard TinyMCE Plugin.\n return Promise.resolve(pluginPath);\n }\n\n return import(pluginPath);\n }));\n\n // Normalise the plugin data to a list of plugin names.\n // Two formats are supported:\n // - a string; and\n // - an array whose first element is the plugin name, and the second element is the plugin configuration.\n const pluginNames = pluginHandlers.map((pluginConfig) => {\n if (typeof pluginConfig === 'string') {\n return pluginConfig;\n }\n if (Array.isArray(pluginConfig)) {\n return pluginConfig[0];\n }\n return null;\n }).filter((value) => value);\n\n // Fetch the list of pluginConfig handlers.\n const pluginConfig = pluginHandlers.map((pluginConfig) => {\n if (Array.isArray(pluginConfig)) {\n return pluginConfig[1];\n }\n return null;\n }).filter((value) => value);\n\n return {\n pluginNames,\n pluginConfig,\n };\n};\n\n/**\n * Fetch the language data for the specified language.\n *\n * @param {string} language The language identifier\n * @returns {object}\n */\nconst fetchLanguage = (language) => fetch(\n `${M.cfg.wwwroot}/lib/editor/tiny/lang.php/${M.cfg.langrev}/${language}`\n).then(response => response.json());\n\n/**\n * Get a list of all Editors in a Map, keyed by the DOM Node that the Editor is associated with.\n *\n * @returns {Map}\n */\nexport const getAllInstances = () => new Map(instanceMap.entries());\n\n/**\n * Get the TinyMCE instance for the specified Node ID.\n *\n * @param {string} elementId\n * @returns {TinyMCE|undefined}\n */\nexport const getInstanceForElementId = elementId => getInstanceForElement(document.getElementById(elementId));\n\n/*\n * Get the TinyMCE instance for the specified HTMLElement.\n *\n * @param {HTMLElement} element\n * @returns {TinyMCE|undefined}\n */\nexport const getInstanceForElement = element => {\n const instance = instanceMap.get(element);\n if (instance && instance.removed) {\n instanceMap.remove(element);\n return undefined;\n }\n return instance;\n};\n\n/**\n * Set up TinyMCE for the selector at the specified HTML Node id.\n *\n * @param {object} config The configuration required to setup the editor\n * @param {string} config.elementId The HTML Node ID\n * @param {Object} config.options The editor plugin configuration\n * @return {Promise} The TinyMCE instance\n */\nexport const setupForElementId = ({elementId, options}) => {\n const target = document.getElementById(elementId);\n return setupForTarget(target, options);\n};\n\n/**\n * Initialise the page with standard TinyMCE requirements.\n *\n * Currently this includes the language taken from the HTML lang property.\n */\nconst initialisePage = async() => {\n const lang = document.querySelector('html').lang;\n\n const [tinyMCE, langData] = await Promise.all([getTinyMCE(), fetchLanguage(lang)]);\n tinyMCE.addI18n(lang, langData);\n};\ninitialisePage();\n\n/**\n * Get the list of plugins to load for the specified configuration.\n *\n * If the specified configuration does not include a plugin configuration, then return the default configuration.\n *\n * @param {object} options\n * @param {array} [options.plugins=null] The plugin list\n * @returns {object}\n */\nconst getPlugins = ({plugins = null} = {}) => {\n if (plugins) {\n return plugins;\n }\n\n if (defaultOptions.plugins) {\n return defaultOptions.plugins;\n }\n\n return {};\n};\n\n/**\n * Nest the dropdown menu inside the parent DOM.\n *\n * The TinyMCE menu has a significant issue with the Overflow style,\n * and the Boost theme heavily uses Overflow for drawer navigation.\n * Moving the menu container into the parent editor container makes it work correctly.\n *\n * @param {object} editor\n */\n const nestMenu = (editor) => {\n const container = editor.getContainer();\n const menuContainer = document.querySelector('body > .tox.tox-tinymce-aux');\n container.parentNode.appendChild(menuContainer);\n};\n\n/**\n * Get the standard configuration for the specified options.\n *\n * @param {Node} target\n * @param {tinyMCE} tinyMCE\n * @param {object} options\n * @param {Array} plugins\n * @returns {object}\n */\nconst getStandardConfig = (target, tinyMCE, options, plugins) => {\n const lang = document.querySelector('html').lang;\n\n const config = Object.assign({}, getDefaultConfiguration(), {\n // eslint-disable-next-line camelcase\n base_url: baseUrl,\n\n // Set the editor target.\n // https://www.tiny.cloud/docs/tinymce/6/editor-important-options/#target\n target,\n\n // https://www.tiny.cloud/docs/tinymce/6/customize-ui/#set-maximum-and-minimum-heights-and-widths\n // Set the minimum height to the smallest height that we can fit the Menu bar, Tool bar, Status bar and the text area.\n // eslint-disable-next-line camelcase\n min_height: 175,\n\n // Base the height on the size of the text area. Account for lack of height value when the editor is initially hidden,\n // in which case use CSS calc() to approximate the same based on number of rows and target line height.\n height: target.clientHeight || `calc(${target.rows} * ${window.getComputedStyle(target).lineHeight || '22px'})`,\n\n // Set the language.\n // https://www.tiny.cloud/docs/tinymce/6/ui-localization/#language\n // eslint-disable-next-line camelcase\n language: lang,\n\n // Load the editor stylesheet into the editor iframe.\n // https://www.tiny.cloud/docs/tinymce/6/add-css-options/\n // eslint-disable-next-line camelcase\n content_css: [\n options.css,\n ],\n\n // Do not convert URLs to relative URLs.\n // https://www.tiny.cloud/docs/tinymce/6/url-handling/#convert_urls\n // eslint-disable-next-line camelcase\n convert_urls: false,\n\n // Enabled 'advanced' a11y options.\n // This includes allowing role=\"presentation\" from the image uploader.\n // https://www.tiny.cloud/docs/tinymce/6/accessibility/\n // eslint-disable-next-line camelcase\n a11y_advanced_options: true,\n\n extended_valid_elements: 'script[*]',\n\n // Disable quickbars entirely.\n // The UI is not ideal and we'll wait for it to improve in future before we enable it in Moodle.\n // eslint-disable-next-line camelcase\n quickbars_insert_toolbar: '',\n\n // Override the standard block formats property (removing h1 & h2).\n // https://www.tiny.cloud/docs/tinymce/6/user-formatting-options/#block_formats\n // eslint-disable-next-line camelcase\n block_formats: 'Paragraph=p; Heading 3=h3; Heading 4=h4; Heading 5=h5; Heading 6=h6;',\n\n // The list of plugins to include in the instance.\n // https://www.tiny.cloud/docs/tinymce/6/editor-important-options/#plugins\n plugins: [\n ...plugins,\n ],\n\n // Skins\n skin: 'oxide',\n\n // Remove the \"Upgrade\" link for Tiny.\n // https://www.tiny.cloud/docs/tinymce/6/editor-premium-upgrade-promotion/\n promotion: false,\n\n // Allow the administrator to disable branding.\n // https://www.tiny.cloud/docs/tinymce/6/statusbar-configuration-options/#branding\n branding: options.branding,\n\n // Put th cells in a thead element.\n // https://www.tiny.cloud/docs/tinymce/6/table-options/#table_header_type\n // eslint-disable-next-line camelcase\n table_header_type: 'sectionCells',\n\n setup: (editor) => {\n Options.register(editor, options);\n\n editor.on('PreInit', function() {\n // Work around a bug in TinyMCE with Firefox.\n // When an editor is removed, and replaced with an identically attributed editor (same ID),\n // and the Firefox window is freshly opened (e.g. Behat, Private browsing), the wrong contentWindow\n // is assigned to the editor instance leading to an NS_ERROR_UNEXPECTED error in Firefox.\n // This is a workaround for that issue.\n this.contentWindow = this.iframeElement.contentWindow;\n });\n editor.on('init', function() {\n // Hide justify alignment sub-menu.\n removeSubmenuItem(editor, 'align', 'tiny:justify');\n });\n\n editor.on('PostRender', function() {\n // Nest menu if set.\n if (options.nestedmenu) {\n nestMenu(editor);\n }\n });\n },\n });\n\n config.toolbar = addToolbarSection(config.toolbar, 'content', 'formatting', true);\n config.toolbar = addToolbarButton(config.toolbar, 'content', 'link');\n\n // Add directionality plugins, always.\n config.toolbar = addToolbarSection(config.toolbar, 'directionality', 'alignment', true);\n config.toolbar = addToolbarButtons(config.toolbar, 'directionality', ['ltr', 'rtl']);\n\n // Remove the align justify button from the toolbar.\n config.toolbar = removeToolbarButton(config.toolbar, 'alignment', 'alignjustify');\n\n return config;\n};\n\n/**\n * Fetch the TinyMCE configuration for this editor instance.\n *\n * @param {HTMLElement} target\n * @param {TinyMCE} tinyMCE The TinyMCE API\n * @param {Object} options The editor plugin configuration\n * @param {object} pluginValues\n * @param {object} pluginValues.pluginConfig The list of plugin configuration\n * @param {object} pluginValues.pluginNames The list of plugins to load\n * @returns {object} The TinyMCE Configuration\n */\nconst getEditorConfiguration = (target, tinyMCE, options, pluginValues) => {\n const {\n pluginNames,\n pluginConfig,\n } = pluginValues;\n\n // Allow plugins to modify the configuration.\n // This seems a little strange, but we must double-process the config slightly.\n\n // First we fetch the standard configuration.\n const instanceConfig = getStandardConfig(target, tinyMCE, options, pluginNames);\n\n // Next we make any standard changes.\n // Here we remove the file menu, as it doesn't offer any useful functionality.\n // We only empty the items list so that a plugin may choose to add to it themselves later if they wish.\n if (instanceConfig.menu.file) {\n instanceConfig.menu.file.items = '';\n }\n\n // We disable the styles, backcolor, and forecolor plugins from the format menu.\n // These are not useful for Moodle and we don't want to encourage their use.\n if (instanceConfig.menu.format) {\n instanceConfig.menu.format.items = instanceConfig.menu.format.items\n // Remove forecolor and backcolor.\n .replace(/forecolor ?/, '')\n .replace(/backcolor ?/, '')\n\n // Remove fontfamily for now.\n .replace(/fontfamily ?/, '')\n\n // Remove fontsize for now.\n .replace(/fontsize ?/, '')\n\n // Remove styles - it just duplicates the format menu in a way which does not respect configuration\n .replace(/styles ?/, '')\n\n // Remove any duplicate separators.\n .replaceAll(/\\| *\\|/g, '|');\n }\n\n // eslint-disable-next-line camelcase\n instanceConfig.quickbars_selection_toolbar = instanceConfig.quickbars_selection_toolbar.replace('h2 h3', 'h3 h4 h5 h6');\n\n // Next we call the `configure` function for any plugin which defines it.\n // We pass the current instanceConfig in here, to allow them to make certain changes to the global configuration.\n // For example, to add themselves to any menu, toolbar, and so on.\n // Any plugin which wishes to have configuration options must register those options here.\n pluginConfig.filter((pluginConfig) => typeof pluginConfig.configure === 'function').forEach((pluginConfig) => {\n const pluginInstanceOverride = pluginConfig.configure(instanceConfig, options);\n Object.assign(instanceConfig, pluginInstanceOverride);\n });\n\n // Next we convert the plugin configuration into a format that TinyMCE understands.\n Object.assign(instanceConfig, Options.getInitialPluginConfiguration(options));\n\n return instanceConfig;\n};\n\n/**\n * Set up TinyMCE for the HTML Element.\n *\n * @param {HTMLElement} target\n * @param {Object} [options={}] The editor plugin configuration\n * @return {Promise} The TinyMCE instance\n */\nexport const setupForTarget = async(target, options = {}) => {\n const instance = getInstanceForElement(target);\n if (instance) {\n return Promise.resolve(instance);\n }\n\n // Register a new pending promise to ensure that Behat waits for the editor setup to complete before continuing.\n const pendingPromise = new Pending('editor_tiny/editor:setupForTarget');\n\n // Get the list of plugins.\n const plugins = getPlugins(options);\n\n // Fetch the tinyMCE API, and instantiate the plugins.\n const [tinyMCE, pluginValues] = await Promise.all([\n getTinyMCE(),\n importPluginList(Object.keys(plugins)),\n ]);\n\n // TinyMCE uses the element ID as a map key internally, even if the target has changed.\n // In the case where we have an editor in a modal form which has been detached from the DOM, but the editor not removed,\n // we need to manually destroy the editor.\n // We could theoretically do this with a Mutation Observer, but in some cases the Node may be moved,\n // or added back elsewhere in the DOM.\n\n // First remove any detached editors.\n tinyMCE.get().filter((editor) => !editor.getElement().isConnected).forEach((editor) => {\n editor.remove();\n });\n\n // Now check for any existing editor which shares the same ID.\n const existingEditor = tinyMCE.EditorManager.get(target.id);\n if (existingEditor) {\n if (existingEditor.getElement() === target) {\n pendingPromise.resolve();\n return Promise.resolve(existingEditor);\n } else {\n pendingPromise.resolve();\n throw new Error('TinyMCE instance already exists for different target with same ID');\n }\n }\n\n // Get the editor configuration for this editor.\n const instanceConfig = getEditorConfiguration(target, tinyMCE, options, pluginValues);\n\n // Initialise the editor instance for the given configuration.\n // At this point any plugin which has configuration options registered will have them applied for this instance.\n const [editor] = await tinyMCE.init(instanceConfig);\n\n // Store the editor instance in the instanceMap and register a listener on removal to remove it from the map.\n instanceMap.set(target, editor);\n editor.on('remove', ({target}) => {\n // Handle removal of the editor from the map on destruction.\n instanceMap.delete(target.targetElm);\n });\n\n // If the editor is part of a form, also listen to the jQuery submit event.\n // The jQuery submit event will not trigger the native submit event, and therefore the content will not be saved.\n // We cannot rely on listening to the bubbled submit event on the document because other events on child nodes may\n // consume the data before it is saved.\n if (target.form) {\n jQuery(target.form).on('submit', () => {\n editor.save();\n });\n }\n\n // Save the editor content to the textarea when the editor is blurred.\n editor.on('blur', () => {\n editor.save();\n });\n\n pendingPromise.resolve();\n return editor;\n};\n\n/**\n * Set the default editor configuration.\n *\n * This configuration is used when an editor is initialised without any configuration.\n *\n * @param {object} [options={}]\n */\nexport const configureDefaultEditor = (options = {}) => {\n defaultOptions = options;\n};\n"],"names":["instanceMap","Map","defaultOptions","importPluginList","async","pluginHandlers","Promise","all","pluginList","map","pluginPath","indexOf","resolve","pluginNames","pluginConfig","Array","isArray","filter","value","entries","elementId","getInstanceForElement","document","getElementById","element","instance","get","removed","remove","_ref","options","target","setupForTarget","lang","querySelector","tinyMCE","langData","language","fetch","M","cfg","wwwroot","langrev","then","response","json","addI18n","initialisePage","getPlugins","plugins","getStandardConfig","config","Object","assign","base_url","baseUrl","min_height","height","clientHeight","rows","window","getComputedStyle","lineHeight","content_css","css","convert_urls","a11y_advanced_options","extended_valid_elements","quickbars_insert_toolbar","block_formats","skin","promotion","branding","table_header_type","setup","editor","Options","register","on","contentWindow","this","iframeElement","nestedmenu","container","getContainer","menuContainer","parentNode","appendChild","nestMenu","toolbar","getEditorConfiguration","pluginValues","instanceConfig","menu","file","items","format","replace","replaceAll","quickbars_selection_toolbar","configure","forEach","pluginInstanceOverride","getInitialPluginConfiguration","pendingPromise","Pending","keys","getElement","isConnected","existingEditor","EditorManager","id","Error","init","set","_ref2","delete","targetElm","form","save"],"mappings":"4oDAmCMA,YAAc,IAAIC,QAMpBC,eAAiB,SAQfC,iBAAmBC,MAAAA,mBAGfC,qBAAuBC,QAAQC,IAAIC,WAAWC,KAAIC,aACnB,IAA7BA,WAAWC,QAAQ,KAEZL,QAAQM,QAAQF,4NAGbA,4WAAAA,gBAOZG,YAAcR,eAAeI,KAAKK,cACR,iBAAjBA,aACAA,aAEPC,MAAMC,QAAQF,cACPA,aAAa,GAEjB,OACRG,QAAQC,OAAUA,cAUd,CACHL,YAAAA,YACAC,aATiBT,eAAeI,KAAKK,cACjCC,MAAMC,QAAQF,cACPA,aAAa,GAEjB,OACRG,QAAQC,OAAUA,mCAuBM,IAAM,IAAIjB,IAAID,YAAYmB,4CAQlBC,WAAaC,sBAAsBC,SAASC,eAAeH,kBAQrFC,sBAAwBG,gBAC3BC,SAAWzB,YAAY0B,IAAIF,aAC7BC,WAAYA,SAASE,eAIlBF,SAHHzB,YAAY4B,OAAOJ,0FAcMK,WAACT,UAACA,UAADU,QAAYA,oBACpCC,OAAST,SAASC,eAAeH,kBAChCY,eAAeD,OAAQD,UAQX1B,iBACb6B,KAAOX,SAASY,cAAc,QAAQD,MAErCE,QAASC,gBAAkB9B,QAAQC,IAAI,EAAC,yBAvD5B8B,SAuDwDJ,KAvD3CK,gBAC7BC,EAAEC,IAAIC,6CAAoCF,EAAEC,IAAIE,oBAAWL,WAChEM,MAAKC,UAAYA,SAASC,YAFLR,IAAAA,SAwDnBF,QAAQW,QAAQb,KAAMG,WAE1BW,SAWMC,WAAa,eAACC,QAACA,QAAU,6DAAQ,UAC/BA,UAIA/C,eAAe+C,QACR/C,eAAe+C,QAGnB,KA2BLC,kBAAoB,CAACnB,OAAQI,QAASL,QAASmB,iBAC3ChB,KAAOX,SAASY,cAAc,QAAQD,KAEtCkB,OAASC,OAAOC,OAAO,IAAI,uCAA2B,CAExDC,SAAUC,gBAIVxB,OAAAA,OAKAyB,WAAY,IAIZC,OAAQ1B,OAAO2B,6BAAwB3B,OAAO4B,mBAAUC,OAAOC,iBAAiB9B,QAAQ+B,YAAc,YAKtGzB,SAAUJ,KAKV8B,YAAa,CACTjC,QAAQkC,KAMZC,cAAc,EAMdC,uBAAuB,EAEvBC,wBAAyB,YAKzBC,yBAA0B,GAK1BC,cAAe,uEAIfpB,QAAS,IACFA,SAIPqB,KAAM,QAINC,WAAW,EAIXC,SAAU1C,QAAQ0C,SAKlBC,kBAAmB,eAEnBC,MAAQC,SACJC,QAAQC,SAASF,OAAQ7C,SAEzB6C,OAAOG,GAAG,WAAW,gBAMZC,cAAgBC,KAAKC,cAAcF,iBAE5CJ,OAAOG,GAAG,QAAQ,wCAEIH,OAAQ,QAAS,mBAGvCA,OAAOG,GAAG,cAAc,WAEhBhD,QAAQoD,YA9GTP,CAAAA,eACTQ,UAAYR,OAAOS,eACnBC,cAAgB/D,SAASY,cAAc,+BAC7CiD,UAAUG,WAAWC,YAAYF,gBA4GjBG,CAASb,qBAMzBxB,OAAOsC,SAAU,4BAAkBtC,OAAOsC,QAAS,UAAW,cAAc,GAC5EtC,OAAOsC,SAAU,2BAAiBtC,OAAOsC,QAAS,UAAW,QAG7DtC,OAAOsC,SAAU,4BAAkBtC,OAAOsC,QAAS,iBAAkB,aAAa,GAClFtC,OAAOsC,SAAU,4BAAkBtC,OAAOsC,QAAS,iBAAkB,CAAC,MAAO,QAG7EtC,OAAOsC,SAAU,8BAAoBtC,OAAOsC,QAAS,YAAa,gBAE3DtC,QAcLuC,uBAAyB,CAAC3D,OAAQI,QAASL,QAAS6D,sBAChD9E,YACFA,YADEC,aAEFA,cACA6E,aAMEC,eAAiB1C,kBAAkBnB,OAAQI,EAASL,QAASjB,oBAK/D+E,eAAeC,KAAKC,OACpBF,eAAeC,KAAKC,KAAKC,MAAQ,IAKjCH,eAAeC,KAAKG,SACpBJ,eAAeC,KAAKG,OAAOD,MAAQH,eAAeC,KAAKG,OAAOD,MAEzDE,QAAQ,cAAe,IACvBA,QAAQ,cAAe,IAGvBA,QAAQ,eAAgB,IAGxBA,QAAQ,aAAc,IAGtBA,QAAQ,WAAY,IAGpBC,WAAW,UAAW,MAI/BN,eAAeO,4BAA8BP,eAAeO,4BAA4BF,QAAQ,QAAS,eAMzGnF,aAAaG,QAAQH,cAAmD,mBAA3BA,aAAasF,YAA0BC,SAASvF,qBACnFwF,uBAAyBxF,aAAasF,UAAUR,eAAgB9D,SACtEsB,OAAOC,OAAOuC,eAAgBU,2BAIlClD,OAAOC,OAAOuC,eAAgBhB,QAAQ2B,8BAA8BzE,UAE7D8D,gBAUE5D,eAAiB5B,eAAM2B,YAAQD,+DAAU,SAC5CL,SAAWJ,sBAAsBU,WACnCN,gBACOnB,QAAQM,QAAQa,gBAIrB+E,eAAiB,IAAIC,iBAAQ,qCAG7BxD,QAAUD,WAAWlB,UAGpBK,QAASwD,oBAAsBrF,QAAQC,IAAI,EAC9C,wBACAJ,iBAAiBiD,OAAOsD,KAAKzD,YAUjCd,QAAQT,MAAMT,QAAQ0D,SAAYA,OAAOgC,aAAaC,cAAaP,SAAS1B,SACxEA,OAAO/C,kBAILiF,eAAiB1E,QAAQ2E,cAAcpF,IAAIK,OAAOgF,OACpDF,eAAgB,IACZA,eAAeF,eAAiB5E,cAChCyE,eAAe5F,UACRN,QAAQM,QAAQiG,sBAEvBL,eAAe5F,UACT,IAAIoG,MAAM,2EAKlBpB,eAAiBF,uBAAuB3D,OAAQI,EAASL,QAAS6D,eAIjEhB,cAAgBxC,QAAQ8E,KAAKrB,uBAGpC5F,YAAYkH,IAAInF,OAAQ4C,QACxBA,OAAOG,GAAG,UAAUqC,YAACpF,OAACA,cAElB/B,YAAYoH,OAAOrF,OAAOsF,cAO1BtF,OAAOuF,0BACAvF,OAAOuF,MAAMxC,GAAG,UAAU,KAC7BH,OAAO4C,UAKf5C,OAAOG,GAAG,QAAQ,KACdH,OAAO4C,UAGXf,eAAe5F,UACR+D,+EAU2B,eAAC7C,+DAAU,GAC7C5B,eAAiB4B"} \ No newline at end of file +{"version":3,"file":"editor.min.js","sources":["../src/editor.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 * TinyMCE Editor Manager.\n *\n * @module editor_tiny/editor\n * @copyright 2022 Andrew Lyons \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport jQuery from 'jquery';\nimport Pending from 'core/pending';\nimport {getDefaultConfiguration} from './defaults';\nimport {getTinyMCE, baseUrl} from './loader';\nimport * as Options from './options';\nimport {addToolbarButton, addToolbarButtons, addToolbarSection,\n removeToolbarButton, removeSubmenuItem} from './utils';\n\n/**\n * Storage for the TinyMCE instances on the page.\n * @type {Map}\n */\nconst instanceMap = new Map();\n\n/**\n * The default editor configuration.\n * @type {Object}\n */\nlet defaultOptions = {};\n\n/**\n * Require the modules for the named set of TinyMCE plugins.\n *\n * @param {string[]} pluginList The list of plugins\n * @return {Promise[]} A matching set of Promises relating to the requested plugins\n */\nconst importPluginList = async(pluginList) => {\n // Fetch all of the plugins from the list of plugins.\n // If a plugin contains a '/' then it is assumed to be a Moodle AMD module to import.\n const pluginHandlers = await Promise.all(pluginList.map(pluginPath => {\n if (pluginPath.indexOf('/') === -1) {\n // A standard TinyMCE Plugin.\n return Promise.resolve(pluginPath);\n }\n\n return import(pluginPath);\n }));\n\n // Normalise the plugin data to a list of plugin names.\n // Two formats are supported:\n // - a string; and\n // - an array whose first element is the plugin name, and the second element is the plugin configuration.\n const pluginNames = pluginHandlers.map((pluginConfig) => {\n if (typeof pluginConfig === 'string') {\n return pluginConfig;\n }\n if (Array.isArray(pluginConfig)) {\n return pluginConfig[0];\n }\n return null;\n }).filter((value) => value);\n\n // Fetch the list of pluginConfig handlers.\n const pluginConfig = pluginHandlers.map((pluginConfig) => {\n if (Array.isArray(pluginConfig)) {\n return pluginConfig[1];\n }\n return null;\n }).filter((value) => value);\n\n return {\n pluginNames,\n pluginConfig,\n };\n};\n\n/**\n * Fetch the language data for the specified language.\n *\n * @param {string} language The language identifier\n * @returns {object}\n */\nconst fetchLanguage = (language) => fetch(\n `${M.cfg.wwwroot}/lib/editor/tiny/lang.php/${M.cfg.langrev}/${language}`\n).then(response => response.json());\n\n/**\n * Get a list of all Editors in a Map, keyed by the DOM Node that the Editor is associated with.\n *\n * @returns {Map}\n */\nexport const getAllInstances = () => new Map(instanceMap.entries());\n\n/**\n * Get the TinyMCE instance for the specified Node ID.\n *\n * @param {string} elementId\n * @returns {TinyMCE|undefined}\n */\nexport const getInstanceForElementId = elementId => getInstanceForElement(document.getElementById(elementId));\n\n/*\n * Get the TinyMCE instance for the specified HTMLElement.\n *\n * @param {HTMLElement} element\n * @returns {TinyMCE|undefined}\n */\nexport const getInstanceForElement = element => {\n const instance = instanceMap.get(element);\n if (instance && instance.removed) {\n instanceMap.remove(element);\n return undefined;\n }\n return instance;\n};\n\n/**\n * Set up TinyMCE for the selector at the specified HTML Node id.\n *\n * @param {object} config The configuration required to setup the editor\n * @param {string} config.elementId The HTML Node ID\n * @param {Object} config.options The editor plugin configuration\n * @return {Promise} The TinyMCE instance\n */\nexport const setupForElementId = ({elementId, options}) => {\n const target = document.getElementById(elementId);\n return setupForTarget(target, options);\n};\n\n/**\n * Initialise the page with standard TinyMCE requirements.\n *\n * Currently this includes the language taken from the HTML lang property.\n */\nconst initialisePage = async() => {\n const lang = document.querySelector('html').lang;\n\n const [tinyMCE, langData] = await Promise.all([getTinyMCE(), fetchLanguage(lang)]);\n tinyMCE.addI18n(lang, langData);\n};\ninitialisePage();\n\n/**\n * Get the list of plugins to load for the specified configuration.\n *\n * If the specified configuration does not include a plugin configuration, then return the default configuration.\n *\n * @param {object} options\n * @param {array} [options.plugins=null] The plugin list\n * @returns {object}\n */\nconst getPlugins = ({plugins = null} = {}) => {\n if (plugins) {\n return plugins;\n }\n\n if (defaultOptions.plugins) {\n return defaultOptions.plugins;\n }\n\n return {};\n};\n\n/**\n * Nest the dropdown menu inside the parent DOM.\n *\n * The TinyMCE menu has a significant issue with the Overflow style,\n * and the Boost theme heavily uses Overflow for drawer navigation.\n * Moving the menu container into the parent editor container makes it work correctly.\n *\n * @param {object} editor\n */\n const nestMenu = (editor) => {\n const container = editor.getContainer();\n const menuContainer = document.querySelector('body > .tox.tox-tinymce-aux');\n container.parentNode.appendChild(menuContainer);\n};\n\n/**\n * Fix the Tiny menu position if the editor is in fullscreen mode on the Boost theme.\n *\n * The boost theme makes the TinyMCE editor rendered in a scrollable container,\n * scrolling the editor’s container will cause TinyMCE UI elements to be detached from the anchor.\n * Therefore, to keep the tinyMCE menu in the correct position,\n * adjustments must be made on the page drawers style.\n *\n * @param {object} params\n * @param {Boolean} params.open True if editor in fullscreen mode, otherwise false.\n */\nconst fixMenuPositionIfInFullsreen = (params) => {\n if (params.open) {\n // Keep the menu remains visible and properly aligned.\n document.querySelector('.tox-fullscreen').style.overflow = 'unset';\n }\n\n const pageWithDrawers = document.querySelector('#page.drawers');\n if (pageWithDrawers) {\n pageWithDrawers.style.overflow = params.open ? \"unset\" : \"\";\n }\n};\n\n/**\n * Fix the dialogue window positioning issue of TinyMCE editor in Safari browsers.\n *\n * When using TinyMCE editor in Safari browsers, a problem may occur where the dialogue\n * windows (such as modal dialogs) overlap with page drawers due to a specific behavior\n * in Safari's rendering. This function addresses the issue by adjusting the CSS overflow\n * property of the page drawers, ensuring they do not obscure the dialogue windows.\n *\n * @param {object} params\n * @param {object} params.browser Browser environment.\n * @param {object} params.fsplugin Fullscreen plugin.\n * @param {Boolean} params.open True if the dialogue window opens, otherwise false.\n */\nconst fixDialoguePositionIfOpen = (params) => {\n // To avoid modification the existing overflow value that has been set fixMenuPositionIfInFullsreen(),\n // the fix only applied if the editor not in fullscreen mode.\n if (params.browser.isSafari() && !params.fsplugin.isFullscreen()) {\n const pageWithDrawers = document.querySelector('#page.drawers');\n if (pageWithDrawers) {\n pageWithDrawers.style.overflow = params.open ? \"unset\" : \"\";\n }\n }\n};\n\n/**\n * Get the standard configuration for the specified options.\n *\n * @param {Node} target\n * @param {tinyMCE} tinyMCE\n * @param {object} options\n * @param {Array} plugins\n * @returns {object}\n */\nconst getStandardConfig = (target, tinyMCE, options, plugins) => {\n const lang = document.querySelector('html').lang;\n\n const config = Object.assign({}, getDefaultConfiguration(), {\n // eslint-disable-next-line camelcase\n base_url: baseUrl,\n\n // Set the editor target.\n // https://www.tiny.cloud/docs/tinymce/6/editor-important-options/#target\n target,\n\n // https://www.tiny.cloud/docs/tinymce/6/customize-ui/#set-maximum-and-minimum-heights-and-widths\n // Set the minimum height to the smallest height that we can fit the Menu bar, Tool bar, Status bar and the text area.\n // eslint-disable-next-line camelcase\n min_height: 175,\n\n // Base the height on the size of the text area. Account for lack of height value when the editor is initially hidden,\n // in which case use CSS calc() to approximate the same based on number of rows and target line height.\n height: target.clientHeight || `calc(${target.rows} * ${window.getComputedStyle(target).lineHeight || '22px'})`,\n\n // Set the language.\n // https://www.tiny.cloud/docs/tinymce/6/ui-localization/#language\n // eslint-disable-next-line camelcase\n language: lang,\n\n // Load the editor stylesheet into the editor iframe.\n // https://www.tiny.cloud/docs/tinymce/6/add-css-options/\n // eslint-disable-next-line camelcase\n content_css: [\n options.css,\n ],\n\n // Do not convert URLs to relative URLs.\n // https://www.tiny.cloud/docs/tinymce/6/url-handling/#convert_urls\n // eslint-disable-next-line camelcase\n convert_urls: false,\n\n // Enabled 'advanced' a11y options.\n // This includes allowing role=\"presentation\" from the image uploader.\n // https://www.tiny.cloud/docs/tinymce/6/accessibility/\n // eslint-disable-next-line camelcase\n a11y_advanced_options: true,\n\n extended_valid_elements: 'script[*]',\n\n // Disable quickbars entirely.\n // The UI is not ideal and we'll wait for it to improve in future before we enable it in Moodle.\n // eslint-disable-next-line camelcase\n quickbars_insert_toolbar: '',\n\n // Override the standard block formats property (removing h1 & h2).\n // https://www.tiny.cloud/docs/tinymce/6/user-formatting-options/#block_formats\n // eslint-disable-next-line camelcase\n block_formats: 'Paragraph=p; Heading 3=h3; Heading 4=h4; Heading 5=h5; Heading 6=h6;',\n\n // The list of plugins to include in the instance.\n // https://www.tiny.cloud/docs/tinymce/6/editor-important-options/#plugins\n plugins: [\n ...plugins,\n ],\n\n // Skins\n skin: 'oxide',\n\n // Remove the \"Upgrade\" link for Tiny.\n // https://www.tiny.cloud/docs/tinymce/6/editor-premium-upgrade-promotion/\n promotion: false,\n\n // Allow the administrator to disable branding.\n // https://www.tiny.cloud/docs/tinymce/6/statusbar-configuration-options/#branding\n branding: options.branding,\n\n // Put th cells in a thead element.\n // https://www.tiny.cloud/docs/tinymce/6/table-options/#table_header_type\n // eslint-disable-next-line camelcase\n table_header_type: 'sectionCells',\n\n setup: (editor) => {\n Options.register(editor, options);\n\n editor.on('PreInit', function() {\n // Work around a bug in TinyMCE with Firefox.\n // When an editor is removed, and replaced with an identically attributed editor (same ID),\n // and the Firefox window is freshly opened (e.g. Behat, Private browsing), the wrong contentWindow\n // is assigned to the editor instance leading to an NS_ERROR_UNEXPECTED error in Firefox.\n // This is a workaround for that issue.\n this.contentWindow = this.iframeElement.contentWindow;\n });\n editor.on('init', function() {\n // Hide justify alignment sub-menu.\n removeSubmenuItem(editor, 'align', 'tiny:justify');\n });\n\n editor.on('PostRender', function() {\n // Nest menu if set.\n if (options.nestedmenu) {\n nestMenu(editor);\n }\n });\n\n // The Boost and Classic theme cause issues if editor is in fullscreen mode.\n // The problem was resolved by changing the overflow value to related elements.\n editor.on('FullscreenStateChanged', function(e) {\n fixMenuPositionIfInFullsreen({\n open: e.state\n });\n });\n\n // The Boost theme uses Overflow=auto in the course index drawer,\n // it causes the dialogue window to be not correctly displayed in Safari browser.\n // The problem was resolved by changing the overflow value to the drawer.\n editor.on('OpenWindow CloseWindow', function(e) {\n fixDialoguePositionIfOpen({\n browser: tinyMCE.Env.browser,\n fsplugin: editor.plugins.fullscreen,\n open: e.type == \"openwindow\"\n });\n });\n },\n });\n\n config.toolbar = addToolbarSection(config.toolbar, 'content', 'formatting', true);\n config.toolbar = addToolbarButton(config.toolbar, 'content', 'link');\n\n // Add directionality plugins, always.\n config.toolbar = addToolbarSection(config.toolbar, 'directionality', 'alignment', true);\n config.toolbar = addToolbarButtons(config.toolbar, 'directionality', ['ltr', 'rtl']);\n\n // Remove the align justify button from the toolbar.\n config.toolbar = removeToolbarButton(config.toolbar, 'alignment', 'alignjustify');\n\n return config;\n};\n\n/**\n * Fetch the TinyMCE configuration for this editor instance.\n *\n * @param {HTMLElement} target\n * @param {TinyMCE} tinyMCE The TinyMCE API\n * @param {Object} options The editor plugin configuration\n * @param {object} pluginValues\n * @param {object} pluginValues.pluginConfig The list of plugin configuration\n * @param {object} pluginValues.pluginNames The list of plugins to load\n * @returns {object} The TinyMCE Configuration\n */\nconst getEditorConfiguration = (target, tinyMCE, options, pluginValues) => {\n const {\n pluginNames,\n pluginConfig,\n } = pluginValues;\n\n // Allow plugins to modify the configuration.\n // This seems a little strange, but we must double-process the config slightly.\n\n // First we fetch the standard configuration.\n const instanceConfig = getStandardConfig(target, tinyMCE, options, pluginNames);\n\n // Next we make any standard changes.\n // Here we remove the file menu, as it doesn't offer any useful functionality.\n // We only empty the items list so that a plugin may choose to add to it themselves later if they wish.\n if (instanceConfig.menu.file) {\n instanceConfig.menu.file.items = '';\n }\n\n // We disable the styles, backcolor, and forecolor plugins from the format menu.\n // These are not useful for Moodle and we don't want to encourage their use.\n if (instanceConfig.menu.format) {\n instanceConfig.menu.format.items = instanceConfig.menu.format.items\n // Remove forecolor and backcolor.\n .replace(/forecolor ?/, '')\n .replace(/backcolor ?/, '')\n\n // Remove fontfamily for now.\n .replace(/fontfamily ?/, '')\n\n // Remove fontsize for now.\n .replace(/fontsize ?/, '')\n\n // Remove styles - it just duplicates the format menu in a way which does not respect configuration\n .replace(/styles ?/, '')\n\n // Remove any duplicate separators.\n .replaceAll(/\\| *\\|/g, '|');\n }\n\n // eslint-disable-next-line camelcase\n instanceConfig.quickbars_selection_toolbar = instanceConfig.quickbars_selection_toolbar.replace('h2 h3', 'h3 h4 h5 h6');\n\n // Next we call the `configure` function for any plugin which defines it.\n // We pass the current instanceConfig in here, to allow them to make certain changes to the global configuration.\n // For example, to add themselves to any menu, toolbar, and so on.\n // Any plugin which wishes to have configuration options must register those options here.\n pluginConfig.filter((pluginConfig) => typeof pluginConfig.configure === 'function').forEach((pluginConfig) => {\n const pluginInstanceOverride = pluginConfig.configure(instanceConfig, options);\n Object.assign(instanceConfig, pluginInstanceOverride);\n });\n\n // Next we convert the plugin configuration into a format that TinyMCE understands.\n Object.assign(instanceConfig, Options.getInitialPluginConfiguration(options));\n\n return instanceConfig;\n};\n\n/**\n * Set up TinyMCE for the HTML Element.\n *\n * @param {HTMLElement} target\n * @param {Object} [options={}] The editor plugin configuration\n * @return {Promise} The TinyMCE instance\n */\nexport const setupForTarget = async(target, options = {}) => {\n const instance = getInstanceForElement(target);\n if (instance) {\n return Promise.resolve(instance);\n }\n\n // Register a new pending promise to ensure that Behat waits for the editor setup to complete before continuing.\n const pendingPromise = new Pending('editor_tiny/editor:setupForTarget');\n\n // Get the list of plugins.\n const plugins = getPlugins(options);\n\n // Fetch the tinyMCE API, and instantiate the plugins.\n const [tinyMCE, pluginValues] = await Promise.all([\n getTinyMCE(),\n importPluginList(Object.keys(plugins)),\n ]);\n\n // TinyMCE uses the element ID as a map key internally, even if the target has changed.\n // In the case where we have an editor in a modal form which has been detached from the DOM, but the editor not removed,\n // we need to manually destroy the editor.\n // We could theoretically do this with a Mutation Observer, but in some cases the Node may be moved,\n // or added back elsewhere in the DOM.\n\n // First remove any detached editors.\n tinyMCE.get().filter((editor) => !editor.getElement().isConnected).forEach((editor) => {\n editor.remove();\n });\n\n // Now check for any existing editor which shares the same ID.\n const existingEditor = tinyMCE.EditorManager.get(target.id);\n if (existingEditor) {\n if (existingEditor.getElement() === target) {\n pendingPromise.resolve();\n return Promise.resolve(existingEditor);\n } else {\n pendingPromise.resolve();\n throw new Error('TinyMCE instance already exists for different target with same ID');\n }\n }\n\n // Get the editor configuration for this editor.\n const instanceConfig = getEditorConfiguration(target, tinyMCE, options, pluginValues);\n\n // Initialise the editor instance for the given configuration.\n // At this point any plugin which has configuration options registered will have them applied for this instance.\n const [editor] = await tinyMCE.init(instanceConfig);\n\n // Store the editor instance in the instanceMap and register a listener on removal to remove it from the map.\n instanceMap.set(target, editor);\n editor.on('remove', ({target}) => {\n // Handle removal of the editor from the map on destruction.\n instanceMap.delete(target.targetElm);\n });\n\n // If the editor is part of a form, also listen to the jQuery submit event.\n // The jQuery submit event will not trigger the native submit event, and therefore the content will not be saved.\n // We cannot rely on listening to the bubbled submit event on the document because other events on child nodes may\n // consume the data before it is saved.\n if (target.form) {\n jQuery(target.form).on('submit', () => {\n editor.save();\n });\n }\n\n // Save the editor content to the textarea when the editor is blurred.\n editor.on('blur', () => {\n editor.save();\n });\n\n pendingPromise.resolve();\n return editor;\n};\n\n/**\n * Set the default editor configuration.\n *\n * This configuration is used when an editor is initialised without any configuration.\n *\n * @param {object} [options={}]\n */\nexport const configureDefaultEditor = (options = {}) => {\n defaultOptions = options;\n};\n"],"names":["instanceMap","Map","defaultOptions","importPluginList","async","pluginHandlers","Promise","all","pluginList","map","pluginPath","indexOf","resolve","pluginNames","pluginConfig","Array","isArray","filter","value","entries","elementId","getInstanceForElement","document","getElementById","element","instance","get","removed","remove","_ref","options","target","setupForTarget","lang","querySelector","tinyMCE","langData","language","fetch","M","cfg","wwwroot","langrev","then","response","json","addI18n","initialisePage","getPlugins","plugins","getStandardConfig","config","Object","assign","base_url","baseUrl","min_height","height","clientHeight","rows","window","getComputedStyle","lineHeight","content_css","css","convert_urls","a11y_advanced_options","extended_valid_elements","quickbars_insert_toolbar","block_formats","skin","promotion","branding","table_header_type","setup","editor","Options","register","on","contentWindow","this","iframeElement","nestedmenu","container","getContainer","menuContainer","parentNode","appendChild","nestMenu","e","params","open","style","overflow","pageWithDrawers","fixMenuPositionIfInFullsreen","state","browser","isSafari","fsplugin","isFullscreen","fixDialoguePositionIfOpen","Env","fullscreen","type","toolbar","getEditorConfiguration","pluginValues","instanceConfig","menu","file","items","format","replace","replaceAll","quickbars_selection_toolbar","configure","forEach","pluginInstanceOverride","getInitialPluginConfiguration","pendingPromise","Pending","keys","getElement","isConnected","existingEditor","EditorManager","id","Error","init","set","_ref2","delete","targetElm","form","save"],"mappings":"4oDAmCMA,YAAc,IAAIC,QAMpBC,eAAiB,SAQfC,iBAAmBC,MAAAA,mBAGfC,qBAAuBC,QAAQC,IAAIC,WAAWC,KAAIC,aACnB,IAA7BA,WAAWC,QAAQ,KAEZL,QAAQM,QAAQF,4NAGbA,4WAAAA,gBAOZG,YAAcR,eAAeI,KAAKK,cACR,iBAAjBA,aACAA,aAEPC,MAAMC,QAAQF,cACPA,aAAa,GAEjB,OACRG,QAAQC,OAAUA,cAUd,CACHL,YAAAA,YACAC,aATiBT,eAAeI,KAAKK,cACjCC,MAAMC,QAAQF,cACPA,aAAa,GAEjB,OACRG,QAAQC,OAAUA,mCAuBM,IAAM,IAAIjB,IAAID,YAAYmB,4CAQlBC,WAAaC,sBAAsBC,SAASC,eAAeH,kBAQrFC,sBAAwBG,gBAC3BC,SAAWzB,YAAY0B,IAAIF,aAC7BC,WAAYA,SAASE,eAIlBF,SAHHzB,YAAY4B,OAAOJ,0FAcMK,WAACT,UAACA,UAADU,QAAYA,oBACpCC,OAAST,SAASC,eAAeH,kBAChCY,eAAeD,OAAQD,UAQX1B,iBACb6B,KAAOX,SAASY,cAAc,QAAQD,MAErCE,QAASC,gBAAkB9B,QAAQC,IAAI,EAAC,yBAvD5B8B,SAuDwDJ,KAvD3CK,gBAC7BC,EAAEC,IAAIC,6CAAoCF,EAAEC,IAAIE,oBAAWL,WAChEM,MAAKC,UAAYA,SAASC,YAFLR,IAAAA,SAwDnBF,QAAQW,QAAQb,KAAMG,WAE1BW,SAWMC,WAAa,eAACC,QAACA,QAAU,6DAAQ,UAC/BA,UAIA/C,eAAe+C,QACR/C,eAAe+C,QAGnB,KA0ELC,kBAAoB,CAACnB,OAAQI,QAASL,QAASmB,iBAC3ChB,KAAOX,SAASY,cAAc,QAAQD,KAEtCkB,OAASC,OAAOC,OAAO,IAAI,uCAA2B,CAExDC,SAAUC,gBAIVxB,OAAAA,OAKAyB,WAAY,IAIZC,OAAQ1B,OAAO2B,6BAAwB3B,OAAO4B,mBAAUC,OAAOC,iBAAiB9B,QAAQ+B,YAAc,YAKtGzB,SAAUJ,KAKV8B,YAAa,CACTjC,QAAQkC,KAMZC,cAAc,EAMdC,uBAAuB,EAEvBC,wBAAyB,YAKzBC,yBAA0B,GAK1BC,cAAe,uEAIfpB,QAAS,IACFA,SAIPqB,KAAM,QAINC,WAAW,EAIXC,SAAU1C,QAAQ0C,SAKlBC,kBAAmB,eAEnBC,MAAQC,SACJC,QAAQC,SAASF,OAAQ7C,SAEzB6C,OAAOG,GAAG,WAAW,gBAMZC,cAAgBC,KAAKC,cAAcF,iBAE5CJ,OAAOG,GAAG,QAAQ,wCAEIH,OAAQ,QAAS,mBAGvCA,OAAOG,GAAG,cAAc,WAEhBhD,QAAQoD,YA7JTP,CAAAA,eACTQ,UAAYR,OAAOS,eACnBC,cAAgB/D,SAASY,cAAc,+BAC7CiD,UAAUG,WAAWC,YAAYF,gBA2JjBG,CAASb,WAMjBA,OAAOG,GAAG,0BAA0B,SAASW,GAnJnBC,CAAAA,SAC9BA,OAAOC,OAEPrE,SAASY,cAAc,mBAAmB0D,MAAMC,SAAW,eAGzDC,gBAAkBxE,SAASY,cAAc,iBAC3C4D,kBACAA,gBAAgBF,MAAMC,SAAWH,OAAOC,KAAO,QAAU,KA4IjDI,CAA6B,CACzBJ,KAAMF,EAAEO,WAOhBrB,OAAOG,GAAG,0BAA0B,SAASW,GAnItBC,CAAAA,YAG3BA,OAAOO,QAAQC,aAAeR,OAAOS,SAASC,eAAgB,OACxDN,gBAAkBxE,SAASY,cAAc,iBAC3C4D,kBACAA,gBAAgBF,MAAMC,SAAWH,OAAOC,KAAO,QAAU,MA8HrDU,CAA0B,CACtBJ,QAAS9D,QAAQmE,IAAIL,QACrBE,SAAUxB,OAAO1B,QAAQsD,WACzBZ,KAAgB,cAAVF,EAAEe,oBAMxBrD,OAAOsD,SAAU,4BAAkBtD,OAAOsD,QAAS,UAAW,cAAc,GAC5EtD,OAAOsD,SAAU,2BAAiBtD,OAAOsD,QAAS,UAAW,QAG7DtD,OAAOsD,SAAU,4BAAkBtD,OAAOsD,QAAS,iBAAkB,aAAa,GAClFtD,OAAOsD,SAAU,4BAAkBtD,OAAOsD,QAAS,iBAAkB,CAAC,MAAO,QAG7EtD,OAAOsD,SAAU,8BAAoBtD,OAAOsD,QAAS,YAAa,gBAE3DtD,QAcLuD,uBAAyB,CAAC3E,OAAQI,QAASL,QAAS6E,sBAChD9F,YACFA,YADEC,aAEFA,cACA6F,aAMEC,eAAiB1D,kBAAkBnB,OAAQI,QAASL,QAASjB,oBAK/D+F,eAAeC,KAAKC,OACpBF,eAAeC,KAAKC,KAAKC,MAAQ,IAKjCH,eAAeC,KAAKG,SACpBJ,eAAeC,KAAKG,OAAOD,MAAQH,eAAeC,KAAKG,OAAOD,MAEzDE,QAAQ,cAAe,IACvBA,QAAQ,cAAe,IAGvBA,QAAQ,eAAgB,IAGxBA,QAAQ,aAAc,IAGtBA,QAAQ,WAAY,IAGpBC,WAAW,UAAW,MAI/BN,eAAeO,4BAA8BP,eAAeO,4BAA4BF,QAAQ,QAAS,eAMzGnG,aAAaG,QAAQH,cAAmD,mBAA3BA,aAAasG,YAA0BC,SAASvG,qBACnFwG,uBAAyBxG,aAAasG,UAAUR,eAAgB9E,SACtEsB,OAAOC,OAAOuD,eAAgBU,2BAIlClE,OAAOC,OAAOuD,eAAgBhC,QAAQ2C,8BAA8BzF,UAE7D8E,gBAUE5E,eAAiB5B,eAAM2B,YAAQD,+DAAU,SAC5CL,SAAWJ,sBAAsBU,WACnCN,gBACOnB,QAAQM,QAAQa,gBAIrB+F,eAAiB,IAAIC,iBAAQ,qCAG7BxE,QAAUD,WAAWlB,UAGpBK,QAASwE,oBAAsBrG,QAAQC,IAAI,EAC9C,wBACAJ,iBAAiBiD,OAAOsE,KAAKzE,YAUjCd,QAAQT,MAAMT,QAAQ0D,SAAYA,OAAOgD,aAAaC,cAAaP,SAAS1C,SACxEA,OAAO/C,kBAILiG,eAAiB1F,QAAQ2F,cAAcpG,IAAIK,OAAOgG,OACpDF,eAAgB,IACZA,eAAeF,eAAiB5F,cAChCyF,eAAe5G,UACRN,QAAQM,QAAQiH,sBAEvBL,eAAe5G,UACT,IAAIoH,MAAM,2EAKlBpB,eAAiBF,uBAAuB3E,OAAQI,QAASL,QAAS6E,eAIjEhC,cAAgBxC,QAAQ8F,KAAKrB,uBAGpC5G,YAAYkI,IAAInG,OAAQ4C,QACxBA,OAAOG,GAAG,UAAUqD,YAACpG,OAACA,cAElB/B,YAAYoI,OAAOrG,OAAOsG,cAO1BtG,OAAOuG,0BACAvG,OAAOuG,MAAMxD,GAAG,UAAU,KAC7BH,OAAO4D,UAKf5D,OAAOG,GAAG,QAAQ,KACdH,OAAO4D,UAGXf,eAAe5G,UACR+D,+EAU2B,eAAC7C,+DAAU,GAC7C5B,eAAiB4B"} \ No newline at end of file diff --git a/lib/editor/tiny/amd/src/editor.js b/lib/editor/tiny/amd/src/editor.js index 26466753afcbf..1b1d1f4c275eb 100644 --- a/lib/editor/tiny/amd/src/editor.js +++ b/lib/editor/tiny/amd/src/editor.js @@ -189,6 +189,53 @@ const getPlugins = ({plugins = null} = {}) => { container.parentNode.appendChild(menuContainer); }; +/** + * Fix the Tiny menu position if the editor is in fullscreen mode on the Boost theme. + * + * The boost theme makes the TinyMCE editor rendered in a scrollable container, + * scrolling the editor’s container will cause TinyMCE UI elements to be detached from the anchor. + * Therefore, to keep the tinyMCE menu in the correct position, + * adjustments must be made on the page drawers style. + * + * @param {object} params + * @param {Boolean} params.open True if editor in fullscreen mode, otherwise false. + */ +const fixMenuPositionIfInFullsreen = (params) => { + if (params.open) { + // Keep the menu remains visible and properly aligned. + document.querySelector('.tox-fullscreen').style.overflow = 'unset'; + } + + const pageWithDrawers = document.querySelector('#page.drawers'); + if (pageWithDrawers) { + pageWithDrawers.style.overflow = params.open ? "unset" : ""; + } +}; + +/** + * Fix the dialogue window positioning issue of TinyMCE editor in Safari browsers. + * + * When using TinyMCE editor in Safari browsers, a problem may occur where the dialogue + * windows (such as modal dialogs) overlap with page drawers due to a specific behavior + * in Safari's rendering. This function addresses the issue by adjusting the CSS overflow + * property of the page drawers, ensuring they do not obscure the dialogue windows. + * + * @param {object} params + * @param {object} params.browser Browser environment. + * @param {object} params.fsplugin Fullscreen plugin. + * @param {Boolean} params.open True if the dialogue window opens, otherwise false. + */ +const fixDialoguePositionIfOpen = (params) => { + // To avoid modification the existing overflow value that has been set fixMenuPositionIfInFullsreen(), + // the fix only applied if the editor not in fullscreen mode. + if (params.browser.isSafari() && !params.fsplugin.isFullscreen()) { + const pageWithDrawers = document.querySelector('#page.drawers'); + if (pageWithDrawers) { + pageWithDrawers.style.overflow = params.open ? "unset" : ""; + } + } +}; + /** * Get the standard configuration for the specified options. * @@ -297,6 +344,25 @@ const getStandardConfig = (target, tinyMCE, options, plugins) => { nestMenu(editor); } }); + + // The Boost and Classic theme cause issues if editor is in fullscreen mode. + // The problem was resolved by changing the overflow value to related elements. + editor.on('FullscreenStateChanged', function(e) { + fixMenuPositionIfInFullsreen({ + open: e.state + }); + }); + + // The Boost theme uses Overflow=auto in the course index drawer, + // it causes the dialogue window to be not correctly displayed in Safari browser. + // The problem was resolved by changing the overflow value to the drawer. + editor.on('OpenWindow CloseWindow', function(e) { + fixDialoguePositionIfOpen({ + browser: tinyMCE.Env.browser, + fsplugin: editor.plugins.fullscreen, + open: e.type == "openwindow" + }); + }); }, }); From 0975a83b7a9e8e40bb0315c0f229e2a15d705d17 Mon Sep 17 00:00:00 2001 From: Jun Pataleta Date: Fri, 4 Aug 2023 14:42:12 +0800 Subject: [PATCH 36/61] MDL-78904 user: Remove non-existent require * And enclose context parameter in an array. --- user/repository.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/user/repository.php b/user/repository.php index d398a8380c7d9..444f35dc3a934 100644 --- a/user/repository.php +++ b/user/repository.php @@ -55,14 +55,11 @@ echo $OUTPUT->header(); -$currenttab = 'repositories'; -require('tabs.php'); - echo $OUTPUT->heading($configstr); echo $OUTPUT->box_start(); $params = array(); -$params['context'] = $coursecontext; +$params['context'] = [$coursecontext]; $params['currentcontext'] = $PAGE->context; $params['userid'] = $USER->id; if (!$instances = repository::get_instances($params)) { From 614563082bc772f1c98cd7e660244b832bb15582 Mon Sep 17 00:00:00 2001 From: Sara Arjona Date: Fri, 4 Aug 2023 12:40:10 +0200 Subject: [PATCH 37/61] weekly release 4.1.4+ --- version.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.php b/version.php index e388d9281f502..c6889b77694f3 100644 --- a/version.php +++ b/version.php @@ -29,9 +29,9 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2022112804.09; // 20221128 = branching date YYYYMMDD - do not modify! +$version = 2022112804.10; // 20221128 = branching date YYYYMMDD - do not modify! // RR = release increments - 00 in DEV branches. // .XX = incremental changes. -$release = '4.1.4+ (Build: 20230728)'; // Human-friendly version name +$release = '4.1.4+ (Build: 20230804)'; // Human-friendly version name $branch = '401'; // This version's branch. $maturity = MATURITY_STABLE; // This version's maturity level. From 772236e08cec2ff5a7c17c6868201cf84dd0e745 Mon Sep 17 00:00:00 2001 From: "Eloy Lafuente (stronk7)" Date: Sun, 23 Jul 2023 20:18:07 +0200 Subject: [PATCH 38/61] MDL-78800 composer: Bump dependencies to current ones This bumps direct dependencies to current ones: - phpunit: 9.5.x No updates here, there is the 9.6.x series available but a lot of deprecation warnings have been introduced there without any alternative to use. See: - https://github.com/sebastianbergmann/phpunit/issues/5160 - https://github.com/sebastianbergmann/phpunit/issues/5062 - ... While that will be handy to prepare ourselves to PHPUnit 10 in some months... we cannot force everybody to jump to 9.6.x because that will make a lot of tests to start emitting warnings. So we stay with PHPUnit 9.5.x for the life of this branch. - mink-phpwebdriver: 1.2.1 No updates here, just changed the constraint because we cannot advance to 1.3.x yet, there is a change there causing some app tests to fail. See: - https://github.com/oleg-andreyev/MinkPhpWebDriver/pull/81 So we stay with 1.2.x until that issue is fixed/clarified, only then we'll review the status. - behat: 3.12.x => 3.13.x And also, automatically, a bunch of 2nd and deepest dependencies. Generated with php74 that is the lowest php version supported by this branch and, also, by some of the dependencies, as per documented @ https://moodledev.io/general/development/tools/composer Worth mentioning behat/mink-goutte-driver, that we should move to mink-browserkit-driver, but that's out from this issue scope. --- composer.json | 6 +- composer.lock | 355 ++++++++++++++++++++++++++------------------------ 2 files changed, 185 insertions(+), 176 deletions(-) diff --git a/composer.json b/composer.json index 071dfe30dc5bd..9b69ce18be776 100644 --- a/composer.json +++ b/composer.json @@ -10,9 +10,9 @@ "behat/mink": "^1.10.0", "friends-of-behat/mink-extension": "^2.7.2", "behat/mink-goutte-driver": "^2.0", - "symfony/process": "^4.4 || ^5.0", - "behat/behat": "3.12.*", - "oleg-andreyev/mink-phpwebdriver": "^1.2.1" + "symfony/process": "^4.4 || ^5.0 || ^6.0", + "behat/behat": "3.13.*", + "oleg-andreyev/mink-phpwebdriver": "1.2.*" }, "autoload-dev": { "psr-0": { diff --git a/composer.lock b/composer.lock index 7192796e2f780..fd0452661a143 100644 --- a/composer.lock +++ b/composer.lock @@ -4,21 +4,21 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "71522fc9834426e64d51273f01714eaa", + "content-hash": "00ebd18e25df51908b15e70c2b2e9098", "packages": [], "packages-dev": [ { "name": "behat/behat", - "version": "v3.12.0", + "version": "v3.13.0", "source": { "type": "git", "url": "https://github.com/Behat/Behat.git", - "reference": "2f059c9172764ba1f1759b3679aca499b665330a" + "reference": "9dd7cdb309e464ddeab095cd1a5151c2dccba4ab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Behat/Behat/zipball/2f059c9172764ba1f1759b3679aca499b665330a", - "reference": "2f059c9172764ba1f1759b3679aca499b665330a", + "url": "https://api.github.com/repos/Behat/Behat/zipball/9dd7cdb309e464ddeab095cd1a5151c2dccba4ab", + "reference": "9dd7cdb309e464ddeab095cd1a5151c2dccba4ab", "shasum": "" }, "require": { @@ -90,9 +90,9 @@ ], "support": { "issues": "https://github.com/Behat/Behat/issues", - "source": "https://github.com/Behat/Behat/tree/v3.12.0" + "source": "https://github.com/Behat/Behat/tree/v3.13.0" }, - "time": "2022-11-29T15:30:11+00:00" + "time": "2023-04-18T15:40:53+00:00" }, { "name": "behat/gherkin", @@ -343,6 +343,7 @@ "issues": "https://github.com/minkphp/MinkGoutteDriver/issues", "source": "https://github.com/minkphp/MinkGoutteDriver/tree/v2.0.0" }, + "abandoned": "behat/mink-browserkit-driver", "time": "2021-12-29T10:56:50+00:00" }, { @@ -466,28 +467,29 @@ }, { "name": "fabpot/goutte", - "version": "v4.0.2", + "version": "v4.0.3", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/Goutte.git", - "reference": "f51940fbe0db060bc4fc0b3f1d19bc4ff3054b17" + "reference": "e3f28671c87a48a0f13ada1baea0d95acc2138c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/f51940fbe0db060bc4fc0b3f1d19bc4ff3054b17", - "reference": "f51940fbe0db060bc4fc0b3f1d19bc4ff3054b17", + "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/e3f28671c87a48a0f13ada1baea0d95acc2138c3", + "reference": "e3f28671c87a48a0f13ada1baea0d95acc2138c3", "shasum": "" }, "require": { "php": ">=7.1.3", "symfony/browser-kit": "^4.4|^5.0|^6.0", "symfony/css-selector": "^4.4|^5.0|^6.0", + "symfony/deprecation-contracts": "^2.1|^3", "symfony/dom-crawler": "^4.4|^5.0|^6.0", "symfony/http-client": "^4.4|^5.0|^6.0", "symfony/mime": "^4.4|^5.0|^6.0" }, "require-dev": { - "symfony/phpunit-bridge": "^5.0|^6.0" + "symfony/phpunit-bridge": "^6.0" }, "type": "application", "autoload": { @@ -515,9 +517,10 @@ ], "support": { "issues": "https://github.com/FriendsOfPHP/Goutte/issues", - "source": "https://github.com/FriendsOfPHP/Goutte/tree/v4.0.2" + "source": "https://github.com/FriendsOfPHP/Goutte/tree/v4.0.3" }, - "time": "2021-12-17T17:15:01+00:00" + "abandoned": "symfony/browser-kit", + "time": "2023-04-01T09:05:33+00:00" }, { "name": "friends-of-behat/mink-extension", @@ -637,16 +640,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.11.0", + "version": "1.11.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", "shasum": "" }, "require": { @@ -684,7 +687,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" }, "funding": [ { @@ -692,20 +695,20 @@ "type": "tidelift" } ], - "time": "2022-03-03T13:19:32+00:00" + "time": "2023-03-08T13:26:56+00:00" }, { "name": "nikic/php-parser", - "version": "v4.15.2", + "version": "v4.16.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc" + "reference": "19526a33fb561ef417e822e85f08a00db4059c17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc", - "reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/19526a33fb561ef417e822e85f08a00db4059c17", + "reference": "19526a33fb561ef417e822e85f08a00db4059c17", "shasum": "" }, "require": { @@ -746,9 +749,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.2" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.16.0" }, - "time": "2022-11-12T15:38:23+00:00" + "time": "2023-06-25T14:52:30+00:00" }, { "name": "oleg-andreyev/mink-phpwebdriver", @@ -928,37 +931,38 @@ }, { "name": "php-webdriver/webdriver", - "version": "1.13.1", + "version": "1.14.0", "source": { "type": "git", "url": "https://github.com/php-webdriver/php-webdriver.git", - "reference": "6dfe5f814b796c1b5748850aa19f781b9274c36c" + "reference": "3ea4f924afb43056bf9c630509e657d951608563" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/6dfe5f814b796c1b5748850aa19f781b9274c36c", - "reference": "6dfe5f814b796c1b5748850aa19f781b9274c36c", + "url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/3ea4f924afb43056bf9c630509e657d951608563", + "reference": "3ea4f924afb43056bf9c630509e657d951608563", "shasum": "" }, "require": { "ext-curl": "*", "ext-json": "*", "ext-zip": "*", - "php": "^5.6 || ~7.0 || ^8.0", + "php": "^7.3 || ^8.0", "symfony/polyfill-mbstring": "^1.12", - "symfony/process": "^2.8 || ^3.1 || ^4.0 || ^5.0 || ^6.0" + "symfony/process": "^5.0 || ^6.0" }, "replace": { "facebook/webdriver": "*" }, "require-dev": { - "ondram/ci-detector": "^2.1 || ^3.5 || ^4.0", + "ergebnis/composer-normalize": "^2.20.0", + "ondram/ci-detector": "^4.0", "php-coveralls/php-coveralls": "^2.4", - "php-mock/php-mock-phpunit": "^1.1 || ^2.0", + "php-mock/php-mock-phpunit": "^2.0", "php-parallel-lint/php-parallel-lint": "^1.2", - "phpunit/phpunit": "^5.7 || ^7 || ^8 || ^9", + "phpunit/phpunit": "^9.3", "squizlabs/php_codesniffer": "^3.5", - "symfony/var-dumper": "^3.3 || ^4.0 || ^5.0 || ^6.0" + "symfony/var-dumper": "^5.0 || ^6.0" }, "suggest": { "ext-SimpleXML": "For Firefox profile creation" @@ -987,29 +991,29 @@ ], "support": { "issues": "https://github.com/php-webdriver/php-webdriver/issues", - "source": "https://github.com/php-webdriver/php-webdriver/tree/1.13.1" + "source": "https://github.com/php-webdriver/php-webdriver/tree/1.14.0" }, - "time": "2022-10-11T11:49:44+00:00" + "time": "2023-02-09T12:12:19+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.23", + "version": "9.2.27", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c" + "reference": "b0a88255cb70d52653d80c890bd7f38740ea50d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c", - "reference": "9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/b0a88255cb70d52653d80c890bd7f38740ea50d1", + "reference": "b0a88255cb70d52653d80c890bd7f38740ea50d1", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.14", + "nikic/php-parser": "^4.15", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -1024,8 +1028,8 @@ "phpunit/phpunit": "^9.3" }, "suggest": { - "ext-pcov": "*", - "ext-xdebug": "*" + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "type": "library", "extra": { @@ -1058,7 +1062,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.23" + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.27" }, "funding": [ { @@ -1066,7 +1071,7 @@ "type": "github" } ], - "time": "2022-12-28T12:41:10+00:00" + "time": "2023-07-26T13:44:30+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1311,20 +1316,20 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.27", + "version": "9.5.28", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "a2bc7ffdca99f92d959b3f2270529334030bba38" + "reference": "954ca3113a03bf780d22f07bf055d883ee04b65e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a2bc7ffdca99f92d959b3f2270529334030bba38", - "reference": "a2bc7ffdca99f92d959b3f2270529334030bba38", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/954ca3113a03bf780d22f07bf055d883ee04b65e", + "reference": "954ca3113a03bf780d22f07bf055d883ee04b65e", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1", + "doctrine/instantiator": "^1.3.1 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", @@ -1393,7 +1398,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.27" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.28" }, "funding": [ { @@ -1409,7 +1414,7 @@ "type": "tidelift" } ], - "time": "2022-12-09T07:31:23+00:00" + "time": "2023-01-14T12:32:24+00:00" }, { "name": "psr/container", @@ -1859,16 +1864,16 @@ }, { "name": "sebastian/diff", - "version": "4.0.4", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", "shasum": "" }, "require": { @@ -1913,7 +1918,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" }, "funding": [ { @@ -1921,20 +1926,20 @@ "type": "github" } ], - "time": "2020-10-26T13:10:38+00:00" + "time": "2023-05-07T05:35:17+00:00" }, { "name": "sebastian/environment", - "version": "5.1.4", + "version": "5.1.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7" + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7", - "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", "shasum": "" }, "require": { @@ -1976,7 +1981,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.4" + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" }, "funding": [ { @@ -1984,7 +1989,7 @@ "type": "github" } ], - "time": "2022-04-03T09:37:03+00:00" + "time": "2023-02-03T06:03:51+00:00" }, { "name": "sebastian/exporter", @@ -2065,16 +2070,16 @@ }, { "name": "sebastian/global-state", - "version": "5.0.5", + "version": "5.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" + "reference": "bde739e7565280bda77be70044ac1047bc007e34" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34", + "reference": "bde739e7565280bda77be70044ac1047bc007e34", "shasum": "" }, "require": { @@ -2117,7 +2122,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6" }, "funding": [ { @@ -2125,7 +2130,7 @@ "type": "github" } ], - "time": "2022-02-14T08:28:10+00:00" + "time": "2023-08-02T09:26:13+00:00" }, { "name": "sebastian/lines-of-code", @@ -2298,16 +2303,16 @@ }, { "name": "sebastian/recursion-context", - "version": "4.0.4", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", "shasum": "" }, "require": { @@ -2346,10 +2351,10 @@ } ], "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" }, "funding": [ { @@ -2357,7 +2362,7 @@ "type": "github" } ], - "time": "2020-10-26T13:17:30+00:00" + "time": "2023-02-03T06:07:39+00:00" }, { "name": "sebastian/resource-operations", @@ -2416,16 +2421,16 @@ }, { "name": "sebastian/type", - "version": "3.2.0", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e" + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", - "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", "shasum": "" }, "require": { @@ -2460,7 +2465,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.2.0" + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" }, "funding": [ { @@ -2468,7 +2473,7 @@ "type": "github" } ], - "time": "2022-09-12T14:47:03+00:00" + "time": "2023-02-03T06:13:03+00:00" }, { "name": "sebastian/version", @@ -2525,16 +2530,16 @@ }, { "name": "symfony/browser-kit", - "version": "v5.4.11", + "version": "v5.4.21", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "081fe28a26b6bd671dea85ef3a4b5003f3c88027" + "reference": "a866ca7e396f15d7efb6d74a8a7d364d4e05b704" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/081fe28a26b6bd671dea85ef3a4b5003f3c88027", - "reference": "081fe28a26b6bd671dea85ef3a4b5003f3c88027", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/a866ca7e396f15d7efb6d74a8a7d364d4e05b704", + "reference": "a866ca7e396f15d7efb6d74a8a7d364d4e05b704", "shasum": "" }, "require": { @@ -2577,7 +2582,7 @@ "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/browser-kit/tree/v5.4.11" + "source": "https://github.com/symfony/browser-kit/tree/v5.4.21" }, "funding": [ { @@ -2593,20 +2598,20 @@ "type": "tidelift" } ], - "time": "2022-07-27T15:50:05+00:00" + "time": "2023-02-14T08:03:56+00:00" }, { "name": "symfony/config", - "version": "v5.4.11", + "version": "v5.4.26", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "ec79e03125c1d2477e43dde8528535d90cc78379" + "reference": "8109892f27beed9252bd1f1c1880aeb4ad842650" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/ec79e03125c1d2477e43dde8528535d90cc78379", - "reference": "ec79e03125c1d2477e43dde8528535d90cc78379", + "url": "https://api.github.com/repos/symfony/config/zipball/8109892f27beed9252bd1f1c1880aeb4ad842650", + "reference": "8109892f27beed9252bd1f1c1880aeb4ad842650", "shasum": "" }, "require": { @@ -2656,7 +2661,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v5.4.11" + "source": "https://github.com/symfony/config/tree/v5.4.26" }, "funding": [ { @@ -2672,20 +2677,20 @@ "type": "tidelift" } ], - "time": "2022-07-20T13:00:38+00:00" + "time": "2023-07-19T20:21:11+00:00" }, { "name": "symfony/console", - "version": "v5.4.17", + "version": "v5.4.26", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "58422fdcb0e715ed05b385f70d3e8b5ed4bbd45f" + "reference": "b504a3d266ad2bb632f196c0936ef2af5ff6e273" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/58422fdcb0e715ed05b385f70d3e8b5ed4bbd45f", - "reference": "58422fdcb0e715ed05b385f70d3e8b5ed4bbd45f", + "url": "https://api.github.com/repos/symfony/console/zipball/b504a3d266ad2bb632f196c0936ef2af5ff6e273", + "reference": "b504a3d266ad2bb632f196c0936ef2af5ff6e273", "shasum": "" }, "require": { @@ -2750,12 +2755,12 @@ "homepage": "https://symfony.com", "keywords": [ "cli", - "command line", + "command-line", "console", "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.17" + "source": "https://github.com/symfony/console/tree/v5.4.26" }, "funding": [ { @@ -2771,20 +2776,20 @@ "type": "tidelift" } ], - "time": "2022-12-28T14:15:31+00:00" + "time": "2023-07-19T20:11:33+00:00" }, { "name": "symfony/css-selector", - "version": "v5.4.17", + "version": "v5.4.26", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "052ef49b660f9ad2a3adb311c555c9bc11ba61f4" + "reference": "0ad3f7e9a1ab492c5b4214cf22a9dc55dcf8600a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/052ef49b660f9ad2a3adb311c555c9bc11ba61f4", - "reference": "052ef49b660f9ad2a3adb311c555c9bc11ba61f4", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/0ad3f7e9a1ab492c5b4214cf22a9dc55dcf8600a", + "reference": "0ad3f7e9a1ab492c5b4214cf22a9dc55dcf8600a", "shasum": "" }, "require": { @@ -2821,7 +2826,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v5.4.17" + "source": "https://github.com/symfony/css-selector/tree/v5.4.26" }, "funding": [ { @@ -2837,20 +2842,20 @@ "type": "tidelift" } ], - "time": "2022-12-23T11:40:44+00:00" + "time": "2023-07-07T06:10:25+00:00" }, { "name": "symfony/dependency-injection", - "version": "v5.4.17", + "version": "v5.4.26", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "58f2988128d2d278280781db037677a32cf720db" + "reference": "6736a10dcf724725a3b1c3b53e63a9ee03b27db9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/58f2988128d2d278280781db037677a32cf720db", - "reference": "58f2988128d2d278280781db037677a32cf720db", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/6736a10dcf724725a3b1c3b53e63a9ee03b27db9", + "reference": "6736a10dcf724725a3b1c3b53e63a9ee03b27db9", "shasum": "" }, "require": { @@ -2910,7 +2915,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v5.4.17" + "source": "https://github.com/symfony/dependency-injection/tree/v5.4.26" }, "funding": [ { @@ -2926,7 +2931,7 @@ "type": "tidelift" } ], - "time": "2022-12-28T13:55:51+00:00" + "time": "2023-07-19T20:11:33+00:00" }, { "name": "symfony/deprecation-contracts", @@ -2997,16 +3002,16 @@ }, { "name": "symfony/dom-crawler", - "version": "v5.4.17", + "version": "v5.4.25", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "32a07d910edc138a1dd5508c17c6b9bc1eb27a1b" + "reference": "d2aefa5a7acc5511422792931d14d1be96fe9fea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/32a07d910edc138a1dd5508c17c6b9bc1eb27a1b", - "reference": "32a07d910edc138a1dd5508c17c6b9bc1eb27a1b", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/d2aefa5a7acc5511422792931d14d1be96fe9fea", + "reference": "d2aefa5a7acc5511422792931d14d1be96fe9fea", "shasum": "" }, "require": { @@ -3052,7 +3057,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v5.4.17" + "source": "https://github.com/symfony/dom-crawler/tree/v5.4.25" }, "funding": [ { @@ -3068,20 +3073,20 @@ "type": "tidelift" } ], - "time": "2022-12-22T10:31:03+00:00" + "time": "2023-06-05T08:05:41+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v5.4.17", + "version": "v5.4.26", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "8e18a9d559eb8ebc2220588f1faa726a2fcd31c9" + "reference": "5dcc00e03413f05c1e7900090927bb7247cb0aac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/8e18a9d559eb8ebc2220588f1faa726a2fcd31c9", - "reference": "8e18a9d559eb8ebc2220588f1faa726a2fcd31c9", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/5dcc00e03413f05c1e7900090927bb7247cb0aac", + "reference": "5dcc00e03413f05c1e7900090927bb7247cb0aac", "shasum": "" }, "require": { @@ -3137,7 +3142,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.17" + "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.26" }, "funding": [ { @@ -3153,7 +3158,7 @@ "type": "tidelift" } ], - "time": "2022-12-12T15:54:21+00:00" + "time": "2023-07-06T06:34:20+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -3236,16 +3241,16 @@ }, { "name": "symfony/filesystem", - "version": "v5.4.13", + "version": "v5.4.25", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "ac09569844a9109a5966b9438fc29113ce77cf51" + "reference": "0ce3a62c9579a53358d3a7eb6b3dfb79789a6364" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/ac09569844a9109a5966b9438fc29113ce77cf51", - "reference": "ac09569844a9109a5966b9438fc29113ce77cf51", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/0ce3a62c9579a53358d3a7eb6b3dfb79789a6364", + "reference": "0ce3a62c9579a53358d3a7eb6b3dfb79789a6364", "shasum": "" }, "require": { @@ -3280,7 +3285,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v5.4.13" + "source": "https://github.com/symfony/filesystem/tree/v5.4.25" }, "funding": [ { @@ -3296,20 +3301,20 @@ "type": "tidelift" } ], - "time": "2022-09-21T19:53:16+00:00" + "time": "2023-05-31T13:04:02+00:00" }, { "name": "symfony/http-client", - "version": "v5.4.17", + "version": "v5.4.26", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "772129f800fc0bfaa6bd40c40934d544f0957d30" + "reference": "19d48ef7f38e5057ed1789a503cd3eccef039bce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/772129f800fc0bfaa6bd40c40934d544f0957d30", - "reference": "772129f800fc0bfaa6bd40c40934d544f0957d30", + "url": "https://api.github.com/repos/symfony/http-client/zipball/19d48ef7f38e5057ed1789a503cd3eccef039bce", + "reference": "19d48ef7f38e5057ed1789a503cd3eccef039bce", "shasum": "" }, "require": { @@ -3335,6 +3340,7 @@ "guzzlehttp/promises": "^1.4", "nyholm/psr7": "^1.0", "php-http/httplug": "^1.0|^2.0", + "php-http/message-factory": "^1.0", "psr/http-client": "^1.0", "symfony/dependency-injection": "^4.4|^5.0|^6.0", "symfony/http-kernel": "^4.4.13|^5.1.5|^6.0", @@ -3366,8 +3372,11 @@ ], "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", "homepage": "https://symfony.com", + "keywords": [ + "http" + ], "support": { - "source": "https://github.com/symfony/http-client/tree/v5.4.17" + "source": "https://github.com/symfony/http-client/tree/v5.4.26" }, "funding": [ { @@ -3383,7 +3392,7 @@ "type": "tidelift" } ], - "time": "2022-12-13T11:07:37+00:00" + "time": "2023-07-03T12:14:50+00:00" }, { "name": "symfony/http-client-contracts", @@ -3465,16 +3474,16 @@ }, { "name": "symfony/mime", - "version": "v5.4.17", + "version": "v5.4.26", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "2a83d82efc91c3f03a23c8b47a896df168aa5c63" + "reference": "2ea06dfeee20000a319d8407cea1d47533d5a9d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/2a83d82efc91c3f03a23c8b47a896df168aa5c63", - "reference": "2a83d82efc91c3f03a23c8b47a896df168aa5c63", + "url": "https://api.github.com/repos/symfony/mime/zipball/2ea06dfeee20000a319d8407cea1d47533d5a9d2", + "reference": "2ea06dfeee20000a319d8407cea1d47533d5a9d2", "shasum": "" }, "require": { @@ -3489,15 +3498,15 @@ "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", "symfony/mailer": "<4.4", - "symfony/serializer": "<5.4.14|>=6.0,<6.0.14|>=6.1,<6.1.6" + "symfony/serializer": "<5.4.26|>=6,<6.2.13|>=6.3,<6.3.2" }, "require-dev": { - "egulias/email-validator": "^2.1.10|^3.1", + "egulias/email-validator": "^2.1.10|^3.1|^4", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", "symfony/dependency-injection": "^4.4|^5.0|^6.0", "symfony/property-access": "^4.4|^5.1|^6.0", "symfony/property-info": "^4.4|^5.1|^6.0", - "symfony/serializer": "^5.4.14|~6.0.14|^6.1.6" + "symfony/serializer": "^5.4.26|~6.2.13|^6.3.2" }, "type": "library", "autoload": { @@ -3529,7 +3538,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v5.4.17" + "source": "https://github.com/symfony/mime/tree/v5.4.26" }, "funding": [ { @@ -3545,7 +3554,7 @@ "type": "tidelift" } ], - "time": "2022-12-13T09:59:55+00:00" + "time": "2023-07-27T06:29:31+00:00" }, { "name": "symfony/polyfill-ctype", @@ -4283,16 +4292,16 @@ }, { "name": "symfony/process", - "version": "v5.4.11", + "version": "v5.4.26", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "6e75fe6874cbc7e4773d049616ab450eff537bf1" + "reference": "1a44dc377ec86a50fab40d066cd061e28a6b482f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/6e75fe6874cbc7e4773d049616ab450eff537bf1", - "reference": "6e75fe6874cbc7e4773d049616ab450eff537bf1", + "url": "https://api.github.com/repos/symfony/process/zipball/1a44dc377ec86a50fab40d066cd061e28a6b482f", + "reference": "1a44dc377ec86a50fab40d066cd061e28a6b482f", "shasum": "" }, "require": { @@ -4325,7 +4334,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.4.11" + "source": "https://github.com/symfony/process/tree/v5.4.26" }, "funding": [ { @@ -4341,7 +4350,7 @@ "type": "tidelift" } ], - "time": "2022-06-27T16:58:25+00:00" + "time": "2023-07-12T15:44:31+00:00" }, { "name": "symfony/service-contracts", @@ -4428,16 +4437,16 @@ }, { "name": "symfony/string", - "version": "v5.4.17", + "version": "v5.4.26", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "55733a8664b8853b003e70251c58bc8cb2d82a6b" + "reference": "1181fe9270e373537475e826873b5867b863883c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/55733a8664b8853b003e70251c58bc8cb2d82a6b", - "reference": "55733a8664b8853b003e70251c58bc8cb2d82a6b", + "url": "https://api.github.com/repos/symfony/string/zipball/1181fe9270e373537475e826873b5867b863883c", + "reference": "1181fe9270e373537475e826873b5867b863883c", "shasum": "" }, "require": { @@ -4494,7 +4503,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.17" + "source": "https://github.com/symfony/string/tree/v5.4.26" }, "funding": [ { @@ -4510,20 +4519,20 @@ "type": "tidelift" } ], - "time": "2022-12-12T15:54:21+00:00" + "time": "2023-06-28T12:46:07+00:00" }, { "name": "symfony/translation", - "version": "v5.4.14", + "version": "v5.4.24", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "f0ed07675863aa6e3939df8b1bc879450b585cab" + "reference": "de237e59c5833422342be67402d487fbf50334ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/f0ed07675863aa6e3939df8b1bc879450b585cab", - "reference": "f0ed07675863aa6e3939df8b1bc879450b585cab", + "url": "https://api.github.com/repos/symfony/translation/zipball/de237e59c5833422342be67402d487fbf50334ff", + "reference": "de237e59c5833422342be67402d487fbf50334ff", "shasum": "" }, "require": { @@ -4591,7 +4600,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v5.4.14" + "source": "https://github.com/symfony/translation/tree/v5.4.24" }, "funding": [ { @@ -4607,7 +4616,7 @@ "type": "tidelift" } ], - "time": "2022-10-07T08:01:20+00:00" + "time": "2023-05-19T12:34:17+00:00" }, { "name": "symfony/translation-contracts", @@ -4689,16 +4698,16 @@ }, { "name": "symfony/yaml", - "version": "v5.4.17", + "version": "v5.4.23", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "edcdc11498108f8967fe95118a7ec8624b94760e" + "reference": "4cd2e3ea301aadd76a4172756296fe552fb45b0b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/edcdc11498108f8967fe95118a7ec8624b94760e", - "reference": "edcdc11498108f8967fe95118a7ec8624b94760e", + "url": "https://api.github.com/repos/symfony/yaml/zipball/4cd2e3ea301aadd76a4172756296fe552fb45b0b", + "reference": "4cd2e3ea301aadd76a4172756296fe552fb45b0b", "shasum": "" }, "require": { @@ -4744,7 +4753,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v5.4.17" + "source": "https://github.com/symfony/yaml/tree/v5.4.23" }, "funding": [ { @@ -4760,7 +4769,7 @@ "type": "tidelift" } ], - "time": "2022-12-13T09:57:04+00:00" + "time": "2023-04-23T19:33:36+00:00" }, { "name": "theseer/tokenizer", From 896e87228c323a248b4e672675b109c0ebd1e3f5 Mon Sep 17 00:00:00 2001 From: chen levy Date: Mon, 31 Jul 2023 11:37:10 +0300 Subject: [PATCH 39/61] MDL-77658 theme: Fixed RTL manage course categories menu fixed course checkboxes that appears on the categories menu on RTL --- theme/boost/scss/moodle/course.scss | 3 +++ theme/boost/style/moodle.css | 3 +++ theme/classic/style/moodle.css | 3 +++ 3 files changed, 9 insertions(+) diff --git a/theme/boost/scss/moodle/course.scss b/theme/boost/scss/moodle/course.scss index 249fa732d42ed..6f12c9e5670d3 100644 --- a/theme/boost/scss/moodle/course.scss +++ b/theme/boost/scss/moodle/course.scss @@ -930,6 +930,9 @@ span.editinstructions { border-left: calc(#{$list-group-border-width} + 5px) solid map-get($theme-colors, 'primary'); padding-left: calc(#{$list-group-item-padding-x} - 5px); } + &:hover { + z-index: 2; + } } .item-actions { diff --git a/theme/boost/style/moodle.css b/theme/boost/style/moodle.css index 32ffee115e4cf..7b613272472ba 100644 --- a/theme/boost/style/moodle.css +++ b/theme/boost/style/moodle.css @@ -18431,6 +18431,9 @@ span.editinstructions .alert-link { border-left: calc(1px + 5px) solid #0f6cbf; padding-left: calc(1.25rem - 5px); } +#course-category-listings .listitem:hover { + z-index: 2; +} #course-category-listings .item-actions { margin-right: 1em; display: inline-block; diff --git a/theme/classic/style/moodle.css b/theme/classic/style/moodle.css index d0d24ab67d048..d92656dcc2f0f 100644 --- a/theme/classic/style/moodle.css +++ b/theme/classic/style/moodle.css @@ -18431,6 +18431,9 @@ span.editinstructions .alert-link { border-left: calc(1px + 5px) solid #0f6cbf; padding-left: calc(1.25rem - 5px); } +#course-category-listings .listitem:hover { + z-index: 2; +} #course-category-listings .item-actions { margin-right: 1em; display: inline-block; From 7b4d19308e6ea4b18844955cac4c1488d44f1915 Mon Sep 17 00:00:00 2001 From: Helen Foster Date: Mon, 7 Aug 2023 11:30:53 +0100 Subject: [PATCH 40/61] MDL-78896 lang: Import fixed English strings (en_fix) --- lang/en/admin.php | 4 +- lang/en/auth.php | 4 +- lang/en/completion.php | 8 ++-- lang/en/group.php | 12 +++--- lang/en/moodle.php | 37 ++++++++++--------- .../upload/lang/en/repository_upload.php | 2 +- 6 files changed, 34 insertions(+), 33 deletions(-) diff --git a/lang/en/admin.php b/lang/en/admin.php index 2d9a5633307b6..1b26f02ff8a38 100644 --- a/lang/en/admin.php +++ b/lang/en/admin.php @@ -891,8 +891,8 @@ $string['navshowmycoursecategories_help'] = 'If enabled, courses in the user\'s My courses branch will be shown in categories in the navigation block (Classic-based themes only).'; $string['navsortmycourseshiddenlast'] = 'Sort my hidden courses last'; $string['navsortmycourseshiddenlast_help'] = 'If enabled, any hidden courses will be listed after visible courses (for users who can view hidden courses). Otherwise, all courses, regardless of their visibility, will be listed according to the \'Sort my courses\' setting.'; -$string['navsortmycoursessort'] = 'Sort my courses'; -$string['navsortmycoursessort_help'] = 'This determines whether courses are listed under My courses according to the sort order (i.e. the order set in Site administration > Courses > Manage courses and categories) or alphabetically by course setting.'; +$string['navsortmycoursessort'] = 'Order of courses in navigation block'; +$string['navsortmycoursessort_help'] = 'Should courses be listed in the sort order set in Site administration > Courses > Manage courses and categories, or alphabetically by course setting? This setting only applies to themes based on Classic.'; $string['never'] = 'Never'; $string['neverdeleteruns'] = 'Never delete runs'; $string['newestdocindexed'] = 'Newest document indexed'; diff --git a/lang/en/auth.php b/lang/en/auth.php index 75a7243e522a9..4eb470bfb0371 100644 --- a/lang/en/auth.php +++ b/lang/en/auth.php @@ -86,7 +86,7 @@ $string['errorminpassworddigits'] = 'Passwords must have at least {$a} digit(s).'; $string['errorminpasswordlength'] = 'Passwords must be at least {$a} characters long.'; $string['errorminpasswordlower'] = 'Passwords must have at least {$a} lower case letter(s).'; -$string['errorminpasswordnonalphanum'] = 'The password must have at least {$a} special character(s) such as as *, -, or #.'; +$string['errorminpasswordnonalphanum'] = 'The password must have at least {$a} special character(s) such as *, -, or #.'; $string['errorpasswordreused'] = 'This password has been used before, and is not permitted to be reused'; $string['errorminpasswordupper'] = 'Passwords must have at least {$a} upper case letter(s).'; $string['errorpasswordupdate'] = 'Error updating password, password not changed'; @@ -109,7 +109,7 @@ $string['informminpassworddigits'] = 'at least {$a} digit(s)'; $string['informminpasswordlength'] = 'at least {$a} characters'; $string['informminpasswordlower'] = 'at least {$a} lower case letter(s)'; -$string['informminpasswordnonalphanum'] = 'at least {$a} special character(s) such as as *, -, or #'; +$string['informminpasswordnonalphanum'] = 'at least {$a} special character(s) such as *, -, or #'; $string['informminpasswordreuselimit'] = 'Passwords can be reused after {$a} changes'; $string['informminpasswordupper'] = 'at least {$a} upper case letter(s)'; $string['informpasswordpolicy'] = 'The password must have {$a}'; diff --git a/lang/en/completion.php b/lang/en/completion.php index 41aec434b905f..e50a561b63828 100644 --- a/lang/en/completion.php +++ b/lang/en/completion.php @@ -59,9 +59,9 @@ $string['completed'] = 'Completed'; $string['completeactivity'] = 'Complete the activity'; $string['completedunlocked'] = 'Completion options unlocked'; -$string['completedunlockedtext'] = 'When you save changes, completion state for all students will be erased. If you change your mind about this, do not save the form.'; +$string['completedunlockedtext'] = 'When you save this form, completion data will be deleted then recalculated where possible. To avoid this, you can navigate away without saving.'; $string['completedwarning'] = 'Completion options locked'; -$string['completedwarningtext'] = 'This activity has already been marked as completed for {$a} participant(s). Changing completion options will erase their completion state and may cause confusion. Thus the options have been locked and should not be unlocked unless absolutely necessary.'; +$string['completedwarningtext'] = 'This activity is marked as done for one or more students. If you unlock the settings to make a change then save the form, completion data will be deleted then recalculated where possible. Manual completion can\'t be recalculated, so in this case the student will need to mark it as done again.'; $string['completion'] = 'Completion tracking'; $string['completion-alt-auto-enabled'] = 'The system marks this item complete according to conditions: {$a}'; $string['completion-alt-auto-fail'] = 'Completed: {$a} (did not achieve pass grade)'; @@ -154,7 +154,7 @@ $string['hiddenrules'] = 'Some settings specific to {$a} have been hidden. To view unselect other activities'; $string['editcoursecompletionsettings'] = 'Edit course completion settings'; $string['enablecompletion'] = 'Enable completion tracking'; -$string['enablecompletion_help'] = 'If enabled, activity completion conditions may be set in the activity settings and/or course completion conditions may be set. It is recommended to have this enabled so that meaningful data is displayed in the course overview on the Dashboard.'; +$string['enablecompletion_help'] = 'If enabled, you can set conditions for activity completion or course completion.'; $string['enrolmentduration'] = 'Enrolment duration'; $string['enrolmentdurationlength'] = 'User must remain enrolled for'; $string['err_noactivities'] = 'Completion information is not enabled for any activity, so none can be displayed. You can enable completion information by editing the settings for an activity.'; @@ -232,7 +232,7 @@ $string['self'] = 'Self'; $string['selfcompletion'] = 'Self completion'; $string['showcompletionconditions'] = 'Show activity completion conditions'; -$string['showcompletionconditions_help'] = 'Activity completion conditions are always shown on the activity page. This setting determines whether activity completion conditions are also shown below each activity on the course page.'; +$string['showcompletionconditions_help'] = 'Show activity completion conditions on the course page.'; $string['showinguser'] = 'Showing user'; $string['timecompleted'] = 'Time completed'; $string['todo'] = 'To do'; diff --git a/lang/en/group.php b/lang/en/group.php index f93966a7950c2..6b826c108304f 100644 --- a/lang/en/group.php +++ b/lang/en/group.php @@ -111,15 +111,13 @@ $string['groupmemberssee'] = 'See group members'; $string['groupmembersselected'] = 'Members of selected group'; $string['groupmode'] = 'Group mode'; -$string['groupmode_help'] = 'This setting has 3 options: +$string['groupmode_help'] = '* No groups +* Separate groups: Students are divided into groups and can only see their group\'s work. +* Visible groups: Students are divided into groups, but can see the work of other groups. -* No groups -* Separate groups - Each group member can only see their own group, others are invisible -* Visible groups - Each group member works in their own group, but can also see other groups - -The group mode defined at course level is the default mode for all activities within the course. Each activity that supports groups can also define its own group mode, though if the group mode is forced at course level, the group mode setting for each activity is ignored.'; +The group mode set at course level is the default mode for all activities. If the group mode is forced at course level, it can\'t be changed in an activity.'; $string['groupmodeforce'] = 'Force group mode'; -$string['groupmodeforce_help'] = 'If group mode is forced, then the course group mode is applied to every activity in the course. Group mode settings in each activity are then ignored.'; +$string['groupmodeforce_help'] = 'The group mode is enforced for all activities and can\'t be changed in an activity.'; $string['groupmy'] = 'My group'; $string['groupname'] = 'Group name'; $string['groupnameexists'] = 'The group name \'{$a}\' already exists in this course, please choose another one.'; diff --git a/lang/en/moodle.php b/lang/en/moodle.php index fe115e483b81d..56cac3da31d17 100644 --- a/lang/en/moodle.php +++ b/lang/en/moodle.php @@ -352,7 +352,7 @@ $string['coursecreatorsdescription'] = 'Course creators can create new courses.'; $string['coursedetails'] = 'Course details'; $string['coursedisplay'] = 'Course layout'; -$string['coursedisplay_help'] = 'This setting determines whether the whole course is displayed on one page or split over several pages.'; +$string['coursedisplay_help'] = 'Course sections can be displayed all in one page or split over several pages.'; $string['coursedisplay_single'] = 'Show all sections on one page'; $string['coursedisplay_multi'] = 'Show one section per page'; $string['coursedeleted'] = 'Deleted course {$a}'; @@ -379,7 +379,7 @@ $string['courseoverviewfiles'] = 'Course image'; $string['courseoverviewfilesext'] = 'Course image file extensions'; $string['courseoverviewfileslimit'] = 'Course image files limit'; -$string['courseoverviewfiles_help'] = 'The course image is displayed in the course overview on the Dashboard. Additional accepted file types and more than one file may be enabled by a site administrator. If so, these files will be displayed next to the course summary on the list of courses page.'; +$string['courseoverviewfiles_help'] = 'The course image is displayed in My courses and in the list of available courses on the Home page. Site administrators can enable additional file types and the use of multiple files. If you use multiple files, they will all be displayed in the list of available courses in the Home page.'; $string['courseinfo'] = 'Course info'; $string['coursenotaccessible'] = 'This course does not allow public access'; $string['courselegacyfiles'] = 'Legacy course files'; @@ -425,11 +425,12 @@ * -word - don\'t include results containing this word.'; $string['coursestart'] = 'Course start'; $string['coursesummary'] = 'Course summary'; -$string['coursesummary_help'] = 'The course summary is displayed in the list of courses. A course search searches course summary text in addition to course names.'; +$string['coursesummary_help'] = 'A short description of your course. The content you add here is searchable.'; $string['coursetitle'] = 'Course: {$a->course}'; $string['courseupdates'] = 'Course updates'; $string['coursevisibility'] = 'Course visibility'; -$string['coursevisibility_help'] = 'This setting determines whether the course appears in the list of courses and whether students can access it. If set to Hide, then access is restricted to users with the capability to view hidden courses (such as teachers).'; +$string['coursevisibility_help'] = '* Show: The course appears in the list of courses and students can access it. +* Hide: Access is restricted to teachers and other users with the capability to view hidden courses.'; $string['create'] = 'Create'; $string['createaccount'] = 'Create my new account'; $string['createcategory'] = 'Create category'; @@ -785,7 +786,7 @@ $string['enable'] = 'Enable'; $string['encryptedcode'] = 'Encrypted code'; $string['enddate'] = 'Course end date'; -$string['enddate_help'] = 'The course end date determines whether a course is listed as past in the course overview. The course end date may also be used by custom reports. Note that users can still enter the course after the end date; in other words the date does not restrict access.'; +$string['enddate_help'] = 'The end date doesn’t restrict student access to the course. It determines whether a course is listed as Past in My courses.'; $string['english'] = 'English'; $string['enrolmentmethods'] = 'Enrolment methods'; $string['entercourse'] = 'Click to enter this course'; @@ -944,7 +945,7 @@ $string['fulllistofcourses'] = 'All courses'; $string['fullname'] = 'Full name'; /* @deprecated - Use fullnamecourse or fullnameuser or some own context specific string. */ $string['fullnamecourse'] = 'Course full name'; -$string['fullnamecourse_help'] = 'The full name of the course is displayed at the top of each page in the course and in the list of courses.'; +$string['fullnamecourse_help'] = 'The name displayed in My courses and in the list of available courses on the Home page.'; $string['fullnamedisplay'] = '{$a->firstname} {$a->lastname}'; $string['fullnameuser'] = 'User full name'; $string['fullprofile'] = 'Full profile'; @@ -1011,7 +1012,7 @@ $string['hiddenfromstudents'] = 'Hidden from students'; $string['hiddenoncoursepage'] = 'Available but not shown on course page'; $string['hiddensections'] = 'Hidden sections'; -$string['hiddensections_help'] = 'Whether hidden sections are displayed to students as not available (perhaps for a course in weekly format to indicate holidays) or are completely hidden.'; +$string['hiddensections_help'] = 'Hidden sections can be displayed to students as \'not available\' on the course page, or kept completely hidden.'; $string['hiddensectionscollapsed'] = 'Hidden sections are shown as not available'; $string['hiddensectionsinvisible'] = 'Hidden sections are completely invisible'; $string['hiddenwithbrackets'] = '(hidden)'; @@ -1081,7 +1082,7 @@ $string['icon'] = 'Icon'; $string['idnumber'] = 'ID number'; $string['idnumbercourse'] = 'Course ID number'; -$string['idnumbercourse_help'] = 'The ID number of a course is only used when matching the course against external systems and is not displayed anywhere on the site. If the course has an official code name it may be entered, otherwise the field can be left blank.'; +$string['idnumbercourse_help'] = 'Only used to connect the course with external systems. Leave blank if you don\'t have an official course ID.'; $string['idnumbergroup'] = 'Group ID number'; $string['idnumbergroup_help'] = 'The ID number of a group is only used when matching the group against external systems and is not displayed anywhere on the site. If the group has an official code name it may be entered, otherwise the field can be left blank.'; $string['idnumbergrouping'] = 'Grouping ID number'; @@ -1263,7 +1264,7 @@ $string['maximumgradex'] = 'Maximum grade: {$a}'; $string['maximumshort'] = 'Max'; $string['maximumupload'] = 'Maximum upload size'; -$string['maximumupload_help'] = 'This setting determines the largest size of file that can be uploaded to the course, limited by the site-wide setting set by an administrator. Activity modules also include a maximum upload size setting for further restricting the file size.'; +$string['maximumupload_help'] = 'The maximum file size allowed for student uploads to the course. Additionally, you can further restrict the maximum upload size for each activity.'; $string['maxnumberweeks'] = 'Maximum number of sections'; $string['maxnumberweeks_desc'] = 'The maximum value in the number of sections drop-down menu (applies to certain course formats only).'; $string['maxnumcoursesincombo'] = 'Browse {$a->numberofcourses} courses.'; @@ -1455,11 +1456,11 @@ $string['newsitem'] = 'news item'; $string['newsitems'] = 'announcements'; $string['newsitemsnumber'] = 'Number of announcements'; -$string['newsitemsnumber_help'] = 'The announcements forum is a special forum which is created automatically in the course, has forced subscription set by default, and only users with appropriate permissions (by default teachers) can post in it. +$string['newsitemsnumber_help'] = 'The announcements forum has forced subscription set by default. Only teachers and other users with appropriate permission can post in it. -This setting determines how many recent announcements appear in the latest announcements block. +If you don\'t require an announcements forum, set this to zero. -If an announcements forum is not required in the course, this setting should be set to zero.'; +This setting also determines how many recent announcements appear in the latest announcements block.'; $string['newuser'] = 'New user'; $string['newusernewpasswordsubj'] = 'New user account'; $string['newusernewpasswordtext'] = 'Hi {$a->firstname}, @@ -1963,14 +1964,14 @@ $string['shortname'] = 'Short name'; /* @deprecated MDL-34652 - Use shortnamecourse or shortnameuser or some own context specific string. */ $string['shortnamecollisionwarning'] = '[*] = This shortname is already in use by a course and will need to be changed upon approval'; $string['shortnamecourse'] = 'Course short name'; -$string['shortnamecourse_help'] = 'The short name of the course is displayed in the navigation and is used in the subject line of course email messages.'; +$string['shortnamecourse_help'] = 'The name displayed in areas with limited space. It must be unique.'; $string['shortnametaken'] = 'Short name is already used for another course ({$a})'; $string['shortnameuser'] = 'User short name'; $string['shortsitename'] = 'Short name for site (eg single word)'; $string['show'] = 'Show'; $string['showactions'] = 'Show actions'; $string['showactivitydates'] = 'Show activity dates'; -$string['showactivitydates_help'] = 'Activity dates are always shown on the activity page. This setting determines whether activity dates are also shown below each activity on the course page.'; +$string['showactivitydates_help'] = 'Show activity dates on the course page.'; $string['showadvancededitor'] = 'Advanced'; $string['showadvancedsettings'] = 'Show advanced settings'; $string['showall'] = 'Show all {$a}'; @@ -1984,7 +1985,7 @@ $string['showdescription'] = 'Display description on course page'; $string['showdescription_help'] = 'If enabled, the description above will be displayed on the course page just below the link to the activity or resource.'; $string['showgrades'] = 'Show gradebook to students'; -$string['showgrades_help'] = 'Many activities allow grades to be set. This setting determines whether a student can view a list of all their grades in the course.'; +$string['showgrades_help'] = 'Allow students to view a list of all their grades in the course.'; $string['showingacourses'] = 'Showing all {$a} courses'; $string['showingxofycourses'] = 'Showing courses {$a->start} to {$a->end} of {$a->total} courses'; $string['showlistofcourses'] = 'Show list of courses'; @@ -1995,7 +1996,7 @@ $string['showpopoverwindow'] = 'Show popover window'; $string['showrecent'] = 'Show recent activity'; $string['showreports'] = 'Show activity reports'; -$string['showreports_help'] = 'Activity reports are available for each participant that show their activity in the course. As well as listings of their contributions, such as forum posts or assignment submissions, these reports also include access logs. This setting determines whether a student can view their own activity reports via their profile page.'; +$string['showreports_help'] = 'Allow students to view their own activity reports via their profile page.'; $string['showsettings'] = 'Show settings'; $string['showtheselogs'] = 'Show these logs'; $string['showthishelpinlanguage'] = 'Show this help in language: {$a}'; @@ -2047,7 +2048,9 @@ $string['standard'] = 'Standard'; $string['starpending'] = '([*] = course pending approval)'; $string['startdate'] = 'Course start date'; -$string['startdate_help'] = 'This setting determines the start of the first week for a course in weekly format. It also determines the earliest date that logs of course activities are available for. If the course is reset and the course start date changed, all dates in the course will be moved in relation to the new start date.'; +$string['startdate_help'] = 'This date determines the start of the first week for a course in weekly format. It is also the start date for logs of course activities. + +If you reset the course and change this date, all dates in the course will be moved in relation to the new start date.'; $string['startingfrom'] = 'Starting from'; $string['startsignup'] = 'Create new account'; $string['state'] = 'State/Province'; diff --git a/repository/upload/lang/en/repository_upload.php b/repository/upload/lang/en/repository_upload.php index 00d3cfa28303d..c93b7c989254d 100644 --- a/repository/upload/lang/en/repository_upload.php +++ b/repository/upload/lang/en/repository_upload.php @@ -27,7 +27,7 @@ $string['pluginname_help'] = 'Upload a file to Moodle'; $string['pluginname'] = 'Upload a file'; $string['upload:view'] = 'Use uploading in file picker'; -$string['upload_error_ini_size'] = 'The uploaded file exceeds the upload_max_filesize directive in php.ini.'; +$string['upload_error_ini_size'] = 'The file is larger than the maximum size allowed.'; $string['upload_error_form_size'] = 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.'; $string['upload_error_partial'] = 'The uploaded file was only partially uploaded.'; $string['upload_error_no_file'] = 'No file was uploaded.'; From 961c7ef3e0e1c956be4371ca3c6d822cf93a0e24 Mon Sep 17 00:00:00 2001 From: Rodrigo Mady Date: Wed, 5 Jul 2023 10:51:09 -0300 Subject: [PATCH 41/61] MDL-78559 mod_chat: Fix chat report loading with TeX filter --- mod/chat/report.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mod/chat/report.php b/mod/chat/report.php index 050c1446daf16..07f37d05ebeab 100644 --- a/mod/chat/report.php +++ b/mod/chat/report.php @@ -51,12 +51,11 @@ throw new \moodle_exception('coursemisconf'); } +require_login($course, false, $cm); $context = context_module::instance($cm->id); $PAGE->set_context($context); $PAGE->set_heading($course->fullname); -require_login($course, false, $cm); - if (empty($chat->studentlogs) && !has_capability('mod/chat:readlog', $context)) { notice(get_string('nopermissiontoseethechatlog', 'chat')); } From 86ff18c9fedf9d5c9f81958bef71ad5b51089dcb Mon Sep 17 00:00:00 2001 From: AMOS bot Date: Tue, 8 Aug 2023 00:10:31 +0000 Subject: [PATCH 42/61] Automatically generated installer lang files --- install/lang/kk/moodle.php | 1 + 1 file changed, 1 insertion(+) diff --git a/install/lang/kk/moodle.php b/install/lang/kk/moodle.php index 395d9338acb04..71a4dc967d313 100644 --- a/install/lang/kk/moodle.php +++ b/install/lang/kk/moodle.php @@ -30,5 +30,6 @@ defined('MOODLE_INTERNAL') || die(); $string['language'] = 'Тіл'; +$string['moodlelogo'] = 'Moodle логотипі'; $string['next'] = 'Келесі'; $string['previous'] = 'Алдыңғы'; From 75de5704def67cdf39164e1a7189496456ff5231 Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Tue, 11 Jul 2023 15:29:28 +0100 Subject: [PATCH 43/61] MDL-76123 course: multilingual module icon alt text attribute. --- course/format/classes/output/local/content/cm/cmname.php | 6 ++---- course/format/templates/local/content/cm/cmname.mustache | 9 ++++++--- lang/en/moodle.php | 1 + 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/course/format/classes/output/local/content/cm/cmname.php b/course/format/classes/output/local/content/cm/cmname.php index 9c7c9ad321001..99825d10818e1 100644 --- a/course/format/classes/output/local/content/cm/cmname.php +++ b/course/format/classes/output/local/content/cm/cmname.php @@ -110,14 +110,12 @@ public function export_for_template(\renderer_base $output): array { 'iconclass' => $iconclass, 'modname' => $mod->modname, 'textclasses' => $displayoptions['textclasses'] ?? '', + 'pluginname' => get_string('pluginname', 'mod_' . $mod->modname), + 'showpluginname' => $this->format->show_editor(), 'purpose' => plugin_supports('mod', $mod->modname, FEATURE_MOD_PURPOSE, MOD_PURPOSE_OTHER), 'activityname' => $this->get_title_data($output), ]; - if ($this->format->show_editor()) { - $data['pluginname'] = get_string('pluginname', 'mod_' . $mod->modname); - } - return $data; } diff --git a/course/format/templates/local/content/cm/cmname.mustache b/course/format/templates/local/content/cm/cmname.mustache index 62389885190b8..c001b82477a0b 100644 --- a/course/format/templates/local/content/cm/cmname.mustache +++ b/course/format/templates/local/content/cm/cmname.mustache @@ -27,6 +27,7 @@ "icon": "../../../pix/help.svg", "iconclass": "", "pluginname": "File", + "showpluginname": 1, "textclasses": "", "purpose": "content", "modname": "resource", @@ -48,14 +49,16 @@
- {{{modname}}} icon + {{#cleanstr}} activityicon, moodle, {{{pluginname}}} {{/cleanstr}}
- {{#pluginname}} + {{#showpluginname}}
{{{pluginname}}}
- {{/pluginname}} + {{/showpluginname}}
{{#activityname}} {{$ core/inplace_editable }} diff --git a/lang/en/moodle.php b/lang/en/moodle.php index fe115e483b81d..e6c8dafc145d3 100644 --- a/lang/en/moodle.php +++ b/lang/en/moodle.php @@ -34,6 +34,7 @@ $string['activities_help'] = 'Activities, such as forums, quizzes and wikis, enable interactive content to be added to the course.'; $string['activity'] = 'Activity'; $string['activityclipboard'] = 'Moving this activity: {$a}'; +$string['activityicon'] = '{$a} icon'; $string['activityiscurrentlyhidden'] = 'Sorry, this activity is currently hidden'; $string['activityheader'] = 'Activity menu'; $string['activitymodule'] = 'Activity module'; From 737f657bade55fafdab68ea3b733114d51a2bf92 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Wed, 9 Aug 2023 09:05:23 +0800 Subject: [PATCH 44/61] MDL-78714 editor_tiny: Move TinyMCE import instructions to correct path --- lib/editor/tiny/{ => js/tinymce}/readme_moodle.md | 2 +- lib/editor/tiny/readme_moodle.txt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) rename lib/editor/tiny/{ => js/tinymce}/readme_moodle.md (99%) create mode 100644 lib/editor/tiny/readme_moodle.txt diff --git a/lib/editor/tiny/readme_moodle.md b/lib/editor/tiny/js/tinymce/readme_moodle.md similarity index 99% rename from lib/editor/tiny/readme_moodle.md rename to lib/editor/tiny/js/tinymce/readme_moodle.md index 72982c89cc5fb..592be1dfb4303 100644 --- a/lib/editor/tiny/readme_moodle.md +++ b/lib/editor/tiny/js/tinymce/readme_moodle.md @@ -10,7 +10,7 @@ 2. Check out a clean copy of TinyMCE of the target version. - ``` + ```../../ tinymce=`mktemp -d` cd "${tinymce}" git clone https://github.com/tinymce/tinymce.git diff --git a/lib/editor/tiny/readme_moodle.txt b/lib/editor/tiny/readme_moodle.txt new file mode 100644 index 0000000000000..016ae378cface --- /dev/null +++ b/lib/editor/tiny/readme_moodle.txt @@ -0,0 +1 @@ +For instructions on how to import TinyMCE into Moodle, see js/tinymce/readme_moodle.md. From e8eb8943e1f2490c3d0a9fbcb475dc763f0d0fe0 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Tue, 8 Aug 2023 14:17:16 +0800 Subject: [PATCH 45/61] MDL-78714 editor_tiny: Add xss_sanitize option to TinyMCE To address a potential data loss issue, a feature introduced in TinyMCE 6.4.0 to disable client-side XSS sanitisation must be backported. --- .../tiny/js/tinymce/plugins/media/plugin.js | 7 ++- .../js/tinymce/plugins/media/plugin.min.js | 2 +- lib/editor/tiny/js/tinymce/readme_moodle.md | 48 ++++++++++++++++--- .../tiny/js/tinymce/themes/silver/theme.js | 20 ++++---- .../js/tinymce/themes/silver/theme.min.js | 2 +- lib/editor/tiny/js/tinymce/tinymce.d.ts | 3 ++ lib/editor/tiny/js/tinymce/tinymce.js | 48 +++++++++++-------- lib/editor/tiny/js/tinymce/tinymce.min.js | 2 +- 8 files changed, 88 insertions(+), 44 deletions(-) diff --git a/lib/editor/tiny/js/tinymce/plugins/media/plugin.js b/lib/editor/tiny/js/tinymce/plugins/media/plugin.js index 0b53bc86c8c3d..e2c21661c7551 100644 --- a/lib/editor/tiny/js/tinymce/plugins/media/plugin.js +++ b/lib/editor/tiny/js/tinymce/plugins/media/plugin.js @@ -1044,8 +1044,13 @@ }; const parseAndSanitize = (editor, context, html) => { + const getEditorOption = editor.options.get; + const sanitize = getEditorOption('xss_sanitization'); const validate = shouldFilterHtml(editor); - return Parser(editor.schema, { validate }).parse(html, { context }); + return Parser(editor.schema, { + sanitize, + validate + }).parse(html, { context }); }; const setup$1 = editor => { diff --git a/lib/editor/tiny/js/tinymce/plugins/media/plugin.min.js b/lib/editor/tiny/js/tinymce/plugins/media/plugin.min.js index 400c66ef48510..09a09302c5efe 100644 --- a/lib/editor/tiny/js/tinymce/plugins/media/plugin.min.js +++ b/lib/editor/tiny/js/tinymce/plugins/media/plugin.min.js @@ -1,4 +1,4 @@ /** * TinyMCE version 6.2.0 (2022-09-08) */ -!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const t=e=>t=>(e=>{const t=typeof e;return null===e?"null":"object"===t&&Array.isArray(e)?"array":"object"===t&&(r=a=e,(o=String).prototype.isPrototypeOf(r)||a.constructor?.name===o.name)?"string":t;var r,a,o})(t)===e,r=t("string"),a=t("object"),o=t("array"),s=e=>!(e=>null==e)(e);class i{constructor(e,t){this.tag=e,this.value=t}static some(e){return new i(!0,e)}static none(){return i.singletonNone}fold(e,t){return this.tag?t(this.value):e()}isSome(){return this.tag}isNone(){return!this.tag}map(e){return this.tag?i.some(e(this.value)):i.none()}bind(e){return this.tag?e(this.value):i.none()}exists(e){return this.tag&&e(this.value)}forall(e){return!this.tag||e(this.value)}filter(e){return!this.tag||e(this.value)?this:i.none()}getOr(e){return this.tag?this.value:e}or(e){return this.tag?this:e}getOrThunk(e){return this.tag?this.value:e()}orThunk(e){return this.tag?this:e()}getOrDie(e){if(this.tag)return this.value;throw new Error(e??"Called getOrDie on None")}static from(e){return s(e)?i.some(e):i.none()}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(e){this.tag&&e(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}i.singletonNone=new i(!1);const c=Array.prototype.push,n=(e,t)=>{for(let r=0,a=e.length;r{const t=[];for(let r=0,a=e.length;rh(e,t)?i.from(e[t]):i.none(),h=(e,t)=>u.call(e,t),p=e=>t=>t.options.get(e),g=p("audio_template_callback"),b=p("video_template_callback"),w=p("iframe_template_callback"),f=p("media_live_embeds"),y=p("media_filter_html"),v=p("media_url_resolver"),x=p("media_alt_source"),_=p("media_poster"),j=p("media_dimensions");var k=tinymce.util.Tools.resolve("tinymce.util.Tools"),O=tinymce.util.Tools.resolve("tinymce.dom.DOMUtils"),A=tinymce.util.Tools.resolve("tinymce.html.DomParser");const S=O.DOM,C=e=>e.replace(/px$/,""),D=e=>{const t=e.attr("style"),r=t?S.parseStyle(t):{};return{type:"ephox-embed-iri",source:e.attr("data-ephox-embed-iri"),altsource:"",poster:"",width:d(r,"max-width").map(C).getOr(""),height:d(r,"max-height").map(C).getOr("")}},T=(e,t)=>{let r={};for(let a=A({validate:!1,forced_root_block:!1},t).parse(e);a;a=a.walk())if(1===a.type){const e=a.name;if(a.attr("data-ephox-embed-iri")){r=D(a);break}r.source||"param"!==e||(r.source=a.attr("movie")),"iframe"!==e&&"object"!==e&&"embed"!==e&&"video"!==e&&"audio"!==e||(r.type||(r.type=e),r=k.extend(a.attributes.map,r)),"script"===e&&(r={type:"script",source:a.attr("src")}),"source"===e&&(r.source?r.altsource||(r.altsource=a.attr("src")):r.source=a.attr("src")),"img"!==e||r.poster||(r.poster=a.attr("src"))}return r.source=r.source||r.src||"",r.altsource=r.altsource||"",r.poster=r.poster||"",r},$=e=>{const t=e.toLowerCase().split(".").pop()??"";return d({mp3:"audio/mpeg",m4a:"audio/x-m4a",wav:"audio/wav",mp4:"video/mp4",webm:"video/webm",ogg:"video/ogg",swf:"application/x-shockwave-flash"},t).getOr("")};var z=tinymce.util.Tools.resolve("tinymce.html.Node"),M=tinymce.util.Tools.resolve("tinymce.html.Serializer");const F=(e,t={})=>A({forced_root_block:!1,validate:!1,allow_conditional_comments:!0,...t},e),N=O.DOM,R=e=>/^[0-9.]+$/.test(e)?e+"px":e,U=(e,t)=>{const r=t.attr("style"),a=r?N.parseStyle(r):{};s(e.width)&&(a["max-width"]=R(e.width)),s(e.height)&&(a["max-height"]=R(e.height)),t.attr("style",N.serializeStyle(a))},P=["source","altsource"],E=(e,t,r,a)=>{let o=0,s=0;const i=F(a);i.addNodeFilter("source",(e=>o=e.length));const c=i.parse(e);for(let e=c;e;e=e.walk())if(1===e.type){const a=e.name;if(e.attr("data-ephox-embed-iri")){U(t,e);break}switch(a){case"video":case"object":case"embed":case"img":case"iframe":void 0!==t.height&&void 0!==t.width&&(e.attr("width",t.width),e.attr("height",t.height))}if(r)switch(a){case"video":e.attr("poster",t.poster),e.attr("src",null);for(let r=o;r<2;r++)if(t[P[r]]){const a=new z("source",1);a.attr("src",t[P[r]]),a.attr("type",t[P[r]+"mime"]||null),e.append(a)}break;case"iframe":e.attr("src",t.source);break;case"object":const r=e.getAll("img").length>0;if(t.poster&&!r){e.attr("src",t.poster);const r=new z("img",1);r.attr("src",t.poster),r.attr("width",t.width),r.attr("height",t.height),e.append(r)}break;case"source":if(s<2&&(e.attr("src",t[P[s]]),e.attr("type",t[P[s]+"mime"]||null),!t[P[s]])){e.remove();continue}s++;break;case"img":t.poster||e.remove()}}return M({},a).serialize(c)},L=[{regex:/youtu\.be\/([\w\-_\?&=.]+)/i,type:"iframe",w:560,h:314,url:"www.youtube.com/embed/$1",allowFullscreen:!0},{regex:/youtube\.com(.+)v=([^&]+)(&([a-z0-9&=\-_]+))?/i,type:"iframe",w:560,h:314,url:"www.youtube.com/embed/$2?$4",allowFullscreen:!0},{regex:/youtube.com\/embed\/([a-z0-9\?&=\-_]+)/i,type:"iframe",w:560,h:314,url:"www.youtube.com/embed/$1",allowFullscreen:!0},{regex:/vimeo\.com\/([0-9]+)/,type:"iframe",w:425,h:350,url:"player.vimeo.com/video/$1?title=0&byline=0&portrait=0&color=8dc7dc",allowFullscreen:!0},{regex:/vimeo\.com\/(.*)\/([0-9]+)/,type:"iframe",w:425,h:350,url:"player.vimeo.com/video/$2?title=0&byline=0",allowFullscreen:!0},{regex:/maps\.google\.([a-z]{2,3})\/maps\/(.+)msid=(.+)/,type:"iframe",w:425,h:350,url:'maps.google.com/maps/ms?msid=$2&output=embed"',allowFullscreen:!1},{regex:/dailymotion\.com\/video\/([^_]+)/,type:"iframe",w:480,h:270,url:"www.dailymotion.com/embed/video/$1",allowFullscreen:!0},{regex:/dai\.ly\/([^_]+)/,type:"iframe",w:480,h:270,url:"www.dailymotion.com/embed/video/$1",allowFullscreen:!0}],I=(e,t)=>{const r=(e=>{const t=e.match(/^(https?:\/\/|www\.)(.+)$/i);return t&&t.length>1?"www."===t[1]?"https://":t[1]:"https://"})(t),a=e.regex.exec(t);let o=r+e.url;if(s(a))for(let e=0;ea[e]?a[e]:""));return o.replace(/\?$/,"")},B=(e,t)=>{const r=k.extend({},t);if(!r.source&&(k.extend(r,T(r.embed??"",e.schema)),!r.source))return"";r.altsource||(r.altsource=""),r.poster||(r.poster=""),r.source=e.convertURL(r.source,"source"),r.altsource=e.convertURL(r.altsource,"source"),r.sourcemime=$(r.source),r.altsourcemime=$(r.altsource),r.poster=e.convertURL(r.poster,"poster");const a=(e=>{const t=L.filter((t=>t.regex.test(e)));return t.length>0?k.extend({},t[0],{url:I(t[0],e)}):null})(r.source);if(a&&(r.source=a.url,r.type=a.type,r.allowfullscreen=a.allowFullscreen,r.width=r.width||String(a.w),r.height=r.height||String(a.h)),r.embed)return E(r.embed,r,!0,e.schema);{const t=g(e),a=b(e),o=w(e);return r.width=r.width||"300",r.height=r.height||"150",k.each(r,((t,a)=>{r[a]=e.dom.encode(""+t)})),"iframe"===r.type?((e,t)=>{if(t)return t(e);{const t=e.allowfullscreen?' allowFullscreen="1"':"";return'"}})(r,o):"application/x-shockwave-flash"===r.sourcemime?(e=>{let t='';return e.poster&&(t+=''),t+="",t})(r):-1!==r.sourcemime.indexOf("audio")?((e,t)=>t?t(e):'")(r,t):"script"===r.type?(e=>'