diff --git a/.grunt/babel-plugin-add-module-to-define.js b/.grunt/babel-plugin-add-module-to-define.js index 2e76d31c3187c..804bb80fd2222 100644 --- a/.grunt/babel-plugin-add-module-to-define.js +++ b/.grunt/babel-plugin-add-module-to-define.js @@ -111,8 +111,22 @@ module.exports = ({template, types}) => { if (!this.seenDefine && path.get('callee').isIdentifier({name: 'define'})) { // We only want to modify the first instance of define that we find. this.seenDefine = true; + // Get the Moodle component for the file being processed. var moduleName = getModuleNameFromFileName(this.file.opts.filename); + + // The function signature of `define()` is: + // define = function (name, deps, callback) {...} + // Ensure that if the moduel supplied its own name that it is replaced. + if (path.node.arguments.length > 0) { + // Ensure that there is only one name. + if (path.node.arguments[0].type === 'StringLiteral') { + // eslint-disable-next-line + console.log(`Replacing module name '${path.node.arguments[0].extra.rawValue}' with ${moduleName}`); + path.node.arguments.shift(); + } + } + // Add the module name as the first argument to the define function. path.node.arguments.unshift(types.stringLiteral(moduleName)); // Add a space after the define function in the built file so that previous versions diff --git a/admin/purgecaches.php b/admin/purgecaches.php index b43aa7777a2d6..c9658bde8504b 100644 --- a/admin/purgecaches.php +++ b/admin/purgecaches.php @@ -38,12 +38,11 @@ if ($data = $form->get_data()) { // Valid request. Purge, and redirect the user back to where they came from. - $selected = $data->purgeselectedoptions; - purge_caches($selected); - if (isset($data->all)) { + purge_caches(); $message = get_string('purgecachesfinished', 'admin'); } else { + purge_caches($data->purgeselectedoptions); $message = get_string('purgeselectedcachesfinished', 'admin'); } diff --git a/admin/repository.php b/admin/repository.php index 97c54efcc85e4..cf8143480e680 100644 --- a/admin/repository.php +++ b/admin/repository.php @@ -142,6 +142,8 @@ function repository_action_url($repository) { $success = true; if (!$repoid = $type->create()) { $success = false; + } else { + add_to_config_log('repository_visibility', '', (int)$visible, $plugin); } $data = data_submitted(); } @@ -184,14 +186,14 @@ function repository_action_url($repository) { print_error('confirmsesskeybad', '', $baseurl); } $class = \core_plugin_manager::resolve_plugininfo_class('repository'); - $class::enable_plugin($repository, true); + $class::enable_plugin($repository, 1); $return = true; } else if ($action == 'hide') { if (!confirm_sesskey()) { print_error('confirmsesskeybad', '', $baseurl); } $class = \core_plugin_manager::resolve_plugininfo_class('repository'); - $class::enable_plugin($repository, false); + $class::enable_plugin($repository, 0); $return = true; } else if ($action == 'delete') { $repositorytype = repository::get_type_by_typename($repository); @@ -202,6 +204,8 @@ function repository_action_url($repository) { } if ($repositorytype->delete($downloadcontents)) { + // Include this information into config changes table. + add_to_config_log('repository_visibility', $repositorytype->get_visible(), '', $repository); core_plugin_manager::reset_caches(); redirect($baseurl); } else { diff --git a/admin/search.php b/admin/search.php index 88c2b35011e58..1bcf3c38c17cc 100644 --- a/admin/search.php +++ b/admin/search.php @@ -60,7 +60,6 @@ } $PAGE->has_secondary_navigation_setter(false); -$PAGE->set_primary_active_tab('siteadminnode'); // and finally, if we get here, then there are matching settings and we have to print a form // to modify them diff --git a/admin/templates/setting_configcolourpicker.mustache b/admin/templates/setting_configcolourpicker.mustache index 50b7da1011b52..7b52846f9b4e7 100644 --- a/admin/templates/setting_configcolourpicker.mustache +++ b/admin/templates/setting_configcolourpicker.mustache @@ -48,6 +48,6 @@ {{#haspreviewconfig}} - + {{/haspreviewconfig}} diff --git a/admin/tool/behat/tests/behat/i_am_on_page.feature b/admin/tool/behat/tests/behat/i_am_on_page.feature index 9c60a8b3da929..8f4abc23ac4a8 100644 --- a/admin/tool/behat/tests/behat/i_am_on_page.feature +++ b/admin/tool/behat/tests/behat/i_am_on_page.feature @@ -71,9 +71,9 @@ Feature: Use core page resolvers for the I am on the page steps Then I should see "" Examples: - | description | identifier | shouldsee | - | Admin page | "Admin notifications" | Check for available updates | - | Home page | Homepage | Course overview | + | description | identifier | shouldsee | + | Admin page | "Admin notifications" | Check for available updates | + | Home page | Homepage | Calendar | Scenario Outline: When I am on a named page logged in as When I am on the page logged in as admin @@ -82,4 +82,4 @@ Feature: Use core page resolvers for the I am on the page steps Examples: | description | identifier | shouldsee | | Admin page | "Admin notifications" | Check for available updates | - | Home page | Homepage | Course overview | + | Home page | Homepage | Calendar | diff --git a/admin/tool/dataprivacy/tests/behat/dataexport.feature b/admin/tool/dataprivacy/tests/behat/dataexport.feature index ce458a7df87d7..ff4f0f1354516 100644 --- a/admin/tool/dataprivacy/tests/behat/dataexport.feature +++ b/admin/tool/dataprivacy/tests/behat/dataexport.feature @@ -54,7 +54,7 @@ Feature: Data export from the privacy API And I reload the page And I should see "Download ready" in the "Victim User 1" "table_row" And I open the action menu in "Victim User 1" "table_row" - And following "Download" should download between "1" and "146000" bytes + And following "Download" should download between "1" and "150000" bytes And the following config values are set as admin: | privacyrequestexpiry | 1 | tool_dataprivacy | And I wait "1" seconds @@ -89,7 +89,7 @@ Feature: Data export from the privacy API And I reload the page And I should see "Download ready" in the "Export all of my personal data" "table_row" And I open the action menu in "Victim User 1" "table_row" - And following "Download" should download between "1" and "146000" bytes + And following "Download" should download between "1" and "150000" bytes And the following config values are set as admin: | privacyrequestexpiry | 1 | tool_dataprivacy | @@ -126,7 +126,7 @@ Feature: Data export from the privacy API And I reload the page And I should see "Download ready" in the "Victim User 1" "table_row" And I open the action menu in "Victim User 1" "table_row" - And following "Download" should download between "1" and "146000" bytes + And following "Download" should download between "1" and "150000" bytes And the following config values are set as admin: | privacyrequestexpiry | 1 | tool_dataprivacy | diff --git a/admin/tool/policy/templates/page_agreedocs.mustache b/admin/tool/policy/templates/page_agreedocs.mustache index 60efaa6838e28..046eb8338adf6 100644 --- a/admin/tool/policy/templates/page_agreedocs.mustache +++ b/admin/tool/policy/templates/page_agreedocs.mustache @@ -134,9 +134,9 @@ {{# str }}somefieldsrequired, form, {{/ str }}
- + {{#cancancel}} - + {{/cancancel}} diff --git a/admin/tool/policy/tests/behat/acceptances.feature b/admin/tool/policy/tests/behat/acceptances.feature index 85df22dbaa9a5..998049ca794c3 100644 --- a/admin/tool/policy/tests/behat/acceptances.feature +++ b/admin/tool/policy/tests/behat/acceptances.feature @@ -36,7 +36,7 @@ Feature: Viewing acceptances reports and accepting on behalf of other users And I press "Next" And I set the field "I agree to the This site policy" to "1" And I press "Next" - And I should see "Course overview" + And I should see "Calendar" And I log out And I log in as "manager" And I press "Next" @@ -82,7 +82,7 @@ Feature: Viewing acceptances reports and accepting on behalf of other users And I press "Next" And I set the field "I agree to the This site policy" to "1" And I press "Next" - And I should see "Course overview" + And I should see "Calendar" And I navigate to "Users > Privacy and policies > Manage policies" in site administration And I click on "1 of 4 (25%)" "link" in the "This site policy" "table_row" And I click on "Accept This site policy" "link" in the "User One" "table_row" @@ -111,7 +111,7 @@ Feature: Viewing acceptances reports and accepting on behalf of other users And I set the field "I agree to the This site policy" to "1" And I set the field "I agree to the This privacy policy" to "1" And I press "Next" - And I should see "Course overview" + And I should see "Calendar" And I log out And I log in as "manager" And I press "Next" @@ -300,7 +300,7 @@ Feature: Viewing acceptances reports and accepting on behalf of other users And I press "Next" And I set the field "I agree to the This site policy" to "1" And I press "Next" - And I should see "Course overview" + And I should see "Calendar" And I log out And I log in as "admin" And I navigate to "Users > Privacy and policies > Manage policies" in site administration @@ -311,4 +311,4 @@ Feature: Viewing acceptances reports and accepting on behalf of other users And I press "Continue" And I log out When I log in as "user1" - Then I should see "Course overview" + Then I should see "Calendar" diff --git a/admin/tool/usertours/tests/behat/create_tour.feature b/admin/tool/usertours/tests/behat/create_tour.feature index 92b676f3e6e96..b1e1b3edaac8f 100644 --- a/admin/tool/usertours/tests/behat/create_tour.feature +++ b/admin/tool/usertours/tests/behat/create_tour.feature @@ -20,7 +20,7 @@ Feature: Add a new user tour | Display in middle of page | Welcome | Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful | And I add steps to the "First tour" tour: | targettype | targetvalue_block | Title | Content | - | Block | Course overview | Course overview | This area shows you what's happening in some of your courses | + | Block | Timeline | Timeline | This is the Timeline. All of your upcoming activities can be found here | | Block | Calendar | Calendar | This is the Calendar. All of your assignments and due dates can be found here | And I add steps to the "First tour" tour: | targettype | targetvalue_selector | Title | Content | @@ -28,7 +28,7 @@ Feature: Add a new user tour When I am on homepage Then I should see "Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful" And I click on "Next" "button" in the "[data-role='flexitour-step']" "css_element" - And I should see "This area shows you what's happening in some of your courses" + And I should see "This is the Timeline. All of your upcoming activities can be found here" And I should not see "This is the Calendar. All of your assignments and due dates can be found here" And I click on "Next" "button" in the "[data-role='flexitour-step']" "css_element" And I should see "This is the Calendar. All of your assignments and due dates can be found here" @@ -95,7 +95,7 @@ Feature: Add a new user tour | Display in middle of page | Welcome | First step of the Tour | And I add steps to the "Steps tour" tour: | targettype | targetvalue_block | Title | Content | - | Block | Course overview | Course overview | Second step of the Tour | + | Block | Timeline | Timeline | Second step of the Tour | | Block | Calendar | Calendar | Third step of the Tour | When I am on homepage Then I should see "First step of the Tour" @@ -129,7 +129,7 @@ Feature: Add a new user tour | Display in middle of page | Welcome | First step of the Tour | And I add steps to the "Steps tour" tour: | targettype | targetvalue_block | Title | Content | - | Block | Course overview | Course overview | Second step of the Tour | + | Block | Timeline | Timeline | Second step of the Tour | | Block | Calendar | Calendar | Third step of the Tour | When I am on homepage Then I should see "First step of the Tour" diff --git a/admin/tool/usertours/tests/behat/tour_accessibility.feature b/admin/tool/usertours/tests/behat/tour_accessibility.feature index 6dc9d81b8ec67..dbb567d90b785 100644 --- a/admin/tool/usertours/tests/behat/tour_accessibility.feature +++ b/admin/tool/usertours/tests/behat/tour_accessibility.feature @@ -14,7 +14,7 @@ Feature: Apply accessibility to a tour And I add steps to the tour: | targettype | targetvalue_selector | Title | Content | | Selector | .usermenu | User menu | Next page | - | Selector | input,button | Page 2 | Next page | + | Selector | .navbar-brand | Page 2 | Next page | And I add steps to the tour: | targettype | Title | Content | | Display in middle of page | Page 3 | Final page. | @@ -66,11 +66,11 @@ Feature: Apply accessibility to a tour And I am on site homepage When I click on "Next" "button" And I click on "Next" "button" - Then "input[aria-describedby^='tour-step-tool_usertours'],button[aria-describedby^='tour-step-tool_usertours']" "css_element" should exist - And "input[tabindex],button[tabindex]" "css_element" should exist + Then ".navbar-brand[aria-describedby^='tour-step-tool_usertours']" "css_element" should exist + And ".navbar-brand[tabindex]" "css_element" should exist When I click on "Next" "button" - Then "input[aria-describedby^='tour-step-tool_usertours'],button[aria-describedby^='tour-step-tool_usertours']" "css_element" should not exist - And "input[tabindex]:not([tabindex='-1']),button[tabindex]:not([tabindex='-1'])" "css_element" should not exist + Then ".navbar-brand[aria-describedby^='tour-step-tool_usertours']" "css_element" should not exist + And ".navbar-brand[tabindex]:not([tabindex='-1'])" "css_element" should not exist When I click on "End tour" "button" - Then "input[aria-describedby^='tour-step-tool_usertours'],button[aria-describedby^='tour-step-tool_usertours']" "css_element" should not exist - And "input[tabindex]:not([tabindex='0']),button[tabindex]:not([tabindex='0'])" "css_element" should not exist + Then ".navbar-brand[aria-describedby^='tour-step-tool_usertours']" "css_element" should not exist + And ".navbar-brand[tabindex]:not([tabindex='0'])" "css_element" should not exist diff --git a/admin/tool/usertours/tests/behat/tour_navigation.feature b/admin/tool/usertours/tests/behat/tour_navigation.feature index 5e577d10c4002..12123d2322669 100644 --- a/admin/tool/usertours/tests/behat/tour_navigation.feature +++ b/admin/tool/usertours/tests/behat/tour_navigation.feature @@ -53,14 +53,14 @@ Feature: Steps can be navigated within a tour | Display in middle of page | Welcome | Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful | And I add steps to the "First tour" tour: | targettype | targetvalue_block | Title | Content | - | Block | Course overview | Course overview | This area shows you what's happening in some of your courses | + | Block | Timeline | Timeline | This is the Timeline. All of your upcoming activities can be found here | | Block | Calendar | Calendar | This is the Calendar. All of your assignments and due dates can be found here | When I am on homepage Then I should see "Skip tour" And I should see "Next (1/3)" And I click on "Next (1/3)" "button" in the "Welcome" "dialogue" And I should see "Skip tour" - And I click on "Next (2/3)" "button" in the "Course overview" "dialogue" + And I click on "Next (2/3)" "button" in the "Timeline" "dialogue" And I should see "End tour" @javascript diff --git a/badges/renderer.php b/badges/renderer.php index 36929f6a7656e..cc79994797dea 100644 --- a/badges/renderer.php +++ b/badges/renderer.php @@ -59,7 +59,8 @@ public function print_badges_list($badges, $userid, $profile = false, $external $name = html_writer::tag('span', $bname, array('class' => 'badge-name')); - $image = html_writer::empty_tag('img', ['src' => $imageurl, 'class' => 'badge-image', 'alt' => $badge->imagecaption]); + $imagecaption = $badge->imagecaption ?? ''; + $image = html_writer::empty_tag('img', ['src' => $imageurl, 'class' => 'badge-image', 'alt' => $imagecaption]); if (!empty($badge->dateexpire) && $badge->dateexpire < time()) { $image .= $this->output->pix_icon('i/expired', get_string('expireddate', 'badges', userdate($badge->dateexpire)), diff --git a/blocks/calendar_month/block_calendar_month.php b/blocks/calendar_month/block_calendar_month.php index 336e5953e58a3..fe3b998a6c8e2 100644 --- a/blocks/calendar_month/block_calendar_month.php +++ b/blocks/calendar_month/block_calendar_month.php @@ -59,10 +59,6 @@ public function get_content() { $renderer = $this->page->get_renderer('core_calendar'); $this->content->text .= $renderer->render_from_template($template, $data); - if ($this->page->course->id != SITEID) { - $this->content->text .= $renderer->event_filter(); - } - $options = [ 'showfullcalendarlink' => true ]; diff --git a/blocks/calendar_month/tests/behat/block_calendar_month.feature b/blocks/calendar_month/tests/behat/block_calendar_month.feature index 246044a95bbf1..33ba9904dfa60 100644 --- a/blocks/calendar_month/tests/behat/block_calendar_month.feature +++ b/blocks/calendar_month/tests/behat/block_calendar_month.feature @@ -38,25 +38,6 @@ Feature: Enable the calendar block in a course and test it's functionality And I hover over today in the mini-calendar block Then I should see "Site Event" - @javascript - Scenario: Filter site events in the calendar block - Given I log in as "admin" - And I create a calendar event with form data: - | id_eventtype | Site | - | id_name | Site Event | - And I log out - When I log in as "teacher1" - And I am on "Course 1" course homepage with editing mode on - And I add the "Calendar" block - And I create a calendar event with form data: - | id_eventtype | Course | - | id_name | Course Event | - And I am on "Course 1" course homepage - And I follow "Hide site events" - Then I should not see "Site Event" - And I hover over today in the mini-calendar block - And I should see "Course Event" - @javascript Scenario: View a course event in the calendar block Given I log in as "teacher1" @@ -69,24 +50,6 @@ Feature: Enable the calendar block in a course and test it's functionality And I hover over today in the mini-calendar block Then I should see "Course Event" - @javascript - Scenario: Filter course events in the calendar block - Given I log in as "teacher1" - And I am on "Course 1" course homepage with editing mode on - And I add the "Calendar" block - And I create a calendar event with form data: - | id_eventtype | Course | - | id_name | Course Event | - And I am on "Course 1" course homepage - And I create a calendar event with form data: - | id_eventtype | User | - | id_name | User Event | - And I am on "Course 1" course homepage - And I follow "Hide course events" - Then I should not see "Course Event" - And I hover over today in the mini-calendar block - And I should see "User Event" - @javascript Scenario: View a user event in the calendar block Given I log in as "teacher1" @@ -99,24 +62,6 @@ Feature: Enable the calendar block in a course and test it's functionality And I hover over today in the mini-calendar block Then I should see "User Event" - @javascript - Scenario: Filter user events in the calendar block - Given I log in as "teacher1" - And I am on "Course 1" course homepage with editing mode on - And I add the "Calendar" block - And I create a calendar event with form data: - | id_eventtype | Course | - | id_name | Course Event | - And I am on "Course 1" course homepage - And I create a calendar event with form data: - | id_eventtype | User | - | id_name | User Event | - When I am on "Course 1" course homepage - And I follow "Hide user events" - Then I should not see "User Event" - And I hover over today in the mini-calendar block - And I should see "Course Event" - @javascript Scenario: View a group event in the calendar block Given the following "groups" exist: @@ -153,42 +98,3 @@ Feature: Enable the calendar block in a course and test it's functionality And I log in as "student2" And I am on "Course 1" course homepage And I should not see "Group Event" - - @javascript - Scenario: Filter group events in the calendar block - Given the following "groups" exist: - | name | course | idnumber | - | Group 1 | C1 | G1 | - | Group 2 | C1 | G2 | - And the following "group members" exist: - | user | group | - | student1 | G1 | - | student2 | G2 | - When I log in as "teacher1" - And I am on "Course 1" course homepage - And I navigate to "Settings" in current page administration - And I set the following fields to these values: - | id_groupmode | Separate groups | - | id_groupmodeforce | Yes | - And I press "Save and display" - And I turn editing mode on - And I add the "Calendar" block - And I create a calendar event with form data: - | id_eventtype | Course | - | id_name | Course Event 1 | - And I am on "Course 1" course homepage - And I click on "Full calendar" "link" - And I click on "New event" "button" - And I set the following fields to these values: - | id_eventtype | Group | - | id_name | Group Event 1 | - And I set the following fields to these values: - | Group | Group 1 | - And I press "Save" - And I log out - Then I log in as "student1" - And I am on "Course 1" course homepage - And I follow "Hide group events" - And I should not see "Group Event 1" - And I hover over today in the mini-calendar block - And I should see "Course Event 1" diff --git a/blocks/moodleblock.class.php b/blocks/moodleblock.class.php index 76de67eef6af5..d50478fecb70b 100644 --- a/blocks/moodleblock.class.php +++ b/blocks/moodleblock.class.php @@ -249,7 +249,7 @@ public function get_content_for_output($output) { $this->arialabel = $bc->arialabel; } - if ($this->page->user_is_editing()) { + if ($this->page->user_is_editing() && $this->instance_can_be_edited()) { $bc->controls = $this->page->blocks->edit_controls($this); } else { // we must not use is_empty on hidden blocks @@ -692,6 +692,15 @@ public function instance_can_be_collapsed() { return true; } + /** + * If overridden and set to false by the block it will not be editable. + * + * @return bool + */ + public function instance_can_be_edited() { + return true; + } + /** @callback callback functions for comments api */ public static function comment_template($options) { $ret = << $configs, ]; } + + /** + * Disable block editing on the my courses page. + * + * @return boolean + */ + public function instance_can_be_edited() { + if ($this->page->blocks->is_known_region(BLOCK_POS_LEFT) || $this->page->blocks->is_known_region(BLOCK_POS_RIGHT)) { + return true; + } else { + return false; + } + } } diff --git a/blocks/myoverview/db/upgrade.php b/blocks/myoverview/db/upgrade.php index 823981818bf99..47160e4684773 100644 --- a/blocks/myoverview/db/upgrade.php +++ b/blocks/myoverview/db/upgrade.php @@ -25,6 +25,8 @@ defined('MOODLE_INTERNAL') || die(); +require_once($CFG->dirroot . '/my/lib.php'); + /** * Upgrade code for the MyOverview block. * @@ -81,5 +83,77 @@ function xmldb_block_myoverview_upgrade($oldversion) { // Automatically generated Moodle v3.9.0 release upgrade line. // Put any upgrade step following this. + if ($oldversion < 2021052504) { + /** + * Small helper function for this version upgrade to delete instances of this block. + * + * @param stdClass $instance DB record of a block that we need to delete within Moodle. + */ + function delete_block_instance(stdClass $instance) { + global $DB; + if ($instance) { + list($sql, $params) = $DB->get_in_or_equal($instance->id, SQL_PARAMS_NAMED); + $params['contextlevel'] = CONTEXT_BLOCK; + $DB->delete_records_select('context', "contextlevel=:contextlevel AND instanceid " . $sql, $params); + $DB->delete_records('block_positions', ['blockinstanceid' => $instance->id]); + $DB->delete_records('block_instances', ['id' => $instance->id]); + $DB->delete_records_list('user_preferences', 'name', + ['block' . $instance->id . 'hidden', 'docked_block_instance_' . $instance->id]); + } + } + + // Delete the default indexsys version of the block. + $mysubpagepattern = $DB->get_record( + 'my_pages', + ['userid' => null, 'name' => MY_PAGE_DEFAULT, 'private' => MY_PAGE_PRIVATE], + 'id', + IGNORE_MULTIPLE + )->id; + $instances = $DB->get_records('block_instances', ['blockname' => 'myoverview', + 'pagetypepattern' => 'my-index', 'subpagepattern' => $mysubpagepattern]); + foreach ($instances as $instance) { + delete_block_instance($instance); + } + + // Begin looking for any and all instances of course overview in customised /my pages. + $pageselect = 'name = :name and private = :private and userid IS NOT NULL'; + $pageparams['name'] = MY_PAGE_DEFAULT; + $pageparams['private'] = MY_PAGE_PRIVATE; + + $pages = $DB->get_recordset_select('my_pages', $pageselect, $pageparams); + foreach ($pages as $page) { + $blocksql = 'blockname = :blockname and pagetypepattern = :pagetypepattern and subpagepattern = :subpagepattern'; + $blockparams['blockname'] = 'myoverview'; + $blockparams['pagetypepattern'] = 'my-index'; + $blockparams['subpagepattern'] = $page->id; + $instances = $DB->get_records_select('block_instances', $blocksql, $blockparams); + foreach ($instances as $instance) { + delete_block_instance($instance); + } + } + $pages->close(); + + // Add new instance to the /my/courses.php page. + $subpagepattern = $DB->get_record( + 'my_pages', + ['userid' => null, 'name' => MY_PAGE_COURSES, 'private' => MY_PAGE_PUBLIC], + 'id', + IGNORE_MULTIPLE + )->id; + + // See if this block already somehow exists, it should not but who knows. + if (!$DB->get_record('block_instances', ['blockname' => 'myoverview', + 'pagetypepattern' => 'my-index', 'subpagepattern' => $subpagepattern])) { + $page = new moodle_page(); + $systemcontext = context_system::instance(); + $page->set_context($systemcontext); + // Add the block to the default /my/courses. + $page->blocks->add_region('content'); + $page->blocks->add_block('myoverview', 'content', 0, false, 'my-index', $subpagepattern); + } + + upgrade_block_savepoint(true, 2021052504, 'myoverview', false); + } + return true; } diff --git a/blocks/myoverview/lib.php b/blocks/myoverview/lib.php index 7080d73f4b63a..8352f63ab05a5 100644 --- a/blocks/myoverview/lib.php +++ b/blocks/myoverview/lib.php @@ -101,7 +101,7 @@ function block_myoverview_user_preferences() { $preferences['block_myoverview_user_sort_preference'] = array( 'null' => NULL_NOT_ALLOWED, - 'default' => BLOCK_MYOVERVIEW_SORTING_TITLE, + 'default' => BLOCK_MYOVERVIEW_SORTING_LASTACCESSED, 'type' => PARAM_ALPHA, 'choices' => array( BLOCK_MYOVERVIEW_SORTING_TITLE, diff --git a/blocks/myoverview/tests/behat/block_myoverview_adminsettings.feature b/blocks/myoverview/tests/behat/block_myoverview_adminsettings.feature index e9356fabd5bdf..92a51af321bd9 100644 --- a/blocks/myoverview/tests/behat/block_myoverview_adminsettings.feature +++ b/blocks/myoverview/tests/behat/block_myoverview_adminsettings.feature @@ -32,7 +32,7 @@ Feature: The my overview block allows admins to easily configure the students' c And I set the field "All" to "1" And I press "Save" And I log out - Then I log in as "student1" + Then I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" # We have to check for the data attribute instead of the list element text as we would get false positives from the "All (except removed from view)" element otherwise Then "[data-value='allincludinghidden']" "css_element" should exist in the ".block_myoverview .dropdown-menu" "css_element" @@ -43,7 +43,7 @@ Feature: The my overview block allows admins to easily configure the students' c And I set the field "All" to "0" And I press "Save" And I log out - Then I log in as "student1" + Then I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" # We have to check for the data attribute instead of the list element text as we would get false negatives "All (except removed from view)" element otherwise Then "[data-value='allincludinghidden']" "css_element" should not exist in the ".block_myoverview .dropdown-menu" "css_element" @@ -54,7 +54,7 @@ Feature: The my overview block allows admins to easily configure the students' c And I set the field "All (except removed from view)" to "1" And I press "Save" And I log out - Then I log in as "student1" + Then I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" Then "All (except removed from view)" "list_item" should exist in the ".block_myoverview .dropdown-menu" "css_element" @@ -64,7 +64,7 @@ Feature: The my overview block allows admins to easily configure the students' c And I set the field "All (except removed from view)" to "0" And I press "Save" And I log out - Then I log in as "student1" + Then I am on the "My courses" page logged in as "student1" # 'All (except removed from view)' option has been disabled, so the button is falling back to the 'In progress' option which is the next enabled option. And I click on "In progress" "button" in the "Course overview" "block" Then "All (except removed from view)" "list_item" should not exist in the ".block_myoverview .dropdown-menu" "css_element" @@ -75,7 +75,7 @@ Feature: The my overview block allows admins to easily configure the students' c And I set the field "In progress" to "1" And I press "Save" And I log out - Then I log in as "student1" + Then I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" Then "In progress" "list_item" should exist in the ".block_myoverview .dropdown-menu" "css_element" @@ -85,7 +85,7 @@ Feature: The my overview block allows admins to easily configure the students' c And I set the field "In progress" to "0" And I press "Save" And I log out - Then I log in as "student1" + Then I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" Then "In progress" "list_item" should not exist in the ".block_myoverview .dropdown-menu" "css_element" @@ -95,7 +95,7 @@ Feature: The my overview block allows admins to easily configure the students' c And I set the field "Future" to "1" And I press "Save" And I log out - Then I log in as "student1" + Then I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" Then "Future" "list_item" should exist in the ".block_myoverview .dropdown-menu" "css_element" @@ -105,7 +105,7 @@ Feature: The my overview block allows admins to easily configure the students' c And I set the field "Future" to "0" And I press "Save" And I log out - Then I log in as "student1" + Then I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" Then "Future" "list_item" should not exist in the ".block_myoverview .dropdown-menu" "css_element" @@ -115,7 +115,7 @@ Feature: The my overview block allows admins to easily configure the students' c And I set the field "Past" to "1" And I press "Save" And I log out - Then I log in as "student1" + Then I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" Then "Past" "list_item" should exist in the ".block_myoverview .dropdown-menu" "css_element" @@ -125,7 +125,7 @@ Feature: The my overview block allows admins to easily configure the students' c And I set the field "Past" to "0" And I press "Save" And I log out - Then I log in as "student1" + Then I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" Then "Past" "list_item" should not exist in the ".block_myoverview .dropdown-menu" "css_element" @@ -135,7 +135,7 @@ Feature: The my overview block allows admins to easily configure the students' c And I set the field "Starred" to "1" And I press "Save" And I log out - Then I log in as "student1" + Then I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" Then "Starred" "list_item" should exist in the ".block_myoverview .dropdown-menu" "css_element" @@ -145,7 +145,7 @@ Feature: The my overview block allows admins to easily configure the students' c And I set the field "Starred" to "0" And I press "Save" And I log out - Then I log in as "student1" + Then I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" Then "Starred" "list_item" should not exist in the ".block_myoverview .dropdown-menu" "css_element" @@ -155,7 +155,7 @@ Feature: The my overview block allows admins to easily configure the students' c And I set the field "Removed from view" to "1" And I press "Save" And I log out - Then I log in as "student1" + Then I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" Then "Removed from view" "list_item" should exist in the ".block_myoverview .dropdown-menu" "css_element" @@ -165,7 +165,7 @@ Feature: The my overview block allows admins to easily configure the students' c And I set the field "Removed from view" to "0" And I press "Save" And I log out - Then I log in as "student1" + Then I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" Then "Removed from view" "list_item" should not exist in the ".block_myoverview .dropdown-menu" "css_element" @@ -181,7 +181,7 @@ Feature: The my overview block allows admins to easily configure the students' c And I set the field "Removed from view" to "0" And I press "Save" And I log out - And I log in as "student1" + And I am on the "My courses" page logged in as "student1" Then "button#groupingdropdown" "css_element" should not exist in the ".block_myoverview" "css_element" And I should see "Course 1" in the "Course overview" "block" And I should see "Course 2" in the "Course overview" "block" @@ -201,7 +201,7 @@ Feature: The my overview block allows admins to easily configure the students' c And I set the field "Removed from view" to "0" And I press "Save" And I log out - And I log in as "student1" + And I am on the "My courses" page logged in as "student1" Then "button#groupingdropdown" "css_element" should not exist in the ".block_myoverview" "css_element" And I should see "Course 2" in the "Course overview" "block" And I should see "Course 3" in the "Course overview" "block" diff --git a/blocks/myoverview/tests/behat/block_myoverview_customfield.feature b/blocks/myoverview/tests/behat/block_myoverview_customfield.feature index c866d5d0c227a..096b136895e65 100644 --- a/blocks/myoverview/tests/behat/block_myoverview_customfield.feature +++ b/blocks/myoverview/tests/behat/block_myoverview_customfield.feature @@ -35,7 +35,7 @@ Feature: The my overview block allows users to group courses by custom fields Given the following config values are set as admin: | displaygroupingcustomfield | 1 | block_myoverview | | customfiltergrouping | checkboxfield | block_myoverview | - And I log in as "student1" + And I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" When I click on "Checkbox field: Yes" "link" in the "Course overview" "block" Then I should see "Course 1" in the "Course overview" "block" @@ -48,7 +48,7 @@ Feature: The my overview block allows users to group courses by custom fields Given the following config values are set as admin: | displaygroupingcustomfield | 1 | block_myoverview | | customfiltergrouping | checkboxfield | block_myoverview | - And I log in as "student1" + And I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" When I click on "Checkbox field: No" "link" in the "Course overview" "block" Then I should not see "Course 1" in the "Course overview" "block" @@ -61,7 +61,7 @@ Feature: The my overview block allows users to group courses by custom fields Given the following config values are set as admin: | displaygroupingcustomfield | 1 | block_myoverview | | customfiltergrouping | datefield | block_myoverview | - And I log in as "student1" + And I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" When I click on "1 February 2001" "link" in the "Course overview" "block" Then I should see "Course 1" in the "Course overview" "block" @@ -74,7 +74,7 @@ Feature: The my overview block allows users to group courses by custom fields Given the following config values are set as admin: | displaygroupingcustomfield | 1 | block_myoverview | | customfiltergrouping | datefield | block_myoverview | - And I log in as "student1" + And I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" When I click on "6 August 1980" "link" in the "Course overview" "block" Then I should not see "Course 1" in the "Course overview" "block" @@ -87,7 +87,7 @@ Feature: The my overview block allows users to group courses by custom fields Given the following config values are set as admin: | displaygroupingcustomfield | 1 | block_myoverview | | customfiltergrouping | datefield | block_myoverview | - And I log in as "student1" + And I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" When I click on "No Date field" "link" in the "Course overview" "block" Then I should not see "Course 1" in the "Course overview" "block" @@ -100,7 +100,7 @@ Feature: The my overview block allows users to group courses by custom fields Given the following config values are set as admin: | displaygroupingcustomfield | 1 | block_myoverview | | customfiltergrouping | selectfield | block_myoverview | - And I log in as "student1" + And I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" And I should not see "Option 4" in the "Course overview" "block" When I click on "Option 1" "link" in the "Course overview" "block" @@ -114,7 +114,7 @@ Feature: The my overview block allows users to group courses by custom fields Given the following config values are set as admin: | displaygroupingcustomfield | 1 | block_myoverview | | customfiltergrouping | selectfield | block_myoverview | - And I log in as "student1" + And I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" When I click on "Option 2" "link" in the "Course overview" "block" Then I should not see "Course 1" in the "Course overview" "block" @@ -127,7 +127,7 @@ Feature: The my overview block allows users to group courses by custom fields Given the following config values are set as admin: | displaygroupingcustomfield | 1 | block_myoverview | | customfiltergrouping | selectfield | block_myoverview | - And I log in as "student1" + And I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" When I click on "No Select field" "link" in the "Course overview" "block" Then I should not see "Course 1" in the "Course overview" "block" @@ -140,7 +140,7 @@ Feature: The my overview block allows users to group courses by custom fields Given the following config values are set as admin: | displaygroupingcustomfield | 1 | block_myoverview | | customfiltergrouping | textfield | block_myoverview | - And I log in as "student1" + And I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" When I click on "fish" "link" in the "Course overview" "block" Then I should see "Course 1" in the "Course overview" "block" @@ -153,7 +153,7 @@ Feature: The my overview block allows users to group courses by custom fields Given the following config values are set as admin: | displaygroupingcustomfield | 1 | block_myoverview | | customfiltergrouping | textfield | block_myoverview | - And I log in as "student1" + And I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" When I click on "dog" "link" in the "Course overview" "block" Then I should not see "Course 1" in the "Course overview" "block" @@ -166,7 +166,7 @@ Feature: The my overview block allows users to group courses by custom fields Given the following config values are set as admin: | displaygroupingcustomfield | 1 | block_myoverview | | customfiltergrouping | textfield | block_myoverview | - And I log in as "student1" + And I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" When I click on "No Text field" "link" in the "Course overview" "block" Then I should not see "Course 1" in the "Course overview" "block" @@ -191,7 +191,7 @@ Feature: The my overview block allows users to group courses by custom fields And I set the field "Visible to" to "Nobody" And I press "Save changes" And I log out - When I log in as "student1" + When I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" Then I should not see "penguin" in the "Course overview" "block" Then I should not see "No text field" in the "Course overview" "block" diff --git a/blocks/myoverview/tests/behat/block_myoverview_dashboard.feature b/blocks/myoverview/tests/behat/block_myoverview_dashboard.feature index b1dcc7bd0b231..ce381bcb1c1a8 100644 --- a/blocks/myoverview/tests/behat/block_myoverview_dashboard.feature +++ b/blocks/myoverview/tests/behat/block_myoverview_dashboard.feature @@ -27,7 +27,7 @@ Feature: The my overview block allows users to easily access their courses | student1 | C5 | student | Scenario: View past courses - Given I log in as "student1" + Given I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" When I click on "Past" "link" in the "Course overview" "block" Then I should see "Course 1" in the "Course overview" "block" @@ -37,7 +37,7 @@ Feature: The my overview block allows users to easily access their courses And I should not see "Course 5" in the "Course overview" "block" Scenario: View future courses - Given I log in as "student1" + Given I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" When I click on "Future" "link" in the "Course overview" "block" Then I should see "Course 5" in the "Course overview" "block" @@ -47,7 +47,7 @@ Feature: The my overview block allows users to easily access their courses And I should not see "Course 4" in the "Course overview" "block" Scenario: View inprogress courses - Given I log in as "student1" + Given I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" When I click on "In progress" "link" in the "Course overview" "block" Then I should see "Course 2" in the "Course overview" "block" @@ -57,7 +57,7 @@ Feature: The my overview block allows users to easily access their courses And I should not see "Course 5" in the "Course overview" "block" Scenario: View all (except removed) courses - Given I log in as "student1" + Given I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" When I click on "All (except removed from view)" "link" in the "Course overview" "block" Then I should see "Course 1" in the "Course overview" "block" @@ -70,7 +70,7 @@ Feature: The my overview block allows users to easily access their courses Given the following config values are set as admin: | config | value | plugin | | displaygroupingallincludinghidden | 1 | block_myoverview | - And I log in as "student1" + And I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" # We have to click on the data attribute instead of the button element text as we might risk to click on the false positive "All (except removed from view)" element instead When I click on "[data-value='allincludinghidden']" "css_element" in the "Course overview" "block" @@ -81,7 +81,7 @@ Feature: The my overview block allows users to easily access their courses Then I should see "Course 5" in the "Course overview" "block" Scenario: View inprogress courses - test persistence - Given I log in as "student1" + Given I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" And I click on "In progress" "link" in the "Course overview" "block" And I reload the page @@ -93,7 +93,7 @@ Feature: The my overview block allows users to easily access their courses And I should not see "Course 5" in the "Course overview" "block" Scenario: View all (except removed) courses - w/ persistence - Given I log in as "student1" + Given I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" When I click on "All (except removed from view)" "link" in the "Course overview" "block" And I reload the page @@ -105,7 +105,7 @@ Feature: The my overview block allows users to easily access their courses Then I should see "Course 5" in the "Course overview" "block" Scenario: View past courses - w/ persistence - Given I log in as "student1" + Given I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" When I click on "Past" "link" in the "Course overview" "block" And I reload the page @@ -117,7 +117,7 @@ Feature: The my overview block allows users to easily access their courses And I should not see "Course 5" in the "Course overview" "block" Scenario: View future courses - w/ persistence - Given I log in as "student1" + Given I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" When I click on "Future" "link" in the "Course overview" "block" And I reload the page @@ -129,7 +129,7 @@ Feature: The my overview block allows users to easily access their courses And I should not see "Course 4" in the "Course overview" "block" Scenario: View favourite courses - w/ persistence - Given I log in as "student1" + Given I am on the "My courses" page logged in as "student1" And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element" And I click on "Star this course" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element" And I click on "All (except removed from view)" "button" in the "Course overview" "block" @@ -143,7 +143,7 @@ Feature: The my overview block allows users to easily access their courses And I should not see "Course 5" in the "Course overview" "block" Scenario: List display persistence - Given I log in as "student1" + Given I am on the "My courses" page logged in as "student1" And I click on "Display drop-down menu" "button" in the "Course overview" "block" And I click on "List" "link" in the "Course overview" "block" And I reload the page @@ -151,7 +151,7 @@ Feature: The my overview block allows users to easily access their courses And "[data-display='list']" "css_element" in the "Course overview" "block" should be visible Scenario: Cards display persistence - Given I log in as "student1" + Given I am on the "My courses" page logged in as "student1" And I click on "Display drop-down menu" "button" in the "Course overview" "block" And I click on "Card" "link" in the "Course overview" "block" And I reload the page @@ -159,7 +159,7 @@ Feature: The my overview block allows users to easily access their courses And "[data-display='card']" "css_element" in the "Course overview" "block" should be visible Scenario: Summary display persistence - Given I log in as "student1" + Given I am on the "My courses" page logged in as "student1" And I click on "Display drop-down menu" "button" in the "Course overview" "block" And I click on "Summary" "link" in the "Course overview" "block" And I reload the page @@ -167,7 +167,7 @@ Feature: The my overview block allows users to easily access their courses And "[data-display='summary']" "css_element" in the "Course overview" "block" should be visible Scenario: Course name sort persistence - Given I log in as "student1" + Given I am on the "My courses" page logged in as "student1" And I click on "sortingdropdown" "button" in the "Course overview" "block" And I click on "Course name" "link" in the "Course overview" "block" And I reload the page @@ -175,7 +175,7 @@ Feature: The my overview block allows users to easily access their courses And "[data-sort='fullname']" "css_element" in the "Course overview" "block" should be visible Scenario: Last accessed sort persistence - Given I log in as "student1" + Given I am on the "My courses" page logged in as "student1" And I click on "sortingdropdown" "button" in the "Course overview" "block" And I click on "Last accessed" "link" in the "Course overview" "block" And I reload the page @@ -183,7 +183,7 @@ Feature: The my overview block allows users to easily access their courses And "[data-sort='ul.timeaccess desc']" "css_element" in the "Course overview" "block" should be visible Scenario: Short name sort persistence - Given I log in as "student1" + Given I am on the "My courses" page logged in as "student1" When I click on "sortingdropdown" "button" in the "Course overview" "block" Then I should not see "Short name" in the "Course overview" "block" When the following config values are set as admin: @@ -197,7 +197,7 @@ Feature: The my overview block allows users to easily access their courses And "[data-sort='shortname']" "css_element" in the "Course overview" "block" should be visible Scenario: View inprogress courses with hide persistent functionality - Given I log in as "student1" + Given I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" When I click on "In progress" "link" in the "Course overview" "block" And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element" @@ -210,7 +210,7 @@ Feature: The my overview block allows users to easily access their courses And I should not see "Course 5" in the "Course overview" "block" Scenario: View past courses with hide persistent functionality - Given I log in as "student1" + Given I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" When I click on "Past" "link" in the "Course overview" "block" And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 1')]" "xpath_element" @@ -223,7 +223,7 @@ Feature: The my overview block allows users to easily access their courses And I should not see "Course 5" in the "Course overview" "block" Scenario: View future courses with hide persistent functionality - Given I log in as "student1" + Given I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" When I click on "Future" "link" in the "Course overview" "block" And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 5')]" "xpath_element" @@ -236,7 +236,7 @@ Feature: The my overview block allows users to easily access their courses And I should not see "Course 4" in the "Course overview" "block" Scenario: View all (except hidden) courses with hide persistent functionality - Given I log in as "student1" + Given I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" When I click on "All (except removed from view)" "link" in the "Course overview" "block" And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 5')]" "xpath_element" @@ -252,7 +252,7 @@ Feature: The my overview block allows users to easily access their courses Given the following config values are set as admin: | config | value | plugin | | displaygroupingallincludinghidden | 1 | block_myoverview | - And I log in as "student1" + And I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" # We have to click on the data attribute instead of the button element text as we might risk to click on the false positive "All (except removed from view)" element instead When I click on "[data-value='allincludinghidden']" "css_element" in the "Course overview" "block" @@ -268,7 +268,7 @@ Feature: The my overview block allows users to easily access their courses Scenario: Show course category in cards display Given the following config values are set as admin: | displaycategories | 1 | block_myoverview | - And I log in as "student1" + And I am on the "My courses" page logged in as "student1" And I click on "Display drop-down menu" "button" in the "Course overview" "block" When I click on "Card" "link" in the "Course overview" "block" Then I should see "Category 1" in the "Course overview" "block" @@ -276,7 +276,7 @@ Feature: The my overview block allows users to easily access their courses Scenario: Show course category in list display Given the following config values are set as admin: | displaycategories | 1 | block_myoverview | - And I log in as "student1" + And I am on the "My courses" page logged in as "student1" And I click on "Display drop-down menu" "button" in the "Course overview" "block" When I click on "List" "link" in the "Course overview" "block" Then I should see "Category 1" in the "Course overview" "block" @@ -284,7 +284,7 @@ Feature: The my overview block allows users to easily access their courses Scenario: Show course category in summary display Given the following config values are set as admin: | displaycategories | 1 | block_myoverview | - And I log in as "student1" + And I am on the "My courses" page logged in as "student1" And I click on "Display drop-down menu" "button" in the "Course overview" "block" When I click on "Summary" "link" in the "Course overview" "block" Then I should see "Category 1" in the "Course overview" "block" @@ -292,7 +292,7 @@ Feature: The my overview block allows users to easily access their courses Scenario: Hide course category in cards display Given the following config values are set as admin: | displaycategories | 0 | block_myoverview | - And I log in as "student1" + And I am on the "My courses" page logged in as "student1" And I click on "Display drop-down menu" "button" in the "Course overview" "block" When I click on "Card" "link" in the "Course overview" "block" Then I should not see "Category 1" in the "Course overview" "block" @@ -300,7 +300,7 @@ Feature: The my overview block allows users to easily access their courses Scenario: Hide course category in list display Given the following config values are set as admin: | displaycategories | 0 | block_myoverview | - And I log in as "student1" + And I am on the "My courses" page logged in as "student1" And I click on "Display drop-down menu" "button" in the "Course overview" "block" When I click on "List" "link" in the "Course overview" "block" Then I should not see "Category 1" in the "Course overview" "block" @@ -308,12 +308,12 @@ Feature: The my overview block allows users to easily access their courses Scenario: Show course category in summary display Given the following config values are set as admin: | displaycategories | 0 | block_myoverview | - And I log in as "student1" + And I am on the "My courses" page logged in as "student1" And I click on "Display drop-down menu" "button" in the "Course overview" "block" When I click on "Summary" "link" in the "Course overview" "block" Then I should not see "Category 1" in the "Course overview" "block" @accessibility Scenario: The dashboard page must have sufficient colour contrast - When I log in as "student1" + When I am on the "My courses" page logged in as "student1" Then the page should meet "wcag143" accessibility standards diff --git a/blocks/myoverview/tests/behat/block_myoverview_favourite.feature b/blocks/myoverview/tests/behat/block_myoverview_favourite.feature index fbf575663c275..71d140ea4ca61 100644 --- a/blocks/myoverview/tests/behat/block_myoverview_favourite.feature +++ b/blocks/myoverview/tests/behat/block_myoverview_favourite.feature @@ -24,7 +24,7 @@ Feature: The my overview block allows users to favourite their courses | student1 | C5 | student | Scenario: Favourite a course on a course card - Given I log in as "student1" + Given I am on the "My courses" page logged in as "student1" When I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element" And I click on "Star this course" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element" And I reload the page @@ -34,7 +34,7 @@ Feature: The my overview block allows users to favourite their courses And "//div[@class='card dashboard-card' and contains(.,'Course 3')]//span[@data-region='is-favourite' and @aria-hidden='true']" "xpath_element" should exist Scenario: Star a course and switch display to list - Given I log in as "student1" + Given I am on the "My courses" page logged in as "student1" When I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 5')]" "xpath_element" And I click on "Star this course" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 5')]" "xpath_element" And I reload the page @@ -47,7 +47,7 @@ Feature: The my overview block allows users to favourite their courses And "//li[contains(concat(' ', normalize-space(@class), ' '), 'list-group-item') and contains(.,'Course 3')]//span[@data-region='is-favourite' and @aria-hidden='true']" "xpath_element" should exist Scenario: Star a course and switch display to summary - Given I log in as "student1" + Given I am on the "My courses" page logged in as "student1" When I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 5')]" "xpath_element" And I click on "Star this course" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 5')]" "xpath_element" And I reload the page diff --git a/blocks/myoverview/tests/behat/block_myoverview_hidden.feature b/blocks/myoverview/tests/behat/block_myoverview_hidden.feature index 3c4a12de23b7e..e82f34bdf0d84 100644 --- a/blocks/myoverview/tests/behat/block_myoverview_hidden.feature +++ b/blocks/myoverview/tests/behat/block_myoverview_hidden.feature @@ -24,7 +24,7 @@ Feature: The my overview block allows users to hide their courses | student1 | C5 | student | Scenario: Test hide toggle functionality - Given I log in as "student1" + Given I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" When I click on "All (except removed from view)" "link" in the "Course overview" "block" And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element" @@ -33,7 +33,7 @@ Feature: The my overview block allows users to hide their courses Then I should not see "Course 2" in the "Course overview" "block" Scenario: Test hide toggle functionality w/ favorites - Given I log in as "student1" + Given I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" When I click on "All (except removed from view)" "link" in the "Course overview" "block" And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element" @@ -50,7 +50,7 @@ Feature: The my overview block allows users to hide their courses Then I should see "Course 2" in the "Course overview" "block" Scenario: Test show toggle functionality - Given I log in as "student1" + Given I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" When I click on "All (except removed from view)" "link" in the "Course overview" "block" And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element" @@ -65,7 +65,7 @@ Feature: The my overview block allows users to hide their courses Then I should see "Course 2" in the "Course overview" "block" Scenario: Test show toggle functionality w/ favorites - Given I log in as "student1" + Given I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" When I click on "All (except removed from view)" "link" in the "Course overview" "block" And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element" @@ -87,7 +87,7 @@ Feature: The my overview block allows users to hide their courses Then I should see "Course 2" in the "Course overview" "block" Scenario: Test a course is hidden directly with "All (except removed from view)" courses - Given I log in as "student1" + Given I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" When I click on "All (except removed from view)" "link" in the "Course overview" "block" And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element" @@ -98,7 +98,7 @@ Feature: The my overview block allows users to hide their courses Given the following config values are set as admin: | config | value | plugin | | displaygroupingallincludinghidden | 1 | block_myoverview | - And I log in as "student1" + And I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" # We have to click on the data attribute instead of the button element text as we might risk to click on the false positive "All (except hidden)" element instead When I click on "[data-value='allincludinghidden']" "css_element" in the "Course overview" "block" diff --git a/blocks/myoverview/tests/behat/block_myoverview_pagelimit_persistence.feature b/blocks/myoverview/tests/behat/block_myoverview_pagelimit_persistence.feature index fef9a8b936184..1822a2d7ab9cc 100644 --- a/blocks/myoverview/tests/behat/block_myoverview_pagelimit_persistence.feature +++ b/blocks/myoverview/tests/behat/block_myoverview_pagelimit_persistence.feature @@ -37,7 +37,7 @@ Feature: The my overview block allows users to persistence of their page limits | student1 | C13 | student | Scenario: Toggle the page limit between page reloads - Given I log in as "student1" + Given I am on the "My courses" page logged in as "student1" When I click on "[data-action='limit-toggle']" "css_element" in the "Course overview" "block" And I click on "All" "link" in the ".dropdown-menu.show" "css_element" Then I should see "Course 13" @@ -46,7 +46,7 @@ Feature: The my overview block allows users to persistence of their page limits And I should see "All" in the "[data-action='limit-toggle']" "css_element" Scenario: Toggle the page limit between grouping changes - Given I log in as "student1" + Given I am on the "My courses" page logged in as "student1" When I click on "[data-action='limit-toggle']" "css_element" in the "Course overview" "block" And I click on "All" "link" in the ".dropdown-menu.show" "css_element" And I click on "All (except removed from view)" "button" in the "Course overview" "block" diff --git a/blocks/myoverview/tests/behat/block_myoverview_pagination.feature b/blocks/myoverview/tests/behat/block_myoverview_pagination.feature index b3a6b514f478e..05f5ea63fe4c4 100644 --- a/blocks/myoverview/tests/behat/block_myoverview_pagination.feature +++ b/blocks/myoverview/tests/behat/block_myoverview_pagination.feature @@ -34,7 +34,7 @@ Feature: My overview block pagination | Course 25 | C25 | 0 | Scenario: The pagination controls should be hidden if I am not enrolled in any courses - When I log in as "student1" + When I am on the "My courses" page logged in as "student1" Then I should see "No courses" in the "Course overview" "block" And I should not see "Show" in the "Course overview" "block" And ".block_myoverview .dropdown-menu.show" "css_element" should not be visible @@ -57,7 +57,7 @@ Feature: My overview block pagination | student1 | C10 | student | | student1 | C11 | student | | student1 | C12 | student | - When I log in as "student1" + When I am on the "My courses" page logged in as "student1" Then I should not see "Show" in the "Course overview" "block" And ".block_myoverview .dropdown-menu.show" "css_element" should not be visible And ".block_myoverview [data-control='next']" "css_element" should not be visible @@ -80,7 +80,7 @@ Feature: My overview block pagination | student1 | C11 | student | | student1 | C12 | student | | student1 | C13 | student | - When I log in as "student1" + When I am on the "My courses" page logged in as "student1" Then I should see "12" in the "[data-action='limit-toggle']" "css_element" And I log out @@ -100,7 +100,7 @@ Feature: My overview block pagination | student1 | C11 | student | | student1 | C12 | student | | student1 | C13 | student | - And I log in as "student1" + And I am on the "My courses" page logged in as "student1" When I click on "[data-action='limit-toggle']" "css_element" in the "Course overview" "block" Then I should see "All" in the ".dropdown-menu.show" "css_element" And I should see "12" in the ".dropdown-menu.show" "css_element" @@ -125,7 +125,7 @@ Feature: My overview block pagination | student1 | C11 | student | | student1 | C12 | student | | student1 | C13 | student | - When I log in as "student1" + When I am on the "My courses" page logged in as "student1" Then the "class" attribute of ".block_myoverview [data-control='previous']" "css_element" should contain "disabled" And I log out @@ -145,7 +145,7 @@ Feature: My overview block pagination | student1 | C11 | student | | student1 | C12 | student | | student1 | C13 | student | - When I log in as "student1" + When I am on the "My courses" page logged in as "student1" And I click on "[data-control='next']" "css_element" in the "Course overview" "block" Then the "class" attribute of ".block_myoverview [data-control='next']" "css_element" should contain "disabled" And I log out @@ -178,7 +178,8 @@ Feature: My overview block pagination | student1 | C23 | student | | student1 | C24 | student | | student1 | C25 | student | - When I log in as "student1" + When I am on the "My courses" page logged in as "student1" + And I wait until ".block_myoverview [data-control='next']" "css_element" exists And I click on "[data-control='next']" "css_element" in the "Course overview" "block" Then the "class" attribute of ".block_myoverview [data-control='next']" "css_element" should not contain "disabled" And the "class" attribute of ".block_myoverview [data-control='previous']" "css_element" should not contain "disabled" diff --git a/blocks/myoverview/tests/behat/block_myoverview_progress.feature b/blocks/myoverview/tests/behat/block_myoverview_progress.feature index 30f133477d110..3d503a2ba07aa 100644 --- a/blocks/myoverview/tests/behat/block_myoverview_progress.feature +++ b/blocks/myoverview/tests/behat/block_myoverview_progress.feature @@ -21,7 +21,7 @@ Feature: Course overview block show users their progress on courses | student1 | C1 | student | Scenario: Course progress percentage should not be displayed if completion is not enabled - Given I log in as "student1" + Given I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" When I click on "All (except removed from view)" "link" in the "Course overview" "block" Then I should not see "0%" in the "Course overview" "block" @@ -34,12 +34,11 @@ Feature: Course overview block show users their progress on courses | id_completionview | 1 | And I press "Save and return to course" And I log out - When I log in as "student1" + When I am on the "My courses" page logged in as "student1" And I click on "All (except removed from view)" "button" in the "Course overview" "block" Then I should see "Course 1" in the "Course overview" "block" And I should see "0%" in the "Course overview" "block" - And I am on the "Test choice 1" "choice activity" page - And I follow "Dashboard" in the user menu + And I am on the "My courses" page And I click on "All (except removed from view)" "button" in the "Course overview" "block" And I should see "100%" in the "Course overview" "block" diff --git a/blocks/myoverview/tests/behat/block_myoverview_search.feature b/blocks/myoverview/tests/behat/block_myoverview_search.feature index 0878b639f4d0f..6f6214678d485 100644 --- a/blocks/myoverview/tests/behat/block_myoverview_search.feature +++ b/blocks/myoverview/tests/behat/block_myoverview_search.feature @@ -39,21 +39,21 @@ Feature: My overview block searching | student1 | C13 | student | Scenario: The search should return no courses if I am not enrolled in any - When I log in as "student2" + When I am on the "My courses" page logged in as "student2" Then I should see "No courses" in the "Course overview" "block" And I set the field "Search courses" to "Fake example" And I should see "No courses" in the "Course overview" "block" And I log out Scenario: Single page search - Given I log in as "student1" + Given I am on the "My courses" page logged in as "student1" And I set the field "Search courses" to "Course 0" Then I should see "Course 01" in the "Course overview" "block" And I should not see "Course 13" in the "Course overview" "block" And I log out Scenario: Paginated search - Given I log in as "student1" + Given I am on the "My courses" page logged in as "student1" And I set the field "Search courses" to "Course" And I should see "Course 01" in the "Course overview" "block" And I should not see "Course 13" in the "Course overview" "block" diff --git a/blocks/myoverview/tests/myoverview_test.php b/blocks/myoverview/tests/myoverview_test.php index eba4b29bc4adf..13f130cfc6f46 100644 --- a/blocks/myoverview/tests/myoverview_test.php +++ b/blocks/myoverview/tests/myoverview_test.php @@ -66,15 +66,15 @@ public function test_get_block_config_for_external() { $this->setUser($user); $context = context_user::instance($user->id); - if (!$currentpage = my_get_page($user->id, MY_PAGE_PRIVATE)) { + if (!$currentpage = my_get_page($user->id, MY_PAGE_PUBLIC, MY_PAGE_COURSES)) { throw new moodle_exception('mymoodlesetup'); } - $PAGE->set_url('/my/index.php'); // Need this because some internal API calls require the $PAGE url to be set. + $PAGE->set_url('/my/courses.php'); // Need this because some internal API calls require the $PAGE url to be set. $PAGE->set_context($context); $PAGE->set_pagelayout('mydashboard'); $PAGE->set_pagetype('my-index'); - $PAGE->blocks->add_region('content'); // Need to add this special regition to retrieve the central blocks. + $PAGE->blocks->add_region('content'); // Need to add this special region to retrieve the central blocks. $PAGE->set_subpage($currentpage->id); // Load the block instances for all the regions. diff --git a/blocks/myoverview/version.php b/blocks/myoverview/version.php index 988fee790f497..b8ed58d669a26 100644 --- a/blocks/myoverview/version.php +++ b/blocks/myoverview/version.php @@ -24,6 +24,6 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2021052503; // The current plugin version (Date: YYYYMMDDXX). +$plugin->version = 2021052504; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2021052500; // Requires this Moodle version. $plugin->component = 'block_myoverview'; // Full name of the plugin (used for diagnostics). diff --git a/blocks/tests/behat/configure_block_throughout_site.feature b/blocks/tests/behat/configure_block_throughout_site.feature index f633c84d57018..f6cfc09be4af1 100644 --- a/blocks/tests/behat/configure_block_throughout_site.feature +++ b/blocks/tests/behat/configure_block_throughout_site.feature @@ -70,4 +70,4 @@ Feature: Add and configure blocks throughout the site | Text block title | Foo " onload="document.getElementsByTagName('body')[0].remove()" alt=" | | Content | Example | When I press "Save changes" - Then I should see "Course overview" + Then I should see "Example" diff --git a/blocks/tests/externallib_test.php b/blocks/tests/externallib_test.php index 937b640a71b45..26904bceba7d3 100644 --- a/blocks/tests/externallib_test.php +++ b/blocks/tests/externallib_test.php @@ -327,9 +327,14 @@ public function test_get_dashboard_blocks_default_dashboard() { // Force a setting change to check the returned blocks settings. set_config('displaycategories', 0, 'block_myoverview'); + $systempage = $DB->get_record('my_pages', array('userid' => null, 'name' => MY_PAGE_DEFAULT, 'private' => true)); // Get the expected default blocks. - $alldefaultblocksordered = $DB->get_records_menu('block_instances', - array('pagetypepattern' => 'my-index'), 'defaultregion, defaultweight ASC', 'id, blockname'); + $alldefaultblocksordered = $DB->get_records_menu( + 'block_instances', + array('pagetypepattern' => 'my-index', 'subpagepattern' => $systempage->id), + 'defaultregion, defaultweight ASC', + 'id, blockname' + ); $this->setUser($user); @@ -368,8 +373,13 @@ public function test_get_dashboard_blocks_default_dashboard_including_sticky_blo $user = $this->getDataGenerator()->create_user(); $PAGE->set_url('/my/index.php'); // Need this because some internal API calls require the $PAGE url to be set. + $systempage = $DB->get_record('my_pages', array('userid' => null, 'name' => MY_PAGE_DEFAULT, 'private' => true)); // Get the expected default blocks. - $alldefaultblocks = $DB->get_records_menu('block_instances', array('pagetypepattern' => 'my-index'), '', 'id, blockname'); + $alldefaultblocks = $DB->get_records_menu( + 'block_instances', array('pagetypepattern' => 'my-index', 'subpagepattern' => $systempage->id), + '', + 'id, blockname' + ); // Now, add a sticky block. $page = new moodle_page(); @@ -411,8 +421,14 @@ public function test_get_dashboard_blocks_custom_user_dashboard() { $user = $this->getDataGenerator()->create_user(); $PAGE->set_url('/my/index.php'); // Need this because some internal API calls require the $PAGE url to be set. + $systempage = $DB->get_record('my_pages', array('userid' => null, 'name' => MY_PAGE_DEFAULT, 'private' => true)); // Get the expected default blocks. - $alldefaultblocks = $DB->get_records_menu('block_instances', array('pagetypepattern' => 'my-index'), '', 'id, blockname'); + $alldefaultblocks = $DB->get_records_menu( + 'block_instances', + array('pagetypepattern' => 'my-index', 'subpagepattern' => $systempage->id), + '', + 'id, blockname' + ); // Add a custom block. $page = new moodle_page(); diff --git a/blocks/timeline/templates/event-list-item.mustache b/blocks/timeline/templates/event-list-item.mustache index ad1365bff8a26..4d60eaa830db1 100644 --- a/blocks/timeline/templates/event-list-item.mustache +++ b/blocks/timeline/templates/event-list-item.mustache @@ -57,8 +57,8 @@
+ title="{{name}}" + aria-label='{{#cleanstr}} ariaeventlistitem, block_timeline, { "name": {{#quote}}{{{activityname}}}{{/quote}}, "course": {{#quote}}{{{course.fullnamedisplay}}}{{/quote}}, "date": "{{#userdate}} {{timesort}}, {{#str}} strftimedatetime, core_langconfig {{/str}} {{/userdate}}" } {{/cleanstr}}'> {{{activityname}}} {{#overdue}}{{#str}} overdue, block_timeline {{/str}}{{/overdue}}
diff --git a/calendar/export.php b/calendar/export.php index 59e785b18ab5d..cb48f4cc8b771 100644 --- a/calendar/export.php +++ b/calendar/export.php @@ -123,11 +123,13 @@ $pagetitle = get_string('export', 'calendar'); $PAGE->navbar->add(get_string('managesubscriptions', 'calendar'), $managesubscriptionsurl); -$PAGE->navbar->add($pagetitle); +$PAGE->navbar->add(get_string('exportcalendar', 'calendar'), $url); // Print title and header. +$headingstr = get_string('calendar', 'core_calendar'); +$headingstr = ($courseid != SITEID && !empty($courseid)) ? "{$headingstr}: {$COURSE->shortname}" : $headingstr; $PAGE->set_title($course->shortname.': '.get_string('calendar', 'calendar').': '.$pagetitle); -$PAGE->set_heading($course->fullname); +$PAGE->set_heading($headingstr); $PAGE->set_pagelayout('standard'); $renderer = $PAGE->get_renderer('core_calendar'); diff --git a/calendar/import.php b/calendar/import.php index 1e561b7009f66..52bb68eeb0677 100644 --- a/calendar/import.php +++ b/calendar/import.php @@ -98,14 +98,15 @@ } $heading = get_string('importcalendar', 'calendar'); +$headingstr = get_string('calendar', 'core_calendar'); +$headingstr = ($courseid != SITEID && !empty($courseid)) ? "{$headingstr}: {$COURSE->shortname}" : $headingstr; $pagetitle = $course->shortname . ': ' . get_string('calendar', 'calendar') . ': ' . $heading; $PAGE->set_title($pagetitle); -$PAGE->set_heading($heading); +$PAGE->set_heading($headingstr); $PAGE->set_url($pageurl); $PAGE->set_pagelayout('admin'); -$PAGE->navbar->add(get_string('managesubscriptions', 'calendar'), $managesubscriptionsurl); -$PAGE->navbar->add($heading); +$PAGE->navbar->add($heading, $pageurl); $renderer = $PAGE->get_renderer('core_calendar'); $customdata = [ diff --git a/calendar/managesubscriptions.php b/calendar/managesubscriptions.php index 316b1f6b3952c..26c03a170fac3 100644 --- a/calendar/managesubscriptions.php +++ b/calendar/managesubscriptions.php @@ -67,8 +67,7 @@ if (!calendar_user_can_add_event($course)) { print_error('errorcannotimport', 'calendar'); } - -$PAGE->navbar->add(get_string('managesubscriptions', 'calendar')); +$PAGE->navbar->add(get_string('managesubscriptions', 'calendar'), $PAGE->url); $types = calendar_get_allowed_event_types($courseid); @@ -140,7 +139,9 @@ // Print title and header. $PAGE->set_title("$course->shortname: ".get_string('calendar', 'calendar').": ".get_string('subscriptions', 'calendar')); -$PAGE->set_heading($course->fullname); +$heading = get_string('calendar', 'core_calendar'); +$heading = ($courseid != SITEID && !empty($courseid)) ? "{$heading}: {$COURSE->shortname}" : $heading; +$PAGE->set_heading($heading); $renderer = $PAGE->get_renderer('core_calendar'); diff --git a/calendar/view.php b/calendar/view.php index 827bbdaa2338f..1f3f8b8409360 100644 --- a/calendar/view.php +++ b/calendar/view.php @@ -138,8 +138,8 @@ $PAGE->set_pagelayout('standard'); $PAGE->set_title("$course->shortname: $strcalendar: $pagetitle"); -$headingstr = ($iscoursecalendar) ? get_string('coursecalendar', 'core_calendar', $COURSE->shortname) : - get_string('calendar', 'core_calendar'); +$headingstr = get_string('calendar', 'core_calendar'); +$headingstr = ($iscoursecalendar) ? "{$headingstr}: {$COURSE->shortname}" : $headingstr; $PAGE->set_heading($headingstr); $renderer = $PAGE->get_renderer('core_calendar'); diff --git a/cohort/tests/datasource_test.php b/cohort/tests/datasource_test.php index eb6533d533c90..0d47358b27f31 100644 --- a/cohort/tests/datasource_test.php +++ b/cohort/tests/datasource_test.php @@ -18,8 +18,11 @@ namespace core_cohort\reportbuilder\datasource; -use core_reportbuilder_testcase; use core_reportbuilder_generator; +use core_reportbuilder_testcase; +use core_reportbuilder\manager; +use core_reportbuilder\local\filters\user; +use core_user; defined('MOODLE_INTERNAL') || die(); @@ -71,4 +74,62 @@ public function test_cohorts_datasource(): void { 'Lionel Richards', // User. ], $contentrow); } + + /** + * Data provider for {@see test_cohorts_datasource_user_select} + * + * @return array[] + */ + public function cohorts_datasource_user_select_provider(): array { + return [ + ['user01', 'Cohort01'], + ['user02', 'Cohort02'], + ]; + } + + /** + * Test cohorts datasource, while adding the user select condition + * + * @param string $username + * @param string $expectedcohort + * + * @dataProvider cohorts_datasource_user_select_provider + */ + public function test_cohorts_datasource_user_select(string $username, string $expectedcohort): void { + $this->resetAfterTest(); + + // First cohort/user member. + $cohort01 = $this->getDataGenerator()->create_cohort(['name' => 'Cohort01']); + $user01 = $this->getDataGenerator()->create_user(['username' => 'user01']); + cohort_add_member($cohort01->id, $user01->id); + + // Second cohort/user member. + $cohort02 = $this->getDataGenerator()->create_cohort(['name' => 'Cohort02']); + $user02 = $this->getDataGenerator()->create_user(['username' => 'user02']); + cohort_add_member($cohort02->id, $user02->id); + + /** @var core_reportbuilder_generator $generator */ + $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder'); + $report = $generator->create_report(['name' => 'User cohorts', 'source' => cohorts::class, 'default' => 0]); + + // Add cohort name and user fullname columns. + $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'cohort:name']); + $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:username']); + + // Add condition to limit report data to current user. + $condition = $generator->create_condition(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:userselect']); + manager::get_report_from_persistent($report)->set_condition_values([ + $condition->get('uniqueidentifier') . '_operator' => user::USER_CURRENT, + ]); + + // Switch user, request report. + $currentuser = core_user::get_user_by_username($username); + $this->setUser($currentuser); + + $content = $this->get_custom_report_content($report->get('id')); + $this->assertCount(1, $content); + + $contentrow = array_values(reset($content)); + $this->assertEquals([$expectedcohort, $username], $contentrow); + } } diff --git a/course/amd/build/actions.min.js b/course/amd/build/actions.min.js index d93722dd68e24..cb4575e9aa9f5 100644 --- a/course/amd/build/actions.min.js +++ b/course/amd/build/actions.min.js @@ -1,2 +1,2 @@ -define ("core_course/actions",["jquery","core/ajax","core/templates","core/notification","core/str","core/url","core/yui","core/modal_factory","core/modal_events","core/key_codes","core/log","core_courseformat/courseeditor","core/event_dispatcher","core_course/events"],function(a,b,c,d,e,f,g,h,i,j,k,l,m,n){var o=["moveSection","moveCm","addSection","deleteSection"],p=l.getCurrentCourseEditor(),q,r={EDITINPROGRESS:"editinprogress",SECTIONDRAGGABLE:"sectiondraggable",EDITINGMOVE:"editing_move"},s={ACTIVITYLI:"li.activity",ACTIONAREA:".actions",ACTIVITYACTION:"a.cm-edit-action",MENU:".moodle-actionmenu[data-enhance=moodle-core-actionmenu]",TOGGLE:".toggle-display,.dropdown-toggle",SECTIONLI:"li.section",SECTIONACTIONMENU:".section_action_menu",ADDSECTIONS:".changenumsections [data-add-sections]"};g.use("moodle-course-coursebase",function(){var a=M.course.format.get_section_selector();if(a){s.SECTIONLI=a}});var t=function(a,b,c,d){if(!(c instanceof Element)&&c.get!==void 0){c=c.get(0)}return m.dispatchEvent(a,b,c,d)},u=function(a){var b=a.get(0);if(b.dataset.id){return b.dataset.id}var c;g.use("moodle-course-util",function(a){c=a.Moodle.core_course.util.cm.getId(a.Node(b))});return c},v=function(a){var b;g.use("moodle-course-util",function(c){b=c.Moodle.core_course.util.cm.getName(c.Node(a.get(0)))});var c=p.state,d=u(a);if(!b&&c&&d){var e;b=null===(e=c.cm.get(d))||void 0===e?void 0:e.name}return b},w=function(a){a.addClass(r.EDITINPROGRESS);var b=a.find(s.ACTIONAREA).get(0);if(b){var c=M.util.add_spinner(g,g.Node(b));c.show();if(a.data("id")!==void 0){p.dispatch("cmLock",[a.data("id")],!0)}return c}return null},x=function(a){a.addClass(r.EDITINPROGRESS);var b=a.find(s.SECTIONACTIONMENU).get(0);if(b){var c=M.util.add_spinner(g,g.Node(b));c.show();if(a.data("id")!==void 0){p.dispatch("sectionLock",[a.data("id")],!0)}return c}return null},y=function(a){var b=M.util.add_lightbox(g,g.Node(a.get(0)));b.show();return b},z=function(a,b,c){window.setTimeout(function(){a.removeClass(r.EDITINPROGRESS);if(b){b.hide()}if(a.data("id")!==void 0){var c="section"===a.data("for")?"sectionLock":"cmLock";p.dispatch(c,[a.data("id")],!1)}},c)},A=function(a,b){if(a){window.setTimeout(function(){a.hide()},b)}},B=function(a){g.use("moodle-course-coursebase",function(){M.course.coursebase.invoke_function("setup_for_resource","#"+a)});if(M.core.actionmenu&&M.core.actionmenu.newDOMNode){M.core.actionmenu.newDOMNode(g.one("#"+a))}},C=function(b,c){var d=a("#"+b),e="[data-action="+c+"]";if("groupsseparate"===c||"groupsvisible"===c||"groupsnone"===c){e="[data-action=groupsseparate],[data-action=groupsvisible],[data-action=groupsnone]"}if(d.find(e).is(":visible")){d.find(e).focus()}else{d.find(s.MENU).find(s.TOGGLE).focus()}},D=function(b){var c=a("a:visible"),d=!1,e=null;c.each(function(){if(a.contains(b[0],this)){d=!0}else if(d){e=this;return!1}return!0});return e},E=function(c,e,f){var g=f.attr("data-action"),h=w(c),i=b.call([{methodname:"core_course_edit_module",args:{id:e,action:g,sectionreturn:f.attr("data-sectionreturn")?f.attr("data-sectionreturn"):0}}],!0),j;if("duplicate"===g){j=y(f.closest(s.SECTIONLI))}a.when.apply(a,i).done(function(b){var d=D(c);c.replaceWith(b);var f=[];a("
"+b+"
").find(s.ACTIVITYLI).each(function(b){B(a(this).attr("id"));if(0===b){C(a(this).attr("id"),g);d=null}f.push(u(a(this)))});if(d){d.focus()}z(c,h,400);A(j,400);c.trigger(a.Event("coursemoduleedited",{ajaxreturn:b,action:g}));p.dispatch("legacyActivityAction",g,e,f)}).fail(function(b){z(c,h);A(j);var f=a.Event("coursemoduleeditfailed",{exception:b,action:g});c.trigger(f);if(!f.isDefaultPrevented()){d.exception(b)}})},F=function(c,d,e){if(e===void 0){e=p.sectionReturn}var f=a(c),g=w(f),h=b.call([{methodname:"core_course_get_module",args:{id:d,sectionreturn:e}}],!0);return new Promise(function(b,c){a.when.apply(a,h).done(function(a){z(f,g,400);L(a);b(a)}).fail(function(){z(f,g);c()})})},G=function(a,b){var c=a.attr("class").match(/modtype_([^\s]*)/)[1],f=v(a);e.get_string("pluginname",c).done(function(a){e.get_strings([{key:"confirm",component:"core"},{key:null===f?"deletechecktype":"deletechecktypename",param:{type:a,name:f}},{key:"yes"},{key:"no"}]).done(function(a){d.confirm(a[0],a[1],a[2],a[3],b)})})},H=function(a,b){e.get_strings([{key:"confirm"},{key:"yes"},{key:"no"}]).done(function(c){d.confirm(c[0],a,c[1],c[2],b)})},I=function(a,b,f,g,h){return e.get_strings([{key:f,component:g}]).then(function(d){a.find("span.menu-action-text").html(d[0]);return c.renderPix(b,"core")}).then(function(b){a.find(".icon").replaceWith(b);a.attr("data-action",h)}).catch(d.exception)},J=function(b,c,d,e,f){var g=c.attr("data-action");if("hide"===g||"show"===g){if("hide"===g){b.addClass("hidden");I(c,"i/show","showfromothers","format_"+e,"show")}else{b.removeClass("hidden");I(c,"i/hide","hidefromothers","format_"+e,"hide")}if(d.modules!==void 0){for(var h in d.modules){L(d.modules[h])}}if(d.section_availability!==void 0){b.find(".section_availability").first().replaceWith(d.section_availability)}var k=p.state.section.get(f);if(k!==void 0){p.dispatch("sectionState",[f])}}else if("setmarker"===g){var i=a(s.SECTIONLI+".current"),j=i.find(s.SECTIONACTIONMENU+" a[data-action=removemarker]");i.removeClass("current");I(j,"i/marker","highlight","core","setmarker");b.addClass("current");I(c,"i/marked","highlightoff","core","removemarker");p.dispatch("legacySectionAction",g,f)}else if("removemarker"===g){b.removeClass("current");I(c,"i/marker","highlight","core","setmarker");p.dispatch("legacySectionAction",g,f)}},K=function(a){var b=document.getElementById(a);if(!b||!b.contains(document.activeElement)){return}if(b.querySelector(s.ACTIONAREA).contains(document.activeElement)){return"".concat(s.ACTIONAREA," [tabindex=\"0\"]")}if(document.activeElement.id){return"#".concat(document.activeElement.id)}},L=function(b){a("
"+b+"
").find(s.ACTIVITYLI).each(function(){var c=a(this).attr("id"),d=K(c);a(s.ACTIVITYLI+"#"+c).replaceWith(b);B(c);if(d){var e,f=document.getElementById(c);null===(e=f.querySelector(d))||void 0===e?void 0:e.focus()}})},N=function(c,e,f,g){var h=f.attr("data-action"),i=f.attr("data-sectionreturn")?f.attr("data-sectionreturn"):0;if(p.supportComponents&&o.includes(h)){return!1}var j=x(c),k=b.call([{methodname:"core_course_edit_section",args:{id:e,action:h,sectionreturn:i}}],!0),l=y(c);a.when.apply(a,k).done(function(b){var d=a.parseJSON(b);z(c,j);A(l);c.find(s.SECTIONACTIONMENU).find(s.TOGGLE).focus();var i=a.Event("coursesectionedited",{ajaxreturn:d,action:h});c.trigger(i);if(!i.isDefaultPrevented()){J(c,f,d,g,e)}}).fail(function(b){z(c,j);A(l);var f=a.Event("coursesectioneditfailed",{exception:b,action:h});c.trigger(f);if(!f.isDefaultPrevented()){d.exception(b)}});return!0};g.use("moodle-course-coursebase",function(){M.course.coursebase.register_module({set_visibility_resource_ui:function set_visibility_resource_ui(b){var c=a(b.element.getDOMNode()),d=u(c);if(d){var e=c.find("."+r.EDITINGMOVE).attr("data-sectionreturn");F(c,d,e)}},updateMovedCmState:function updateMovedCmState(a){var b=p.state,c=b.cm.get(a.cmid);if(c!==void 0){p.dispatch("sectionState",[c.sectionid])}p.dispatch("cmState",[a.cmid])},updateMovedSectionState:function updateMovedSectionState(){p.dispatch("courseState")}})});p.addMutations({legacyActivityAction:function legacyActivityAction(a,b,c,d){var e=a.state,f=e.cm.get(c);if(f===void 0){return}var g=e.section.get(f.sectionid);if(g===void 0){return}a.setReadOnly(!1);f.locked=!0;a.setReadOnly(!0);a.setReadOnly(!1);f.locked=!1;switch(b){case"delete":g.cmlist=g.cmlist.reduce(function(a,b){if(b!=c){a.push(b)}return a},[]);e.cm.delete(c);break;case"hide":case"show":f.visible="show"===b?!0:!1;break;case"duplicate":p.dispatch("cmState",d);break;}a.setReadOnly(!0)},legacySectionAction:function legacySectionAction(a,b,c){var d=a.state,e=d.section.get(c);if(e===void 0){return}a.setReadOnly(!1);e.locked=!0;a.setReadOnly(!0);a.setReadOnly(!1);e.locked=!1;switch(b){case"setmarker":d.section.forEach(function(a){if(a.id!=c){a.current=!1}});e.current=!0;break;case"removemarker":e.current=!1;break;}a.setReadOnly(!0)}});return{initCoursePage:function initCoursePage(b){q=b;a("body").on("click keypress",s.ACTIVITYLI+" "+s.ACTIVITYACTION+"[data-action]",function(b){if("keypress"===b.type&&13!==b.keyCode){return}var c=a(this),d=c.closest(s.ACTIVITYLI),e=c.attr("data-action"),f=u(d);switch(e){case"moveleft":case"moveright":case"delete":case"duplicate":case"hide":case"stealth":case"show":case"groupsseparate":case"groupsvisible":case"groupsnone":break;default:return;}if(!f){return}b.preventDefault();if("delete"===e){G(d,function(){E(d,f,c)})}else{E(d,f,c)}});a("body").on("click keypress",s.SECTIONLI+" "+s.SECTIONACTIONMENU+"[data-sectionid] a[data-action]",function(c){if("keypress"===c.type&&13!==c.keyCode){return}var d=a(this),e=d.closest(s.SECTIONLI),f=d.closest(s.SECTIONACTIONMENU).attr("data-sectionid"),g=!0;if(d.attr("data-confirm")){H(d.attr("data-confirm"),function(){g=N(e,f,d,b)})}else{g=N(e,f,d,b)}if(g){c.preventDefault()}});a("body").on("updated","".concat(s.SECTIONLI," [data-inplaceeditable]"),function(a){if(a.ajaxreturn&&a.ajaxreturn.itemid){var b=p.state,c=b.section.get(a.ajaxreturn.itemid);if(c!==void 0){p.dispatch("sectionState",[a.ajaxreturn.itemid])}}});a("body").on("updated","".concat(s.ACTIVITYLI," [data-inplaceeditable]"),function(a){if(a.ajaxreturn&&a.ajaxreturn.itemid){p.dispatch("cmState",[a.ajaxreturn.itemid])}});if(p.supportComponents&&o.includes("addSection")){return}e.get_string("numberweeks").done(function(b){var c=a(s.ADDSECTIONS),d=c.attr("data-add-sections"),e=c.attr("data-new-sections"),f=a("
");f.find("label").html(b);h.create({title:d,type:h.types.SAVE_CANCEL,body:f.html()},c).done(function(b){var e=a(b.getBody()).find("#add_section_numsections"),f=function(){if(""+parseInt(e.val())===e.val()&&1<=parseInt(e.val())){document.location=c.attr("href")+"&numsections="+parseInt(e.val())}};b.setSaveButtonText(d);b.getRoot().on(i.shown,function(){e.focus().select().on("keydown",function(a){if(a.keyCode===j.enter){f()}})});b.getRoot().on(i.save,function(a){a.preventDefault();f()})})})},replaceSectionActionItem:function replaceSectionActionItem(a,b,c,d,e,f){k.debug("replaceSectionActionItem() is deprecated and will be removed.");var g=a.find(s.SECTIONACTIONMENU+" "+b);I(g,c,d,e,f)},refreshModule:F,refreshSection:function refreshSection(c,e,f){if(f===void 0){f=p.sectionReturn}var g=a(c),h="refresh",i=b.call([{methodname:"core_course_edit_section",args:{id:e,action:h,sectionreturn:f}}],!0),j=x(g);return new Promise(function(b,c){a.when.apply(a,i).done(function(c){z(g,j);var d=a.parseJSON(c),f=a(d.content);g.replaceWith(f);a("".concat(s.SECTIONLI,"#").concat(e," ").concat(s.ACTIVITYLI)).each(function(a,b){B(b.data("id"))});var i=t(n.sectionRefreshed,{ajaxreturn:d,action:h,newSectionElement:f.get(0)},f);if(!i.defaultPrevented){J(f,a(s.SECTIONLI+"#"+e),d,q,e)}b(d)}).fail(function(a){var b=t("coursesectionrefreshfailed",{exception:a,action:h},g);if(!b.defaultPrevented){d.exception(a)}c()})})}}}); +define ("core_course/actions",["jquery","core/ajax","core/templates","core/notification","core/str","core/url","core/yui","core/modal_factory","core/modal_events","core/key_codes","core/log","core_courseformat/courseeditor","core/event_dispatcher","core_course/events"],function(a,b,c,d,e,f,g,h,i,j,k,l,m,n){var o=["moveSection","moveCm","addSection","deleteSection"],p=l.getCurrentCourseEditor(),q,r={EDITINPROGRESS:"editinprogress",SECTIONDRAGGABLE:"sectiondraggable",EDITINGMOVE:"editing_move"},s={ACTIVITYLI:"li.activity",ACTIONAREA:".actions",ACTIVITYACTION:"a.cm-edit-action",MENU:".moodle-actionmenu[data-enhance=moodle-core-actionmenu]",TOGGLE:".toggle-display,.dropdown-toggle",SECTIONLI:"li.section",SECTIONACTIONMENU:".section_action_menu",ADDSECTIONS:".changenumsections [data-add-sections]"};g.use("moodle-course-coursebase",function(){var a=M.course.format.get_section_selector();if(a){s.SECTIONLI=a}});var t=function(a,b,c,d){if(!(c instanceof Element)&&c.get!==void 0){c=c.get(0)}return m.dispatchEvent(a,b,c,d)},u=function(a){var b=a.get(0);if(b.dataset.id){return b.dataset.id}var c;g.use("moodle-course-util",function(a){c=a.Moodle.core_course.util.cm.getId(a.Node(b))});return c},v=function(a){var b;g.use("moodle-course-util",function(c){b=c.Moodle.core_course.util.cm.getName(c.Node(a.get(0)))});var c=p.state,d=u(a);if(!b&&c&&d){var e;b=null===(e=c.cm.get(d))||void 0===e?void 0:e.name}return b},w=function(a){a.addClass(r.EDITINPROGRESS);var b=a.find(s.ACTIONAREA).get(0);if(b){var c=M.util.add_spinner(g,g.Node(b));c.show();if(a.data("id")!==void 0){p.dispatch("cmLock",[a.data("id")],!0)}return c}return null},x=function(a){a.addClass(r.EDITINPROGRESS);var b=a.find(s.SECTIONACTIONMENU).get(0);if(b){var c=M.util.add_spinner(g,g.Node(b));c.show();if(a.data("id")!==void 0){p.dispatch("sectionLock",[a.data("id")],!0)}return c}return null},y=function(a){var b=a.get(0),c=M.util.add_lightbox(g,g.Node(b));if("section"==b.dataset.for&&b.dataset.id){p.dispatch("sectionLock",[b.dataset.id],!0);c.setAttribute("data-state","section");c.setAttribute("data-state-id",b.dataset.id)}c.show();return c},z=function(a,b,c){window.setTimeout(function(){a.removeClass(r.EDITINPROGRESS);if(b){b.hide()}if(a.data("id")!==void 0){var c="section"===a.data("for")?"sectionLock":"cmLock";p.dispatch(c,[a.data("id")],!1)}},c)},A=function(a,b){if(a){window.setTimeout(function(){a.hide();if(a.getAttribute("data-state")){p.dispatch("".concat(a.getAttribute("data-state"),"Lock"),[a.getAttribute("data-state-id")],!1)}},b)}},B=function(a){g.use("moodle-course-coursebase",function(){M.course.coursebase.invoke_function("setup_for_resource","#"+a)});if(M.core.actionmenu&&M.core.actionmenu.newDOMNode){M.core.actionmenu.newDOMNode(g.one("#"+a))}},C=function(b,c){var d=a("#"+b),e="[data-action="+c+"]";if("groupsseparate"===c||"groupsvisible"===c||"groupsnone"===c){e="[data-action=groupsseparate],[data-action=groupsvisible],[data-action=groupsnone]"}if(d.find(e).is(":visible")){d.find(e).focus()}else{d.find(s.MENU).find(s.TOGGLE).focus()}},D=function(b){var c=a("a:visible"),d=!1,e=null;c.each(function(){if(a.contains(b[0],this)){d=!0}else if(d){e=this;return!1}return!0});return e},E=function(c,e,f){var g=f.attr("data-action"),h=w(c),i=b.call([{methodname:"core_course_edit_module",args:{id:e,action:g,sectionreturn:f.attr("data-sectionreturn")?f.attr("data-sectionreturn"):0}}],!0),j;if("duplicate"===g){j=y(f.closest(s.SECTIONLI))}a.when.apply(a,i).done(function(b){var d=D(c);c.replaceWith(b);var f=[];a("
"+b+"
").find(s.ACTIVITYLI).each(function(b){B(a(this).attr("id"));if(0===b){C(a(this).attr("id"),g);d=null}f.push(u(a(this)))});if(d){d.focus()}z(c,h,400);A(j,400);c.trigger(a.Event("coursemoduleedited",{ajaxreturn:b,action:g}));p.dispatch("legacyActivityAction",g,e,f)}).fail(function(b){z(c,h);A(j);var f=a.Event("coursemoduleeditfailed",{exception:b,action:g});c.trigger(f);if(!f.isDefaultPrevented()){d.exception(b)}})},F=function(c,d,e){if(e===void 0){e=p.sectionReturn}var f=a(c),g=w(f),h=b.call([{methodname:"core_course_get_module",args:{id:d,sectionreturn:e}}],!0);return new Promise(function(b,c){a.when.apply(a,h).done(function(a){z(f,g,400);L(a);b(a)}).fail(function(){z(f,g);c()})})},G=function(a,b){var c=a.attr("class").match(/modtype_([^\s]*)/)[1],f=v(a);e.get_string("pluginname",c).done(function(a){e.get_strings([{key:"confirm",component:"core"},{key:null===f?"deletechecktype":"deletechecktypename",param:{type:a,name:f}},{key:"yes"},{key:"no"}]).done(function(a){d.confirm(a[0],a[1],a[2],a[3],b)})})},H=function(a,b){e.get_strings([{key:"confirm"},{key:"yes"},{key:"no"}]).done(function(c){d.confirm(c[0],a,c[1],c[2],b)})},I=function(a,b,f,g,h){return e.get_strings([{key:f,component:g}]).then(function(d){a.find("span.menu-action-text").html(d[0]);return c.renderPix(b,"core")}).then(function(b){a.find(".icon").replaceWith(b);a.attr("data-action",h)}).catch(d.exception)},J=function(b,c,d,e,f){var g=c.attr("data-action");if("hide"===g||"show"===g){if("hide"===g){b.addClass("hidden");I(c,"i/show","showfromothers","format_"+e,"show")}else{b.removeClass("hidden");I(c,"i/hide","hidefromothers","format_"+e,"hide")}if(d.modules!==void 0){for(var h in d.modules){L(d.modules[h])}}if(d.section_availability!==void 0){b.find(".section_availability").first().replaceWith(d.section_availability)}var k=p.state.section.get(f);if(k!==void 0){p.dispatch("sectionState",[f])}}else if("setmarker"===g){var i=a(s.SECTIONLI+".current"),j=i.find(s.SECTIONACTIONMENU+" a[data-action=removemarker]");i.removeClass("current");I(j,"i/marker","highlight","core","setmarker");b.addClass("current");I(c,"i/marked","highlightoff","core","removemarker");p.dispatch("legacySectionAction",g,f)}else if("removemarker"===g){b.removeClass("current");I(c,"i/marker","highlight","core","setmarker");p.dispatch("legacySectionAction",g,f)}},K=function(a){var b=document.getElementById(a);if(!b||!b.contains(document.activeElement)){return}if(b.querySelector(s.ACTIONAREA).contains(document.activeElement)){return"".concat(s.ACTIONAREA," [tabindex=\"0\"]")}if(document.activeElement.id){return"#".concat(document.activeElement.id)}},L=function(b){a("
"+b+"
").find(s.ACTIVITYLI).each(function(){var c=a(this).attr("id"),d=K(c);a(s.ACTIVITYLI+"#"+c).replaceWith(b);B(c);if(d){var e,f=document.getElementById(c);null===(e=f.querySelector(d))||void 0===e?void 0:e.focus()}})},N=function(c,e,f,g){var h=f.attr("data-action"),i=f.attr("data-sectionreturn")?f.attr("data-sectionreturn"):0;if(p.supportComponents&&o.includes(h)){return!1}var j=x(c),k=b.call([{methodname:"core_course_edit_section",args:{id:e,action:h,sectionreturn:i}}],!0),l=y(c);a.when.apply(a,k).done(function(b){var d=a.parseJSON(b);z(c,j);A(l);c.find(s.SECTIONACTIONMENU).find(s.TOGGLE).focus();var i=a.Event("coursesectionedited",{ajaxreturn:d,action:h});c.trigger(i);if(!i.isDefaultPrevented()){J(c,f,d,g,e)}}).fail(function(b){z(c,j);A(l);var f=a.Event("coursesectioneditfailed",{exception:b,action:h});c.trigger(f);if(!f.isDefaultPrevented()){d.exception(b)}});return!0};g.use("moodle-course-coursebase",function(){M.course.coursebase.register_module({set_visibility_resource_ui:function set_visibility_resource_ui(b){var c=a(b.element.getDOMNode()),d=u(c);if(d){var e=c.find("."+r.EDITINGMOVE).attr("data-sectionreturn");F(c,d,e)}},updateMovedCmState:function updateMovedCmState(a){var b=p.state,c=b.cm.get(a.cmid);if(c!==void 0){p.dispatch("sectionState",[c.sectionid])}p.dispatch("cmState",[a.cmid])},updateMovedSectionState:function updateMovedSectionState(){p.dispatch("courseState")}})});p.addMutations({legacyActivityAction:function legacyActivityAction(a,b,c,d){var e=a.state,f=e.cm.get(c);if(f===void 0){return}var g=e.section.get(f.sectionid);if(g===void 0){return}p.dispatch("cmLock",[f.id],!0);a.setReadOnly(!1);f.locked=!1;switch(b){case"delete":g.cmlist=g.cmlist.reduce(function(a,b){if(b!=c){a.push(b)}return a},[]);e.cm.delete(c);break;case"hide":case"show":f.visible="show"===b?!0:!1;break;case"duplicate":p.dispatch("cmState",d);break;}a.setReadOnly(!0)},legacySectionAction:function legacySectionAction(a,b,c){var d=a.state,e=d.section.get(c);if(e===void 0){return}a.setReadOnly(!1);e.locked=!0;a.setReadOnly(!0);a.setReadOnly(!1);e.locked=!1;switch(b){case"setmarker":d.section.forEach(function(a){if(a.id!=c){a.current=!1}});e.current=!0;break;case"removemarker":e.current=!1;break;}a.setReadOnly(!0)}});return{initCoursePage:function initCoursePage(b){q=b;a("body").on("click keypress",s.ACTIVITYLI+" "+s.ACTIVITYACTION+"[data-action]",function(b){if("keypress"===b.type&&13!==b.keyCode){return}var c=a(this),d=c.closest(s.ACTIVITYLI),e=c.attr("data-action"),f=u(d);switch(e){case"moveleft":case"moveright":case"delete":case"duplicate":case"hide":case"stealth":case"show":case"groupsseparate":case"groupsvisible":case"groupsnone":break;default:return;}if(!f){return}b.preventDefault();if("delete"===e){G(d,function(){E(d,f,c)})}else{E(d,f,c)}});a("body").on("click keypress",s.SECTIONLI+" "+s.SECTIONACTIONMENU+"[data-sectionid] a[data-action]",function(c){if("keypress"===c.type&&13!==c.keyCode){return}var d=a(this),e=d.closest(s.SECTIONLI),f=d.closest(s.SECTIONACTIONMENU).attr("data-sectionid"),g=!0;if(d.attr("data-confirm")){H(d.attr("data-confirm"),function(){g=N(e,f,d,b)})}else{g=N(e,f,d,b)}if(g){c.preventDefault()}});a("body").on("updated","".concat(s.SECTIONLI," [data-inplaceeditable]"),function(a){if(a.ajaxreturn&&a.ajaxreturn.itemid){var b=p.state,c=b.section.get(a.ajaxreturn.itemid);if(c!==void 0){p.dispatch("sectionState",[a.ajaxreturn.itemid])}}});a("body").on("updated","".concat(s.ACTIVITYLI," [data-inplaceeditable]"),function(a){if(a.ajaxreturn&&a.ajaxreturn.itemid){p.dispatch("cmState",[a.ajaxreturn.itemid])}});if(p.supportComponents&&o.includes("addSection")){return}e.get_string("numberweeks").done(function(b){var c=a(s.ADDSECTIONS),d=c.attr("data-add-sections"),e=c.attr("data-new-sections"),f=a("
");f.find("label").html(b);h.create({title:d,type:h.types.SAVE_CANCEL,body:f.html()},c).done(function(b){var e=a(b.getBody()).find("#add_section_numsections"),f=function(){if(""+parseInt(e.val())===e.val()&&1<=parseInt(e.val())){document.location=c.attr("href")+"&numsections="+parseInt(e.val())}};b.setSaveButtonText(d);b.getRoot().on(i.shown,function(){e.focus().select().on("keydown",function(a){if(a.keyCode===j.enter){f()}})});b.getRoot().on(i.save,function(a){a.preventDefault();f()})})})},replaceSectionActionItem:function replaceSectionActionItem(a,b,c,d,e,f){k.debug("replaceSectionActionItem() is deprecated and will be removed.");var g=a.find(s.SECTIONACTIONMENU+" "+b);I(g,c,d,e,f)},refreshModule:F,refreshSection:function refreshSection(c,e,f){if(f===void 0){f=p.sectionReturn}var g=a(c),h="refresh",i=b.call([{methodname:"core_course_edit_section",args:{id:e,action:h,sectionreturn:f}}],!0),j=x(g);return new Promise(function(b,c){a.when.apply(a,i).done(function(c){z(g,j);var d=a.parseJSON(c),f=a(d.content);g.replaceWith(f);a("".concat(s.SECTIONLI,"#").concat(e," ").concat(s.ACTIVITYLI)).each(function(a,b){B(b.data("id"))});var i=t(n.sectionRefreshed,{ajaxreturn:d,action:h,newSectionElement:f.get(0)},f);if(!i.defaultPrevented){J(f,a(s.SECTIONLI+"#"+e),d,q,e)}b(d)}).fail(function(a){var b=t("coursesectionrefreshfailed",{exception:a,action:h},g);if(!b.defaultPrevented){d.exception(a)}c()})})}}}); //# sourceMappingURL=actions.min.js.map diff --git a/course/amd/build/actions.min.js.map b/course/amd/build/actions.min.js.map index 681509f9cb2b4..c4a15ba265198 100644 --- a/course/amd/build/actions.min.js.map +++ b/course/amd/build/actions.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/actions.js"],"names":["define","$","ajax","templates","notification","str","url","Y","ModalFactory","ModalEvents","KeyCodes","log","editor","EventDispatcher","CourseEvents","componentActions","courseeditor","getCurrentCourseEditor","formatname","CSS","EDITINPROGRESS","SECTIONDRAGGABLE","EDITINGMOVE","SELECTOR","ACTIVITYLI","ACTIONAREA","ACTIVITYACTION","MENU","TOGGLE","SECTIONLI","SECTIONACTIONMENU","ADDSECTIONS","use","courseformatselector","M","course","format","get_section_selector","dispatchEvent","eventName","detail","container","options","Element","get","getModuleId","element","item","dataset","id","Moodle","core_course","util","cm","getId","Node","getModuleName","name","getName","state","cmid","addActivitySpinner","activity","addClass","actionarea","find","spinner","add_spinner","show","data","dispatch","addSectionSpinner","sectionelement","addSectionLightbox","lightbox","add_lightbox","removeSpinner","delay","window","setTimeout","removeClass","hide","mutation","removeLightbox","initActionMenu","elementid","coursebase","invoke_function","core","actionmenu","newDOMNode","one","focusActionItem","elementId","action","mainelement","selector","is","focus","findNextFocusable","mainElement","tabables","isInside","foundElement","each","contains","editModule","moduleElement","target","attr","promises","call","methodname","args","sectionreturn","closest","when","apply","done","elementToFocus","replaceWith","affectedids","index","push","trigger","Event","ajaxreturn","fail","ex","e","exception","isDefaultPrevented","refreshModule","sectionReturn","activityElement","Promise","resolve","reject","replaceActivityHtmlWith","confirmDeleteModule","onconfirm","modtypename","match","modulename","get_string","pluginname","get_strings","key","component","param","type","s","confirm","confirmEditSection","message","replaceActionItem","actionitem","image","stringname","stringcomponent","newaction","then","strings","html","renderPix","pixhtml","catch","defaultEditSectionHandler","sectionElement","actionItem","courseformat","sectionid","modules","i","section_availability","first","section","oldmarker","oldActionItem","getActivityFocusedElement","document","getElementById","activeElement","querySelector","activityHTML","focusedPath","newItem","editSection","supportComponents","includes","dataencoded","parseJSON","register_module","set_visibility_resource_ui","getDOMNode","updateMovedCmState","params","updateMovedSectionState","addMutations","legacyActivityAction","statemanager","setReadOnly","locked","cmlist","reduce","current","delete","visible","legacySectionAction","forEach","initCoursePage","on","keyCode","moduleId","preventDefault","sectionId","isExecuted","itemid","strNumberSections","modalTitle","newSections","modalBody","create","title","types","SAVE_CANCEL","body","modal","numSections","getBody","addSections","parseInt","val","location","setSaveButtonText","getRoot","shown","select","enter","save","replaceSectionActionItem","debug","refreshSection","newSectionElement","content","event","sectionRefreshed","defaultPrevented"],"mappings":"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","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 * Various actions on modules and sections in the editing mode - hiding, duplicating, deleting, etc.\n *\n * @module core_course/actions\n * @copyright 2016 Marina Glancy\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 3.3\n */\ndefine(\n [\n 'jquery',\n 'core/ajax',\n 'core/templates',\n 'core/notification',\n 'core/str',\n 'core/url',\n 'core/yui',\n 'core/modal_factory',\n 'core/modal_events',\n 'core/key_codes',\n 'core/log',\n 'core_courseformat/courseeditor',\n 'core/event_dispatcher',\n 'core_course/events'\n ],\n function(\n $,\n ajax,\n templates,\n notification,\n str,\n url,\n Y,\n ModalFactory,\n ModalEvents,\n KeyCodes,\n log,\n editor,\n EventDispatcher,\n CourseEvents\n ) {\n\n // Eventually, core_courseformat/local/content/actions will handle all actions for\n // component compatible formats and the default actions.js won't be necessary anymore.\n // Meanwhile, we filter the migrated actions.\n const componentActions = ['moveSection', 'moveCm', 'addSection', 'deleteSection'];\n\n // The course reactive instance.\n const courseeditor = editor.getCurrentCourseEditor();\n\n // The current course format name (loaded on init).\n let formatname;\n\n var CSS = {\n EDITINPROGRESS: 'editinprogress',\n SECTIONDRAGGABLE: 'sectiondraggable',\n EDITINGMOVE: 'editing_move'\n };\n var SELECTOR = {\n ACTIVITYLI: 'li.activity',\n ACTIONAREA: '.actions',\n ACTIVITYACTION: 'a.cm-edit-action',\n MENU: '.moodle-actionmenu[data-enhance=moodle-core-actionmenu]',\n TOGGLE: '.toggle-display,.dropdown-toggle',\n SECTIONLI: 'li.section',\n SECTIONACTIONMENU: '.section_action_menu',\n ADDSECTIONS: '.changenumsections [data-add-sections]'\n };\n\n Y.use('moodle-course-coursebase', function() {\n var courseformatselector = M.course.format.get_section_selector();\n if (courseformatselector) {\n SELECTOR.SECTIONLI = courseformatselector;\n }\n });\n\n /**\n * Dispatch event wrapper.\n *\n * Old jQuery events will be replaced by native events gradually.\n *\n * @method dispatchEvent\n * @param {String} eventName The name of the event\n * @param {Object} detail Any additional details to pass into the eveent\n * @param {Node|HTMLElement} container The point at which to dispatch the event\n * @param {Object} options\n * @param {Boolean} options.bubbles Whether to bubble up the DOM\n * @param {Boolean} options.cancelable Whether preventDefault() can be called\n * @param {Boolean} options.composed Whether the event can bubble across the ShadowDOM boundary\n * @returns {CustomEvent}\n */\n const dispatchEvent = function(eventName, detail, container, options) {\n // Most actions still uses jQuery node instead of regular HTMLElement.\n if (!(container instanceof Element) && container.get !== undefined) {\n container = container.get(0);\n }\n return EventDispatcher.dispatchEvent(eventName, detail, container, options);\n };\n\n /**\n * Wrapper for Y.Moodle.core_course.util.cm.getId\n *\n * @param {JQuery} element\n * @returns {Integer}\n */\n var getModuleId = function(element) {\n // Check if we have a data-id first.\n const item = element.get(0);\n if (item.dataset.id) {\n return item.dataset.id;\n }\n // Use YUI way if data-id is not present.\n let id;\n Y.use('moodle-course-util', function(Y) {\n id = Y.Moodle.core_course.util.cm.getId(Y.Node(item));\n });\n return id;\n };\n\n /**\n * Wrapper for Y.Moodle.core_course.util.cm.getName\n *\n * @param {JQuery} element\n * @returns {String}\n */\n var getModuleName = function(element) {\n var name;\n Y.use('moodle-course-util', function(Y) {\n name = Y.Moodle.core_course.util.cm.getName(Y.Node(element.get(0)));\n });\n // Check if we have the name in the course state.\n const state = courseeditor.state;\n const cmid = getModuleId(element);\n if (!name && state && cmid) {\n name = state.cm.get(cmid)?.name;\n }\n return name;\n };\n\n /**\n * Wrapper for M.util.add_spinner for an activity\n *\n * @param {JQuery} activity\n * @returns {Node}\n */\n var addActivitySpinner = function(activity) {\n activity.addClass(CSS.EDITINPROGRESS);\n var actionarea = activity.find(SELECTOR.ACTIONAREA).get(0);\n if (actionarea) {\n var spinner = M.util.add_spinner(Y, Y.Node(actionarea));\n spinner.show();\n // Lock the activity state element.\n if (activity.data('id') !== undefined) {\n courseeditor.dispatch('cmLock', [activity.data('id')], true);\n }\n return spinner;\n }\n return null;\n };\n\n /**\n * Wrapper for M.util.add_spinner for a section\n *\n * @param {JQuery} sectionelement\n * @returns {Node}\n */\n var addSectionSpinner = function(sectionelement) {\n sectionelement.addClass(CSS.EDITINPROGRESS);\n var actionarea = sectionelement.find(SELECTOR.SECTIONACTIONMENU).get(0);\n if (actionarea) {\n var spinner = M.util.add_spinner(Y, Y.Node(actionarea));\n spinner.show();\n // Lock the section state element.\n if (sectionelement.data('id') !== undefined) {\n courseeditor.dispatch('sectionLock', [sectionelement.data('id')], true);\n }\n return spinner;\n }\n return null;\n };\n\n /**\n * Wrapper for M.util.add_lightbox\n *\n * @param {JQuery} sectionelement\n * @returns {Node}\n */\n var addSectionLightbox = function(sectionelement) {\n var lightbox = M.util.add_lightbox(Y, Y.Node(sectionelement.get(0)));\n lightbox.show();\n return lightbox;\n };\n\n /**\n * Removes the spinner element\n *\n * @param {JQuery} element\n * @param {Node} spinner\n * @param {Number} delay\n */\n var removeSpinner = function(element, spinner, delay) {\n window.setTimeout(function() {\n element.removeClass(CSS.EDITINPROGRESS);\n if (spinner) {\n spinner.hide();\n }\n // Unlock the state element.\n if (element.data('id') !== undefined) {\n const mutation = (element.data('for') === 'section') ? 'sectionLock' : 'cmLock';\n courseeditor.dispatch(mutation, [element.data('id')], false);\n }\n }, delay);\n };\n\n /**\n * Removes the lightbox element\n *\n * @param {Node} lightbox lighbox YUI element returned by addSectionLightbox\n * @param {Number} delay\n */\n var removeLightbox = function(lightbox, delay) {\n if (lightbox) {\n window.setTimeout(function() {\n lightbox.hide();\n }, delay);\n }\n };\n\n /**\n * Initialise action menu for the element (section or module)\n *\n * @param {String} elementid CSS id attribute of the element\n */\n var initActionMenu = function(elementid) {\n // Initialise action menu in the new activity.\n Y.use('moodle-course-coursebase', function() {\n M.course.coursebase.invoke_function('setup_for_resource', '#' + elementid);\n });\n if (M.core.actionmenu && M.core.actionmenu.newDOMNode) {\n M.core.actionmenu.newDOMNode(Y.one('#' + elementid));\n }\n };\n\n /**\n * Returns focus to the element that was clicked or \"Edit\" link if element is no longer visible.\n *\n * @param {String} elementId CSS id attribute of the element\n * @param {String} action data-action property of the element that was clicked\n */\n var focusActionItem = function(elementId, action) {\n var mainelement = $('#' + elementId);\n var selector = '[data-action=' + action + ']';\n if (action === 'groupsseparate' || action === 'groupsvisible' || action === 'groupsnone') {\n // New element will have different data-action.\n selector = '[data-action=groupsseparate],[data-action=groupsvisible],[data-action=groupsnone]';\n }\n if (mainelement.find(selector).is(':visible')) {\n mainelement.find(selector).focus();\n } else {\n // Element not visible, focus the \"Edit\" link.\n mainelement.find(SELECTOR.MENU).find(SELECTOR.TOGGLE).focus();\n }\n };\n\n /**\n * Find next after the element\n *\n * @param {JQuery} mainElement element that is about to be deleted\n * @returns {JQuery}\n */\n var findNextFocusable = function(mainElement) {\n var tabables = $(\"a:visible\");\n var isInside = false;\n var foundElement = null;\n tabables.each(function() {\n if ($.contains(mainElement[0], this)) {\n isInside = true;\n } else if (isInside) {\n foundElement = this;\n return false; // Returning false in .each() is equivalent to \"break;\" inside the loop in php.\n }\n return true;\n });\n return foundElement;\n };\n\n /**\n * Performs an action on a module (moving, deleting, duplicating, hiding, etc.)\n *\n * @param {JQuery} moduleElement activity element we perform action on\n * @param {Number} cmid\n * @param {JQuery} target the element (menu item) that was clicked\n */\n var editModule = function(moduleElement, cmid, target) {\n var action = target.attr('data-action');\n var spinner = addActivitySpinner(moduleElement);\n var promises = ajax.call([{\n methodname: 'core_course_edit_module',\n args: {id: cmid,\n action: action,\n sectionreturn: target.attr('data-sectionreturn') ? target.attr('data-sectionreturn') : 0\n }\n }], true);\n\n var lightbox;\n if (action === 'duplicate') {\n lightbox = addSectionLightbox(target.closest(SELECTOR.SECTIONLI));\n }\n $.when.apply($, promises)\n .done(function(data) {\n var elementToFocus = findNextFocusable(moduleElement);\n moduleElement.replaceWith(data);\n let affectedids = [];\n // Initialise action menu for activity(ies) added as a result of this.\n $('
' + data + '
').find(SELECTOR.ACTIVITYLI).each(function(index) {\n initActionMenu($(this).attr('id'));\n if (index === 0) {\n focusActionItem($(this).attr('id'), action);\n elementToFocus = null;\n }\n // Save any activity id in cmids.\n affectedids.push(getModuleId($(this)));\n });\n // In case of activity deletion focus the next focusable element.\n if (elementToFocus) {\n elementToFocus.focus();\n }\n // Remove spinner and lightbox with a delay.\n removeSpinner(moduleElement, spinner, 400);\n removeLightbox(lightbox, 400);\n // Trigger event that can be observed by course formats.\n moduleElement.trigger($.Event('coursemoduleedited', {ajaxreturn: data, action: action}));\n\n // Modify cm state.\n courseeditor.dispatch('legacyActivityAction', action, cmid, affectedids);\n\n }).fail(function(ex) {\n // Remove spinner and lightbox.\n removeSpinner(moduleElement, spinner);\n removeLightbox(lightbox);\n // Trigger event that can be observed by course formats.\n var e = $.Event('coursemoduleeditfailed', {exception: ex, action: action});\n moduleElement.trigger(e);\n if (!e.isDefaultPrevented()) {\n notification.exception(ex);\n }\n });\n };\n\n /**\n * Requests html for the module via WS core_course_get_module and updates the module on the course page\n *\n * Used after d&d of the module to another section\n *\n * @param {JQuery|Element} element\n * @param {Number} cmid\n * @param {Number} sectionreturn\n * @return {Promise} the refresh promise\n */\n var refreshModule = function(element, cmid, sectionreturn) {\n\n if (sectionreturn === undefined) {\n sectionreturn = courseeditor.sectionReturn;\n }\n\n const activityElement = $(element);\n var spinner = addActivitySpinner(activityElement);\n var promises = ajax.call([{\n methodname: 'core_course_get_module',\n args: {id: cmid, sectionreturn: sectionreturn}\n }], true);\n\n return new Promise((resolve, reject) => {\n $.when.apply($, promises)\n .done(function(data) {\n removeSpinner(activityElement, spinner, 400);\n replaceActivityHtmlWith(data);\n resolve(data);\n }).fail(function() {\n removeSpinner(activityElement, spinner);\n reject();\n });\n });\n };\n\n /**\n * Requests html for the section via WS core_course_edit_section and updates the section on the course page\n *\n * @param {JQuery|Element} element\n * @param {Number} sectionid\n * @param {Number} sectionreturn\n * @return {Promise} the refresh promise\n */\n var refreshSection = function(element, sectionid, sectionreturn) {\n\n if (sectionreturn === undefined) {\n sectionreturn = courseeditor.sectionReturn;\n }\n\n const sectionElement = $(element);\n const action = 'refresh';\n const promises = ajax.call([{\n methodname: 'core_course_edit_section',\n args: {id: sectionid, action, sectionreturn},\n }], true);\n\n var spinner = addSectionSpinner(sectionElement);\n return new Promise((resolve, reject) => {\n $.when.apply($, promises)\n .done(dataencoded => {\n\n removeSpinner(sectionElement, spinner);\n const data = $.parseJSON(dataencoded);\n\n const newSectionElement = $(data.content);\n sectionElement.replaceWith(newSectionElement);\n\n // Init modules menus.\n $(`${SELECTOR.SECTIONLI}#${sectionid} ${SELECTOR.ACTIVITYLI}`).each(\n (index, activity) => {\n initActionMenu(activity.data('id'));\n }\n );\n\n // Trigger event that can be observed by course formats.\n const event = dispatchEvent(\n CourseEvents.sectionRefreshed,\n {\n ajaxreturn: data,\n action: action,\n newSectionElement: newSectionElement.get(0),\n },\n newSectionElement\n );\n\n if (!event.defaultPrevented) {\n defaultEditSectionHandler(\n newSectionElement, $(SELECTOR.SECTIONLI + '#' + sectionid),\n data,\n formatname,\n sectionid\n );\n }\n resolve(data);\n }).fail(ex => {\n // Trigger event that can be observed by course formats.\n const event = dispatchEvent(\n 'coursesectionrefreshfailed',\n {exception: ex, action: action},\n sectionElement\n );\n if (!event.defaultPrevented) {\n notification.exception(ex);\n }\n reject();\n });\n });\n };\n\n /**\n * Displays the delete confirmation to delete a module\n *\n * @param {JQuery} mainelement activity element we perform action on\n * @param {function} onconfirm function to execute on confirm\n */\n var confirmDeleteModule = function(mainelement, onconfirm) {\n var modtypename = mainelement.attr('class').match(/modtype_([^\\s]*)/)[1];\n var modulename = getModuleName(mainelement);\n\n str.get_string('pluginname', modtypename).done(function(pluginname) {\n var plugindata = {\n type: pluginname,\n name: modulename\n };\n str.get_strings([\n {key: 'confirm', component: 'core'},\n {key: modulename === null ? 'deletechecktype' : 'deletechecktypename', param: plugindata},\n {key: 'yes'},\n {key: 'no'}\n ]).done(function(s) {\n notification.confirm(s[0], s[1], s[2], s[3], onconfirm);\n }\n );\n });\n };\n\n /**\n * Displays the delete confirmation to delete a section\n *\n * @param {String} message confirmation message\n * @param {function} onconfirm function to execute on confirm\n */\n var confirmEditSection = function(message, onconfirm) {\n str.get_strings([\n {key: 'confirm'}, // TODO link text\n {key: 'yes'},\n {key: 'no'}\n ]).done(function(s) {\n notification.confirm(s[0], message, s[1], s[2], onconfirm);\n }\n );\n };\n\n /**\n * Replaces an action menu item with another one (for example Show->Hide, Set marker->Remove marker)\n *\n * @param {JQuery} actionitem\n * @param {String} image new image name (\"i/show\", \"i/hide\", etc.)\n * @param {String} stringname new string for the action menu item\n * @param {String} stringcomponent\n * @param {String} newaction new value for data-action attribute of the link\n * @return {Promise} promise which is resolved when the replacement has completed\n */\n var replaceActionItem = function(actionitem, image, stringname,\n stringcomponent, newaction) {\n\n var stringRequests = [{key: stringname, component: stringcomponent}];\n // Do not provide an icon with duplicate, different text to the menu item.\n\n return str.get_strings(stringRequests).then(function(strings) {\n actionitem.find('span.menu-action-text').html(strings[0]);\n\n return templates.renderPix(image, 'core');\n }).then(function(pixhtml) {\n actionitem.find('.icon').replaceWith(pixhtml);\n actionitem.attr('data-action', newaction);\n return;\n }).catch(notification.exception);\n };\n\n /**\n * Default post-processing for section AJAX edit actions.\n *\n * This can be overridden in course formats by listening to event coursesectionedited:\n *\n * $('body').on('coursesectionedited', 'li.section', function(e) {\n * var action = e.action,\n * sectionElement = $(e.target),\n * data = e.ajaxreturn;\n * // ... Do some processing here.\n * e.preventDefault(); // Prevent default handler.\n * });\n *\n * @param {JQuery} sectionElement\n * @param {JQuery} actionItem\n * @param {Object} data\n * @param {String} courseformat\n * @param {Number} sectionid\n */\n var defaultEditSectionHandler = function(sectionElement, actionItem, data, courseformat, sectionid) {\n var action = actionItem.attr('data-action');\n if (action === 'hide' || action === 'show') {\n if (action === 'hide') {\n sectionElement.addClass('hidden');\n replaceActionItem(actionItem, 'i/show',\n 'showfromothers', 'format_' + courseformat, 'show');\n } else {\n sectionElement.removeClass('hidden');\n replaceActionItem(actionItem, 'i/hide',\n 'hidefromothers', 'format_' + courseformat, 'hide');\n }\n // Replace the modules with new html (that indicates that they are now hidden or not hidden).\n if (data.modules !== undefined) {\n for (var i in data.modules) {\n replaceActivityHtmlWith(data.modules[i]);\n }\n }\n // Replace the section availability information.\n if (data.section_availability !== undefined) {\n sectionElement.find('.section_availability').first().replaceWith(data.section_availability);\n }\n // Modify course state.\n const section = courseeditor.state.section.get(sectionid);\n if (section !== undefined) {\n courseeditor.dispatch('sectionState', [sectionid]);\n }\n } else if (action === 'setmarker') {\n var oldmarker = $(SELECTOR.SECTIONLI + '.current'),\n oldActionItem = oldmarker.find(SELECTOR.SECTIONACTIONMENU + ' ' + 'a[data-action=removemarker]');\n oldmarker.removeClass('current');\n replaceActionItem(oldActionItem, 'i/marker',\n 'highlight', 'core', 'setmarker');\n sectionElement.addClass('current');\n replaceActionItem(actionItem, 'i/marked',\n 'highlightoff', 'core', 'removemarker');\n courseeditor.dispatch('legacySectionAction', action, sectionid);\n } else if (action === 'removemarker') {\n sectionElement.removeClass('current');\n replaceActionItem(actionItem, 'i/marker',\n 'highlight', 'core', 'setmarker');\n courseeditor.dispatch('legacySectionAction', action, sectionid);\n }\n };\n\n /**\n * Get the focused element path in an activity if any.\n *\n * This method is used to restore focus when the activity HTML is refreshed.\n * Only the main course editor elements can be refocused as they are always present\n * even if the activity content changes.\n *\n * @param {String} id the element id the activity element\n * @return {String|undefined} the inner path of the focused element or undefined\n */\n const getActivityFocusedElement = function(id) {\n const element = document.getElementById(id);\n if (!element || !element.contains(document.activeElement)) {\n return undefined;\n }\n // Check if the actions menu toggler is focused.\n if (element.querySelector(SELECTOR.ACTIONAREA).contains(document.activeElement)) {\n return `${SELECTOR.ACTIONAREA} [tabindex=\"0\"]`;\n }\n // Return the current element id if any.\n if (document.activeElement.id) {\n return `#${document.activeElement.id}`;\n }\n return undefined;\n };\n\n /**\n * Replaces the course module with the new html (used to update module after it was edited or its visibility was changed).\n *\n * @param {String} activityHTML\n */\n var replaceActivityHtmlWith = function(activityHTML) {\n $('
' + activityHTML + '
').find(SELECTOR.ACTIVITYLI).each(function() {\n // Extract id from the new activity html.\n var id = $(this).attr('id');\n // Check if the current focused element is inside the activity.\n let focusedPath = getActivityFocusedElement(id);\n // Find the existing element with the same id and replace its contents with new html.\n $(SELECTOR.ACTIVITYLI + '#' + id).replaceWith(activityHTML);\n // Initialise action menu.\n initActionMenu(id);\n // Re-focus the previous elements.\n if (focusedPath) {\n const newItem = document.getElementById(id);\n newItem.querySelector(focusedPath)?.focus();\n }\n\n });\n };\n\n /**\n * Performs an action on a module (moving, deleting, duplicating, hiding, etc.)\n *\n * @param {JQuery} sectionElement section element we perform action on\n * @param {Nunmber} sectionid\n * @param {JQuery} target the element (menu item) that was clicked\n * @param {String} courseformat\n * @return {boolean} true the action call is sent to the server or false if it is ignored.\n */\n var editSection = function(sectionElement, sectionid, target, courseformat) {\n var action = target.attr('data-action'),\n sectionreturn = target.attr('data-sectionreturn') ? target.attr('data-sectionreturn') : 0;\n\n // Filter direct component handled actions.\n if (courseeditor.supportComponents && componentActions.includes(action)) {\n return false;\n }\n\n var spinner = addSectionSpinner(sectionElement);\n var promises = ajax.call([{\n methodname: 'core_course_edit_section',\n args: {id: sectionid, action: action, sectionreturn: sectionreturn}\n }], true);\n\n var lightbox = addSectionLightbox(sectionElement);\n $.when.apply($, promises)\n .done(function(dataencoded) {\n var data = $.parseJSON(dataencoded);\n removeSpinner(sectionElement, spinner);\n removeLightbox(lightbox);\n sectionElement.find(SELECTOR.SECTIONACTIONMENU).find(SELECTOR.TOGGLE).focus();\n // Trigger event that can be observed by course formats.\n var e = $.Event('coursesectionedited', {ajaxreturn: data, action: action});\n sectionElement.trigger(e);\n if (!e.isDefaultPrevented()) {\n defaultEditSectionHandler(sectionElement, target, data, courseformat, sectionid);\n }\n }).fail(function(ex) {\n // Remove spinner and lightbox.\n removeSpinner(sectionElement, spinner);\n removeLightbox(lightbox);\n // Trigger event that can be observed by course formats.\n var e = $.Event('coursesectioneditfailed', {exception: ex, action: action});\n sectionElement.trigger(e);\n if (!e.isDefaultPrevented()) {\n notification.exception(ex);\n }\n });\n return true;\n };\n\n // Register a function to be executed after D&D of an activity.\n Y.use('moodle-course-coursebase', function() {\n M.course.coursebase.register_module({\n // Ignore camelcase eslint rule for the next line because it is an expected name of the callback.\n // eslint-disable-next-line camelcase\n set_visibility_resource_ui: function(args) {\n var mainelement = $(args.element.getDOMNode());\n var cmid = getModuleId(mainelement);\n if (cmid) {\n var sectionreturn = mainelement.find('.' + CSS.EDITINGMOVE).attr('data-sectionreturn');\n refreshModule(mainelement, cmid, sectionreturn);\n }\n },\n /**\n * Update the course state when some cm is moved via YUI.\n * @param {*} params\n */\n updateMovedCmState: (params) => {\n const state = courseeditor.state;\n\n // Update old section.\n const cm = state.cm.get(params.cmid);\n if (cm !== undefined) {\n courseeditor.dispatch('sectionState', [cm.sectionid]);\n }\n // Update cm state.\n courseeditor.dispatch('cmState', [params.cmid]);\n },\n /**\n * Update the course state when some section is moved via YUI.\n */\n updateMovedSectionState: () => {\n courseeditor.dispatch('courseState');\n },\n });\n });\n\n // From Moodle 4.0 all edit actions are being re-implemented as state mutation.\n // This means all method from this \"actions\" module will be deprecated when all the course\n // interface is migrated to reactive components.\n // Most legacy actions did not provide enough information to regenarate the course so they\n // use the mutations courseState, sectionState and cmState to get the updated state from\n // the server. However, some activity actions where we can prevent an extra webservice\n // call by implementing an adhoc mutation.\n courseeditor.addMutations({\n /**\n * Compatibility function to update Moodle 4.0 course state using legacy actions.\n *\n * This method only updates some actions which does not require to use cmState mutation\n * to get updated data form the server.\n *\n * @param {Object} statemanager the current state in read write mode\n * @param {String} action the performed action\n * @param {Number} cmid the affected course module id\n * @param {Array} affectedids all affected cm ids (for duplicate action)\n */\n legacyActivityAction: function(statemanager, action, cmid, affectedids) {\n\n const state = statemanager.state;\n const cm = state.cm.get(cmid);\n if (cm === undefined) {\n return;\n }\n const section = state.section.get(cm.sectionid);\n if (section === undefined) {\n return;\n }\n\n // Send the element is locked. Reactive events are only triggered when the state\n // read only mode is restored. We want to notify the interface the element is\n // locked so we need to do a quick lock operation before performing the rest\n // of the mutation.\n statemanager.setReadOnly(false);\n cm.locked = true;\n statemanager.setReadOnly(true);\n\n // Now we do the real mutation.\n statemanager.setReadOnly(false);\n\n // This locked will take effect when the read only is restored.\n cm.locked = false;\n\n switch (action) {\n case 'delete':\n // Remove from section.\n section.cmlist = section.cmlist.reduce(\n (cmlist, current) => {\n if (current != cmid) {\n cmlist.push(current);\n }\n return cmlist;\n },\n []\n );\n // Delete form list.\n state.cm.delete(cmid);\n break;\n\n case 'hide':\n case 'show':\n cm.visible = (action === 'show') ? true : false;\n break;\n\n case 'duplicate':\n // Duplicate requires to get extra data from the server.\n courseeditor.dispatch('cmState', affectedids);\n break;\n }\n statemanager.setReadOnly(true);\n },\n legacySectionAction: function(statemanager, action, sectionid) {\n\n const state = statemanager.state;\n const section = state.section.get(sectionid);\n if (section === undefined) {\n return;\n }\n\n // Send the element is locked. Reactive events are only triggered when the state\n // read only mode is restored. We want to notify the interface the element is\n // locked so we need to do a quick lock operation before performing the rest\n // of the mutation.\n statemanager.setReadOnly(false);\n section.locked = true;\n statemanager.setReadOnly(true);\n\n // Now we do the real mutation.\n statemanager.setReadOnly(false);\n\n // This locked will take effect when the read only is restored.\n section.locked = false;\n\n switch (action) {\n case 'setmarker':\n // Remove previous marker.\n state.section.forEach((current) => {\n if (current.id != sectionid) {\n current.current = false;\n }\n });\n section.current = true;\n break;\n\n case 'removemarker':\n section.current = false;\n break;\n }\n statemanager.setReadOnly(true);\n },\n });\n\n return /** @alias module:core_course/actions */ {\n\n /**\n * Initialises course page\n *\n * @method init\n * @param {String} courseformat name of the current course format (for fetching strings)\n */\n initCoursePage: function(courseformat) {\n\n formatname = courseformat;\n\n // Add a handler for course module actions.\n $('body').on('click keypress', SELECTOR.ACTIVITYLI + ' ' +\n SELECTOR.ACTIVITYACTION + '[data-action]', function(e) {\n if (e.type === 'keypress' && e.keyCode !== 13) {\n return;\n }\n var actionItem = $(this),\n moduleElement = actionItem.closest(SELECTOR.ACTIVITYLI),\n action = actionItem.attr('data-action'),\n moduleId = getModuleId(moduleElement);\n switch (action) {\n case 'moveleft':\n case 'moveright':\n case 'delete':\n case 'duplicate':\n case 'hide':\n case 'stealth':\n case 'show':\n case 'groupsseparate':\n case 'groupsvisible':\n case 'groupsnone':\n break;\n default:\n // Nothing to do here!\n return;\n }\n if (!moduleId) {\n return;\n }\n e.preventDefault();\n if (action === 'delete') {\n // Deleting requires confirmation.\n confirmDeleteModule(moduleElement, function() {\n editModule(moduleElement, moduleId, actionItem);\n });\n } else {\n editModule(moduleElement, moduleId, actionItem);\n }\n });\n\n // Add a handler for section show/hide actions.\n $('body').on('click keypress', SELECTOR.SECTIONLI + ' ' +\n SELECTOR.SECTIONACTIONMENU + '[data-sectionid] ' +\n 'a[data-action]', function(e) {\n if (e.type === 'keypress' && e.keyCode !== 13) {\n return;\n }\n var actionItem = $(this),\n sectionElement = actionItem.closest(SELECTOR.SECTIONLI),\n sectionId = actionItem.closest(SELECTOR.SECTIONACTIONMENU).attr('data-sectionid');\n\n let isExecuted = true;\n if (actionItem.attr('data-confirm')) {\n // Action requires confirmation.\n confirmEditSection(actionItem.attr('data-confirm'), function() {\n isExecuted = editSection(sectionElement, sectionId, actionItem, courseformat);\n });\n } else {\n isExecuted = editSection(sectionElement, sectionId, actionItem, courseformat);\n }\n // Prevent any other module from capturing the action if it is already in execution.\n if (isExecuted) {\n e.preventDefault();\n }\n });\n\n // The section and activity names are edited using inplace editable.\n // The \"update\" jQuery event must be captured in order to update the course state.\n $('body').on('updated', `${SELECTOR.SECTIONLI} [data-inplaceeditable]`, function(e) {\n if (e.ajaxreturn && e.ajaxreturn.itemid) {\n const state = courseeditor.state;\n const section = state.section.get(e.ajaxreturn.itemid);\n if (section !== undefined) {\n courseeditor.dispatch('sectionState', [e.ajaxreturn.itemid]);\n }\n }\n });\n $('body').on('updated', `${SELECTOR.ACTIVITYLI} [data-inplaceeditable]`, function(e) {\n if (e.ajaxreturn && e.ajaxreturn.itemid) {\n courseeditor.dispatch('cmState', [e.ajaxreturn.itemid]);\n }\n });\n\n // Component-based formats don't use modals to create sections.\n if (courseeditor.supportComponents && componentActions.includes('addSection')) {\n return;\n }\n\n // Add a handler for \"Add sections\" link to ask for a number of sections to add.\n str.get_string('numberweeks').done(function(strNumberSections) {\n var trigger = $(SELECTOR.ADDSECTIONS),\n modalTitle = trigger.attr('data-add-sections'),\n newSections = trigger.attr('data-new-sections');\n var modalBody = $('
' +\n '
');\n modalBody.find('label').html(strNumberSections);\n ModalFactory.create({\n title: modalTitle,\n type: ModalFactory.types.SAVE_CANCEL,\n body: modalBody.html()\n }, trigger)\n .done(function(modal) {\n var numSections = $(modal.getBody()).find('#add_section_numsections'),\n addSections = function() {\n // Check if value of the \"Number of sections\" is a valid positive integer and redirect\n // to adding a section script.\n if ('' + parseInt(numSections.val()) === numSections.val() && parseInt(numSections.val()) >= 1) {\n document.location = trigger.attr('href') + '&numsections=' + parseInt(numSections.val());\n }\n };\n modal.setSaveButtonText(modalTitle);\n modal.getRoot().on(ModalEvents.shown, function() {\n // When modal is shown focus and select the input and add a listener to keypress of \"Enter\".\n numSections.focus().select().on('keydown', function(e) {\n if (e.keyCode === KeyCodes.enter) {\n addSections();\n }\n });\n });\n modal.getRoot().on(ModalEvents.save, function(e) {\n // When modal \"Add\" button is pressed.\n e.preventDefault();\n addSections();\n });\n });\n });\n },\n\n /**\n * Replaces a section action menu item with another one (for example Show->Hide, Set marker->Remove marker)\n *\n * This method can be used by course formats in their listener to the coursesectionedited event\n *\n * @deprecated since Moodle 3.9\n * @param {JQuery} sectionelement\n * @param {String} selector CSS selector inside the section element, for example \"a[data-action=show]\"\n * @param {String} image new image name (\"i/show\", \"i/hide\", etc.)\n * @param {String} stringname new string for the action menu item\n * @param {String} stringcomponent\n * @param {String} newaction new value for data-action attribute of the link\n */\n replaceSectionActionItem: function(sectionelement, selector, image, stringname,\n stringcomponent, newaction) {\n log.debug('replaceSectionActionItem() is deprecated and will be removed.');\n var actionitem = sectionelement.find(SELECTOR.SECTIONACTIONMENU + ' ' + selector);\n replaceActionItem(actionitem, image, stringname, stringcomponent, newaction);\n },\n // Method to refresh a module.\n refreshModule,\n refreshSection,\n };\n });\n"],"file":"actions.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/actions.js"],"names":["define","$","ajax","templates","notification","str","url","Y","ModalFactory","ModalEvents","KeyCodes","log","editor","EventDispatcher","CourseEvents","componentActions","courseeditor","getCurrentCourseEditor","formatname","CSS","EDITINPROGRESS","SECTIONDRAGGABLE","EDITINGMOVE","SELECTOR","ACTIVITYLI","ACTIONAREA","ACTIVITYACTION","MENU","TOGGLE","SECTIONLI","SECTIONACTIONMENU","ADDSECTIONS","use","courseformatselector","M","course","format","get_section_selector","dispatchEvent","eventName","detail","container","options","Element","get","getModuleId","element","item","dataset","id","Moodle","core_course","util","cm","getId","Node","getModuleName","name","getName","state","cmid","addActivitySpinner","activity","addClass","actionarea","find","spinner","add_spinner","show","data","dispatch","addSectionSpinner","sectionelement","addSectionLightbox","lightbox","add_lightbox","for","setAttribute","removeSpinner","delay","window","setTimeout","removeClass","hide","mutation","removeLightbox","getAttribute","initActionMenu","elementid","coursebase","invoke_function","core","actionmenu","newDOMNode","one","focusActionItem","elementId","action","mainelement","selector","is","focus","findNextFocusable","mainElement","tabables","isInside","foundElement","each","contains","editModule","moduleElement","target","attr","promises","call","methodname","args","sectionreturn","closest","when","apply","done","elementToFocus","replaceWith","affectedids","index","push","trigger","Event","ajaxreturn","fail","ex","e","exception","isDefaultPrevented","refreshModule","sectionReturn","activityElement","Promise","resolve","reject","replaceActivityHtmlWith","confirmDeleteModule","onconfirm","modtypename","match","modulename","get_string","pluginname","get_strings","key","component","param","type","s","confirm","confirmEditSection","message","replaceActionItem","actionitem","image","stringname","stringcomponent","newaction","then","strings","html","renderPix","pixhtml","catch","defaultEditSectionHandler","sectionElement","actionItem","courseformat","sectionid","modules","i","section_availability","first","section","oldmarker","oldActionItem","getActivityFocusedElement","document","getElementById","activeElement","querySelector","activityHTML","focusedPath","newItem","editSection","supportComponents","includes","dataencoded","parseJSON","register_module","set_visibility_resource_ui","getDOMNode","updateMovedCmState","params","updateMovedSectionState","addMutations","legacyActivityAction","statemanager","setReadOnly","locked","cmlist","reduce","current","delete","visible","legacySectionAction","forEach","initCoursePage","on","keyCode","moduleId","preventDefault","sectionId","isExecuted","itemid","strNumberSections","modalTitle","newSections","modalBody","create","title","types","SAVE_CANCEL","body","modal","numSections","getBody","addSections","parseInt","val","location","setSaveButtonText","getRoot","shown","select","enter","save","replaceSectionActionItem","debug","refreshSection","newSectionElement","content","event","sectionRefreshed","defaultPrevented"],"mappings":"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","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 * Various actions on modules and sections in the editing mode - hiding, duplicating, deleting, etc.\n *\n * @module core_course/actions\n * @copyright 2016 Marina Glancy\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 3.3\n */\ndefine(\n [\n 'jquery',\n 'core/ajax',\n 'core/templates',\n 'core/notification',\n 'core/str',\n 'core/url',\n 'core/yui',\n 'core/modal_factory',\n 'core/modal_events',\n 'core/key_codes',\n 'core/log',\n 'core_courseformat/courseeditor',\n 'core/event_dispatcher',\n 'core_course/events'\n ],\n function(\n $,\n ajax,\n templates,\n notification,\n str,\n url,\n Y,\n ModalFactory,\n ModalEvents,\n KeyCodes,\n log,\n editor,\n EventDispatcher,\n CourseEvents\n ) {\n\n // Eventually, core_courseformat/local/content/actions will handle all actions for\n // component compatible formats and the default actions.js won't be necessary anymore.\n // Meanwhile, we filter the migrated actions.\n const componentActions = ['moveSection', 'moveCm', 'addSection', 'deleteSection'];\n\n // The course reactive instance.\n const courseeditor = editor.getCurrentCourseEditor();\n\n // The current course format name (loaded on init).\n let formatname;\n\n var CSS = {\n EDITINPROGRESS: 'editinprogress',\n SECTIONDRAGGABLE: 'sectiondraggable',\n EDITINGMOVE: 'editing_move'\n };\n var SELECTOR = {\n ACTIVITYLI: 'li.activity',\n ACTIONAREA: '.actions',\n ACTIVITYACTION: 'a.cm-edit-action',\n MENU: '.moodle-actionmenu[data-enhance=moodle-core-actionmenu]',\n TOGGLE: '.toggle-display,.dropdown-toggle',\n SECTIONLI: 'li.section',\n SECTIONACTIONMENU: '.section_action_menu',\n ADDSECTIONS: '.changenumsections [data-add-sections]'\n };\n\n Y.use('moodle-course-coursebase', function() {\n var courseformatselector = M.course.format.get_section_selector();\n if (courseformatselector) {\n SELECTOR.SECTIONLI = courseformatselector;\n }\n });\n\n /**\n * Dispatch event wrapper.\n *\n * Old jQuery events will be replaced by native events gradually.\n *\n * @method dispatchEvent\n * @param {String} eventName The name of the event\n * @param {Object} detail Any additional details to pass into the eveent\n * @param {Node|HTMLElement} container The point at which to dispatch the event\n * @param {Object} options\n * @param {Boolean} options.bubbles Whether to bubble up the DOM\n * @param {Boolean} options.cancelable Whether preventDefault() can be called\n * @param {Boolean} options.composed Whether the event can bubble across the ShadowDOM boundary\n * @returns {CustomEvent}\n */\n const dispatchEvent = function(eventName, detail, container, options) {\n // Most actions still uses jQuery node instead of regular HTMLElement.\n if (!(container instanceof Element) && container.get !== undefined) {\n container = container.get(0);\n }\n return EventDispatcher.dispatchEvent(eventName, detail, container, options);\n };\n\n /**\n * Wrapper for Y.Moodle.core_course.util.cm.getId\n *\n * @param {JQuery} element\n * @returns {Integer}\n */\n var getModuleId = function(element) {\n // Check if we have a data-id first.\n const item = element.get(0);\n if (item.dataset.id) {\n return item.dataset.id;\n }\n // Use YUI way if data-id is not present.\n let id;\n Y.use('moodle-course-util', function(Y) {\n id = Y.Moodle.core_course.util.cm.getId(Y.Node(item));\n });\n return id;\n };\n\n /**\n * Wrapper for Y.Moodle.core_course.util.cm.getName\n *\n * @param {JQuery} element\n * @returns {String}\n */\n var getModuleName = function(element) {\n var name;\n Y.use('moodle-course-util', function(Y) {\n name = Y.Moodle.core_course.util.cm.getName(Y.Node(element.get(0)));\n });\n // Check if we have the name in the course state.\n const state = courseeditor.state;\n const cmid = getModuleId(element);\n if (!name && state && cmid) {\n name = state.cm.get(cmid)?.name;\n }\n return name;\n };\n\n /**\n * Wrapper for M.util.add_spinner for an activity\n *\n * @param {JQuery} activity\n * @returns {Node}\n */\n var addActivitySpinner = function(activity) {\n activity.addClass(CSS.EDITINPROGRESS);\n var actionarea = activity.find(SELECTOR.ACTIONAREA).get(0);\n if (actionarea) {\n var spinner = M.util.add_spinner(Y, Y.Node(actionarea));\n spinner.show();\n // Lock the activity state element.\n if (activity.data('id') !== undefined) {\n courseeditor.dispatch('cmLock', [activity.data('id')], true);\n }\n return spinner;\n }\n return null;\n };\n\n /**\n * Wrapper for M.util.add_spinner for a section\n *\n * @param {JQuery} sectionelement\n * @returns {Node}\n */\n var addSectionSpinner = function(sectionelement) {\n sectionelement.addClass(CSS.EDITINPROGRESS);\n var actionarea = sectionelement.find(SELECTOR.SECTIONACTIONMENU).get(0);\n if (actionarea) {\n var spinner = M.util.add_spinner(Y, Y.Node(actionarea));\n spinner.show();\n // Lock the section state element.\n if (sectionelement.data('id') !== undefined) {\n courseeditor.dispatch('sectionLock', [sectionelement.data('id')], true);\n }\n return spinner;\n }\n return null;\n };\n\n /**\n * Wrapper for M.util.add_lightbox\n *\n * @param {JQuery} sectionelement\n * @returns {Node}\n */\n var addSectionLightbox = function(sectionelement) {\n const item = sectionelement.get(0);\n var lightbox = M.util.add_lightbox(Y, Y.Node(item));\n if (item.dataset.for == 'section' && item.dataset.id) {\n courseeditor.dispatch('sectionLock', [item.dataset.id], true);\n lightbox.setAttribute('data-state', 'section');\n lightbox.setAttribute('data-state-id', item.dataset.id);\n }\n lightbox.show();\n return lightbox;\n };\n\n /**\n * Removes the spinner element\n *\n * @param {JQuery} element\n * @param {Node} spinner\n * @param {Number} delay\n */\n var removeSpinner = function(element, spinner, delay) {\n window.setTimeout(function() {\n element.removeClass(CSS.EDITINPROGRESS);\n if (spinner) {\n spinner.hide();\n }\n // Unlock the state element.\n if (element.data('id') !== undefined) {\n const mutation = (element.data('for') === 'section') ? 'sectionLock' : 'cmLock';\n courseeditor.dispatch(mutation, [element.data('id')], false);\n }\n }, delay);\n };\n\n /**\n * Removes the lightbox element\n *\n * @param {Node} lightbox lighbox YUI element returned by addSectionLightbox\n * @param {Number} delay\n */\n var removeLightbox = function(lightbox, delay) {\n if (lightbox) {\n window.setTimeout(function() {\n lightbox.hide();\n // Unlock state if necessary.\n if (lightbox.getAttribute('data-state')) {\n courseeditor.dispatch(\n `${lightbox.getAttribute('data-state')}Lock`,\n [lightbox.getAttribute('data-state-id')],\n false\n );\n }\n }, delay);\n }\n };\n\n /**\n * Initialise action menu for the element (section or module)\n *\n * @param {String} elementid CSS id attribute of the element\n */\n var initActionMenu = function(elementid) {\n // Initialise action menu in the new activity.\n Y.use('moodle-course-coursebase', function() {\n M.course.coursebase.invoke_function('setup_for_resource', '#' + elementid);\n });\n if (M.core.actionmenu && M.core.actionmenu.newDOMNode) {\n M.core.actionmenu.newDOMNode(Y.one('#' + elementid));\n }\n };\n\n /**\n * Returns focus to the element that was clicked or \"Edit\" link if element is no longer visible.\n *\n * @param {String} elementId CSS id attribute of the element\n * @param {String} action data-action property of the element that was clicked\n */\n var focusActionItem = function(elementId, action) {\n var mainelement = $('#' + elementId);\n var selector = '[data-action=' + action + ']';\n if (action === 'groupsseparate' || action === 'groupsvisible' || action === 'groupsnone') {\n // New element will have different data-action.\n selector = '[data-action=groupsseparate],[data-action=groupsvisible],[data-action=groupsnone]';\n }\n if (mainelement.find(selector).is(':visible')) {\n mainelement.find(selector).focus();\n } else {\n // Element not visible, focus the \"Edit\" link.\n mainelement.find(SELECTOR.MENU).find(SELECTOR.TOGGLE).focus();\n }\n };\n\n /**\n * Find next
after the element\n *\n * @param {JQuery} mainElement element that is about to be deleted\n * @returns {JQuery}\n */\n var findNextFocusable = function(mainElement) {\n var tabables = $(\"a:visible\");\n var isInside = false;\n var foundElement = null;\n tabables.each(function() {\n if ($.contains(mainElement[0], this)) {\n isInside = true;\n } else if (isInside) {\n foundElement = this;\n return false; // Returning false in .each() is equivalent to \"break;\" inside the loop in php.\n }\n return true;\n });\n return foundElement;\n };\n\n /**\n * Performs an action on a module (moving, deleting, duplicating, hiding, etc.)\n *\n * @param {JQuery} moduleElement activity element we perform action on\n * @param {Number} cmid\n * @param {JQuery} target the element (menu item) that was clicked\n */\n var editModule = function(moduleElement, cmid, target) {\n var action = target.attr('data-action');\n var spinner = addActivitySpinner(moduleElement);\n var promises = ajax.call([{\n methodname: 'core_course_edit_module',\n args: {id: cmid,\n action: action,\n sectionreturn: target.attr('data-sectionreturn') ? target.attr('data-sectionreturn') : 0\n }\n }], true);\n\n var lightbox;\n if (action === 'duplicate') {\n lightbox = addSectionLightbox(target.closest(SELECTOR.SECTIONLI));\n }\n $.when.apply($, promises)\n .done(function(data) {\n var elementToFocus = findNextFocusable(moduleElement);\n moduleElement.replaceWith(data);\n let affectedids = [];\n // Initialise action menu for activity(ies) added as a result of this.\n $('
' + data + '
').find(SELECTOR.ACTIVITYLI).each(function(index) {\n initActionMenu($(this).attr('id'));\n if (index === 0) {\n focusActionItem($(this).attr('id'), action);\n elementToFocus = null;\n }\n // Save any activity id in cmids.\n affectedids.push(getModuleId($(this)));\n });\n // In case of activity deletion focus the next focusable element.\n if (elementToFocus) {\n elementToFocus.focus();\n }\n // Remove spinner and lightbox with a delay.\n removeSpinner(moduleElement, spinner, 400);\n removeLightbox(lightbox, 400);\n // Trigger event that can be observed by course formats.\n moduleElement.trigger($.Event('coursemoduleedited', {ajaxreturn: data, action: action}));\n\n // Modify cm state.\n courseeditor.dispatch('legacyActivityAction', action, cmid, affectedids);\n\n }).fail(function(ex) {\n // Remove spinner and lightbox.\n removeSpinner(moduleElement, spinner);\n removeLightbox(lightbox);\n // Trigger event that can be observed by course formats.\n var e = $.Event('coursemoduleeditfailed', {exception: ex, action: action});\n moduleElement.trigger(e);\n if (!e.isDefaultPrevented()) {\n notification.exception(ex);\n }\n });\n };\n\n /**\n * Requests html for the module via WS core_course_get_module and updates the module on the course page\n *\n * Used after d&d of the module to another section\n *\n * @param {JQuery|Element} element\n * @param {Number} cmid\n * @param {Number} sectionreturn\n * @return {Promise} the refresh promise\n */\n var refreshModule = function(element, cmid, sectionreturn) {\n\n if (sectionreturn === undefined) {\n sectionreturn = courseeditor.sectionReturn;\n }\n\n const activityElement = $(element);\n var spinner = addActivitySpinner(activityElement);\n var promises = ajax.call([{\n methodname: 'core_course_get_module',\n args: {id: cmid, sectionreturn: sectionreturn}\n }], true);\n\n return new Promise((resolve, reject) => {\n $.when.apply($, promises)\n .done(function(data) {\n removeSpinner(activityElement, spinner, 400);\n replaceActivityHtmlWith(data);\n resolve(data);\n }).fail(function() {\n removeSpinner(activityElement, spinner);\n reject();\n });\n });\n };\n\n /**\n * Requests html for the section via WS core_course_edit_section and updates the section on the course page\n *\n * @param {JQuery|Element} element\n * @param {Number} sectionid\n * @param {Number} sectionreturn\n * @return {Promise} the refresh promise\n */\n var refreshSection = function(element, sectionid, sectionreturn) {\n\n if (sectionreturn === undefined) {\n sectionreturn = courseeditor.sectionReturn;\n }\n\n const sectionElement = $(element);\n const action = 'refresh';\n const promises = ajax.call([{\n methodname: 'core_course_edit_section',\n args: {id: sectionid, action, sectionreturn},\n }], true);\n\n var spinner = addSectionSpinner(sectionElement);\n return new Promise((resolve, reject) => {\n $.when.apply($, promises)\n .done(dataencoded => {\n\n removeSpinner(sectionElement, spinner);\n const data = $.parseJSON(dataencoded);\n\n const newSectionElement = $(data.content);\n sectionElement.replaceWith(newSectionElement);\n\n // Init modules menus.\n $(`${SELECTOR.SECTIONLI}#${sectionid} ${SELECTOR.ACTIVITYLI}`).each(\n (index, activity) => {\n initActionMenu(activity.data('id'));\n }\n );\n\n // Trigger event that can be observed by course formats.\n const event = dispatchEvent(\n CourseEvents.sectionRefreshed,\n {\n ajaxreturn: data,\n action: action,\n newSectionElement: newSectionElement.get(0),\n },\n newSectionElement\n );\n\n if (!event.defaultPrevented) {\n defaultEditSectionHandler(\n newSectionElement, $(SELECTOR.SECTIONLI + '#' + sectionid),\n data,\n formatname,\n sectionid\n );\n }\n resolve(data);\n }).fail(ex => {\n // Trigger event that can be observed by course formats.\n const event = dispatchEvent(\n 'coursesectionrefreshfailed',\n {exception: ex, action: action},\n sectionElement\n );\n if (!event.defaultPrevented) {\n notification.exception(ex);\n }\n reject();\n });\n });\n };\n\n /**\n * Displays the delete confirmation to delete a module\n *\n * @param {JQuery} mainelement activity element we perform action on\n * @param {function} onconfirm function to execute on confirm\n */\n var confirmDeleteModule = function(mainelement, onconfirm) {\n var modtypename = mainelement.attr('class').match(/modtype_([^\\s]*)/)[1];\n var modulename = getModuleName(mainelement);\n\n str.get_string('pluginname', modtypename).done(function(pluginname) {\n var plugindata = {\n type: pluginname,\n name: modulename\n };\n str.get_strings([\n {key: 'confirm', component: 'core'},\n {key: modulename === null ? 'deletechecktype' : 'deletechecktypename', param: plugindata},\n {key: 'yes'},\n {key: 'no'}\n ]).done(function(s) {\n notification.confirm(s[0], s[1], s[2], s[3], onconfirm);\n }\n );\n });\n };\n\n /**\n * Displays the delete confirmation to delete a section\n *\n * @param {String} message confirmation message\n * @param {function} onconfirm function to execute on confirm\n */\n var confirmEditSection = function(message, onconfirm) {\n str.get_strings([\n {key: 'confirm'}, // TODO link text\n {key: 'yes'},\n {key: 'no'}\n ]).done(function(s) {\n notification.confirm(s[0], message, s[1], s[2], onconfirm);\n }\n );\n };\n\n /**\n * Replaces an action menu item with another one (for example Show->Hide, Set marker->Remove marker)\n *\n * @param {JQuery} actionitem\n * @param {String} image new image name (\"i/show\", \"i/hide\", etc.)\n * @param {String} stringname new string for the action menu item\n * @param {String} stringcomponent\n * @param {String} newaction new value for data-action attribute of the link\n * @return {Promise} promise which is resolved when the replacement has completed\n */\n var replaceActionItem = function(actionitem, image, stringname,\n stringcomponent, newaction) {\n\n var stringRequests = [{key: stringname, component: stringcomponent}];\n // Do not provide an icon with duplicate, different text to the menu item.\n\n return str.get_strings(stringRequests).then(function(strings) {\n actionitem.find('span.menu-action-text').html(strings[0]);\n\n return templates.renderPix(image, 'core');\n }).then(function(pixhtml) {\n actionitem.find('.icon').replaceWith(pixhtml);\n actionitem.attr('data-action', newaction);\n return;\n }).catch(notification.exception);\n };\n\n /**\n * Default post-processing for section AJAX edit actions.\n *\n * This can be overridden in course formats by listening to event coursesectionedited:\n *\n * $('body').on('coursesectionedited', 'li.section', function(e) {\n * var action = e.action,\n * sectionElement = $(e.target),\n * data = e.ajaxreturn;\n * // ... Do some processing here.\n * e.preventDefault(); // Prevent default handler.\n * });\n *\n * @param {JQuery} sectionElement\n * @param {JQuery} actionItem\n * @param {Object} data\n * @param {String} courseformat\n * @param {Number} sectionid\n */\n var defaultEditSectionHandler = function(sectionElement, actionItem, data, courseformat, sectionid) {\n var action = actionItem.attr('data-action');\n if (action === 'hide' || action === 'show') {\n if (action === 'hide') {\n sectionElement.addClass('hidden');\n replaceActionItem(actionItem, 'i/show',\n 'showfromothers', 'format_' + courseformat, 'show');\n } else {\n sectionElement.removeClass('hidden');\n replaceActionItem(actionItem, 'i/hide',\n 'hidefromothers', 'format_' + courseformat, 'hide');\n }\n // Replace the modules with new html (that indicates that they are now hidden or not hidden).\n if (data.modules !== undefined) {\n for (var i in data.modules) {\n replaceActivityHtmlWith(data.modules[i]);\n }\n }\n // Replace the section availability information.\n if (data.section_availability !== undefined) {\n sectionElement.find('.section_availability').first().replaceWith(data.section_availability);\n }\n // Modify course state.\n const section = courseeditor.state.section.get(sectionid);\n if (section !== undefined) {\n courseeditor.dispatch('sectionState', [sectionid]);\n }\n } else if (action === 'setmarker') {\n var oldmarker = $(SELECTOR.SECTIONLI + '.current'),\n oldActionItem = oldmarker.find(SELECTOR.SECTIONACTIONMENU + ' ' + 'a[data-action=removemarker]');\n oldmarker.removeClass('current');\n replaceActionItem(oldActionItem, 'i/marker',\n 'highlight', 'core', 'setmarker');\n sectionElement.addClass('current');\n replaceActionItem(actionItem, 'i/marked',\n 'highlightoff', 'core', 'removemarker');\n courseeditor.dispatch('legacySectionAction', action, sectionid);\n } else if (action === 'removemarker') {\n sectionElement.removeClass('current');\n replaceActionItem(actionItem, 'i/marker',\n 'highlight', 'core', 'setmarker');\n courseeditor.dispatch('legacySectionAction', action, sectionid);\n }\n };\n\n /**\n * Get the focused element path in an activity if any.\n *\n * This method is used to restore focus when the activity HTML is refreshed.\n * Only the main course editor elements can be refocused as they are always present\n * even if the activity content changes.\n *\n * @param {String} id the element id the activity element\n * @return {String|undefined} the inner path of the focused element or undefined\n */\n const getActivityFocusedElement = function(id) {\n const element = document.getElementById(id);\n if (!element || !element.contains(document.activeElement)) {\n return undefined;\n }\n // Check if the actions menu toggler is focused.\n if (element.querySelector(SELECTOR.ACTIONAREA).contains(document.activeElement)) {\n return `${SELECTOR.ACTIONAREA} [tabindex=\"0\"]`;\n }\n // Return the current element id if any.\n if (document.activeElement.id) {\n return `#${document.activeElement.id}`;\n }\n return undefined;\n };\n\n /**\n * Replaces the course module with the new html (used to update module after it was edited or its visibility was changed).\n *\n * @param {String} activityHTML\n */\n var replaceActivityHtmlWith = function(activityHTML) {\n $('
' + activityHTML + '
').find(SELECTOR.ACTIVITYLI).each(function() {\n // Extract id from the new activity html.\n var id = $(this).attr('id');\n // Check if the current focused element is inside the activity.\n let focusedPath = getActivityFocusedElement(id);\n // Find the existing element with the same id and replace its contents with new html.\n $(SELECTOR.ACTIVITYLI + '#' + id).replaceWith(activityHTML);\n // Initialise action menu.\n initActionMenu(id);\n // Re-focus the previous elements.\n if (focusedPath) {\n const newItem = document.getElementById(id);\n newItem.querySelector(focusedPath)?.focus();\n }\n\n });\n };\n\n /**\n * Performs an action on a module (moving, deleting, duplicating, hiding, etc.)\n *\n * @param {JQuery} sectionElement section element we perform action on\n * @param {Nunmber} sectionid\n * @param {JQuery} target the element (menu item) that was clicked\n * @param {String} courseformat\n * @return {boolean} true the action call is sent to the server or false if it is ignored.\n */\n var editSection = function(sectionElement, sectionid, target, courseformat) {\n var action = target.attr('data-action'),\n sectionreturn = target.attr('data-sectionreturn') ? target.attr('data-sectionreturn') : 0;\n\n // Filter direct component handled actions.\n if (courseeditor.supportComponents && componentActions.includes(action)) {\n return false;\n }\n\n var spinner = addSectionSpinner(sectionElement);\n var promises = ajax.call([{\n methodname: 'core_course_edit_section',\n args: {id: sectionid, action: action, sectionreturn: sectionreturn}\n }], true);\n\n var lightbox = addSectionLightbox(sectionElement);\n $.when.apply($, promises)\n .done(function(dataencoded) {\n var data = $.parseJSON(dataencoded);\n removeSpinner(sectionElement, spinner);\n removeLightbox(lightbox);\n sectionElement.find(SELECTOR.SECTIONACTIONMENU).find(SELECTOR.TOGGLE).focus();\n // Trigger event that can be observed by course formats.\n var e = $.Event('coursesectionedited', {ajaxreturn: data, action: action});\n sectionElement.trigger(e);\n if (!e.isDefaultPrevented()) {\n defaultEditSectionHandler(sectionElement, target, data, courseformat, sectionid);\n }\n }).fail(function(ex) {\n // Remove spinner and lightbox.\n removeSpinner(sectionElement, spinner);\n removeLightbox(lightbox);\n // Trigger event that can be observed by course formats.\n var e = $.Event('coursesectioneditfailed', {exception: ex, action: action});\n sectionElement.trigger(e);\n if (!e.isDefaultPrevented()) {\n notification.exception(ex);\n }\n });\n return true;\n };\n\n // Register a function to be executed after D&D of an activity.\n Y.use('moodle-course-coursebase', function() {\n M.course.coursebase.register_module({\n // Ignore camelcase eslint rule for the next line because it is an expected name of the callback.\n // eslint-disable-next-line camelcase\n set_visibility_resource_ui: function(args) {\n var mainelement = $(args.element.getDOMNode());\n var cmid = getModuleId(mainelement);\n if (cmid) {\n var sectionreturn = mainelement.find('.' + CSS.EDITINGMOVE).attr('data-sectionreturn');\n refreshModule(mainelement, cmid, sectionreturn);\n }\n },\n /**\n * Update the course state when some cm is moved via YUI.\n * @param {*} params\n */\n updateMovedCmState: (params) => {\n const state = courseeditor.state;\n\n // Update old section.\n const cm = state.cm.get(params.cmid);\n if (cm !== undefined) {\n courseeditor.dispatch('sectionState', [cm.sectionid]);\n }\n // Update cm state.\n courseeditor.dispatch('cmState', [params.cmid]);\n },\n /**\n * Update the course state when some section is moved via YUI.\n */\n updateMovedSectionState: () => {\n courseeditor.dispatch('courseState');\n },\n });\n });\n\n // From Moodle 4.0 all edit actions are being re-implemented as state mutation.\n // This means all method from this \"actions\" module will be deprecated when all the course\n // interface is migrated to reactive components.\n // Most legacy actions did not provide enough information to regenarate the course so they\n // use the mutations courseState, sectionState and cmState to get the updated state from\n // the server. However, some activity actions where we can prevent an extra webservice\n // call by implementing an adhoc mutation.\n courseeditor.addMutations({\n /**\n * Compatibility function to update Moodle 4.0 course state using legacy actions.\n *\n * This method only updates some actions which does not require to use cmState mutation\n * to get updated data form the server.\n *\n * @param {Object} statemanager the current state in read write mode\n * @param {String} action the performed action\n * @param {Number} cmid the affected course module id\n * @param {Array} affectedids all affected cm ids (for duplicate action)\n */\n legacyActivityAction: function(statemanager, action, cmid, affectedids) {\n\n const state = statemanager.state;\n const cm = state.cm.get(cmid);\n if (cm === undefined) {\n return;\n }\n const section = state.section.get(cm.sectionid);\n if (section === undefined) {\n return;\n }\n\n // Send the element is locked.\n courseeditor.dispatch('cmLock', [cm.id], true);\n\n // Now we do the real mutation.\n statemanager.setReadOnly(false);\n\n // This unlocked will take effect when the read only is restored.\n cm.locked = false;\n\n switch (action) {\n case 'delete':\n // Remove from section.\n section.cmlist = section.cmlist.reduce(\n (cmlist, current) => {\n if (current != cmid) {\n cmlist.push(current);\n }\n return cmlist;\n },\n []\n );\n // Delete form list.\n state.cm.delete(cmid);\n break;\n\n case 'hide':\n case 'show':\n cm.visible = (action === 'show') ? true : false;\n break;\n\n case 'duplicate':\n // Duplicate requires to get extra data from the server.\n courseeditor.dispatch('cmState', affectedids);\n break;\n }\n statemanager.setReadOnly(true);\n },\n legacySectionAction: function(statemanager, action, sectionid) {\n\n const state = statemanager.state;\n const section = state.section.get(sectionid);\n if (section === undefined) {\n return;\n }\n\n // Send the element is locked. Reactive events are only triggered when the state\n // read only mode is restored. We want to notify the interface the element is\n // locked so we need to do a quick lock operation before performing the rest\n // of the mutation.\n statemanager.setReadOnly(false);\n section.locked = true;\n statemanager.setReadOnly(true);\n\n // Now we do the real mutation.\n statemanager.setReadOnly(false);\n\n // This locked will take effect when the read only is restored.\n section.locked = false;\n\n switch (action) {\n case 'setmarker':\n // Remove previous marker.\n state.section.forEach((current) => {\n if (current.id != sectionid) {\n current.current = false;\n }\n });\n section.current = true;\n break;\n\n case 'removemarker':\n section.current = false;\n break;\n }\n statemanager.setReadOnly(true);\n },\n });\n\n return /** @alias module:core_course/actions */ {\n\n /**\n * Initialises course page\n *\n * @method init\n * @param {String} courseformat name of the current course format (for fetching strings)\n */\n initCoursePage: function(courseformat) {\n\n formatname = courseformat;\n\n // Add a handler for course module actions.\n $('body').on('click keypress', SELECTOR.ACTIVITYLI + ' ' +\n SELECTOR.ACTIVITYACTION + '[data-action]', function(e) {\n if (e.type === 'keypress' && e.keyCode !== 13) {\n return;\n }\n var actionItem = $(this),\n moduleElement = actionItem.closest(SELECTOR.ACTIVITYLI),\n action = actionItem.attr('data-action'),\n moduleId = getModuleId(moduleElement);\n switch (action) {\n case 'moveleft':\n case 'moveright':\n case 'delete':\n case 'duplicate':\n case 'hide':\n case 'stealth':\n case 'show':\n case 'groupsseparate':\n case 'groupsvisible':\n case 'groupsnone':\n break;\n default:\n // Nothing to do here!\n return;\n }\n if (!moduleId) {\n return;\n }\n e.preventDefault();\n if (action === 'delete') {\n // Deleting requires confirmation.\n confirmDeleteModule(moduleElement, function() {\n editModule(moduleElement, moduleId, actionItem);\n });\n } else {\n editModule(moduleElement, moduleId, actionItem);\n }\n });\n\n // Add a handler for section show/hide actions.\n $('body').on('click keypress', SELECTOR.SECTIONLI + ' ' +\n SELECTOR.SECTIONACTIONMENU + '[data-sectionid] ' +\n 'a[data-action]', function(e) {\n if (e.type === 'keypress' && e.keyCode !== 13) {\n return;\n }\n var actionItem = $(this),\n sectionElement = actionItem.closest(SELECTOR.SECTIONLI),\n sectionId = actionItem.closest(SELECTOR.SECTIONACTIONMENU).attr('data-sectionid');\n\n let isExecuted = true;\n if (actionItem.attr('data-confirm')) {\n // Action requires confirmation.\n confirmEditSection(actionItem.attr('data-confirm'), function() {\n isExecuted = editSection(sectionElement, sectionId, actionItem, courseformat);\n });\n } else {\n isExecuted = editSection(sectionElement, sectionId, actionItem, courseformat);\n }\n // Prevent any other module from capturing the action if it is already in execution.\n if (isExecuted) {\n e.preventDefault();\n }\n });\n\n // The section and activity names are edited using inplace editable.\n // The \"update\" jQuery event must be captured in order to update the course state.\n $('body').on('updated', `${SELECTOR.SECTIONLI} [data-inplaceeditable]`, function(e) {\n if (e.ajaxreturn && e.ajaxreturn.itemid) {\n const state = courseeditor.state;\n const section = state.section.get(e.ajaxreturn.itemid);\n if (section !== undefined) {\n courseeditor.dispatch('sectionState', [e.ajaxreturn.itemid]);\n }\n }\n });\n $('body').on('updated', `${SELECTOR.ACTIVITYLI} [data-inplaceeditable]`, function(e) {\n if (e.ajaxreturn && e.ajaxreturn.itemid) {\n courseeditor.dispatch('cmState', [e.ajaxreturn.itemid]);\n }\n });\n\n // Component-based formats don't use modals to create sections.\n if (courseeditor.supportComponents && componentActions.includes('addSection')) {\n return;\n }\n\n // Add a handler for \"Add sections\" link to ask for a number of sections to add.\n str.get_string('numberweeks').done(function(strNumberSections) {\n var trigger = $(SELECTOR.ADDSECTIONS),\n modalTitle = trigger.attr('data-add-sections'),\n newSections = trigger.attr('data-new-sections');\n var modalBody = $('
' +\n '
');\n modalBody.find('label').html(strNumberSections);\n ModalFactory.create({\n title: modalTitle,\n type: ModalFactory.types.SAVE_CANCEL,\n body: modalBody.html()\n }, trigger)\n .done(function(modal) {\n var numSections = $(modal.getBody()).find('#add_section_numsections'),\n addSections = function() {\n // Check if value of the \"Number of sections\" is a valid positive integer and redirect\n // to adding a section script.\n if ('' + parseInt(numSections.val()) === numSections.val() && parseInt(numSections.val()) >= 1) {\n document.location = trigger.attr('href') + '&numsections=' + parseInt(numSections.val());\n }\n };\n modal.setSaveButtonText(modalTitle);\n modal.getRoot().on(ModalEvents.shown, function() {\n // When modal is shown focus and select the input and add a listener to keypress of \"Enter\".\n numSections.focus().select().on('keydown', function(e) {\n if (e.keyCode === KeyCodes.enter) {\n addSections();\n }\n });\n });\n modal.getRoot().on(ModalEvents.save, function(e) {\n // When modal \"Add\" button is pressed.\n e.preventDefault();\n addSections();\n });\n });\n });\n },\n\n /**\n * Replaces a section action menu item with another one (for example Show->Hide, Set marker->Remove marker)\n *\n * This method can be used by course formats in their listener to the coursesectionedited event\n *\n * @deprecated since Moodle 3.9\n * @param {JQuery} sectionelement\n * @param {String} selector CSS selector inside the section element, for example \"a[data-action=show]\"\n * @param {String} image new image name (\"i/show\", \"i/hide\", etc.)\n * @param {String} stringname new string for the action menu item\n * @param {String} stringcomponent\n * @param {String} newaction new value for data-action attribute of the link\n */\n replaceSectionActionItem: function(sectionelement, selector, image, stringname,\n stringcomponent, newaction) {\n log.debug('replaceSectionActionItem() is deprecated and will be removed.');\n var actionitem = sectionelement.find(SELECTOR.SECTIONACTIONMENU + ' ' + selector);\n replaceActionItem(actionitem, image, stringname, stringcomponent, newaction);\n },\n // Method to refresh a module.\n refreshModule,\n refreshSection,\n };\n });\n"],"file":"actions.min.js"} \ No newline at end of file diff --git a/course/amd/src/actions.js b/course/amd/src/actions.js index 491cde1e462ed..8be6d37528802 100644 --- a/course/amd/src/actions.js +++ b/course/amd/src/actions.js @@ -201,7 +201,13 @@ define( * @returns {Node} */ var addSectionLightbox = function(sectionelement) { - var lightbox = M.util.add_lightbox(Y, Y.Node(sectionelement.get(0))); + const item = sectionelement.get(0); + var lightbox = M.util.add_lightbox(Y, Y.Node(item)); + if (item.dataset.for == 'section' && item.dataset.id) { + courseeditor.dispatch('sectionLock', [item.dataset.id], true); + lightbox.setAttribute('data-state', 'section'); + lightbox.setAttribute('data-state-id', item.dataset.id); + } lightbox.show(); return lightbox; }; @@ -237,6 +243,14 @@ define( if (lightbox) { window.setTimeout(function() { lightbox.hide(); + // Unlock state if necessary. + if (lightbox.getAttribute('data-state')) { + courseeditor.dispatch( + `${lightbox.getAttribute('data-state')}Lock`, + [lightbox.getAttribute('data-state-id')], + false + ); + } }, delay); } }; @@ -776,18 +790,13 @@ define( return; } - // Send the element is locked. Reactive events are only triggered when the state - // read only mode is restored. We want to notify the interface the element is - // locked so we need to do a quick lock operation before performing the rest - // of the mutation. - statemanager.setReadOnly(false); - cm.locked = true; - statemanager.setReadOnly(true); + // Send the element is locked. + courseeditor.dispatch('cmLock', [cm.id], true); // Now we do the real mutation. statemanager.setReadOnly(false); - // This locked will take effect when the read only is restored. + // This unlocked will take effect when the read only is restored. cm.locked = false; switch (action) { diff --git a/course/classes/output/activity_navigation.php b/course/classes/output/activity_navigation.php index 84461428b6d8e..7412eaebbfaeb 100644 --- a/course/classes/output/activity_navigation.php +++ b/course/classes/output/activity_navigation.php @@ -74,7 +74,6 @@ public function __construct($prevmod, $nextmod, $activitylist = array()) { $attributes = [ 'class' => 'btn btn-link', 'id' => 'prev-activity-link', - 'title' => $linkname, ]; $this->prevlink = new \action_link($linkurl, $OUTPUT->larrow() . ' ' . $linkname, null, $attributes); } @@ -90,7 +89,6 @@ public function __construct($prevmod, $nextmod, $activitylist = array()) { $attributes = [ 'class' => 'btn btn-link', 'id' => 'next-activity-link', - 'title' => $linkname, ]; $this->nextlink = new \action_link($linkurl, $linkname . ' ' . $OUTPUT->rarrow(), null, $attributes); } diff --git a/course/format/amd/build/local/content/section.min.js.map b/course/format/amd/build/local/content/section.min.js.map index 746de71467d49..c5d9ab03907c3 100644 --- a/course/format/amd/build/local/content/section.min.js.map +++ b/course/format/amd/build/local/content/section.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/local/content/section.js"],"names":["name","selectors","SECTION_ITEM","CM","classes","LOCKED","id","element","dataset","state","configState","reactive","isEditing","supportComponents","sectionItem","getElement","headerComponent","Header","fullregion","configDragDrop","watch","handler","_refreshSection","dropdata","type","sectionReturn","cms","getElements","length","classList","toggle","DRAGGING","dragging","locked","DndSection"],"mappings":"gQAwBA,OACA,O,60FAOa,CAEL,KAAKA,IAAL,CAAY,iBAAZ,CAEA,KAAKC,SAAL,CAAiB,CACbC,YAAY,6BADC,CAEbC,EAAE,wBAFW,CAAjB,CAKA,KAAKC,OAAL,CAAe,CACXC,MAAM,CAAE,gBADG,CAAf,CAKA,KAAKC,EAAL,CAAU,KAAKC,OAAL,CAAaC,OAAb,CAAqBF,EAClC,C,8CAOUG,C,CAAO,CACd,KAAKC,WAAL,CAAiBD,CAAjB,EAEA,GAAI,KAAKE,QAAL,CAAcC,SAAd,EAA2B,KAAKD,QAAL,CAAcE,iBAA7C,CAAgE,CAE5D,GAAMC,CAAAA,CAAW,CAAG,KAAKC,UAAL,CAAgB,KAAKd,SAAL,CAAeC,YAA/B,CAApB,CACA,GAAIY,CAAJ,CAAiB,CAEb,GAAME,CAAAA,CAAe,CAAG,GAAIC,UAAJ,MACjB,IADiB,EAEpBV,OAAO,CAAEO,CAFW,CAGpBI,UAAU,CAAE,KAAKX,OAHG,GAAxB,CAKA,KAAKY,cAAL,CAAoBH,CAApB,CACH,CACJ,CACJ,C,iDAOa,CACV,MAAO,CACH,CAACI,KAAK,mBAAa,KAAKd,EAAlB,aAAN,CAAuCe,OAAO,CAAE,KAAKC,eAArD,CADG,CAGV,C,0DAQeC,C,CAAU,CAEvB,GAAuB,SAAnB,WAAAA,CAAQ,WAARA,SAAAA,CAAQ,CAAEC,IAAV,GAA+D,CAA/B,OAAKb,QAAL,CAAcc,aAAlD,CAAsE,CACjE,QACH,CACD,2DAA8BF,CAA9B,CACH,C,6CAOW,CACR,GAAMG,CAAAA,CAAG,CAAG,KAAKC,WAAL,CAAiB,KAAK1B,SAAL,CAAeE,EAAhC,CAAZ,CAEA,GAAI,CAACuB,CAAD,EAAuB,CAAf,GAAAA,CAAG,CAACE,MAAhB,CAA8B,CAC1B,MAAO,KACV,CACD,MAAOF,CAAAA,CAAG,CAACA,CAAG,CAACE,MAAJ,CAAa,CAAd,CACb,C,0DAQ0B,SAAVrB,CAAU,GAAVA,OAAU,CAEvB,KAAKA,OAAL,CAAasB,SAAb,CAAuBC,MAAvB,CAA8B,KAAK1B,OAAL,CAAa2B,QAA3C,WAAqDxB,CAAO,CAACyB,QAA7D,oBACA,KAAKzB,OAAL,CAAasB,SAAb,CAAuBC,MAAvB,CAA8B,KAAK1B,OAAL,CAAaC,MAA3C,WAAmDE,CAAO,CAAC0B,MAA3D,oBACA,KAAKA,MAAL,CAAc1B,CAAO,CAAC0B,MACzB,C,cA/FwBC,S","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course section format component.\n *\n * @module core_courseformat/local/content/section\n * @class core_courseformat/local/content/section\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Header from 'core_courseformat/local/content/section/header';\nimport DndSection from 'core_courseformat/local/courseeditor/dndsection';\n\nexport default class extends DndSection {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'content_section';\n // Default query selectors.\n this.selectors = {\n SECTION_ITEM: `[data-for='section_title']`,\n CM: `[data-for=\"cmitem\"]`,\n };\n // Most classes will be loaded later by DndCmItem.\n this.classes = {\n LOCKED: 'editinprogress',\n };\n\n // We need our id to watch specific events.\n this.id = this.element.dataset.id;\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the initial state\n */\n stateReady(state) {\n this.configState(state);\n // Drag and drop is only available for components compatible course formats.\n if (this.reactive.isEditing && this.reactive.supportComponents) {\n // Section zero and other formats sections may not have a title to drag.\n const sectionItem = this.getElement(this.selectors.SECTION_ITEM);\n if (sectionItem) {\n // Init the inner dragable element.\n const headerComponent = new Header({\n ...this,\n element: sectionItem,\n fullregion: this.element,\n });\n this.configDragDrop(headerComponent);\n }\n }\n }\n\n /**\n * Component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n {watch: `section[${this.id}]:updated`, handler: this._refreshSection},\n ];\n }\n\n /**\n * Validate if the drop data can be dropped over the component.\n *\n * @param {Object} dropdata the exported drop data.\n * @returns {boolean}\n */\n validateDropData(dropdata) {\n // If the format uses one section per page sections dropping in the content is ignored.\n if (dropdata?.type === 'section' && this.reactive.sectionReturn != 0) {\n return false;\n }\n return super.validateDropData(dropdata);\n }\n\n /**\n * Get the last CM element of that section.\n *\n * @returns {element|null}\n */\n getLastCm() {\n const cms = this.getElements(this.selectors.CM);\n // DndUpload may add extra elements so :last-child selector cannot be used.\n if (!cms || cms.length === 0) {\n return null;\n }\n return cms[cms.length - 1];\n }\n\n /**\n * Update a course index section using the state information.\n *\n * @param {object} param\n * @param {Object} param.element details the update details.\n */\n _refreshSection({element}) {\n // Update classes.\n this.element.classList.toggle(this.classes.DRAGGING, element.dragging ?? false);\n this.element.classList.toggle(this.classes.LOCKED, element.locked ?? false);\n this.locked = element.locked;\n }\n}\n"],"file":"section.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/content/section.js"],"names":["name","selectors","SECTION_ITEM","CM","classes","LOCKED","id","element","dataset","state","configState","reactive","isEditing","supportComponents","sectionItem","getElement","headerComponent","Header","fullregion","configDragDrop","watch","handler","_refreshSection","dropdata","type","sectionReturn","cms","getElements","length","classList","toggle","DRAGGING","dragging","locked","DndSection"],"mappings":"gQAwBA,OACA,O,60FAOa,CAEL,KAAKA,IAAL,CAAY,iBAAZ,CAEA,KAAKC,SAAL,CAAiB,CACbC,YAAY,6BADC,CAEbC,EAAE,wBAFW,CAAjB,CAKA,KAAKC,OAAL,CAAe,CACXC,MAAM,CAAE,gBADG,CAAf,CAKA,KAAKC,EAAL,CAAU,KAAKC,OAAL,CAAaC,OAAb,CAAqBF,EAClC,C,8CAOUG,C,CAAO,CACd,KAAKC,WAAL,CAAiBD,CAAjB,EAEA,GAAI,KAAKE,QAAL,CAAcC,SAAd,EAA2B,KAAKD,QAAL,CAAcE,iBAA7C,CAAgE,CAE5D,GAAMC,CAAAA,CAAW,CAAG,KAAKC,UAAL,CAAgB,KAAKd,SAAL,CAAeC,YAA/B,CAApB,CACA,GAAIY,CAAJ,CAAiB,CAEb,GAAME,CAAAA,CAAe,CAAG,GAAIC,UAAJ,MACjB,IADiB,EAEpBV,OAAO,CAAEO,CAFW,CAGpBI,UAAU,CAAE,KAAKX,OAHG,GAAxB,CAKA,KAAKY,cAAL,CAAoBH,CAApB,CACH,CACJ,CACJ,C,iDAOa,CACV,MAAO,CACH,CAACI,KAAK,mBAAa,KAAKd,EAAlB,aAAN,CAAuCe,OAAO,CAAE,KAAKC,eAArD,CADG,CAGV,C,0DAQgBC,C,CAAU,CAExB,GAAuB,SAAnB,WAAAA,CAAQ,WAARA,SAAAA,CAAQ,CAAEC,IAAV,GAA+D,CAA/B,OAAKb,QAAL,CAAcc,aAAlD,CAAsE,CACjE,QACH,CACD,2DAA8BF,CAA9B,CACH,C,6CAOW,CACR,GAAMG,CAAAA,CAAG,CAAG,KAAKC,WAAL,CAAiB,KAAK1B,SAAL,CAAeE,EAAhC,CAAZ,CAEA,GAAI,CAACuB,CAAD,EAAuB,CAAf,GAAAA,CAAG,CAACE,MAAhB,CAA8B,CAC1B,MAAO,KACV,CACD,MAAOF,CAAAA,CAAG,CAACA,CAAG,CAACE,MAAJ,CAAa,CAAd,CACb,C,0DAQ0B,SAAVrB,CAAU,GAAVA,OAAU,CAEvB,KAAKA,OAAL,CAAasB,SAAb,CAAuBC,MAAvB,CAA8B,KAAK1B,OAAL,CAAa2B,QAA3C,WAAqDxB,CAAO,CAACyB,QAA7D,oBACA,KAAKzB,OAAL,CAAasB,SAAb,CAAuBC,MAAvB,CAA8B,KAAK1B,OAAL,CAAaC,MAA3C,WAAmDE,CAAO,CAAC0B,MAA3D,oBACA,KAAKA,MAAL,CAAc1B,CAAO,CAAC0B,MACzB,C,cA/FwBC,S","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course section format component.\n *\n * @module core_courseformat/local/content/section\n * @class core_courseformat/local/content/section\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Header from 'core_courseformat/local/content/section/header';\nimport DndSection from 'core_courseformat/local/courseeditor/dndsection';\n\nexport default class extends DndSection {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'content_section';\n // Default query selectors.\n this.selectors = {\n SECTION_ITEM: `[data-for='section_title']`,\n CM: `[data-for=\"cmitem\"]`,\n };\n // Most classes will be loaded later by DndCmItem.\n this.classes = {\n LOCKED: 'editinprogress',\n };\n\n // We need our id to watch specific events.\n this.id = this.element.dataset.id;\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the initial state\n */\n stateReady(state) {\n this.configState(state);\n // Drag and drop is only available for components compatible course formats.\n if (this.reactive.isEditing && this.reactive.supportComponents) {\n // Section zero and other formats sections may not have a title to drag.\n const sectionItem = this.getElement(this.selectors.SECTION_ITEM);\n if (sectionItem) {\n // Init the inner dragable element.\n const headerComponent = new Header({\n ...this,\n element: sectionItem,\n fullregion: this.element,\n });\n this.configDragDrop(headerComponent);\n }\n }\n }\n\n /**\n * Component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n {watch: `section[${this.id}]:updated`, handler: this._refreshSection},\n ];\n }\n\n /**\n * Validate if the drop data can be dropped over the component.\n *\n * @param {Object} dropdata the exported drop data.\n * @returns {boolean}\n */\n validateDropData(dropdata) {\n // If the format uses one section per page sections dropping in the content is ignored.\n if (dropdata?.type === 'section' && this.reactive.sectionReturn != 0) {\n return false;\n }\n return super.validateDropData(dropdata);\n }\n\n /**\n * Get the last CM element of that section.\n *\n * @returns {element|null}\n */\n getLastCm() {\n const cms = this.getElements(this.selectors.CM);\n // DndUpload may add extra elements so :last-child selector cannot be used.\n if (!cms || cms.length === 0) {\n return null;\n }\n return cms[cms.length - 1];\n }\n\n /**\n * Update a course index section using the state information.\n *\n * @param {object} param\n * @param {Object} param.element details the update details.\n */\n _refreshSection({element}) {\n // Update classes.\n this.element.classList.toggle(this.classes.DRAGGING, element.dragging ?? false);\n this.element.classList.toggle(this.classes.LOCKED, element.locked ?? false);\n this.locked = element.locked;\n }\n}\n"],"file":"section.min.js"} \ No newline at end of file diff --git a/course/format/amd/src/local/content/section.js b/course/format/amd/src/local/content/section.js index 362adf266e69a..28de7c8d1ee59 100644 --- a/course/format/amd/src/local/content/section.js +++ b/course/format/amd/src/local/content/section.js @@ -82,12 +82,12 @@ export default class extends DndSection { } /** - * Validate if the drop data can be dropped over the component. - * - * @param {Object} dropdata the exported drop data. - * @returns {boolean} - */ - validateDropData(dropdata) { + * Validate if the drop data can be dropped over the component. + * + * @param {Object} dropdata the exported drop data. + * @returns {boolean} + */ + validateDropData(dropdata) { // If the format uses one section per page sections dropping in the content is ignored. if (dropdata?.type === 'section' && this.reactive.sectionReturn != 0) { return false; diff --git a/course/format/templates/local/content/cm/title.mustache b/course/format/templates/local/content/cm/title.mustache index b345a04dabea1..bcb747c4ebcbb 100644 --- a/course/format/templates/local/content/cm/title.mustache +++ b/course/format/templates/local/content/cm/title.mustache @@ -36,7 +36,7 @@
- {{instancename}} {{{altname}}} + {{{instancename}}} {{{altname}}} {{/uservisible}} @@ -44,7 +44,7 @@
- {{instancename}} {{{altname}}} + {{{instancename}}} {{{altname}}}
{{/uservisible}} diff --git a/course/format/tests/behat/courseindex_keyboardnav.feature b/course/format/tests/behat/courseindex_keyboardnav.feature index 34a5d40063603..46d746a3b0249 100644 --- a/course/format/tests/behat/courseindex_keyboardnav.feature +++ b/course/format/tests/behat/courseindex_keyboardnav.feature @@ -20,14 +20,14 @@ Feature: Verify that courseindex is usable with the keyboard And I click on "Side panel" "button" And I click on "Open course index drawer" "button" And I should see "Topic 1" in the "courseindex-content" "region" - And the focused element is ".drawertoggle" "css_element" + And the focused element is "[data-preference='drawer-open-index'] .drawertoggle" "css_element" And I press the tab key Then the focused element is ".courseindex-section" "css_element" @javascript Scenario: General focus on open course index. When I press the shift tab key - And the focused element is ".drawertoggle" "css_element" + And the focused element is "[data-preference='drawer-open-index'] .drawertoggle" "css_element" And I press enter Then I should not see "Topic 1" in the "courseindex-content" "region" diff --git a/course/index.php b/course/index.php index 6688b5471ffd9..910530f43512c 100644 --- a/course/index.php +++ b/course/index.php @@ -65,7 +65,6 @@ $PAGE->set_heading($heading); $content = $courserenderer->course_category($categoryid); -$PAGE->set_primary_active_tab('courses'); $PAGE->set_secondary_active_tab('categorymain'); echo $OUTPUT->header(); diff --git a/course/tests/behat/activities_edit_name.feature b/course/tests/behat/activities_edit_name.feature index 18217384e980f..1a98500d68730 100644 --- a/course/tests/behat/activities_edit_name.feature +++ b/course/tests/behat/activities_edit_name.feature @@ -4,8 +4,7 @@ Feature: Edit activity name in-place As a teacher I need to use inplace editing - @javascript - Scenario: Edit activity name in-place + Background: Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | @@ -21,6 +20,9 @@ Feature: Edit activity name in-place | name | Test forum name | | description | Test forum description | | idnumber | forum1 | + + @javascript + Scenario: Edit activity name in-place When I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on # Rename activity @@ -41,3 +43,11 @@ Feature: Edit activity name in-place And I am on "Course 1" course homepage And I should see "Good news" And I should not see "Terrible news" + + @javascript + Scenario: Edit activity name in-place ensuring correct encoding + When I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + And I set the field "Edit title" in the "Test forum name" "activity" to "Good & bad news" + Then I should not see "Test forum name" in the ".course-content" "css_element" + And I should see "Good & bad news" in the ".course-content" "css_element" diff --git a/enrol/fee/templates/payment_region.mustache b/enrol/fee/templates/payment_region.mustache index 7123554364812..a1bbdc5cbacda 100644 --- a/enrol/fee/templates/payment_region.mustache +++ b/enrol/fee/templates/payment_region.mustache @@ -67,7 +67,7 @@ data-itemid="{{instanceid}}" data-cost="{{cost}}" data-successurl="{{successurl}}" - data-description={{# quote }}{{description}}{{/ quote }} + data-description="{{description}}" > {{# str }} sendpaymentbutton, enrol_fee {{/ str }} diff --git a/enrol/lti/templates/copy_grid.mustache b/enrol/lti/templates/copy_grid.mustache index fee6faf8e6ded..1adb662664fa1 100644 --- a/enrol/lti/templates/copy_grid.mustache +++ b/enrol/lti/templates/copy_grid.mustache @@ -49,7 +49,7 @@ {{#rows}}
{{> core/copy_box }}
diff --git a/grade/report/grader/tests/behat/switch_views.feature b/grade/report/grader/tests/behat/switch_views.feature index fa65ba83ef95d..98b764b86c173 100644 --- a/grade/report/grader/tests/behat/switch_views.feature +++ b/grade/report/grader/tests/behat/switch_views.feature @@ -38,7 +38,6 @@ Feature: We can change what we are viewing on the grader report And I give the grade "80.00" to the user "Student 1" for the grade item "Test assignment name 1" And I give the grade "90.00" to the user "Student 1" for the grade item "Test assignment name 2" And I press "Save changes" - And I turn editing mode off @javascript Scenario: View and minimise the grader report containing hidden activities diff --git a/grade/report/history/classes/helper.php b/grade/report/history/classes/helper.php index 1ff2d1eb33e5f..189cbf7b02261 100644 --- a/grade/report/history/classes/helper.php +++ b/grade/report/history/classes/helper.php @@ -129,10 +129,13 @@ public static function get_users_count($context, $search = '') { */ protected static function get_users_sql_and_params($context, $search = '', $count = false) { global $DB, $USER; - + $userfieldsapi = \core_user\fields::for_identity($context)->with_userpic()->including('username'); + $userfieldssql = $userfieldsapi->get_sql('u', true, '', '', false); // Fields we need from the user table. - // TODO Does not support custom user profile fields (MDL-70456). - $extrafields = \core_user\fields::get_identity_fields($context, false); + $extrafields = []; + foreach ($userfieldsapi->get_required_fields([\core_user\fields::PURPOSE_IDENTITY]) as $field) { + $extrafields[$field] = $userfieldssql->mappings[$field]; + } $params = array(); if (!empty($search)) { list($filtersql, $params) = users_search_sql($search, 'u', true, $extrafields); @@ -141,13 +144,12 @@ protected static function get_users_sql_and_params($context, $search = '', $coun $filtersql = ''; } - $userfieldsapi = \core_user\fields::for_userpic()->including(...(array_merge($extrafields, ['username']))); - $ufields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; + $userfieldjoinssql = $userfieldssql->joins; if ($count) { $select = "SELECT COUNT(DISTINCT u.id) "; $orderby = ""; } else { - $select = "SELECT DISTINCT $ufields "; + $select = "SELECT DISTINCT $userfieldssql->selects "; $orderby = " ORDER BY u.lastname ASC, u.firstname ASC"; } @@ -161,7 +163,6 @@ protected static function get_users_sql_and_params($context, $search = '', $coun if ($groupmode == SEPARATEGROUPS && !has_capability('moodle/site:accessallgroups', $context)) { // Fetch the groups that the user can see. $groups = groups_get_all_groups($courseid, $USER->id, 0, 'g.id'); - // Add join condition to include users that only belong to the same group as the user. list($insql, $inparams) = $DB->get_in_or_equal(array_keys($groups), SQL_PARAMS_NAMED, 'gid', true, 0); $groupjoinsql = " JOIN {groups_members} gm ON gm.userid = u.id "; @@ -173,11 +174,12 @@ protected static function get_users_sql_and_params($context, $search = '', $coun FROM {user} u JOIN {grade_grades_history} ggh ON u.id = ggh.userid JOIN {grade_items} gi ON gi.id = ggh.itemid + $userfieldjoinssql $groupjoinsql WHERE $filtersql gi.courseid = :courseid $groupwheresql"; $sql .= $orderby; $params['courseid'] = $courseid; - + $params = array_merge($userfieldssql->params, $params); return array($sql, $params); } diff --git a/grade/report/history/classes/output/tablelog.php b/grade/report/history/classes/output/tablelog.php index 663e29488f8f1..afacc119d352e 100644 --- a/grade/report/history/classes/output/tablelog.php +++ b/grade/report/history/classes/output/tablelog.php @@ -140,8 +140,7 @@ protected function define_table_configs(\moodle_url $url) { * Setup the headers for the html table. */ protected function define_table_columns() { - // TODO Does not support custom user profile fields (MDL-70456). - $extrafields = \core_user\fields::get_identity_fields($this->context, false); + $extrafields = \core_user\fields::get_identity_fields($this->context); // Define headers and columns. $cols = array( @@ -154,7 +153,7 @@ protected function define_table_columns() { if (get_string_manager()->string_exists($field, 'moodle')) { $cols[$field] = get_string($field); } else { - $cols[$field] = $field; + $cols[$field] = \core_user\fields::get_display_name($field); } } @@ -394,15 +393,24 @@ protected function get_sql_and_params($count = false) { ggh.source, ggh.overridden, ggh.locked, ggh.excluded, ggh.feedback, ggh.feedbackformat, gi.itemtype, gi.itemmodule, gi.iteminstance, gi.itemnumber, '; + $userfieldsapi = \core_user\fields::for_identity($this->context); + $userfieldssql = $userfieldsapi->get_sql('u', true, '', '', true); + $userfieldsselects = ''; + $userfieldsjoins = ''; + $userfieldsparams = []; + if (!$count) { + $userfieldsselects = $userfieldssql->selects; + $userfieldsjoins = $userfieldssql->joins; + $userfieldsparams = $userfieldssql->params; + } + // Add extra user fields that we need for the graded user. - // TODO Does not support custom user profile fields (MDL-70456). - $extrafields = \core_user\fields::get_identity_fields($this->context, false); - foreach ($extrafields as $field) { - $fields .= 'u.' . $field . ', '; + $extrafields = []; + foreach ($userfieldsapi->get_required_fields([\core_user\fields::PURPOSE_IDENTITY]) as $field) { + $extrafields[$field] = $userfieldssql->mappings[$field]; } $userfieldsapi = \core_user\fields::for_name(); - $gradeduserfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; - $fields .= $gradeduserfields . ', '; + $fields .= $userfieldsapi->get_sql('u', false, '', '', false)->selects . ', '; $groupby = $fields; // Add extra user fields that we need for the grader user. @@ -437,12 +445,14 @@ protected function get_sql_and_params($count = false) { list($where, $params) = $this->get_filters_sql_and_params(); - $sql = "SELECT $select + $sql = " SELECT $select $userfieldsselects FROM {grade_grades_history} ggh JOIN {grade_items} gi ON gi.id = ggh.itemid JOIN {user} u ON u.id = ggh.userid + $userfieldsjoins LEFT JOIN {user} ug ON ug.id = ggh.usermodified WHERE $where"; + $params = array_merge($userfieldsparams, $params); // As prevgrade is a dynamic field, we need to wrap the query. This is the only filtering // that should be defined outside the method self::get_filters_sql_and_params(). diff --git a/grade/report/history/templates/user_button.mustache b/grade/report/history/templates/user_button.mustache index 15cd1303c183f..1c881e07787f3 100644 --- a/grade/report/history/templates/user_button.mustache +++ b/grade/report/history/templates/user_button.mustache @@ -47,8 +47,8 @@ {{/params}}
diff --git a/grade/report/history/tests/behat/basic_functionality.feature b/grade/report/history/tests/behat/basic_functionality.feature index bb5ecbc0b5cf6..25972ec3a9e57 100644 --- a/grade/report/history/tests/behat/basic_functionality.feature +++ b/grade/report/history/tests/behat/basic_functionality.feature @@ -9,12 +9,15 @@ Feature: A teacher checks the grade history report in a course Given the following "courses" exist: | fullname | shortname | category | groupmode | | Course 1 | C1 | 0 | 1 | + And the following "custom profile fields" exist: + | datatype | shortname | name | + | text | food | Favourite food | And the following "users" exist: - | username | firstname | lastname | email | - | teacher1 | Teacher | 1 | teacher1@example.com | - | teacher2 | Teacher | 2 | teacher2@example.com | - | student1 | Student | 1 | student1@example.com | - | student2 | Student | 2 | student2@example.com | + | username | firstname | lastname | email | profile_field_food | + | teacher1 | Teacher | 1 | teacher1@example.com | | + | teacher2 | Teacher | 2 | teacher2@example.com | | + | student1 | Student | 1 | student1@example.com | apple | + | student2 | Student | 2 | student2@example.com | orange | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | @@ -25,9 +28,13 @@ Feature: A teacher checks the grade history report in a course | activity | course | section | name | intro | | assign | C1 | 1 | The greatest assignment ever | Write a behat test for Moodle - it's amazing | | assign | C1 | 1 | Rewarding assignment | After writing your behat test go grab a beer! | + Given the following config values are set as admin: + | showuseridentity | email,profile_field_food | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I navigate to "View > Grader report" in the course gradebook + And I should see "apple" in the "student1" "table_row" + And I should see "orange" in the "student2" "table_row" And I turn editing mode on And I give the grade "50.00" to the user "Student 1" for the grade item "The greatest assignment ever" And I give the grade "60.00" to the user "Student 1" for the grade item "Rewarding assignment" @@ -38,6 +45,8 @@ Feature: A teacher checks the grade history report in a course And I log in as "teacher2" And I am on "Course 1" course homepage And I navigate to "View > Grader report" in the course gradebook + And I should see "apple" in the "student1" "table_row" + And I should see "orange" in the "student2" "table_row" And I turn editing mode on And I give the grade "70.00" to the user "Student 1" for the grade item "The greatest assignment ever" And I give the grade "80.00" to the user "Student 1" for the grade item "Rewarding assignment" @@ -47,15 +56,15 @@ Feature: A teacher checks the grade history report in a course And I follow "Grade history" When I press "Submit" Then the following should exist in the "gradereport_history" table: - | First name/Surname | Grade item | Original grade | Revised grade | Grader | - | Student 1 | The greatest assignment ever | | 50.00 | Teacher 1 | - | Student 1 | Rewarding assignment | | 60.00 | Teacher 1 | - | Student 2 | The greatest assignment ever | | 50.00 | Teacher 1 | - | Student 2 | Rewarding assignment | | 60.00 | Teacher 1 | - | Student 1 | The greatest assignment ever | 50.00 | 70.00 | Teacher 2 | - | Student 1 | Rewarding assignment | 60.00 | 80.00 | Teacher 2 | - | Student 2 | The greatest assignment ever | 50.00 | 70.00 | Teacher 2 | - | Student 2 | Rewarding assignment | 60.00 | 80.00 | Teacher 2 | + | First name/Surname | Email address | Favourite food | Grade item | Original grade | Revised grade | Grader | + | Student 1 | student1@example.com | apple | The greatest assignment ever | | 50.00 | Teacher 1 | + | Student 1 | student1@example.com | apple | Rewarding assignment | | 60.00 | Teacher 1 | + | Student 2 | student2@example.com | orange | The greatest assignment ever | | 50.00 | Teacher 1 | + | Student 2 | student2@example.com | orange | Rewarding assignment | | 60.00 | Teacher 1 | + | Student 1 | student1@example.com | apple | The greatest assignment ever | 50.00 | 70.00 | Teacher 2 | + | Student 1 | student1@example.com | apple | Rewarding assignment | 60.00 | 80.00 | Teacher 2 | + | Student 2 | student2@example.com | orange | The greatest assignment ever | 50.00 | 70.00 | Teacher 2 | + | Student 2 | student2@example.com | orange | Rewarding assignment | 60.00 | 80.00 | Teacher 2 | # Test filtering by student. And I press "Select users" And I set the field with xpath "//form/input[@class='usp-search-field']" to "Student 1" @@ -69,6 +78,9 @@ Feature: A teacher checks the grade history report in a course | Student 1 | Rewarding assignment | | 60.00 | Teacher 1 | | Student 1 | The greatest assignment ever | 50.00 | 70.00 | Teacher 2 | | Student 1 | Rewarding assignment | 60.00 | 80.00 | Teacher 2 | + # Test for seeing custom fields contents in the rows. + And I should see "apple" in the "student1" "table_row" + And I should not see "orange" And the following should not exist in the "gradereport_history" table: | Student 2 | The greatest assignment ever | | 50.00 | Teacher 1 | | Student 2 | Rewarding assignment | | 60.00 | Teacher 1 | @@ -88,13 +100,13 @@ Feature: A teacher checks the grade history report in a course And I set the field "Grader" to "Teacher 1" And I press "Submit" And the following should exist in the "gradereport_history" table: - | First name/Surname | Grade item | Original grade | Revised grade | Grader | - | Student 1 | The greatest assignment ever | | 50.00 | Teacher 1 | + | First name/Surname | Email address | Favourite food | Grade item | Original grade | Revised grade | Grader | + | Student 1 | student1@example.com | apple | The greatest assignment ever | | 50.00 | Teacher 1 | And the following should not exist in the "gradereport_history" table: | Student 1 | The greatest assignment ever | 50.00 | 70.00 | Teacher 2 | # Test filtering by revised grades. And I click on "id_revisedonly" "checkbox" And I press "Submit" And the following should exist in the "gradereport_history" table: - | First name/Surname | Grade item | Original grade | Revised grade | Grader | - | Student 1 | The greatest assignment ever | | 50.00 | Teacher 1 | + | First name/Surname | Email address | Favourite food | Grade item | Original grade | Revised grade | Grader | + | Student 1 | student1@example.com | apple | The greatest assignment ever | | 50.00 | Teacher 1 | diff --git a/grade/report/history/tests/report_test.php b/grade/report/history/tests/report_test.php index 7a58ae1363eda..24e407cbb39bc 100644 --- a/grade/report/history/tests/report_test.php +++ b/grade/report/history/tests/report_test.php @@ -241,6 +241,159 @@ public function test_get_users() { $this->assertEquals(1, \gradereport_history\helper::get_users_count($c1ctx, 'c')); } + /** + * Data provider for \gradereport_history_report_testcase::test_get_users_with_profile_fields() + * Testing get_users() and get_users_count() test cases. + * + * @return array List of data sets (test cases) + */ + public function get_users_with_profile_fields_provider(): array { + return [ + // User identity check boxes, 'email', 'profile_field_lang' and 'profile_field_height' are checked. + 'show email,lang and height;search for all users' => + ['email,profile_field_lang,profile_field_height', '', ['u1', 'u2', 'u3', 'u4']], + 'show email,lang and height;search for users on .org ' => + ['email,profile_field_lang,profile_field_height', '.org', ['u1', 'u2', 'u4']], + 'show email,lang and height;search for users on .com ' => + ['email,profile_field_lang,profile_field_height', '.com', []], + 'show email,lang and height;search for users on .uk ' => + ['email,profile_field_lang,profile_field_height', '.uk', ['u3']], + 'show email,lang and height,search for Spanish speakers' => + ['email,profile_field_lang,profile_field_height', 'spanish', ['u1', 'u4']], + 'show email,lang and height,search for Spanish speakers' => + ['email,profile_field_lang,profile_field_height', 'spa', ['u1', 'u4']], + 'show email,lang and height,search for German speakers' => + ['email,profile_field_lang,profile_field_height', 'german', ['u2']], + 'show email,lang and height,search for German speakers' => + ['email,profile_field_lang,profile_field_height', 'ger', ['u2']], + 'show email,lang and height,search for English speakers' => + ['email,profile_field_lang,profile_field_height', 'english', ['u3']], + 'show email,lang and height,search for English speakers' => + ['email,profile_field_lang,profile_field_height', 'eng', ['u3']], + 'show email,lang and height,search for English speakers' => + ['email,profile_field_lang,profile_field_height', 'ish', ['u3']], + 'show email,lang and height,search for users with height 180cm' => + ['email,profile_field_lang,profile_field_height', '180cm', ['u2', 'u3', 'u4']], + 'show email,lang and height,search for users with height 180cm' => + ['email,profile_field_lang,profile_field_height', '180', ['u2', 'u3', 'u4']], + 'show email,lang and height,search for users with height 170cm' => + ['email,profile_field_lang,profile_field_height', '170cm', ['u1']], + 'show email,lang and height,search for users with height 170cm' => + ['email,profile_field_lang,profile_field_height', '170', ['u1']], + + // User identity check boxes, 'email' and 'profile_field_height' are checked. + 'show email and height;search for users on .org' => + ['email,profile_field_height', '.org', ['u1', 'u2', 'u4']], + 'show email and height;search for users on .com' => + ['email,profile_field_height', '.com', []], + 'show email and height;search for users on .co' => + ['email,profile_field_height', '.co', ['u3']], + 'show email and height,search for Spanish/German/English speakers' => + ['email,profile_field_height', 'spanish', []], + 'show email and height,search for Spanish/German/English speakers' => + ['email,profile_field_height', 'german', []], + 'show email and height,search for Spanish/German/English speakers' => + ['email,profile_field_height', 'english', []], + 'show email,lang and height,search for English speakers' => + ['email,profile_field_height', 'english', []], + 'show email and height,search for English speakers' => + ['email,profile_field_height', 'eng', []], + 'show email and height,search for English speakers' => + ['email,profile_field_height', 'ish', []], + 'show email and height,search for users with height 180cm' => + ['email,profile_field_height', '180cm', ['u2', 'u3', 'u4']], + 'show email,lang and height,search for users with height 180cm' => + ['email,profile_field_height', '180', ['u2', 'u3', 'u4']], + 'show email,lang and height,search for users with height 170cm' => + ['email,profile_field_height', '170cm', ['u1']], + 'show email,lang and height,search for users with height 170cm' => + ['email,profile_field_height', '170', ['u1']], + + // User identity check boxes, only 'email' is checked. + 'show email only;search for users on .org' => ['email', '.org', ['u1', 'u2', 'u4']], + 'show email only;search for users on .com' => ['email', '.com', []], + 'show email only;search for users on .co.uk' => ['email', 'co.uk', ['u3']], + 'show email only;search for users on .uk' => ['email', '.uk', ['u3']], + 'show email only;search for users on .co' => ['email', '.co', ['u3']], + 'show email only;search for Spanish speakers' => ['email', 'spanish', []], + 'show email only;search for German speakers' => ['email', 'german', []], + 'show email only;search for English speakers' => ['email', 'english', []], + 'show email only;search for users with height 180cm' => ['email', '180cm', []], + 'show email only;search for users with height 180cm' => ['email', '180', []], + 'show email only;search for users with height 170cm' => ['email', '170cm', []], + 'show email only;search for users with height 170cm' => ['email', '170', []], + ]; + } + + /** + * Testing the search functionality on get_users() and get_users_count() and their inner methods. + * + * @dataProvider get_users_with_profile_fields_provider + * + * @param string $showuseridentity, list of user identities to be shown. + * @param string $searchstring, the string to be searched. + * @param array $expectedusernames, a list of expected usernames. + * @return void + */ + public function test_get_users_with_profile_fields(string $showuseridentity, string $searchstring, + array $expectedusernames): void { + global $CFG, $DB; + require_once($CFG->dirroot . '/user/profile/lib.php'); + $this->resetAfterTest(); + + // Create a couple of custom profile fields, which are in user identity. + $generator = $this->getDataGenerator(); + $generator->create_custom_profile_field(['datatype' => 'text', + 'shortname' => 'lang', 'name' => 'Language']); + $generator->create_custom_profile_field(['datatype' => 'text', + 'shortname' => 'height', 'name' => 'Height']); + + // Create a couple of test users. + $u1 = $generator->create_user(['firstname' => 'Eduardo', 'lastname' => 'Gomes', + 'username' => 'u1', 'email' => 'u1@x.org', 'profile_field_lang' => 'Spanish', + 'profile_field_height' => '170cm']); + $u2 = $generator->create_user(['firstname' => 'Dieter', 'lastname' => 'Schmitt', + 'username' => 'u2', 'email' => 'u2@x.org', 'profile_field_lang' => 'German', + 'profile_field_height' => '180cm']); + + $u3 = $generator->create_user(['firstname' => 'Peter', 'lastname' => 'Jones', + 'username' => 'u3', 'email' => 'u3@x.co.uk', 'profile_field_lang' => 'English', + 'profile_field_height' => '180cm']); + $u4 = $generator->create_user(['firstname' => 'Pedro', 'lastname' => 'Gomes', + 'username' => 'u4', 'email' => 'u3@x.org', 'profile_field_lang' => 'Spanish', + 'profile_field_height' => '180cm']); + + // Do this as admin user. + $this->setAdminUser(); + + // Making the setup. + $c1 = $this->getDataGenerator()->create_course(); + $c1ctx = context_course::instance($c1->id); + $c1m1 = $this->getDataGenerator()->create_module('assign', array('course' => $c1)); + + // Creating grade history for some users. + $gi = grade_item::fetch(array('iteminstance' => $c1m1->id, 'itemtype' => 'mod', 'itemmodule' => 'assign')); + $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u1->id)); + $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u2->id)); + $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u3->id)); + $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u4->id)); + + // Checking fetching some users with this config settings. + set_config('showuseridentity', $showuseridentity); + $numberofexpectedusers = count($expectedusernames); + $users = \gradereport_history\helper::get_users($c1ctx, $searchstring); + $userscount = \gradereport_history\helper::get_users_count($c1ctx, $searchstring); + $this->assertEquals($numberofexpectedusers, $userscount); + $this->assertCount($numberofexpectedusers, $users); + foreach ($users as $user) { + if (in_array($user->username, $expectedusernames)) { + $this->assertArrayHasKey($user->id, $users); + } else { + $this->assertArrayNotHasKey($user->id, $users); + } + } + } + /** * Data provider method for \gradereport_history_report_testcase::test_get_users_with_groups() */ diff --git a/grade/report/history/users_ajax.php b/grade/report/history/users_ajax.php index 038cf0274d4dd..ede28aebc3b15 100644 --- a/grade/report/history/users_ajax.php +++ b/grade/report/history/users_ajax.php @@ -51,8 +51,8 @@ $outcome->response = array('users' => array()); $outcome->response['totalusers'] = \gradereport_history\helper::get_users_count($context, $search);; -// TODO Does not support custom user profile fields (MDL-70456). -$extrafields = \core_user\fields::get_identity_fields($context, false); +$userfieldsapi = \core_user\fields::for_identity($context)->with_userpic(); +$extrafields = $userfieldsapi->get_required_fields([\core_user\fields::PURPOSE_IDENTITY]); $useroptions = array('link' => false, 'visibletoscreenreaders' => false); // Format the user record. @@ -61,11 +61,11 @@ $newuser->userid = $user->id; $newuser->picture = $OUTPUT->user_picture($user, $useroptions); $newuser->fullname = fullname($user); - $fieldvalues = array(); foreach ($extrafields as $field) { - $fieldvalues[] = s($user->{$field}); + if ($user->{$field}) { + $newuser->extrafields[] = $user->{$field}; + } } - $newuser->extrafields = implode(', ', $fieldvalues); $outcome->response['users'][] = $newuser; } diff --git a/grade/report/singleview/templates/button.mustache b/grade/report/singleview/templates/button.mustache index 5d43733af4f1b..52de296e38b83 100644 --- a/grade/report/singleview/templates/button.mustache +++ b/grade/report/singleview/templates/button.mustache @@ -25,4 +25,4 @@ "value": "Save" } }} - + diff --git a/grade/templates/edit_tree.mustache b/grade/templates/edit_tree.mustache index eb678f87a8608..e9317ce42de20 100644 --- a/grade/templates/edit_tree.mustache +++ b/grade/templates/edit_tree.mustache @@ -51,7 +51,7 @@ {{{table}}}
{{#showsave}} - + {{/showsave}} {{#showbulkmove}}
diff --git a/install/lang/hsb/moodle.php b/install/lang/hsb/moodle.php index 11013a706f9da..0dbe66706b13b 100644 --- a/install/lang/hsb/moodle.php +++ b/install/lang/hsb/moodle.php @@ -33,3 +33,4 @@ $string['moodlelogo'] = 'Moodle-logo'; $string['next'] = 'Nadalny'; $string['previous'] = 'Wróćo'; +$string['reload'] = 'Znowa přeprošuja'; diff --git a/install/lang/lo/install.php b/install/lang/lo/install.php index a164c01ca3e09..9dd41e43b85d4 100644 --- a/install/lang/lo/install.php +++ b/install/lang/lo/install.php @@ -29,4 +29,77 @@ defined('MOODLE_INTERNAL') || die(); -$string['availablelangs'] = 'Available language packs'; +$string['admindirname'] = 'ບັນຊີຜູ້ເບິ່ງແຍງ'; +$string['availablelangs'] = 'ຊຸດພາສາທີ່ມີໃຫ້'; +$string['chooselanguagehead'] = 'ເລືອກພາສາ'; +$string['chooselanguagesub'] = 'ກະລຸນາເລືອກພາສາສໍາລັບການຕິດຕັ້ງ. ພາສານີ້ຍັງຈະຖືກໃຊ້ເປັນພາສາເລີ່ມຕົ້ນສໍາລັບເວັບໄຊທ໌, ເຖິງແມ່ນວ່າ ມັນອາດຈະຖືກປ່ຽນແປງໃນພາຍຫຼັງ.'; +$string['clialreadyconfigured'] = 'ໄຟລ໌ການຕັ້ງຄ່າ config.php ມີຢູ່ແລ້ວ. ກະລຸນາໃຊ້ admin/cli/install_database.php ເພື່ອຕິດຕັ້ງ Moodle ສໍາລັບເວັບໄຊທ໌ນີ້.'; +$string['clialreadyinstalled'] = 'ໄຟລ໌ການຕັ້ງຄ່າ config.php ມີຢູ່ແລ້ວ. ກະລຸນາໃຊ້ admin/cli/install_database.php ເພື່ອອັບເກຣດ Moodle ສໍາລັບເວັບໄຊນີ້.'; +$string['cliinstallheader'] = 'ໂຄງການການຕິດຕັ້ງສາຍຄຳສັ່ງ Moodle {$a}'; +$string['clitablesexist'] = 'ຕາຕະລາງຖານຂໍ້ມູນແລ້ວ; ການຕິດຕັ້ງ CLI ບໍ່ສາມາດສືບຕໍ່ໄດ້.'; +$string['databasehost'] = 'ເຈົ້າພາບຖານຂໍ້ມູນ'; +$string['databasename'] = 'ຊື່ຖານຂໍ້ມູນ'; +$string['databasetypehead'] = 'ເລືອກຕົວຂັບເຄື່ອນຖານຂໍ້ມູນ'; +$string['dataroot'] = 'ລາຍການຂໍ້ມູນ'; +$string['datarootpermission'] = 'ການອະນຸຍາດລາຍການຂໍ້ມູນ'; +$string['dbprefix'] = 'ຄໍານໍາຫນ້າຕາຕະລາງ'; +$string['dirroot'] = 'ລາຍການ Moodle'; +$string['environmenthead'] = 'ກຳລັງກວດສອບສະພາບແວດລ້ອມຂອງທ່ານ ...'; +$string['environmentsub2'] = 'ແຕ່ລະລຸ້ນ Moodle ມີຄວາມຕ້ອງການສະບັບ PHP ຕ່ຳສຸດທີ່ ແລະ ຈໍານວນການຂະຫຍາຍ PHP ບັງຄັບ. +ການກວດສອບສະພາບແວດລ້ອມຢ່າງເຕັມທີ່ ແມ່ນເຮັດກ່ອນທີ່ຈະຕິດຕັ້ງ ແລະ ຍົກລະດັບແຕ່ລະຄົນ. ກະລຸນາຕິດຕໍ່ຫາຜູ້ບໍລິຫານເຄື່ອງແມ່ຂ່າຍ ຖ້າຫາກວ່າທ່ານບໍ່ຮູ້ວິທີການຕິດຕັ້ງສະບັບໃໝ່ ຫຼື ເປີດໃຊ້ຂະຫຍາຍ PHP ໄດ້.'; +$string['errorsinenvironment'] = 'ກວດສອບສິ່ງແວດລ້ອມບໍ່ສໍາເລັດ!'; +$string['installation'] = 'ການຕິດຕັ້ງ'; +$string['langdownloaderror'] = 'ຂໍອະໄພ, ບໍ່ສາມາດດາວໂຫຼດພາສາ "{$a}" ໄດ້. ຂະບວນການຕິດຕັ້ງຈະສືບຕໍ່ເປັນພາສາອັງກິດ.'; +$string['memorylimithelp'] = '

ຂີດຈຳກັດຄວາມຈຳ PHP ສຳລັບເຊີບເວີຂອງທ່ານປະຈຸບັນຖືກຕັ້ງເປັນ {$a}.

+ +

ສິ່ງນີ້ອາດເຮັດໃຫ້ Moodle ມີບັນຫາຄວາມຈຳໃນພາຍຫຼັງ, ໂດຍສະເພາະ + ຖ້າທ່ານມີຫຼາຍຊຸດທີ່ເປີດໃຊ້ງານ ແລະ/ຫຼື ຫຼາຍຜູ້ໃຊ້.

+ +

ພວກເຮົາແນະນໍາໃຫ້ທ່ານກໍານົດຄ່າ PHP ທີ່ມີຂອບເຂດທີ່ສູງກວ່າ ຖ້າຫາກວ່າເປັນໄປໄດ້, ເຊັ່ນ 40M. + ມີຫຼາຍວິທີໃນການເຮັດສິ່ງນີ້ທີ່ທ່ານສາມາດລອງໄດ້:

+
    +
  1. ຖ້າທ່ານສາມາດ, ລວບລວມ PHP ຄືນໃໝ່ດ້ວຍ --enable-memory-limit. + ສິ່ງນີ້ຈະເຮັດໃຫ້ Moodle ສາມາດຕັ້ງຂີດຈຳກັດຄວາມຈຳໄດ້ເອງ.
  2. +
  3. ຖ້າທ່ານມີການເຂົ້າເຖິງໄຟລ໌ php.ini ຂອງທ່ານ, ທ່ານສາມາດປ່ຽນ memory_limit ໄດ້. + ຕັ້ງຢູ່ໃນນັ້ນເພື່ອບາງສິ່ງບາງຢ່າງເຊັ່ນ: 40M. ຖ້າທ່ານບໍ່ມີການເຂົ້າເຖິງ ທ່ານອາດຈະ + ສາມາດຂໍໃຫ້ຜູ້ເບິ່ງແຍງລະບົບຂອງທ່ານເຮັດອັນນີ້ໃຫ້ກັບທ່ານໄດ້.
  4. +
  5. ໃນບາງເຊີບເວີ PHP ທ່ານສາມາດສ້າງໄຟລ໌ .htaccess ໃນໄດເລກະທໍລີ Moodle + ປະກອບມີເສັ້ນນີ້: +
    php_value memory_limit 40M
    +

    ຢ່າງໃດກໍຕາມ, ໃນບາງເຊີບເວີ ສິ່ງນີ້ຈະປ້ອງກັນ ທັງໝົດ ໜ້າ PHP ຈາກການເຮັດວຽກ + (ທ່ານຈະເຫັນຂໍ້ຜິດພາດໃນເວລາທີ່ທ່ານເບິ່ງໜ້າຕ່າງໆ) ດັ່ງນັ້ນ ທ່ານຈະຕ້ອງເອົາໄຟລ໌ .htaccess ອອກ.

  6. +
'; +$string['paths'] = 'ເສັ້ນທາງ'; +$string['pathserrcreatedataroot'] = 'ລາຍການຂໍ້ມູນ ({$a->dataroot}) ບໍ່ສາມາດສ້າງໄດ້ໂດຍຕົວຕິດຕັ້ງ.'; +$string['pathshead'] = 'ຢືນຢັນເສັ້ນທາງ'; +$string['pathsrodataroot'] = 'ລາຍການ Dataroot ບໍ່ສາມາດຂຽນໄດ້.'; +$string['pathsroparentdataroot'] = 'ລາຍຊື່ພໍ່ແມ່ ({$a->parent}) ບໍ່ສາມາດຂຽນໄດ້. ລາຍການຂໍ້ມູນ ({$a->dataroot}) ບໍ່ສາມາດສ້າງໄດ້ໂດຍຕົວຕິດຕັ້ງ.'; +$string['pathssubadmindir'] = 'webhosts ຈໍານວນໜ້ອຍດຽວໃຊ້ /admin ເປັນ URL ພິເສດສໍາລັບທ່ານທີ່ຈະເຂົ້າເຖິງກະດານຄວບຄຸມ ຫຼື ບາງສິ່ງບາງຢ່າງ. ແຕ່ໜ້າເສຍດາຍ, ນີ້ຂັດກັບສະຖານທີ່ມາດຕະຖານສໍາລັບໜ້າ ຜູ້ເບິ່ງແຍງລະບົບ Moodle. ທ່ານສາມາດແກ້ໄຂສິ່ງນີ້ໂດຍການປ່ຽນຊື່ຜູ້ເບິ່ງແຍງລະບົບໃນການຕິດຕັ້ງຂອງທ່ານ ແລະ ໃສ່ຊື່ໃໝ່ນີ້ຢູ່ທີ່ນີ້. ຕົວຢ່າງ: moodleadmin. ສິ່ງນີ້ຈະແກ້ໄຂການເຊື່ອມຕໍ່ຜູ້ເບິ່ງແຍງລະບົບໃນ Moodle.'; +$string['pathssubdataroot'] = '

ລາຍການທີ່ Moodle ຈະເກັບຮັກສາເນື້ອຫາໄຟລ໌ທັງໝົດທີ່ອັບໂຫຼດໂດຍຜູ້ໃຊ້.

+

ລາຍການນີ້ຄວນຈະເປັນທັງສາມາດອ່ານໄດ້ ແລະ ຂຽນໄດ້ໂດຍຜູ້ໃຊ້ເວັບເຊີບເວີ (ໂດຍປົກກະຕິແລ້ວ \'www-data\', \'nobody\', ຫຼື \'apache\').

+

ມັນຕ້ອງບໍ່ສາມາດເຂົ້າເຖິງໄດ້ໂດຍກົງຜ່ານເວັບ.

+

ຖ້າບໍ່ມີບັນຊີລາຍຊື່ໃນປັດຈຸບັນ, ຂະບວນການຕິດຕັ້ງຈະພະຍາຍາມສ້າງມັນ.

'; +$string['pathssubdirroot'] = '

ເສັ້ນທາງເຕັມໄປຫາລາຍການທີ່ມີລະຫັດ Moodle.

'; +$string['pathssubwwwroot'] = '

ທີ່ຢູ່ເຕັມທີ່ Moodle ຈະຖືກເຂົ້າຫາເຊັ່ນ: ທີ່ຢູ່ທີ່ຜູ້ໃຊ້ຈະເຂົ້າໄປໃນແຖບທີ່ຢູ່ຂອງຕົວທ່ອງເວັບຂອງເຂົາເຈົ້າເພື່ອເຂົ້າເຖິງ Moodle.

+

ມັນບໍ່ສາມາດເຂົ້າເຖິງ Moodle ໂດຍໃຊ້ຫຼາຍທີ່ຢູ່. ຖ້າເວັບໄຊທ໌ຂອງທ່ານສາມາດເຂົ້າເຖິງໄດ້ຜ່ານຫຼາຍທີ່ຢູ່ ຈາກນັ້ນເລືອກອັນທີ່ງ່າຍທີ່ສຸດ ແລະ ຕັ້ງການປ່ຽນເສັ້ນທາງຖາວອນສຳລັບແຕ່ລະທີ່ຢູ່ອື່ນ.

+

ຖ້າເວັບໄຊຂອງທ່ານສາມາດເຂົ້າເຖິງໄດ້ທັງຈາກອິນເຕີເນັດ ແລະ ຈາກເຄືອຂ່າຍພາຍໃນ (ບາງຄັ້ງເອີ້ນວ່າ ອິນທຣາເນັດ), ຫຼັງຈາກນັ້ນໃຫ້ໃຊ້ທີ່ຢູ່ສາທາລະນະທີ່ນີ້.

+

ຖ້າທີ່ຢູ່ປັດຈຸບັນບໍ່ຖືກຕ້ອງ, ກະລຸນາປ່ຽນ URL ໃນແຖບທີ່ຢູ່ຂອງບຣາວເຊີຂອງທ່ານ ແລະ ເລີ່ມການຕິດຕັ້ງຄືນໃໝ່.

'; +$string['pathsunsecuredataroot'] = 'ສະຖານທີ່ຕັ້ງ Dataroot ແມ່ນບໍ່ປອດໄພ'; +$string['pathswrongadmindir'] = 'ລາຍການບໍລິຫານບໍ່ມີຢູ່'; +$string['phpextension'] = 'ສ່ວນຂະຫຍາຍ PHP {$a}'; +$string['phpversion'] = 'ສະບັບ PHP'; +$string['phpversionhelp'] = '

Moodle ຕ້ອງການສະບັບ PHP ຢ່າງໜ້ອຍ 5.6.5 ຫຼື 7.1 (7.0.x ມີການຈຳກັັດບາງເຄື່ອງຈັກ).

+

ປະຈຸບັນທ່ານກຳລັງແລ່ນລຸ້ນ {$a}.

+

ທ່ານຕ້ອງໄດ້ຍົກລະດັບ PHP ຫຼື ຍ້າຍໄປເປັນເຈົ້າພາບກັບສະບັບທີ່ໃໝ່ກວ່າຂອງ PHP.

'; +$string['welcomep10'] = '{$a->installername} ({$a->installerversion})'; +$string['welcomep20'] = 'ທ່ານກໍາລັງເຫັນໜ້ານີ້ ເພາະວ່າທ່ານໄດ້ຕິດຕັ້ງປະສົບຜົນສໍາເລັດ ແລະ + ເປີດຕົວແພັກເກັດ {$a->packname} {$a->packversion} ໃນຄອມພິວເຕີຂອງທ່ານ. ຊົມເຊີຍ!'; +$string['welcomep30'] = 'ສິ່ງນີ້ໄດ້ປົດປ່ອຍ {$a->installername} ລວມມີໄປຼແກຼມ + ເພື່ອສ້າງສະພາບແວດລ້ອມທີ່ Moodle ຈະດຳເນີນ, ຄື:'; +$string['welcomep40'] = 'ແພັກເກດຍັງລວມມີ Moodle {$a->moodlerelease} ({$a->moodleversion}).'; +$string['welcomep50'] = 'ການນໍາໃຊ້ຄໍາຮ້ອງສະໝັກທັງໝົດໃນແພັດເກດນີ້ ແມ່ນຖືກຄຸ້ມຄອງໂດຍໃບອະນຸຍາດຂອງພວກເຂົາ. ແພັກເກດ {$a->installername} ທີ່ສົມບູນແມ່ນ ແຫຼ່ງຂໍ້ມູນເປີດ ແລະ ຖືກແຈກຢາຍພາຍໃຕ້ ໃບອະນຸຍາດ GPL.'; +$string['welcomep60'] = 'ຫນ້າຕໍ່ໄປນີ້ຈະນໍາທ່ານຜ່ານບາງຂັ້ນຕອນທີ່ງ່າຍຕໍ່ການປະຕິບັດຕາມ + ກຳນົດຄ່າ ແລະ ຕັ້ງຄ່າ Moodle ໃນຄອມພິວເຕີຂອງທ່ານ. ທ່ານອາດຈະຍອມຮັບຄ່າເລີ່ມຕົ້ນ + ການຕັ້ງຄ່າ ຫຼື ທາງເລືອກ, ປັບປຸງໃຫ້ເຂົາເຈົ້າເໝາະສົມກັບຄວາມຕ້ອງການຂອງທ່ານເອງ.'; +$string['welcomep70'] = 'ຄລິກທີ່ປຸ່ມ "ຕໍ່ໄປ" ດ້ານລຸ່ມ ເພື່ອສືບຕໍ່ການຕັ້ງຄ່າ Moodle.'; +$string['wwwroot'] = 'ທີ່ຢູ່ເວັບ'; diff --git a/install/lang/my/error.php b/install/lang/my/error.php index 8f48ac7ce1051..a542e7f749575 100644 --- a/install/lang/my/error.php +++ b/install/lang/my/error.php @@ -33,3 +33,5 @@ $string['cannotdownloadcomponents'] = 'အစိတ်အပိုင်းများကို ဒေါင်းလုတ်ဆွဲမရပါ။'; $string['cannotdownloadzipfile'] = 'ZIP ဖိုင်ကိုဒေါင်းလုတ်ဆွဲမရပါ။'; $string['cannotfindcomponent'] = 'အစိတ်အပိုင်းကို ရှာဖွေမတွေ့ရှိပါ။'; +$string['cannotsavemd5file'] = 'md5 ဖိုင်ကို မသိမ်းဆည်းနိုင်ပါ'; +$string['cannotsavezipfile'] = 'ZIP ဖိုင်ကို မသိမ်းဆည်းနိုင်ပါ'; diff --git a/install/lang/my/langconfig.php b/install/lang/my/langconfig.php index 7f5797961092d..f1e85959d0d63 100644 --- a/install/lang/my/langconfig.php +++ b/install/lang/my/langconfig.php @@ -29,4 +29,5 @@ defined('MOODLE_INTERNAL') || die(); +$string['thisdirection'] = 'ltr'; $string['thislanguage'] = 'ဗမာစာ'; diff --git a/install/lang/tr/langconfig.php b/install/lang/tr/langconfig.php index 5460e9aa4594e..0d18bee4c5a50 100644 --- a/install/lang/tr/langconfig.php +++ b/install/lang/tr/langconfig.php @@ -29,5 +29,6 @@ defined('MOODLE_INTERNAL') || die(); +$string['parentlanguage'] = ''; $string['thisdirection'] = 'ltr'; $string['thislanguage'] = 'Türkçe'; diff --git a/lang/en/admin.php b/lang/en/admin.php index 589b2dc262030..460fe06e21fad 100644 --- a/lang/en/admin.php +++ b/lang/en/admin.php @@ -858,6 +858,7 @@ $string['modulesecurity'] = 'Module security'; $string['multilangforceold'] = 'Force old multilang syntax: <span> without the class="multilang" and <lang>'; $string['mustenablestats'] = 'Statistics have not yet been enabled on this site.'; +$string['mycourses'] = 'My courses'; $string['mycoursesperpage'] = 'Number of courses'; $string['mydashboard'] = 'System default dashboard'; $string['mymoodle'] = 'Dashboard'; diff --git a/lang/en/langconfig.php b/lang/en/langconfig.php index 9b748db8f1c91..4d88b365b759d 100644 --- a/lang/en/langconfig.php +++ b/lang/en/langconfig.php @@ -31,7 +31,6 @@ $string['iso6392'] = 'eng'; $string['labelsep'] = ': '; $string['listsep'] = ','; -$string['listitemssep'] = ', '; $string['locale'] = 'en_AU.UTF-8'; $string['localecldr'] = 'en-AU'; $string['localewin'] = 'English_Australia.1252'; diff --git a/lang/en/my.php b/lang/en/my.php index 97a6bfa46359e..cc0b8f7356706 100644 --- a/lang/en/my.php +++ b/lang/en/my.php @@ -22,6 +22,7 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +$string['coursemanagementoptions'] = 'Course management options'; $string['mymoodle'] = 'Dashboard'; $string['nocourses'] = 'No course information to show.'; $string['noguest'] = 'The Dashboard page is not available to guest users'; diff --git a/lang/en/question.php b/lang/en/question.php index 30a1fb453307b..3b4ef630d4b7f 100644 --- a/lang/en/question.php +++ b/lang/en/question.php @@ -235,7 +235,7 @@ $string['movelinksonly'] = 'Just change where links point to, do not move or copy files.'; $string['moveq'] = 'Move question(s)'; $string['moveqtoanothercontext'] = 'Move question to another context.'; -$string['moveto'] = 'Move to >>'; +$string['moveto'] = 'Move to'; $string['movingcategory'] = 'Moving category'; $string['movingcategoryandfiles'] = 'Are you sure you want to move category {$a->name} and all child categories to context for "{$a->contextto}"?
We have detected {$a->urlcount} files linked from questions in {$a->fromareaname}, would you like to copy or move these to {$a->toareaname}?'; $string['movingcategorynofiles'] = 'Are you sure you want to move category "{$a->name}" and all child categories to context for "{$a->contextto}"?'; diff --git a/lang/en/reportbuilder.php b/lang/en/reportbuilder.php index b9f661e702b43..97527ed85b587 100644 --- a/lang/en/reportbuilder.php +++ b/lang/en/reportbuilder.php @@ -175,6 +175,8 @@ $string['reportsource_help'] = 'The report source defines where the data for the report will come from.'; $string['reportupdated'] = 'Report updated'; $string['resetall'] = 'Reset all'; +$string['resetconditions'] = 'Reset conditions'; +$string['resetconditionsconfirm'] = 'Are you sure you want to reset all conditions for this report?'; $string['selectacondition'] = 'Select a condition'; $string['selectafilter'] = 'Select a filter'; $string['selectareportsource'] = 'Select a report source'; @@ -187,9 +189,14 @@ $string['timeadded'] = 'Time added'; $string['timecreated'] = 'Time created'; $string['timemodified'] = 'Time modified'; +$string['uniquerows'] = 'Show unique rows'; +$string['uniquerows_help'] = 'Show only unique rows in the report. Note this setting has no effect if any report columns are being aggregated'; +$string['userany'] = 'Any user'; +$string['usercurrent'] = 'Current user'; $string['userfullnamewithlink'] = 'Full name with link'; $string['userfullnamewithpicture'] = 'Full name with picture'; $string['userfullnamewithpicturelink'] = 'Full name with picture and link'; $string['usermodified'] = 'Modified by'; $string['userpicture'] = 'User picture'; +$string['userselect'] = 'Select user'; $string['viewreport'] = 'View report'; diff --git a/lib/ajax/blocks.php b/lib/ajax/blocks.php index d12eccea684be..7d1a7625d90a2 100644 --- a/lib/ajax/blocks.php +++ b/lib/ajax/blocks.php @@ -60,6 +60,7 @@ $pagetype = explode('-', $pagetype); switch ($pagetype[0]) { case 'my': + case 'mycourses': $PAGE->set_blocks_editing_capability('moodle/my:manageblocks'); break; case 'user': diff --git a/lib/amd/build/dynamic_tabs.min.js b/lib/amd/build/dynamic_tabs.min.js index 51bdd53451922..c5aa179aecb62 100644 --- a/lib/amd/build/dynamic_tabs.min.js +++ b/lib/amd/build/dynamic_tabs.min.js @@ -1,2 +1,2 @@ -define ("core/dynamic_tabs",["exports","jquery","core/templates","core/notification","core/pending","core/str","core/local/repository/dynamic_tabs","core_form/changechecker"],function(a,b,c,d,e,f,g,h){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=i(b);c=i(c);d=i(d);e=i(e);function i(a){return a&&a.__esModule?a:{default:a}}function j(a,b){var c=Object.keys(a);if(Object.getOwnPropertySymbols){var d=Object.getOwnPropertySymbols(a);if(b)d=d.filter(function(b){return Object.getOwnPropertyDescriptor(a,b).enumerable});c.push.apply(c,d)}return c}function k(a){for(var b=1,c;ba.length)b=a.length;for(var c=0,d=Array(b);ca.length)b=a.length;for(var c=0,d=Array(b);c.\n\n/**\n * Dynamic Tabs UI element with AJAX loading of tabs content\n *\n * @module core/dynamic_tabs\n * @copyright 2021 David Matamoros based on code from Marina Glancy\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport Templates from 'core/templates';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport {get_strings as getStrings} from 'core/str';\nimport {getContent} from 'core/local/repository/dynamic_tabs';\nimport {isAnyWatchedFormDirty, resetAllFormDirtyStates} from 'core_form/changechecker';\n\nconst SELECTORS = {\n dynamicTabs: '.dynamictabs',\n activeTab: '.dynamictabs .nav-link.active',\n allActiveTabs: '.dynamictabs .nav-link[data-toggle=\"tab\"]:not(.disabled)',\n tabContent: '.dynamictabs .tab-pane [data-tab-content]',\n tabToggle: 'a[data-toggle=\"tab\"]',\n tabPane: '.dynamictabs .tab-pane',\n};\n\nSELECTORS.forTabName = tabName => `.dynamictabs [data-tab-content=\"${tabName}\"]`;\nSELECTORS.forTabId = tabName => `.dynamictabs [data-toggle=\"tab\"][href=\"#${tabName}\"]`;\n\n/**\n * Initialises the tabs view on the page (only one tabs view per page is supported)\n */\nexport const init = () => {\n const tabToggle = $(SELECTORS.tabToggle);\n\n // Listen to click, warn user if they are navigating away with unsaved form changes.\n tabToggle.on('click', (event) => {\n if (!isAnyWatchedFormDirty()) {\n return;\n }\n\n event.preventDefault();\n event.stopPropagation();\n\n getStrings([\n {key: 'changesmade', component: 'moodle'},\n {key: 'changesmadereallygoaway', component: 'moodle'},\n {key: 'confirm', component: 'moodle'},\n ]).then(([strChangesMade, strChangesMadeReally, strConfirm]) =>\n // Reset form dirty state on confirmation, re-trigger the event.\n Notification.confirm(strChangesMade, strChangesMadeReally, strConfirm, null, () => {\n resetAllFormDirtyStates();\n $(event.target).trigger(event.type);\n })\n ).catch(Notification.exception);\n });\n\n // This code listens to Bootstrap event 'shown.bs.tab' which is triggered using JQuery and\n // can not be converted yet to native events.\n tabToggle.on('shown.bs.tab', function() {\n const tab = $($(this).attr('href'));\n if (tab.length !== 1) {\n return;\n }\n loadTab(tab.attr('id'));\n });\n\n if (!openTabFromHash()) {\n const tabs = document.querySelector(SELECTORS.allActiveTabs);\n if (tabs) {\n openTab(tabs.getAttribute('aria-controls'));\n } else {\n // We may hide tabs if there is only one available, just load the contents of the first tab.\n const tabPane = document.querySelector(SELECTORS.tabPane);\n if (tabPane) {\n tabPane.classList.add('active', 'show');\n loadTab(tabPane.getAttribute('id'));\n }\n }\n }\n};\n\n/**\n * Show \"loading\" template instead of a node\n *\n * @param {HTMLElement} node\n * @return {Promise}\n */\nconst indicateNodeIsLoading = (node) => {\n return Templates.render('core/loading', {})\n .then((html, js) => {\n return Templates.replaceNodeContents(node, html, js);\n }).catch(Notification.exception);\n};\n\n/**\n * Returns id/name of the currently active tab\n *\n * @return {String|null}\n */\nconst getActiveTabName = () => {\n const element = document.querySelector(SELECTORS.activeTab);\n return element?.getAttribute('aria-controls') || null;\n};\n\n/**\n * Returns the id/name of the first tab\n *\n * @return {String|null}\n */\nconst getFirstTabName = () => {\n const element = document.querySelector(SELECTORS.tabContent);\n return element?.dataset.tabContent || null;\n};\n\n/**\n * Loads contents of a tab using an AJAX request\n *\n * @param {String} tabName\n * @param {Object} additionalData additional data to pass to WS\n */\nconst loadTab = (tabName, additionalData = {}) => {\n // If tabName is not specified find the active tab, or if is not defined, the first available tab.\n tabName = tabName ?? getActiveTabName() ?? getFirstTabName();\n\n const tab = document.querySelector(SELECTORS.forTabName(tabName));\n if (!tab) {\n return;\n }\n\n const pendingPromise = new Pending('core/dynamic_tabs:loadTab:' + tabName);\n const tabdata = tab.closest(SELECTORS.dynamicTabs);\n const wsData = {\n 'reportid': tabdata.dataset.reportid,\n 'id': tabdata.dataset.id,\n ...additionalData\n };\n let tabjs = '';\n tab.textContent = '';\n\n indicateNodeIsLoading(tab)\n .then(() => {\n return getContent(tab.dataset.tabClass, JSON.stringify(wsData));\n })\n .then((data) => {\n tabjs = data.javascript;\n return Templates.render(data.template, JSON.parse(data.content));\n })\n .then((html, js) => {\n return Templates.replaceNodeContents(tab, html, js + tabjs);\n })\n .then(() => {\n pendingPromise.resolve();\n return null;\n })\n .catch(Notification.exception);\n};\n\n/**\n * Return the tab given the tab name\n *\n * @param {String} tabName\n * @return {HTMLElement}\n */\nconst getTab = (tabName) => {\n return document.querySelector(SELECTORS.forTabId(tabName));\n};\n\n/**\n * Return the tab pane given the tab name\n *\n * @param {String} tabName\n * @return {HTMLElement}\n */\nconst getTabPane = (tabName) => {\n return document.getElementById(tabName);\n};\n\n/**\n * Open the tab on page load. If this script loads before theme_boost/tab we need to open tab ourselves\n *\n * @param {String} tabName\n * @return {Boolean}\n */\nconst openTab = (tabName) => {\n const tab = getTab(tabName);\n if (!tab) {\n return false;\n }\n\n loadTab(tabName);\n tab.classList.add('active');\n getTabPane(tabName).classList.add('active', 'show');\n return true;\n};\n\n/**\n * If there is a location hash that is the same as the tab name - open this tab.\n *\n * @return {Boolean}\n */\nconst openTabFromHash = () => {\n const hash = document.location.hash;\n if (hash.match(/^#\\w+$/g)) {\n return openTab(hash.replace(/^#/g, ''));\n }\n\n return false;\n};\n"],"file":"dynamic_tabs.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/dynamic_tabs.js"],"names":["SELECTORS","dynamicTabs","activeTab","allActiveTabs","tabContent","tabToggle","tabPane","forTabName","tabName","forTabId","init","on","event","preventDefault","stopPropagation","key","component","then","strChangesMade","strChangesMadeReally","strConfirm","Notification","confirm","target","trigger","type","catch","exception","previousTabName","getActiveTabName","previousTab","document","querySelector","textContent","tab","attr","length","loadTab","openTabFromHash","tabs","openTab","getAttribute","classList","add","element","getFirstTabName","dataset","additionalData","pendingPromise","Pending","tabdata","closest","wsData","reportid","id","tabjs","tabClass","JSON","stringify","data","javascript","Templates","render","template","parse","content","html","js","replaceNodeContents","resolve","getTab","getTabPane","getElementById","hash","location","match","replace"],"mappings":"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","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 * Dynamic Tabs UI element with AJAX loading of tabs content\n *\n * @module core/dynamic_tabs\n * @copyright 2021 David Matamoros based on code from Marina Glancy\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport Templates from 'core/templates';\nimport {addIconToContainer} from 'core/loadingicon';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport {get_strings as getStrings} from 'core/str';\nimport {getContent} from 'core/local/repository/dynamic_tabs';\nimport {isAnyWatchedFormDirty, resetAllFormDirtyStates} from 'core_form/changechecker';\n\nconst SELECTORS = {\n dynamicTabs: '.dynamictabs',\n activeTab: '.dynamictabs .nav-link.active',\n allActiveTabs: '.dynamictabs .nav-link[data-toggle=\"tab\"]:not(.disabled)',\n tabContent: '.dynamictabs .tab-pane [data-tab-content]',\n tabToggle: 'a[data-toggle=\"tab\"]',\n tabPane: '.dynamictabs .tab-pane',\n};\n\nSELECTORS.forTabName = tabName => `.dynamictabs [data-tab-content=\"${tabName}\"]`;\nSELECTORS.forTabId = tabName => `.dynamictabs [data-toggle=\"tab\"][href=\"#${tabName}\"]`;\n\n/**\n * Initialises the tabs view on the page (only one tabs view per page is supported)\n */\nexport const init = () => {\n const tabToggle = $(SELECTORS.tabToggle);\n\n // Listen to click, warn user if they are navigating away with unsaved form changes.\n tabToggle.on('click', (event) => {\n if (!isAnyWatchedFormDirty()) {\n return;\n }\n\n event.preventDefault();\n event.stopPropagation();\n\n getStrings([\n {key: 'changesmade', component: 'moodle'},\n {key: 'changesmadereallygoaway', component: 'moodle'},\n {key: 'confirm', component: 'moodle'},\n ]).then(([strChangesMade, strChangesMadeReally, strConfirm]) =>\n // Reset form dirty state on confirmation, re-trigger the event.\n Notification.confirm(strChangesMade, strChangesMadeReally, strConfirm, null, () => {\n resetAllFormDirtyStates();\n $(event.target).trigger(event.type);\n })\n ).catch(Notification.exception);\n });\n\n // This code listens to Bootstrap events 'show.bs.tab' and 'shown.bs.tab' which is triggered using JQuery and\n // can not be converted yet to native events.\n tabToggle\n .on('show.bs.tab', function() {\n // Clean content from previous tab.\n const previousTabName = getActiveTabName();\n if (previousTabName) {\n const previousTab = document.querySelector(SELECTORS.forTabName(previousTabName));\n previousTab.textContent = '';\n }\n })\n .on('shown.bs.tab', function() {\n const tab = $($(this).attr('href'));\n if (tab.length !== 1) {\n return;\n }\n loadTab(tab.attr('id'));\n });\n\n if (!openTabFromHash()) {\n const tabs = document.querySelector(SELECTORS.allActiveTabs);\n if (tabs) {\n openTab(tabs.getAttribute('aria-controls'));\n } else {\n // We may hide tabs if there is only one available, just load the contents of the first tab.\n const tabPane = document.querySelector(SELECTORS.tabPane);\n if (tabPane) {\n tabPane.classList.add('active', 'show');\n loadTab(tabPane.getAttribute('id'));\n }\n }\n }\n};\n\n/**\n * Returns id/name of the currently active tab\n *\n * @return {String|null}\n */\nconst getActiveTabName = () => {\n const element = document.querySelector(SELECTORS.activeTab);\n return element?.getAttribute('aria-controls') || null;\n};\n\n/**\n * Returns the id/name of the first tab\n *\n * @return {String|null}\n */\nconst getFirstTabName = () => {\n const element = document.querySelector(SELECTORS.tabContent);\n return element?.dataset.tabContent || null;\n};\n\n/**\n * Loads contents of a tab using an AJAX request\n *\n * @param {String} tabName\n * @param {Object} additionalData additional data to pass to WS\n */\nconst loadTab = (tabName, additionalData = {}) => {\n // If tabName is not specified find the active tab, or if is not defined, the first available tab.\n tabName = tabName ?? getActiveTabName() ?? getFirstTabName();\n\n const tab = document.querySelector(SELECTORS.forTabName(tabName));\n if (!tab) {\n return;\n }\n\n const pendingPromise = new Pending('core/dynamic_tabs:loadTab:' + tabName);\n const tabdata = tab.closest(SELECTORS.dynamicTabs);\n const wsData = {\n 'reportid': tabdata.dataset.reportid,\n 'id': tabdata.dataset.id,\n ...additionalData\n };\n let tabjs = '';\n\n addIconToContainer(tab)\n .then(() => {\n return getContent(tab.dataset.tabClass, JSON.stringify(wsData));\n })\n .then((data) => {\n tabjs = data.javascript;\n return Templates.render(data.template, JSON.parse(data.content));\n })\n .then((html, js) => {\n return Templates.replaceNodeContents(tab, html, js + tabjs);\n })\n .then(() => {\n pendingPromise.resolve();\n return null;\n })\n .catch(Notification.exception);\n};\n\n/**\n * Return the tab given the tab name\n *\n * @param {String} tabName\n * @return {HTMLElement}\n */\nconst getTab = (tabName) => {\n return document.querySelector(SELECTORS.forTabId(tabName));\n};\n\n/**\n * Return the tab pane given the tab name\n *\n * @param {String} tabName\n * @return {HTMLElement}\n */\nconst getTabPane = (tabName) => {\n return document.getElementById(tabName);\n};\n\n/**\n * Open the tab on page load. If this script loads before theme_boost/tab we need to open tab ourselves\n *\n * @param {String} tabName\n * @return {Boolean}\n */\nconst openTab = (tabName) => {\n const tab = getTab(tabName);\n if (!tab) {\n return false;\n }\n\n loadTab(tabName);\n tab.classList.add('active');\n getTabPane(tabName).classList.add('active', 'show');\n return true;\n};\n\n/**\n * If there is a location hash that is the same as the tab name - open this tab.\n *\n * @return {Boolean}\n */\nconst openTabFromHash = () => {\n const hash = document.location.hash;\n if (hash.match(/^#\\w+$/g)) {\n return openTab(hash.replace(/^#/g, ''));\n }\n\n return false;\n};\n"],"file":"dynamic_tabs.min.js"} \ No newline at end of file diff --git a/lib/amd/build/local/reactive/basecomponent.min.js b/lib/amd/build/local/reactive/basecomponent.min.js index 229e16eea47fd..bffac7687263f 100644 --- a/lib/amd/build/local/reactive/basecomponent.min.js +++ b/lib/amd/build/local/reactive/basecomponent.min.js @@ -1,2 +1,2 @@ -define ("core/local/reactive/basecomponent",["exports","core/templates"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);function c(a,b){return h(a)||g(a,b)||e(a,b)||d()}function d(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function e(a,b){if(!a)return;if("string"==typeof a)return f(a,b);var c=Object.prototype.toString.call(a).slice(8,-1);if("Object"===c&&a.constructor)c=a.constructor.name;if("Map"===c||"Set"===c)return Array.from(c);if("Arguments"===c||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c))return f(a,b)}function f(a,b){if(null==b||b>a.length)b=a.length;for(var c=0,d=Array(b);ca.length)b=a.length;for(var c=0,d=Array(b);c.\n\nimport Templates from 'core/templates';\n\n/**\n * Reactive UI component base class.\n *\n * Each UI reactive component should extend this class to interact with a reactive state.\n *\n * @module core/local/reactive/basecomponent\n * @class core/local/reactive/basecomponent\n * @copyright 2020 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class {\n\n /**\n * The component descriptor data structure.\n *\n * This structure is used by any component and init method to define the way the component will interact\n * with the interface and whith reactive instance operates. The logic behind this object is to avoid\n * unnecessary dependancies between the final interface and the state logic.\n *\n * Any component interacts with a single main DOM element (description.element) but it can use internal\n * selector to select elements within this main element (descriptor.selectors). By default each component\n * will provide it's own default selectors, but those can be overridden by the \"descriptor.selectors\"\n * property in case the mustache wants to reuse the same component logic but with a different interface.\n *\n * @typedef {object} descriptor\n * @property {Reactive} reactive an optional reactive module to register in\n * @property {DOMElement} element all components needs an element to anchor events\n * @property {object} [selectors] an optional object to override query selectors\n */\n\n /**\n * The class constructor.\n *\n * The only param this method gets is a constructor with all the mandatory\n * and optional component data. Component will receive the same descriptor\n * as create method param.\n *\n * This method will call the \"create\" method before registering the component into\n * the reactive module. This way any component can add default selectors and events.\n *\n * @param {descriptor} descriptor data to create the object.\n */\n constructor(descriptor) {\n\n if (descriptor.element === undefined || !(descriptor.element instanceof HTMLElement)) {\n throw Error(`Reactive components needs a main DOM element to dispatch events`);\n }\n\n this.element = descriptor.element;\n\n // Variable to track event listeners.\n this.eventHandlers = new Map([]);\n this.eventListeners = [];\n\n // Empty default component selectors.\n this.selectors = {};\n\n // Empty default event list from the static method.\n this.events = this.constructor.getEvents();\n\n // Call create function to get the component defaults.\n this.create(descriptor);\n\n // Overwrite the components selectors if necessary.\n if (descriptor.selectors !== undefined) {\n this.addSelectors(descriptor.selectors);\n }\n\n // Register into a reactive instance.\n if (descriptor.reactive === undefined) {\n // Ask parent components for registration.\n this.element.dispatchEvent(new CustomEvent(\n 'core/reactive:requestRegistration',\n {\n bubbles: true,\n detail: {component: this},\n }\n ));\n } else {\n this.reactive = descriptor.reactive;\n this.reactive.registerComponent(this);\n // Add a listener to register child components.\n this.addEventListener(\n this.element,\n 'core/reactive:requestRegistration',\n (event) => {\n if (event?.detail?.component) {\n event.stopPropagation();\n this.registerChildComponent(event.detail.component);\n }\n }\n );\n }\n }\n\n /**\n * Return the component custom event names.\n *\n * Components may override this method to provide their own events.\n *\n * Component custom events is an important part of component reusability. This function\n * is static because is part of the component definition and should be accessible from\n * outsite the instances. However, values will be available at instance level in the\n * this.events object.\n *\n * @returns {Object} the component events.\n */\n static getEvents() {\n return {};\n }\n\n /**\n * Component create function.\n *\n * Default init method will call \"create\" when all internal attributes are set\n * but before the component is not yet registered in the reactive module.\n *\n * In this method any component can define its own defaults such as:\n * - this.selectors {object} the default query selectors of this component.\n * - this.events {object} a list of event names this component dispatch\n * - extract any data from the main dom element (this.element)\n * - set any other data the component uses\n *\n * @param {descriptor} descriptor the component descriptor\n */\n // eslint-disable-next-line no-unused-vars\n create(descriptor) {\n // Components may override this method to initialize selects, events or other data.\n }\n\n /**\n * Component destroy hook.\n *\n * BaseComponent call this method when a component is unregistered or removed.\n *\n * Components may override this method to clean the HTML or do some action when the\n * component is unregistered or removed.\n */\n destroy() {\n // Components can override this method.\n }\n\n /**\n * Return the list of watchers that component has.\n *\n * Each watcher is represented by an object with two attributes:\n * - watch (string) the specific state event to watch. Example 'section.visible:updated'\n * - handler (function) the function to call when the watching state change happens\n *\n * Any component shoudl override this method to define their state watchers.\n *\n * @returns {array} array of watchers.\n */\n getWatchers() {\n return [];\n }\n\n /**\n * Reactive module will call this method when the state is ready.\n *\n * Component can override this method to update/load the component HTML or to bind\n * listeners to HTML entities.\n */\n stateReady() {\n // Components can override this method.\n }\n\n /**\n * Get the main DOM element of this component or a subelement.\n *\n * @param {string|undefined} query optional subelement query\n * @param {string|undefined} dataId optional data-id value\n * @returns {element|undefined} the DOM element (if any)\n */\n getElement(query, dataId) {\n if (query === undefined && dataId === undefined) {\n return this.element;\n }\n const dataSelector = (dataId) ? `[data-id='${dataId}']` : '';\n const selector = `${query ?? ''}${dataSelector}`;\n return this.element.querySelector(selector);\n }\n\n /**\n * Get the all subelement that match a query selector.\n *\n * @param {string|undefined} query optional subelement query\n * @param {string|undefined} dataId optional data-id value\n * @returns {NodeList} the DOM elements\n */\n getElements(query, dataId) {\n const dataSelector = (dataId) ? `[data-id='${dataId}']` : '';\n const selector = `${query ?? ''}${dataSelector}`;\n return this.element.querySelectorAll(selector);\n }\n\n /**\n * Add or update the component selectors.\n *\n * @param {Object} newSelectors an object of new selectors.\n */\n addSelectors(newSelectors) {\n for (const [selectorName, selector] of Object.entries(newSelectors)) {\n this.selectors[selectorName] = selector;\n }\n }\n\n /**\n * Return a component selector.\n *\n * @param {string} selectorName the selector name\n * @return {string|undefined} the query selector\n */\n getSelector(selectorName) {\n return this.selectors[selectorName];\n }\n\n /**\n * Dispatch a custom event on this.element.\n *\n * This is just a convenient method to dispatch custom events from within a component.\n * Components are free to use an alternative function to dispatch custom\n * events. The only restriction is that it should be dispatched on this.element\n * and specify \"bubbles:true\" to alert any component listeners.\n *\n * @param {string} eventName the event name\n * @param {*} detail event detail data\n */\n dispatchEvent(eventName, detail) {\n this.element.dispatchEvent(new CustomEvent(eventName, {\n bubbles: true,\n detail: detail,\n }));\n }\n\n /**\n * Render a new Component using a mustache file.\n *\n * It is important to note that this method should NOT be used for loading regular mustache files\n * as it returns a Promise that will only be resolved if the mustache registers a component instance.\n *\n * @param {element} target the DOM element that contains the component\n * @param {string} file the component mustache file to render\n * @param {*} data the mustache data\n * @return {Promise} a promise of the resulting component instance\n */\n renderComponent(target, file, data) {\n return new Promise((resolve, reject) => {\n target.addEventListener('ComponentRegistration:Success', ({detail}) => {\n resolve(detail.component);\n });\n target.addEventListener('ComponentRegistration:Fail', () => {\n reject(`Registration of ${file} fails.`);\n });\n Templates.renderForPromise(\n file,\n data\n ).then(({html, js}) => {\n Templates.replaceNodeContents(target, html, js);\n return true;\n }).catch(error => {\n reject(`Rendering of ${file} throws an error.`);\n throw error;\n });\n });\n }\n\n /**\n * Add and bind an event listener to a target and keep track of all event listeners.\n *\n * The native element.addEventListener method is not object oriented friently as the\n * \"this\" represents the element that triggers the event and not the listener class.\n * As components can be unregister and removed at any time, the BaseComponent provides\n * this method to keep track of all component listeners and do all of the bind stuff.\n *\n * @param {Element} target the event target\n * @param {string} type the event name\n * @param {function} listener the class method that recieve the event\n */\n addEventListener(target, type, listener) {\n\n // Check if we have the bind version of that listener.\n let bindListener = this.eventHandlers.get(listener);\n\n if (bindListener === undefined) {\n bindListener = listener.bind(this);\n this.eventHandlers.set(listener, bindListener);\n }\n\n target.addEventListener(type, bindListener);\n\n // Keep track of all component event listeners in case we need to remove them.\n this.eventListeners.push({\n target,\n type,\n bindListener,\n });\n\n }\n\n /**\n * Remove an event listener from a component.\n *\n * This method allows components to remove listeners without keeping track of the\n * listeners bind versions of the method. Both addEventListener and removeEventListener\n * keeps internally the relation between the original class method and the bind one.\n *\n * @param {Element} target the event target\n * @param {string} type the event name\n * @param {function} listener the class method that recieve the event\n */\n removeEventListener(target, type, listener) {\n // Check if we have the bind version of that listener.\n let bindListener = this.eventHandlers.get(listener);\n\n if (bindListener === undefined) {\n // This listener has not been added.\n return;\n }\n\n target.removeEventListener(type, bindListener);\n }\n\n /**\n * Remove all event listeners from this component.\n *\n * This method is called also when the component is unregistered or removed.\n *\n * Note that only listeners registered with the addEventListener method\n * will be removed. Other manual listeners will keep active.\n */\n removeAllEventListeners() {\n this.eventListeners.forEach(({target, type, bindListener}) => {\n target.removeEventListener(type, bindListener);\n });\n this.eventListeners = [];\n }\n\n /**\n * Remove a previously rendered component instance.\n *\n * This method will remove the component HTML and unregister it from the\n * reactive module.\n */\n remove() {\n this.unregister();\n this.element.remove();\n }\n\n /**\n * Unregister the component from the reactive module.\n *\n * This method will disable the component logic, event listeners and watchers\n * but it won't remove any HTML created by the component. However, it will trigger\n * the destroy hook to allow the component to clean parts of the interface.\n */\n unregister() {\n this.reactive.unregisterComponent(this);\n this.removeAllEventListeners();\n this.destroy();\n }\n\n /**\n * Dispatch a component registration event to inform the parent node.\n *\n * The registration event is different from the rest of the component events because\n * is the only way in which components can communicate its existence to a possible parent.\n * Most components will be created by including a mustache file, child components\n * must emit a registration event to the parent DOM element to alert about the registration.\n */\n dispatchRegistrationSuccess() {\n // The registration event does not bubble because we just want to comunicate with the parentNode.\n // Otherwise, any component can get multiple registrations events and could not differentiate\n // between child components and grand child components.\n if (this.element.parentNode === undefined) {\n return;\n }\n // This custom element is captured by renderComponent method.\n this.element.parentNode.dispatchEvent(new CustomEvent(\n 'ComponentRegistration:Success',\n {\n bubbles: false,\n detail: {component: this},\n }\n ));\n }\n\n /**\n * Dispatch a component registration fail event to inform the parent node.\n *\n * As dispatchRegistrationSuccess, this method will communicate the registration fail to the\n * parent node to inform the possible parent component.\n */\n dispatchRegistrationFail() {\n if (this.element.parentNode === undefined) {\n return;\n }\n // This custom element is captured only by renderComponent method.\n this.element.parentNode.dispatchEvent(new CustomEvent(\n 'ComponentRegistration:Fail',\n {\n bubbles: false,\n detail: {component: this},\n }\n ));\n }\n\n /**\n * Register a child component into the reactive instance.\n *\n * @param {self} component the component to register.\n */\n registerChildComponent(component) {\n component.reactive = this.reactive;\n this.reactive.registerComponent(component);\n }\n\n /**\n * Set the lock value and locks or unlocks the element.\n *\n * @param {boolean} locked the new locked value\n */\n set locked(locked) {\n this.setElementLocked(this.element, locked);\n }\n\n /**\n * Get the current locked value from the element.\n *\n * @return {boolean}\n */\n get locked() {\n return this.getElementLocked(this.element);\n }\n\n /**\n * Lock/unlock an element.\n *\n * @param {Element} target the event target\n * @param {boolean} locked the new locked value\n */\n setElementLocked(target, locked) {\n target.dataset.locked = locked ?? false;\n if (locked) {\n // Disable interactions.\n target.style.pointerEvents = 'none';\n target.style.userSelect = 'none';\n // Check if it is draggable.\n if (target.hasAttribute('draggable')) {\n target.setAttribute('draggable', false);\n }\n } else {\n // Enable interactions.\n target.style.pointerEvents = null;\n target.style.userSelect = null;\n // Check if it was draggable.\n if (target.hasAttribute('draggable')) {\n target.setAttribute('draggable', true);\n }\n }\n }\n\n /**\n * Get the current locked value from the element.\n *\n * @param {Element} target the event target\n * @return {boolean}\n */\n getElementLocked(target) {\n return target.dataset.locked ?? false;\n }\n}\n"],"file":"basecomponent.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/reactive/basecomponent.js"],"names":["descriptor","element","HTMLElement","Error","eventHandlers","Map","eventListeners","selectors","events","constructor","getEvents","create","addSelectors","reactive","dispatchEvent","CustomEvent","bubbles","detail","component","registerComponent","addEventListener","event","stopPropagation","registerChildComponent","query","dataId","dataSelector","selector","querySelector","querySelectorAll","newSelectors","Object","entries","selectorName","eventName","target","file","data","Promise","resolve","reject","Templates","renderForPromise","then","html","js","replaceNodeContents","catch","error","type","listener","bindListener","get","bind","set","push","removeEventListener","forEach","unregister","remove","unregisterComponent","removeAllEventListeners","destroy","parentNode","locked","dataset","style","pointerEvents","userSelect","hasAttribute","setAttribute","setElementLocked","getElementLocked"],"mappings":"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","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\nimport Templates from 'core/templates';\n\n/**\n * Reactive UI component base class.\n *\n * Each UI reactive component should extend this class to interact with a reactive state.\n *\n * @module core/local/reactive/basecomponent\n * @class core/local/reactive/basecomponent\n * @copyright 2020 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class {\n\n /**\n * The component descriptor data structure.\n *\n * This structure is used by any component and init method to define the way the component will interact\n * with the interface and whith reactive instance operates. The logic behind this object is to avoid\n * unnecessary dependancies between the final interface and the state logic.\n *\n * Any component interacts with a single main DOM element (description.element) but it can use internal\n * selector to select elements within this main element (descriptor.selectors). By default each component\n * will provide it's own default selectors, but those can be overridden by the \"descriptor.selectors\"\n * property in case the mustache wants to reuse the same component logic but with a different interface.\n *\n * @typedef {object} descriptor\n * @property {Reactive} reactive an optional reactive module to register in\n * @property {DOMElement} element all components needs an element to anchor events\n * @property {object} [selectors] an optional object to override query selectors\n */\n\n /**\n * The class constructor.\n *\n * The only param this method gets is a constructor with all the mandatory\n * and optional component data. Component will receive the same descriptor\n * as create method param.\n *\n * This method will call the \"create\" method before registering the component into\n * the reactive module. This way any component can add default selectors and events.\n *\n * @param {descriptor} descriptor data to create the object.\n */\n constructor(descriptor) {\n\n if (descriptor.element === undefined || !(descriptor.element instanceof HTMLElement)) {\n throw Error(`Reactive components needs a main DOM element to dispatch events`);\n }\n\n this.element = descriptor.element;\n\n // Variable to track event listeners.\n this.eventHandlers = new Map([]);\n this.eventListeners = [];\n\n // Empty default component selectors.\n this.selectors = {};\n\n // Empty default event list from the static method.\n this.events = this.constructor.getEvents();\n\n // Call create function to get the component defaults.\n this.create(descriptor);\n\n // Overwrite the components selectors if necessary.\n if (descriptor.selectors !== undefined) {\n this.addSelectors(descriptor.selectors);\n }\n\n // Register into a reactive instance.\n if (descriptor.reactive === undefined) {\n // Ask parent components for registration.\n this.element.dispatchEvent(new CustomEvent(\n 'core/reactive:requestRegistration',\n {\n bubbles: true,\n detail: {component: this},\n }\n ));\n } else {\n this.reactive = descriptor.reactive;\n this.reactive.registerComponent(this);\n // Add a listener to register child components.\n this.addEventListener(\n this.element,\n 'core/reactive:requestRegistration',\n (event) => {\n if (event?.detail?.component) {\n event.stopPropagation();\n this.registerChildComponent(event.detail.component);\n }\n }\n );\n }\n }\n\n /**\n * Return the component custom event names.\n *\n * Components may override this method to provide their own events.\n *\n * Component custom events is an important part of component reusability. This function\n * is static because is part of the component definition and should be accessible from\n * outsite the instances. However, values will be available at instance level in the\n * this.events object.\n *\n * @returns {Object} the component events.\n */\n static getEvents() {\n return {};\n }\n\n /**\n * Component create function.\n *\n * Default init method will call \"create\" when all internal attributes are set\n * but before the component is not yet registered in the reactive module.\n *\n * In this method any component can define its own defaults such as:\n * - this.selectors {object} the default query selectors of this component.\n * - this.events {object} a list of event names this component dispatch\n * - extract any data from the main dom element (this.element)\n * - set any other data the component uses\n *\n * @param {descriptor} descriptor the component descriptor\n */\n // eslint-disable-next-line no-unused-vars\n create(descriptor) {\n // Components may override this method to initialize selects, events or other data.\n }\n\n /**\n * Component destroy hook.\n *\n * BaseComponent call this method when a component is unregistered or removed.\n *\n * Components may override this method to clean the HTML or do some action when the\n * component is unregistered or removed.\n */\n destroy() {\n // Components can override this method.\n }\n\n /**\n * Return the list of watchers that component has.\n *\n * Each watcher is represented by an object with two attributes:\n * - watch (string) the specific state event to watch. Example 'section.visible:updated'\n * - handler (function) the function to call when the watching state change happens\n *\n * Any component shoudl override this method to define their state watchers.\n *\n * @returns {array} array of watchers.\n */\n getWatchers() {\n return [];\n }\n\n /**\n * Reactive module will call this method when the state is ready.\n *\n * Component can override this method to update/load the component HTML or to bind\n * listeners to HTML entities.\n */\n stateReady() {\n // Components can override this method.\n }\n\n /**\n * Get the main DOM element of this component or a subelement.\n *\n * @param {string|undefined} query optional subelement query\n * @param {string|undefined} dataId optional data-id value\n * @returns {element|undefined} the DOM element (if any)\n */\n getElement(query, dataId) {\n if (query === undefined && dataId === undefined) {\n return this.element;\n }\n const dataSelector = (dataId) ? `[data-id='${dataId}']` : '';\n const selector = `${query ?? ''}${dataSelector}`;\n return this.element.querySelector(selector);\n }\n\n /**\n * Get the all subelement that match a query selector.\n *\n * @param {string|undefined} query optional subelement query\n * @param {string|undefined} dataId optional data-id value\n * @returns {NodeList} the DOM elements\n */\n getElements(query, dataId) {\n const dataSelector = (dataId) ? `[data-id='${dataId}']` : '';\n const selector = `${query ?? ''}${dataSelector}`;\n return this.element.querySelectorAll(selector);\n }\n\n /**\n * Add or update the component selectors.\n *\n * @param {Object} newSelectors an object of new selectors.\n */\n addSelectors(newSelectors) {\n for (const [selectorName, selector] of Object.entries(newSelectors)) {\n this.selectors[selectorName] = selector;\n }\n }\n\n /**\n * Return a component selector.\n *\n * @param {string} selectorName the selector name\n * @return {string|undefined} the query selector\n */\n getSelector(selectorName) {\n return this.selectors[selectorName];\n }\n\n /**\n * Dispatch a custom event on this.element.\n *\n * This is just a convenient method to dispatch custom events from within a component.\n * Components are free to use an alternative function to dispatch custom\n * events. The only restriction is that it should be dispatched on this.element\n * and specify \"bubbles:true\" to alert any component listeners.\n *\n * @param {string} eventName the event name\n * @param {*} detail event detail data\n */\n dispatchEvent(eventName, detail) {\n this.element.dispatchEvent(new CustomEvent(eventName, {\n bubbles: true,\n detail: detail,\n }));\n }\n\n /**\n * Render a new Component using a mustache file.\n *\n * It is important to note that this method should NOT be used for loading regular mustache files\n * as it returns a Promise that will only be resolved if the mustache registers a component instance.\n *\n * @param {element} target the DOM element that contains the component\n * @param {string} file the component mustache file to render\n * @param {*} data the mustache data\n * @return {Promise} a promise of the resulting component instance\n */\n renderComponent(target, file, data) {\n return new Promise((resolve, reject) => {\n target.addEventListener('ComponentRegistration:Success', ({detail}) => {\n resolve(detail.component);\n });\n target.addEventListener('ComponentRegistration:Fail', () => {\n reject(`Registration of ${file} fails.`);\n });\n Templates.renderForPromise(\n file,\n data\n ).then(({html, js}) => {\n Templates.replaceNodeContents(target, html, js);\n return true;\n }).catch(error => {\n reject(`Rendering of ${file} throws an error.`);\n throw error;\n });\n });\n }\n\n /**\n * Add and bind an event listener to a target and keep track of all event listeners.\n *\n * The native element.addEventListener method is not object oriented friently as the\n * \"this\" represents the element that triggers the event and not the listener class.\n * As components can be unregister and removed at any time, the BaseComponent provides\n * this method to keep track of all component listeners and do all of the bind stuff.\n *\n * @param {Element} target the event target\n * @param {string} type the event name\n * @param {function} listener the class method that recieve the event\n */\n addEventListener(target, type, listener) {\n\n // Check if we have the bind version of that listener.\n let bindListener = this.eventHandlers.get(listener);\n\n if (bindListener === undefined) {\n bindListener = listener.bind(this);\n this.eventHandlers.set(listener, bindListener);\n }\n\n target.addEventListener(type, bindListener);\n\n // Keep track of all component event listeners in case we need to remove them.\n this.eventListeners.push({\n target,\n type,\n bindListener,\n });\n\n }\n\n /**\n * Remove an event listener from a component.\n *\n * This method allows components to remove listeners without keeping track of the\n * listeners bind versions of the method. Both addEventListener and removeEventListener\n * keeps internally the relation between the original class method and the bind one.\n *\n * @param {Element} target the event target\n * @param {string} type the event name\n * @param {function} listener the class method that recieve the event\n */\n removeEventListener(target, type, listener) {\n // Check if we have the bind version of that listener.\n let bindListener = this.eventHandlers.get(listener);\n\n if (bindListener === undefined) {\n // This listener has not been added.\n return;\n }\n\n target.removeEventListener(type, bindListener);\n }\n\n /**\n * Remove all event listeners from this component.\n *\n * This method is called also when the component is unregistered or removed.\n *\n * Note that only listeners registered with the addEventListener method\n * will be removed. Other manual listeners will keep active.\n */\n removeAllEventListeners() {\n this.eventListeners.forEach(({target, type, bindListener}) => {\n target.removeEventListener(type, bindListener);\n });\n this.eventListeners = [];\n }\n\n /**\n * Remove a previously rendered component instance.\n *\n * This method will remove the component HTML and unregister it from the\n * reactive module.\n */\n remove() {\n this.unregister();\n this.element.remove();\n }\n\n /**\n * Unregister the component from the reactive module.\n *\n * This method will disable the component logic, event listeners and watchers\n * but it won't remove any HTML created by the component. However, it will trigger\n * the destroy hook to allow the component to clean parts of the interface.\n */\n unregister() {\n this.reactive.unregisterComponent(this);\n this.removeAllEventListeners();\n this.destroy();\n }\n\n /**\n * Dispatch a component registration event to inform the parent node.\n *\n * The registration event is different from the rest of the component events because\n * is the only way in which components can communicate its existence to a possible parent.\n * Most components will be created by including a mustache file, child components\n * must emit a registration event to the parent DOM element to alert about the registration.\n */\n dispatchRegistrationSuccess() {\n // The registration event does not bubble because we just want to comunicate with the parentNode.\n // Otherwise, any component can get multiple registrations events and could not differentiate\n // between child components and grand child components.\n if (this.element.parentNode === undefined) {\n return;\n }\n // This custom element is captured by renderComponent method.\n this.element.parentNode.dispatchEvent(new CustomEvent(\n 'ComponentRegistration:Success',\n {\n bubbles: false,\n detail: {component: this},\n }\n ));\n }\n\n /**\n * Dispatch a component registration fail event to inform the parent node.\n *\n * As dispatchRegistrationSuccess, this method will communicate the registration fail to the\n * parent node to inform the possible parent component.\n */\n dispatchRegistrationFail() {\n if (this.element.parentNode === undefined) {\n return;\n }\n // This custom element is captured only by renderComponent method.\n this.element.parentNode.dispatchEvent(new CustomEvent(\n 'ComponentRegistration:Fail',\n {\n bubbles: false,\n detail: {component: this},\n }\n ));\n }\n\n /**\n * Register a child component into the reactive instance.\n *\n * @param {self} component the component to register.\n */\n registerChildComponent(component) {\n component.reactive = this.reactive;\n this.reactive.registerComponent(component);\n }\n\n /**\n * Set the lock value and locks or unlocks the element.\n *\n * @param {boolean} locked the new locked value\n */\n set locked(locked) {\n this.setElementLocked(this.element, locked);\n }\n\n /**\n * Get the current locked value from the element.\n *\n * @return {boolean}\n */\n get locked() {\n return this.getElementLocked(this.element);\n }\n\n /**\n * Lock/unlock an element.\n *\n * @param {Element} target the event target\n * @param {boolean} locked the new locked value\n */\n setElementLocked(target, locked) {\n target.dataset.locked = locked ?? false;\n if (locked) {\n // Disable interactions.\n target.style.pointerEvents = 'none';\n target.style.userSelect = 'none';\n // Check if it is draggable.\n if (target.hasAttribute('draggable')) {\n target.setAttribute('draggable', false);\n }\n target.setAttribute('aria-busy', true);\n } else {\n // Enable interactions.\n target.style.pointerEvents = null;\n target.style.userSelect = null;\n // Check if it was draggable.\n if (target.hasAttribute('draggable')) {\n target.setAttribute('draggable', true);\n }\n target.setAttribute('aria-busy', false);\n }\n }\n\n /**\n * Get the current locked value from the element.\n *\n * @param {Element} target the event target\n * @return {boolean}\n */\n getElementLocked(target) {\n return target.dataset.locked ?? false;\n }\n}\n"],"file":"basecomponent.min.js"} \ No newline at end of file diff --git a/lib/amd/build/paged_content_factory.min.js b/lib/amd/build/paged_content_factory.min.js index 6843e1bf0f6a8..155af12410408 100644 --- a/lib/amd/build/paged_content_factory.min.js +++ b/lib/amd/build/paged_content_factory.min.js @@ -1,2 +1,2 @@ -define ("core/paged_content_factory",["jquery","core/templates","core/notification","core/paged_content","core/paged_content_events","core/pubsub","core/ajax"],function(a,b,c,d,e,f,g){var h={PAGED_CONTENT:"core/paged_content"},j={ITEMS_PER_PAGE_SINGLE:25,ITEMS_PER_PAGE_ARRAY:[25,50,100,0],MAX_PAGES:3},k=function(){return{pagingbar:!1,pagingdropdown:!1,skipjs:!0,ignorecontrolwhileloading:!0,controlplacementbottom:!1}},l=function(){return{showitemsperpageselector:!1,itemsperpage:35,previous:!0,next:!0,activepagenumber:1,hidecontrolonsinglepage:!0,pages:[]}},m=function(a,b){var c=1;if(0=h){k=b;f=b}else{f=2*f;k=f}e+=k;var l={itemcount:k,content:e};if(1==h){l.active=!0}d.options.push(l)}return d},s=function(a,b,c){var d=k();if(c.hasOwnProperty("ignoreControlWhileLoading")){d.ignorecontrolwhileloading=c.ignoreControlWhileLoading}if(c.hasOwnProperty("controlPlacementBottom")){d.controlplacementbottom=c.controlPlacementBottom}if(c.hasOwnProperty("hideControlOnSinglePage")){d.hidecontrolonsinglepage=c.hideControlOnSinglePage}if(c.hasOwnProperty("ariaLabels")){d.arialabels=c.ariaLabels}if(c.hasOwnProperty("dropdown")&&c.dropdown){d.pagingdropdown=r(b,c)}else{d.pagingbar=q(a,b)}return d},t=function(e,f,g,i){i=i||{};var j=a.Deferred(),k=s(e,f,i);b.render(h.PAGED_CONTENT,k).then(function(b,c){b=a(b);var e=b.attr("id");if(i.hasOwnProperty("eventNamespace")){e=i.eventNamespace}var f=b;d.init(f,g,e);v(e,i);j.resolve(b,c)}).fail(function(a){j.reject(a)}).fail(c.exception);return j.promise()},u=function(a){return function callback(b){g.call([{methodname:"core_user_update_user_preferences",args:{preferences:[{type:a,value:b}]}}])}},v=function(a,b){if(b.hasOwnProperty("persistentLimitKey")){f.subscribe(a+e.SET_ITEMS_PER_PAGE_LIMIT,u(b.persistentLimitKey))}};return{create:function create(a,b){return t(null,null,a,b)},createWithLimit:function createWithLimit(a,b,c){return t(null,a,b,c)},createWithTotalAndLimit:t,createFromStaticList:function createFromStaticList(a,b,c,d){if("undefined"==typeof d){d={}}var e=a.length;return t(e,b,function(b){var d=[];b.forEach(function(b){var c=b.offset,f=b.limit?c+b.limit:e,g=a.slice(c,f);d.push(g)});return c(d)},d)},createFromAjax:t,resetLastPageNumber:function resetLastPageNumber(a,b){f.publish(a+e.ALL_ITEMS_LOADED,b)}}}); +define ("core/paged_content_factory",["jquery","core/templates","core/notification","core/paged_content","core/paged_content_events","core/pubsub","core/ajax"],function(a,b,c,d,e,f,g){var h={PAGED_CONTENT:"core/paged_content"},j={ITEMS_PER_PAGE_SINGLE:25,ITEMS_PER_PAGE_ARRAY:[25,50,100,0],MAX_PAGES:3},k=function(){return{pagingbar:!1,pagingdropdown:!1,skipjs:!0,ignorecontrolwhileloading:!0,controlplacementbottom:!1}},l=function(){return{showitemsperpageselector:!1,itemsperpage:[{value:35,active:!0}],previous:!0,next:!0,activepagenumber:1,hidecontrolonsinglepage:!0,pages:[]}},m=function(a,b){var c=1;if(0=h){k=b;f=b}else{f=2*f;k=f}e+=k;var l={itemcount:k,content:e};if(1==h){l.active=!0}d.options.push(l)}return d},s=function(a,b,c){var d=k();if(c.hasOwnProperty("ignoreControlWhileLoading")){d.ignorecontrolwhileloading=c.ignoreControlWhileLoading}if(c.hasOwnProperty("controlPlacementBottom")){d.controlplacementbottom=c.controlPlacementBottom}if(c.hasOwnProperty("hideControlOnSinglePage")){d.hidecontrolonsinglepage=c.hideControlOnSinglePage}if(c.hasOwnProperty("ariaLabels")){d.arialabels=c.ariaLabels}if(c.hasOwnProperty("dropdown")&&c.dropdown){d.pagingdropdown=r(b,c)}else{d.pagingbar=q(a,b);if(c.hasOwnProperty("showFirstLast")&&c.showFirstLast){d.pagingbar.first=!0;d.pagingbar.last=!0}}return d},t=function(e,f,g,i){i=i||{};var j=a.Deferred(),k=s(e,f,i);b.render(h.PAGED_CONTENT,k).then(function(b,c){b=a(b);var e=b.attr("id");if(i.hasOwnProperty("eventNamespace")){e=i.eventNamespace}var f=b;d.init(f,g,e);v(e,i);j.resolve(b,c)}).fail(function(a){j.reject(a)}).fail(c.exception);return j.promise()},u=function(a){return function callback(b){g.call([{methodname:"core_user_update_user_preferences",args:{preferences:[{type:a,value:b}]}}])}},v=function(a,b){if(b.hasOwnProperty("persistentLimitKey")){f.subscribe(a+e.SET_ITEMS_PER_PAGE_LIMIT,u(b.persistentLimitKey))}};return{create:function create(a,b){return t(null,null,a,b)},createWithLimit:function createWithLimit(a,b,c){return t(null,a,b,c)},createWithTotalAndLimit:t,createFromStaticList:function createFromStaticList(a,b,c,d){if("undefined"==typeof d){d={}}var e=a.length;return t(e,b,function(b){var d=[];b.forEach(function(b){var c=b.offset,f=b.limit?c+b.limit:e,g=a.slice(c,f);d.push(g)});return c(d)},d)},createFromAjax:t,resetLastPageNumber:function resetLastPageNumber(a,b){f.publish(a+e.ALL_ITEMS_LOADED,b)}}}); //# sourceMappingURL=paged_content_factory.min.js.map diff --git a/lib/amd/build/paged_content_factory.min.js.map b/lib/amd/build/paged_content_factory.min.js.map index e541e285c2e25..6cbe328957fbc 100644 --- a/lib/amd/build/paged_content_factory.min.js.map +++ b/lib/amd/build/paged_content_factory.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/paged_content_factory.js"],"names":["define","$","Templates","Notification","PagedContent","PagedContentEvents","PubSub","Ajax","TEMPLATES","PAGED_CONTENT","DEFAULT","ITEMS_PER_PAGE_SINGLE","ITEMS_PER_PAGE_ARRAY","MAX_PAGES","getDefaultTemplateContext","pagingbar","pagingdropdown","skipjs","ignorecontrolwhileloading","controlplacementbottom","getDefaultPagingBarTemplateContext","showitemsperpageselector","itemsperpage","previous","next","activepagenumber","hidecontrolonsinglepage","pages","calculateNumberOfPages","numberOfItems","itemsPerPage","numberOfPages","partial","buildPagingBarTemplateContextKnownLength","isArray","context","i","page","number","active","push","barsize","buildItemsPerPagePagingBarContext","map","num","value","activeItems","filter","item","length","buildPagingBarTemplateContextUnknownLength","buildPagingBarTemplateContext","buildPagingDropdownTemplateContext","config","options","totalItems","lastIncrease","maxPages","hasOwnProperty","itemCount","option","itemcount","content","buildTemplateContext","ignoreControlWhileLoading","controlPlacementBottom","hideControlOnSinglePage","arialabels","ariaLabels","dropdown","createWithTotalAndLimit","renderPagesContentCallback","deferred","Deferred","templateContext","render","then","html","js","id","attr","eventNamespace","container","init","registerEvents","resolve","fail","exception","reject","promise","generateLimitHandler","persistentLimitKey","callback","limit","call","methodname","args","preferences","type","namespace","subscribe","SET_ITEMS_PER_PAGE_LIMIT","create","createWithLimit","createFromStaticList","contentItems","renderContentCallback","pagesData","contentToRender","forEach","pageData","begin","offset","end","items","slice","createFromAjax","resetLastPageNumber","lastPageNumber","publish","ALL_ITEMS_LOADED"],"mappings":"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","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 * Factory to create a paged content widget.\n *\n * @module core/paged_content_factory\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n[\n 'jquery',\n 'core/templates',\n 'core/notification',\n 'core/paged_content',\n 'core/paged_content_events',\n 'core/pubsub',\n 'core/ajax'\n],\nfunction(\n $,\n Templates,\n Notification,\n PagedContent,\n PagedContentEvents,\n PubSub,\n Ajax\n) {\n var TEMPLATES = {\n PAGED_CONTENT: 'core/paged_content'\n };\n\n var DEFAULT = {\n ITEMS_PER_PAGE_SINGLE: 25,\n ITEMS_PER_PAGE_ARRAY: [25, 50, 100, 0],\n MAX_PAGES: 3\n };\n\n /**\n * Get the default context to render the paged content mustache\n * template.\n *\n * @return {object}\n */\n var getDefaultTemplateContext = function() {\n return {\n pagingbar: false,\n pagingdropdown: false,\n skipjs: true,\n ignorecontrolwhileloading: true,\n controlplacementbottom: false\n };\n };\n\n /**\n * Get the default context to render the paging bar mustache template.\n *\n * @return {object}\n */\n var getDefaultPagingBarTemplateContext = function() {\n return {\n showitemsperpageselector: false,\n itemsperpage: 35,\n previous: true,\n next: true,\n activepagenumber: 1,\n hidecontrolonsinglepage: true,\n pages: []\n };\n };\n\n /**\n * Calculate the number of pages required for the given number of items and\n * how many of each item should appear on a page.\n *\n * @param {Number} numberOfItems How many items in total.\n * @param {Number} itemsPerPage How many items will be shown per page.\n * @return {Number} The number of pages required.\n */\n var calculateNumberOfPages = function(numberOfItems, itemsPerPage) {\n var numberOfPages = 1;\n\n if (numberOfItems > 0) {\n var partial = numberOfItems % itemsPerPage;\n\n if (partial) {\n numberOfItems -= partial;\n numberOfPages = (numberOfItems / itemsPerPage) + 1;\n } else {\n numberOfPages = numberOfItems / itemsPerPage;\n }\n }\n\n return numberOfPages;\n };\n\n /**\n * Build the context for the paging bar template when we have a known number\n * of items.\n *\n * @param {Number} numberOfItems How many items in total.\n * @param {Number} itemsPerPage How many items will be shown per page.\n * @return {object} Mustache template\n */\n var buildPagingBarTemplateContextKnownLength = function(numberOfItems, itemsPerPage) {\n if (itemsPerPage === null) {\n itemsPerPage = DEFAULT.ITEMS_PER_PAGE_SINGLE;\n }\n\n if ($.isArray(itemsPerPage)) {\n // If we're given a total number of pages then we don't support a variable\n // set of items per page so just use the first one.\n itemsPerPage = itemsPerPage[0];\n }\n\n var context = getDefaultPagingBarTemplateContext();\n context.itemsperpage = itemsPerPage;\n var numberOfPages = calculateNumberOfPages(numberOfItems, itemsPerPage);\n\n for (var i = 1; i <= numberOfPages; i++) {\n var page = {\n number: i,\n page: \"\" + i,\n };\n\n // Make the first page active by default.\n if (i === 1) {\n page.active = true;\n }\n\n context.pages.push(page);\n }\n\n context.barsize = 10;\n return context;\n };\n\n /**\n * Convert the itemsPerPage value into a format applicable for the mustache template.\n * The given value can be either a single integer or an array of integers / objects.\n *\n * E.g.\n * In: [5, 10]\n * out: [{value: 5, active: true}, {value: 10, active: false}]\n *\n * In: [5, {value: 10, active: true}]\n * Out: [{value: 5, active: false}, {value: 10, active: true}]\n *\n * In: [{value: 5, active: false}, {value: 10, active: true}]\n * Out: [{value: 5, active: false}, {value: 10, active: true}]\n *\n * @param {int|int[]} itemsPerPage Options for number of items per page.\n * @return {int|array}\n */\n var buildItemsPerPagePagingBarContext = function(itemsPerPage) {\n var context = [];\n\n if ($.isArray(itemsPerPage)) {\n // Convert the array into a format accepted by the template.\n context = itemsPerPage.map(function(num) {\n if (typeof num === 'number') {\n // If the item is just a plain number then convert it into\n // an object with value and active keys.\n return {\n value: num,\n active: false\n };\n } else {\n // Otherwise we assume the caller has specified things correctly.\n return num;\n }\n });\n\n var activeItems = context.filter(function(item) {\n return item.active;\n });\n\n // Default the first item to active if one hasn't been specified.\n if (!activeItems.length) {\n context[0].active = true;\n }\n } else {\n // Convert the integer into a format accepted by the template.\n context = [{value: itemsPerPage, active: true}];\n }\n\n return context;\n };\n\n /**\n * Build the context for the paging bar template when we have an unknown\n * number of items.\n *\n * @param {Number} itemsPerPage How many items will be shown per page.\n * @return {object} Mustache template\n */\n var buildPagingBarTemplateContextUnknownLength = function(itemsPerPage) {\n if (itemsPerPage === null) {\n itemsPerPage = DEFAULT.ITEMS_PER_PAGE_ARRAY;\n }\n\n var context = getDefaultPagingBarTemplateContext();\n context.itemsperpage = buildItemsPerPagePagingBarContext(itemsPerPage);\n // Only display the items per page selector if there is more than one to choose from.\n context.showitemsperpageselector = $.isArray(itemsPerPage) && itemsPerPage.length > 1;\n\n return context;\n };\n\n /**\n * Build the context to render the paging bar template with based on the number\n * of pages to show.\n *\n * @param {int|null} numberOfItems How many items are there total.\n * @param {int|null} itemsPerPage How many items will be shown per page.\n * @return {object} The template context.\n */\n var buildPagingBarTemplateContext = function(numberOfItems, itemsPerPage) {\n if (numberOfItems) {\n return buildPagingBarTemplateContextKnownLength(numberOfItems, itemsPerPage);\n } else {\n return buildPagingBarTemplateContextUnknownLength(itemsPerPage);\n }\n };\n\n /**\n * Build the context to render the paging dropdown template based on the number\n * of pages to show and items per page.\n *\n * This control is rendered with a gradual increase of the items per page to\n * limit the number of pages in the dropdown. Each page will show twice as much\n * as the previous page (except for the first two pages).\n *\n * By default there will only be 4 pages shown (including the \"All\" option) unless\n * a different number of pages is defined using the maxPages config value.\n *\n * For example:\n * Items per page = 25\n * Would render a dropdown will 4 options:\n * 25\n * 50\n * 100\n * All\n *\n * @param {Number} itemsPerPage How many items will be shown per page.\n * @param {object} config Configuration options provided by the client.\n * @return {object} The template context.\n */\n var buildPagingDropdownTemplateContext = function(itemsPerPage, config) {\n if (itemsPerPage === null) {\n itemsPerPage = DEFAULT.ITEMS_PER_PAGE_SINGLE;\n }\n\n if ($.isArray(itemsPerPage)) {\n // If we're given an array for the items per page, rather than a number,\n // then just use that as the options for the dropdown.\n return {\n options: itemsPerPage\n };\n }\n\n var context = {\n options: []\n };\n\n var totalItems = 0;\n var lastIncrease = 0;\n var maxPages = DEFAULT.MAX_PAGES;\n\n if (config.hasOwnProperty('maxPages')) {\n maxPages = config.maxPages;\n }\n\n for (var i = 1; i <= maxPages; i++) {\n var itemCount = 0;\n\n if (i <= 2) {\n itemCount = itemsPerPage;\n lastIncrease = itemsPerPage;\n } else {\n lastIncrease = lastIncrease * 2;\n itemCount = lastIncrease;\n }\n\n totalItems += itemCount;\n var option = {\n itemcount: itemCount,\n content: totalItems\n };\n\n // Make the first option active by default.\n if (i === 1) {\n option.active = true;\n }\n\n context.options.push(option);\n }\n\n return context;\n };\n\n /**\n * Build the context to render the paged content template with based on the number\n * of pages to show, items per page, and configuration option.\n *\n * By default the code will render a paging bar for the paging controls unless\n * otherwise specified in the provided config.\n *\n * @param {int|null} numberOfItems Total number of items.\n * @param {int|null|array} itemsPerPage How many items will be shown per page.\n * @param {object} config Configuration options provided by the client.\n * @return {object} The template context.\n */\n var buildTemplateContext = function(numberOfItems, itemsPerPage, config) {\n var context = getDefaultTemplateContext();\n\n if (config.hasOwnProperty('ignoreControlWhileLoading')) {\n context.ignorecontrolwhileloading = config.ignoreControlWhileLoading;\n }\n\n if (config.hasOwnProperty('controlPlacementBottom')) {\n context.controlplacementbottom = config.controlPlacementBottom;\n }\n\n if (config.hasOwnProperty('hideControlOnSinglePage')) {\n context.hidecontrolonsinglepage = config.hideControlOnSinglePage;\n }\n\n if (config.hasOwnProperty('ariaLabels')) {\n context.arialabels = config.ariaLabels;\n }\n\n if (config.hasOwnProperty('dropdown') && config.dropdown) {\n context.pagingdropdown = buildPagingDropdownTemplateContext(itemsPerPage, config);\n } else {\n context.pagingbar = buildPagingBarTemplateContext(numberOfItems, itemsPerPage);\n }\n\n return context;\n };\n\n /**\n * Create a paged content widget where the complete list of items is not loaded\n * up front but will instead be loaded by an ajax request (or similar).\n *\n * The client code must provide a callback function which loads and renders the\n * items for each page. See PagedContent.init for more details.\n *\n * The function will return a deferred that is resolved with a jQuery object\n * for the HTML content and a string for the JavaScript.\n *\n * The current list of configuration options available are:\n * dropdown {bool} True to render the page control as a dropdown (paging bar is default).\n * maxPages {Number} The maximum number of pages to show in the dropdown (only works with dropdown option)\n * ignoreControlWhileLoading {bool} Disable the pagination controls while loading a page (default to true)\n * controlPlacementBottom {bool} Render controls under paged content (default to false)\n *\n * @param {function} renderPagesContentCallback Callback for loading and rendering the items.\n * @param {object} config Configuration options provided by the client.\n * @return {promise} Resolved with jQuery HTML and string JS.\n */\n var create = function(renderPagesContentCallback, config) {\n return createWithTotalAndLimit(null, null, renderPagesContentCallback, config);\n };\n\n /**\n * Create a paged content widget where the complete list of items is not loaded\n * up front but will instead be loaded by an ajax request (or similar).\n *\n * The client code must provide a callback function which loads and renders the\n * items for each page. See PagedContent.init for more details.\n *\n * The function will return a deferred that is resolved with a jQuery object\n * for the HTML content and a string for the JavaScript.\n *\n * The current list of configuration options available are:\n * dropdown {bool} True to render the page control as a dropdown (paging bar is default).\n * maxPages {Number} The maximum number of pages to show in the dropdown (only works with dropdown option)\n * ignoreControlWhileLoading {bool} Disable the pagination controls while loading a page (default to true)\n * controlPlacementBottom {bool} Render controls under paged content (default to false)\n *\n * @param {int|array|null} itemsPerPage How many items will be shown per page.\n * @param {function} renderPagesContentCallback Callback for loading and rendering the items.\n * @param {object} config Configuration options provided by the client.\n * @return {promise} Resolved with jQuery HTML and string JS.\n */\n var createWithLimit = function(itemsPerPage, renderPagesContentCallback, config) {\n return createWithTotalAndLimit(null, itemsPerPage, renderPagesContentCallback, config);\n };\n\n /**\n * Create a paged content widget where the complete list of items is not loaded\n * up front but will instead be loaded by an ajax request (or similar).\n *\n * The client code must provide a callback function which loads and renders the\n * items for each page. See PagedContent.init for more details.\n *\n * The function will return a deferred that is resolved with a jQuery object\n * for the HTML content and a string for the JavaScript.\n *\n * The current list of configuration options available are:\n * dropdown {bool} True to render the page control as a dropdown (paging bar is default).\n * maxPages {Number} The maximum number of pages to show in the dropdown (only works with dropdown option)\n * ignoreControlWhileLoading {bool} Disable the pagination controls while loading a page (default to true)\n * controlPlacementBottom {bool} Render controls under paged content (default to false)\n *\n * @param {int|null} numberOfItems How many items are there in total.\n * @param {int|array|null} itemsPerPage How many items will be shown per page.\n * @param {function} renderPagesContentCallback Callback for loading and rendering the items.\n * @param {object} config Configuration options provided by the client.\n * @return {promise} Resolved with jQuery HTML and string JS.\n */\n var createWithTotalAndLimit = function(numberOfItems, itemsPerPage, renderPagesContentCallback, config) {\n config = config || {};\n\n var deferred = $.Deferred();\n var templateContext = buildTemplateContext(numberOfItems, itemsPerPage, config);\n\n Templates.render(TEMPLATES.PAGED_CONTENT, templateContext)\n .then(function(html, js) {\n html = $(html);\n var id = html.attr('id');\n\n // Set the id to the custom namespace provided\n if (config.hasOwnProperty('eventNamespace')) {\n id = config.eventNamespace;\n }\n\n var container = html;\n\n PagedContent.init(container, renderPagesContentCallback, id);\n\n registerEvents(id, config);\n\n deferred.resolve(html, js);\n return;\n })\n .fail(function(exception) {\n deferred.reject(exception);\n })\n .fail(Notification.exception);\n\n return deferred.promise();\n };\n\n /**\n * Create a paged content widget where the complete list of items is loaded\n * up front.\n *\n * The client code must provide a callback function which renders the\n * items for each page. The callback will be provided with an array where each\n * value in the array is a the list of items to render for the page.\n *\n * The function will return a deferred that is resolved with a jQuery object\n * for the HTML content and a string for the JavaScript.\n *\n * The current list of configuration options available are:\n * dropdown {bool} True to render the page control as a dropdown (paging bar is default).\n * maxPages {Number} The maximum number of pages to show in the dropdown (only works with dropdown option)\n * ignoreControlWhileLoading {bool} Disable the pagination controls while loading a page (default to true)\n * controlPlacementBottom {bool} Render controls under paged content (default to false)\n *\n * @param {array} contentItems The list of items to paginate.\n * @param {Number} itemsPerPage How many items will be shown per page.\n * @param {function} renderContentCallback Callback for rendering the items for the page.\n * @param {object} config Configuration options provided by the client.\n * @return {promise} Resolved with jQuery HTML and string JS.\n */\n var createFromStaticList = function(contentItems, itemsPerPage, renderContentCallback, config) {\n if (typeof config == 'undefined') {\n config = {};\n }\n\n var numberOfItems = contentItems.length;\n return createWithTotalAndLimit(numberOfItems, itemsPerPage, function(pagesData) {\n var contentToRender = [];\n pagesData.forEach(function(pageData) {\n var begin = pageData.offset;\n var end = pageData.limit ? begin + pageData.limit : numberOfItems;\n var items = contentItems.slice(begin, end);\n contentToRender.push(items);\n });\n\n return renderContentCallback(contentToRender);\n }, config);\n };\n\n /**\n * Reset the last page number for the generated paged-content\n * This is used when we need a way to update the last page number outside of the getters callback\n *\n * @param {String} id ID of the paged content container\n * @param {Int} lastPageNumber The last page number\n */\n var resetLastPageNumber = function(id, lastPageNumber) {\n PubSub.publish(id + PagedContentEvents.ALL_ITEMS_LOADED, lastPageNumber);\n };\n\n /**\n * Generate the callback handler for the page limit persistence functionality\n *\n * @param {String} persistentLimitKey\n * @return {callback}\n */\n var generateLimitHandler = function(persistentLimitKey) {\n var callback = function(limit) {\n var args = {\n preferences: [\n {\n type: persistentLimitKey,\n value: limit\n }\n ]\n };\n\n var request = {\n methodname: 'core_user_update_user_preferences',\n args: args\n };\n\n Ajax.call([request]);\n };\n\n return callback;\n };\n\n /**\n * Set up any events based on config key values\n *\n * @param {string} namespace The namespace for this component\n * @param {object} config Config options passed to the factory\n */\n var registerEvents = function(namespace, config) {\n if (config.hasOwnProperty('persistentLimitKey')) {\n PubSub.subscribe(namespace + PagedContentEvents.SET_ITEMS_PER_PAGE_LIMIT,\n generateLimitHandler(config.persistentLimitKey));\n }\n };\n\n return {\n create: create,\n createWithLimit: createWithLimit,\n createWithTotalAndLimit: createWithTotalAndLimit,\n createFromStaticList: createFromStaticList,\n // Backwards compatibility just in case anyone was using this.\n createFromAjax: createWithTotalAndLimit,\n resetLastPageNumber: resetLastPageNumber\n };\n});\n"],"file":"paged_content_factory.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/paged_content_factory.js"],"names":["define","$","Templates","Notification","PagedContent","PagedContentEvents","PubSub","Ajax","TEMPLATES","PAGED_CONTENT","DEFAULT","ITEMS_PER_PAGE_SINGLE","ITEMS_PER_PAGE_ARRAY","MAX_PAGES","getDefaultTemplateContext","pagingbar","pagingdropdown","skipjs","ignorecontrolwhileloading","controlplacementbottom","getDefaultPagingBarTemplateContext","showitemsperpageselector","itemsperpage","value","active","previous","next","activepagenumber","hidecontrolonsinglepage","pages","calculateNumberOfPages","numberOfItems","itemsPerPage","numberOfPages","partial","buildPagingBarTemplateContextKnownLength","isArray","context","buildItemsPerPagePagingBarContext","i","page","number","push","barsize","map","num","activeItems","filter","item","length","buildPagingBarTemplateContextUnknownLength","buildPagingBarTemplateContext","buildPagingDropdownTemplateContext","config","options","totalItems","lastIncrease","maxPages","hasOwnProperty","itemCount","option","itemcount","content","buildTemplateContext","ignoreControlWhileLoading","controlPlacementBottom","hideControlOnSinglePage","arialabels","ariaLabels","dropdown","showFirstLast","first","last","createWithTotalAndLimit","renderPagesContentCallback","deferred","Deferred","templateContext","render","then","html","js","id","attr","eventNamespace","container","init","registerEvents","resolve","fail","exception","reject","promise","generateLimitHandler","persistentLimitKey","callback","limit","call","methodname","args","preferences","type","namespace","subscribe","SET_ITEMS_PER_PAGE_LIMIT","create","createWithLimit","createFromStaticList","contentItems","renderContentCallback","pagesData","contentToRender","forEach","pageData","begin","offset","end","items","slice","createFromAjax","resetLastPageNumber","lastPageNumber","publish","ALL_ITEMS_LOADED"],"mappings":"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","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 * Factory to create a paged content widget.\n *\n * @module core/paged_content_factory\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n[\n 'jquery',\n 'core/templates',\n 'core/notification',\n 'core/paged_content',\n 'core/paged_content_events',\n 'core/pubsub',\n 'core/ajax'\n],\nfunction(\n $,\n Templates,\n Notification,\n PagedContent,\n PagedContentEvents,\n PubSub,\n Ajax\n) {\n var TEMPLATES = {\n PAGED_CONTENT: 'core/paged_content'\n };\n\n var DEFAULT = {\n ITEMS_PER_PAGE_SINGLE: 25,\n ITEMS_PER_PAGE_ARRAY: [25, 50, 100, 0],\n MAX_PAGES: 3\n };\n\n /**\n * Get the default context to render the paged content mustache\n * template.\n *\n * @return {object}\n */\n var getDefaultTemplateContext = function() {\n return {\n pagingbar: false,\n pagingdropdown: false,\n skipjs: true,\n ignorecontrolwhileloading: true,\n controlplacementbottom: false\n };\n };\n\n /**\n * Get the default context to render the paging bar mustache template.\n *\n * @return {object}\n */\n var getDefaultPagingBarTemplateContext = function() {\n return {\n showitemsperpageselector: false,\n itemsperpage: [{value: 35, active: true}],\n previous: true,\n next: true,\n activepagenumber: 1,\n hidecontrolonsinglepage: true,\n pages: []\n };\n };\n\n /**\n * Calculate the number of pages required for the given number of items and\n * how many of each item should appear on a page.\n *\n * @param {Number} numberOfItems How many items in total.\n * @param {Number} itemsPerPage How many items will be shown per page.\n * @return {Number} The number of pages required.\n */\n var calculateNumberOfPages = function(numberOfItems, itemsPerPage) {\n var numberOfPages = 1;\n\n if (numberOfItems > 0) {\n var partial = numberOfItems % itemsPerPage;\n\n if (partial) {\n numberOfItems -= partial;\n numberOfPages = (numberOfItems / itemsPerPage) + 1;\n } else {\n numberOfPages = numberOfItems / itemsPerPage;\n }\n }\n\n return numberOfPages;\n };\n\n /**\n * Build the context for the paging bar template when we have a known number\n * of items.\n *\n * @param {Number} numberOfItems How many items in total.\n * @param {Number} itemsPerPage How many items will be shown per page.\n * @return {object} Mustache template\n */\n var buildPagingBarTemplateContextKnownLength = function(numberOfItems, itemsPerPage) {\n if (itemsPerPage === null) {\n itemsPerPage = DEFAULT.ITEMS_PER_PAGE_SINGLE;\n }\n\n if ($.isArray(itemsPerPage)) {\n // If we're given a total number of pages then we don't support a variable\n // set of items per page so just use the first one.\n itemsPerPage = itemsPerPage[0];\n }\n\n var context = getDefaultPagingBarTemplateContext();\n context.itemsperpage = buildItemsPerPagePagingBarContext(itemsPerPage);\n var numberOfPages = calculateNumberOfPages(numberOfItems, itemsPerPage);\n\n for (var i = 1; i <= numberOfPages; i++) {\n var page = {\n number: i,\n page: \"\" + i,\n };\n\n // Make the first page active by default.\n if (i === 1) {\n page.active = true;\n }\n\n context.pages.push(page);\n }\n\n context.barsize = 10;\n return context;\n };\n\n /**\n * Convert the itemsPerPage value into a format applicable for the mustache template.\n * The given value can be either a single integer or an array of integers / objects.\n *\n * E.g.\n * In: [5, 10]\n * out: [{value: 5, active: true}, {value: 10, active: false}]\n *\n * In: [5, {value: 10, active: true}]\n * Out: [{value: 5, active: false}, {value: 10, active: true}]\n *\n * In: [{value: 5, active: false}, {value: 10, active: true}]\n * Out: [{value: 5, active: false}, {value: 10, active: true}]\n *\n * @param {int|int[]} itemsPerPage Options for number of items per page.\n * @return {int|array}\n */\n var buildItemsPerPagePagingBarContext = function(itemsPerPage) {\n var context = [];\n\n if ($.isArray(itemsPerPage)) {\n // Convert the array into a format accepted by the template.\n context = itemsPerPage.map(function(num) {\n if (typeof num === 'number') {\n // If the item is just a plain number then convert it into\n // an object with value and active keys.\n return {\n value: num,\n active: false\n };\n } else {\n // Otherwise we assume the caller has specified things correctly.\n return num;\n }\n });\n\n var activeItems = context.filter(function(item) {\n return item.active;\n });\n\n // Default the first item to active if one hasn't been specified.\n if (!activeItems.length) {\n context[0].active = true;\n }\n } else {\n // Convert the integer into a format accepted by the template.\n context = [{value: itemsPerPage, active: true}];\n }\n\n return context;\n };\n\n /**\n * Build the context for the paging bar template when we have an unknown\n * number of items.\n *\n * @param {Number} itemsPerPage How many items will be shown per page.\n * @return {object} Mustache template\n */\n var buildPagingBarTemplateContextUnknownLength = function(itemsPerPage) {\n if (itemsPerPage === null) {\n itemsPerPage = DEFAULT.ITEMS_PER_PAGE_ARRAY;\n }\n\n var context = getDefaultPagingBarTemplateContext();\n context.itemsperpage = buildItemsPerPagePagingBarContext(itemsPerPage);\n // Only display the items per page selector if there is more than one to choose from.\n context.showitemsperpageselector = $.isArray(itemsPerPage) && itemsPerPage.length > 1;\n\n return context;\n };\n\n /**\n * Build the context to render the paging bar template with based on the number\n * of pages to show.\n *\n * @param {int|null} numberOfItems How many items are there total.\n * @param {int|null} itemsPerPage How many items will be shown per page.\n * @return {object} The template context.\n */\n var buildPagingBarTemplateContext = function(numberOfItems, itemsPerPage) {\n if (numberOfItems) {\n return buildPagingBarTemplateContextKnownLength(numberOfItems, itemsPerPage);\n } else {\n return buildPagingBarTemplateContextUnknownLength(itemsPerPage);\n }\n };\n\n /**\n * Build the context to render the paging dropdown template based on the number\n * of pages to show and items per page.\n *\n * This control is rendered with a gradual increase of the items per page to\n * limit the number of pages in the dropdown. Each page will show twice as much\n * as the previous page (except for the first two pages).\n *\n * By default there will only be 4 pages shown (including the \"All\" option) unless\n * a different number of pages is defined using the maxPages config value.\n *\n * For example:\n * Items per page = 25\n * Would render a dropdown will 4 options:\n * 25\n * 50\n * 100\n * All\n *\n * @param {Number} itemsPerPage How many items will be shown per page.\n * @param {object} config Configuration options provided by the client.\n * @return {object} The template context.\n */\n var buildPagingDropdownTemplateContext = function(itemsPerPage, config) {\n if (itemsPerPage === null) {\n itemsPerPage = DEFAULT.ITEMS_PER_PAGE_SINGLE;\n }\n\n if ($.isArray(itemsPerPage)) {\n // If we're given an array for the items per page, rather than a number,\n // then just use that as the options for the dropdown.\n return {\n options: itemsPerPage\n };\n }\n\n var context = {\n options: []\n };\n\n var totalItems = 0;\n var lastIncrease = 0;\n var maxPages = DEFAULT.MAX_PAGES;\n\n if (config.hasOwnProperty('maxPages')) {\n maxPages = config.maxPages;\n }\n\n for (var i = 1; i <= maxPages; i++) {\n var itemCount = 0;\n\n if (i <= 2) {\n itemCount = itemsPerPage;\n lastIncrease = itemsPerPage;\n } else {\n lastIncrease = lastIncrease * 2;\n itemCount = lastIncrease;\n }\n\n totalItems += itemCount;\n var option = {\n itemcount: itemCount,\n content: totalItems\n };\n\n // Make the first option active by default.\n if (i === 1) {\n option.active = true;\n }\n\n context.options.push(option);\n }\n\n return context;\n };\n\n /**\n * Build the context to render the paged content template with based on the number\n * of pages to show, items per page, and configuration option.\n *\n * By default the code will render a paging bar for the paging controls unless\n * otherwise specified in the provided config.\n *\n * @param {int|null} numberOfItems Total number of items.\n * @param {int|null|array} itemsPerPage How many items will be shown per page.\n * @param {object} config Configuration options provided by the client.\n * @return {object} The template context.\n */\n var buildTemplateContext = function(numberOfItems, itemsPerPage, config) {\n var context = getDefaultTemplateContext();\n\n if (config.hasOwnProperty('ignoreControlWhileLoading')) {\n context.ignorecontrolwhileloading = config.ignoreControlWhileLoading;\n }\n\n if (config.hasOwnProperty('controlPlacementBottom')) {\n context.controlplacementbottom = config.controlPlacementBottom;\n }\n\n if (config.hasOwnProperty('hideControlOnSinglePage')) {\n context.hidecontrolonsinglepage = config.hideControlOnSinglePage;\n }\n\n if (config.hasOwnProperty('ariaLabels')) {\n context.arialabels = config.ariaLabels;\n }\n\n if (config.hasOwnProperty('dropdown') && config.dropdown) {\n context.pagingdropdown = buildPagingDropdownTemplateContext(itemsPerPage, config);\n } else {\n context.pagingbar = buildPagingBarTemplateContext(numberOfItems, itemsPerPage);\n if (config.hasOwnProperty('showFirstLast') && config.showFirstLast) {\n context.pagingbar.first = true;\n context.pagingbar.last = true;\n }\n }\n\n return context;\n };\n\n /**\n * Create a paged content widget where the complete list of items is not loaded\n * up front but will instead be loaded by an ajax request (or similar).\n *\n * The client code must provide a callback function which loads and renders the\n * items for each page. See PagedContent.init for more details.\n *\n * The function will return a deferred that is resolved with a jQuery object\n * for the HTML content and a string for the JavaScript.\n *\n * The current list of configuration options available are:\n * dropdown {bool} True to render the page control as a dropdown (paging bar is default).\n * maxPages {Number} The maximum number of pages to show in the dropdown (only works with dropdown option)\n * ignoreControlWhileLoading {bool} Disable the pagination controls while loading a page (default to true)\n * controlPlacementBottom {bool} Render controls under paged content (default to false)\n *\n * @param {function} renderPagesContentCallback Callback for loading and rendering the items.\n * @param {object} config Configuration options provided by the client.\n * @return {promise} Resolved with jQuery HTML and string JS.\n */\n var create = function(renderPagesContentCallback, config) {\n return createWithTotalAndLimit(null, null, renderPagesContentCallback, config);\n };\n\n /**\n * Create a paged content widget where the complete list of items is not loaded\n * up front but will instead be loaded by an ajax request (or similar).\n *\n * The client code must provide a callback function which loads and renders the\n * items for each page. See PagedContent.init for more details.\n *\n * The function will return a deferred that is resolved with a jQuery object\n * for the HTML content and a string for the JavaScript.\n *\n * The current list of configuration options available are:\n * dropdown {bool} True to render the page control as a dropdown (paging bar is default).\n * maxPages {Number} The maximum number of pages to show in the dropdown (only works with dropdown option)\n * ignoreControlWhileLoading {bool} Disable the pagination controls while loading a page (default to true)\n * controlPlacementBottom {bool} Render controls under paged content (default to false)\n *\n * @param {int|array|null} itemsPerPage How many items will be shown per page.\n * @param {function} renderPagesContentCallback Callback for loading and rendering the items.\n * @param {object} config Configuration options provided by the client.\n * @return {promise} Resolved with jQuery HTML and string JS.\n */\n var createWithLimit = function(itemsPerPage, renderPagesContentCallback, config) {\n return createWithTotalAndLimit(null, itemsPerPage, renderPagesContentCallback, config);\n };\n\n /**\n * Create a paged content widget where the complete list of items is not loaded\n * up front but will instead be loaded by an ajax request (or similar).\n *\n * The client code must provide a callback function which loads and renders the\n * items for each page. See PagedContent.init for more details.\n *\n * The function will return a deferred that is resolved with a jQuery object\n * for the HTML content and a string for the JavaScript.\n *\n * The current list of configuration options available are:\n * dropdown {bool} True to render the page control as a dropdown (paging bar is default).\n * maxPages {Number} The maximum number of pages to show in the dropdown (only works with dropdown option)\n * ignoreControlWhileLoading {bool} Disable the pagination controls while loading a page (default to true)\n * controlPlacementBottom {bool} Render controls under paged content (default to false)\n *\n * @param {int|null} numberOfItems How many items are there in total.\n * @param {int|array|null} itemsPerPage How many items will be shown per page.\n * @param {function} renderPagesContentCallback Callback for loading and rendering the items.\n * @param {object} config Configuration options provided by the client.\n * @return {promise} Resolved with jQuery HTML and string JS.\n */\n var createWithTotalAndLimit = function(numberOfItems, itemsPerPage, renderPagesContentCallback, config) {\n config = config || {};\n\n var deferred = $.Deferred();\n var templateContext = buildTemplateContext(numberOfItems, itemsPerPage, config);\n\n Templates.render(TEMPLATES.PAGED_CONTENT, templateContext)\n .then(function(html, js) {\n html = $(html);\n var id = html.attr('id');\n\n // Set the id to the custom namespace provided\n if (config.hasOwnProperty('eventNamespace')) {\n id = config.eventNamespace;\n }\n\n var container = html;\n\n PagedContent.init(container, renderPagesContentCallback, id);\n\n registerEvents(id, config);\n\n deferred.resolve(html, js);\n return;\n })\n .fail(function(exception) {\n deferred.reject(exception);\n })\n .fail(Notification.exception);\n\n return deferred.promise();\n };\n\n /**\n * Create a paged content widget where the complete list of items is loaded\n * up front.\n *\n * The client code must provide a callback function which renders the\n * items for each page. The callback will be provided with an array where each\n * value in the array is a the list of items to render for the page.\n *\n * The function will return a deferred that is resolved with a jQuery object\n * for the HTML content and a string for the JavaScript.\n *\n * The current list of configuration options available are:\n * dropdown {bool} True to render the page control as a dropdown (paging bar is default).\n * maxPages {Number} The maximum number of pages to show in the dropdown (only works with dropdown option)\n * ignoreControlWhileLoading {bool} Disable the pagination controls while loading a page (default to true)\n * controlPlacementBottom {bool} Render controls under paged content (default to false)\n *\n * @param {array} contentItems The list of items to paginate.\n * @param {Number} itemsPerPage How many items will be shown per page.\n * @param {function} renderContentCallback Callback for rendering the items for the page.\n * @param {object} config Configuration options provided by the client.\n * @return {promise} Resolved with jQuery HTML and string JS.\n */\n var createFromStaticList = function(contentItems, itemsPerPage, renderContentCallback, config) {\n if (typeof config == 'undefined') {\n config = {};\n }\n\n var numberOfItems = contentItems.length;\n return createWithTotalAndLimit(numberOfItems, itemsPerPage, function(pagesData) {\n var contentToRender = [];\n pagesData.forEach(function(pageData) {\n var begin = pageData.offset;\n var end = pageData.limit ? begin + pageData.limit : numberOfItems;\n var items = contentItems.slice(begin, end);\n contentToRender.push(items);\n });\n\n return renderContentCallback(contentToRender);\n }, config);\n };\n\n /**\n * Reset the last page number for the generated paged-content\n * This is used when we need a way to update the last page number outside of the getters callback\n *\n * @param {String} id ID of the paged content container\n * @param {Int} lastPageNumber The last page number\n */\n var resetLastPageNumber = function(id, lastPageNumber) {\n PubSub.publish(id + PagedContentEvents.ALL_ITEMS_LOADED, lastPageNumber);\n };\n\n /**\n * Generate the callback handler for the page limit persistence functionality\n *\n * @param {String} persistentLimitKey\n * @return {callback}\n */\n var generateLimitHandler = function(persistentLimitKey) {\n var callback = function(limit) {\n var args = {\n preferences: [\n {\n type: persistentLimitKey,\n value: limit\n }\n ]\n };\n\n var request = {\n methodname: 'core_user_update_user_preferences',\n args: args\n };\n\n Ajax.call([request]);\n };\n\n return callback;\n };\n\n /**\n * Set up any events based on config key values\n *\n * @param {string} namespace The namespace for this component\n * @param {object} config Config options passed to the factory\n */\n var registerEvents = function(namespace, config) {\n if (config.hasOwnProperty('persistentLimitKey')) {\n PubSub.subscribe(namespace + PagedContentEvents.SET_ITEMS_PER_PAGE_LIMIT,\n generateLimitHandler(config.persistentLimitKey));\n }\n };\n\n return {\n create: create,\n createWithLimit: createWithLimit,\n createWithTotalAndLimit: createWithTotalAndLimit,\n createFromStaticList: createFromStaticList,\n // Backwards compatibility just in case anyone was using this.\n createFromAjax: createWithTotalAndLimit,\n resetLastPageNumber: resetLastPageNumber\n };\n});\n"],"file":"paged_content_factory.min.js"} \ No newline at end of file diff --git a/lib/amd/build/templates.min.js b/lib/amd/build/templates.min.js index 36b4c671519a5..23d541ec828ad 100644 --- a/lib/amd/build/templates.min.js +++ b/lib/amd/build/templates.min.js @@ -1,2 +1,2 @@ -define ("core/templates",["core/mustache","jquery","core/ajax","core/str","core/notification","core/url","core/config","core/localstorage","core/icon_system","core_filters/events","core/yui","core/log","core/truncate","core/user_date","core/pending"],function(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o){var p=0,q={},r={},s={},t={},u=[],v=!1,w=["js"],x=function(a){if(a){if("moodle"!==a&&"core"!==a){return a}}return"core"},y=function(a){if(a in r){return r[a]}if(a in q){r[a]=b.Deferred().resolve(q[a]).promise();return r[a]}if(0>=M.cfg.templaterev){return null}var c=h.get("core_template/"+M.cfg.templaterev+":"+a);if(c){q[a]=c;r[a]=b.Deferred().resolve(c).promise();return r[a]}return null},z=function(){if(!u.length){return}if(v){return}v=!0;var a=u.slice(),e=b.Deferred(),f=[],g=a.map(function(a){var c=x(a.component),i=a.name,j=a.searchKey,k=a.theme,l=a.deferred,m=null,n=y(j);if(n){m=n}else{f.push({methodname:"core_output_load_template_with_dependencies",args:{component:c,template:i,themename:k,lang:b("html").attr("lang").replace(/-/g,"_")}});var o=f.length-1;m=e.promise().then(function(a){g[j]=a[o].then(function(a){var b=null;a.templates.forEach(function(a){a.component=x(a.component);var d=[k,a.component,a.name].join("/");q[d]=a.value;if(0=}}$1<%={{ }}=%>").replace(/(\r\n|\r|\n)/g," ");return"\""+d+"\""};A.prototype.shortenTextHelper=function(a,b,c){var d=b.match(/(.*?),(.*)/),e=d[1].trim(),f=d[2].trim(),g=c(f,a);return m.truncate(g,{length:e,words:!0,ellipsis:"..."})};A.prototype.userDateHelper=function(a,b,c){var d=b.match(/(.*?),(.*)/),e=c(d[1].trim(),a),f=c(d[2].trim(),a),g=this.requiredDates.length;this.requiredDates.push({timestamp:e,format:f});return"[[_t_"+g+"]]"};A.prototype.addHelperFunction=function(a,b){return function(){return function(c,d){var e=w.reduce(function(a,c){if(b.hasOwnProperty(c)){a[c]=b[c]}return a},{});w.forEach(function(a){b[a]=function(){return""}});var f=a.apply(this,[b,c,d]);for(var g in e){b[g]=e[g]}return f}.bind(this)}.bind(this)};A.prototype.addHelpers=function(a,b){this.currentThemeName=b;this.requiredStrings=[];this.requiredJS=[];a.uniqid=p++;a.str=this.addHelperFunction(this.stringHelper,a);a.pix=this.addHelperFunction(this.pixHelper,a);a.js=this.addHelperFunction(this.jsHelper,a);a.quote=this.addHelperFunction(this.quoteHelper,a);a.shortentext=this.addHelperFunction(this.shortenTextHelper,a);a.userdate=this.addHelperFunction(this.userDateHelper,a);a.globals={config:g};a.currentTheme=b};A.prototype.getJS=function(){var a="";if(0").attr("type","text/javascript").html(a);b("head").append(c)}},C=function(a,c,d,e){var f=b(a);if(f.length){var g=b(c),h=null;if(e){h=new k.NodeList(f.children().get());h.destroy(!0);f.empty();f.append(g)}else{h=new k.NodeList(f.get());h.destroy(!0);f.replaceWith(g)}B(d);j.notifyFilterContentUpdated(g);return g.get()}return[]};A.prototype.scanForPartials=function(b){var c=a.parse(b),d=[],e=function(a,b){var c,d;for(c=0;c"==d[0]||"<"==d[0]){b.push(d[1])}if(4=M.cfg.templaterev){return null}var c=h.get("core_template/"+M.cfg.templaterev+":"+a);if(c){q[a]=c;r[a]=b.Deferred().resolve(c).promise();return r[a]}return null},z=function(){if(!u.length){return}if(v){return}v=!0;var a=u.slice(),e=b.Deferred(),f=[],g=a.map(function(a){var c=x(a.component),i=a.name,j=a.searchKey,k=a.theme,l=a.deferred,m=null,n=y(j);if(n){m=n}else{f.push({methodname:"core_output_load_template_with_dependencies",args:{component:c,template:i,themename:k,lang:b("html").attr("lang").replace(/-/g,"_")}});var o=f.length-1;m=e.promise().then(function(a){g[j]=a[o].then(function(a){var b=null;a.templates.forEach(function(a){a.component=x(a.component);var d=[k,a.component,a.name].join("/");q[d]=a.value;if(0=}}$1<%={{ }}=%>").replace(/(\r\n|\r|\n)/g," ");return"\""+d+"\""};A.prototype.shortenTextHelper=function(a,b,c){var d=b.match(/(.*?),(.*)/),e=d[1].trim(),f=d[2].trim(),g=c(f,a);return m.truncate(g,{length:e,words:!0,ellipsis:"..."})};A.prototype.userDateHelper=function(a,b,c){var d=b.match(/(.*?),(.*)/),e=c(d[1].trim(),a),f=c(d[2].trim(),a),g=this.requiredDates.length;this.requiredDates.push({timestamp:e,format:f});return"[[_t_"+g+"]]"};A.prototype.addHelperFunction=function(a,b){return function(){return function(c,d){var e=w.reduce(function(a,c){if(b.hasOwnProperty(c)){a[c]=b[c]}return a},{});w.forEach(function(a){b[a]=function(){return""}});var f=a.apply(this,[b,c,d]);for(var g in e){b[g]=e[g]}return f}.bind(this)}.bind(this)};A.prototype.addHelpers=function(a,b){this.currentThemeName=b;this.requiredStrings=[];this.requiredJS=[];a.uniqid=p++;a.str=this.addHelperFunction(this.stringHelper,a);a.cleanstr=this.addHelperFunction(this.cleanStringHelper,a);a.pix=this.addHelperFunction(this.pixHelper,a);a.js=this.addHelperFunction(this.jsHelper,a);a.quote=this.addHelperFunction(this.quoteHelper,a);a.shortentext=this.addHelperFunction(this.shortenTextHelper,a);a.userdate=this.addHelperFunction(this.userDateHelper,a);a.globals={config:g};a.currentTheme=b};A.prototype.getJS=function(){var a="";if(0").attr("type","text/javascript").html(a);b("head").append(c)}},C=function(a,c,d,e){var f=b(a);if(f.length){var g=b(c),h=null;if(e){h=new k.NodeList(f.children().get());h.destroy(!0);f.empty();f.append(g)}else{h=new k.NodeList(f.get());h.destroy(!0);f.replaceWith(g)}B(d);j.notifyFilterContentUpdated(g);return g.get()}return[]};A.prototype.scanForPartials=function(b){var c=a.parse(b),d=[],e=function(a,b){var c,d;for(c=0;c"==d[0]||"<"==d[0]){b.push(d[1])}if(4.\n\n/**\n * Template renderer for Moodle. Load and render Moodle templates with Mustache.\n *\n * @module core/templates\n * @copyright 2015 Damyon Wiese \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 2.9\n */\ndefine([\n 'core/mustache',\n 'jquery',\n 'core/ajax',\n 'core/str',\n 'core/notification',\n 'core/url',\n 'core/config',\n 'core/localstorage',\n 'core/icon_system',\n 'core_filters/events',\n 'core/yui',\n 'core/log',\n 'core/truncate',\n 'core/user_date',\n 'core/pending',\n],\nfunction(\n mustache,\n $,\n ajax,\n str,\n notification,\n coreurl,\n config,\n storage,\n IconSystem,\n filterEvents,\n Y,\n Log,\n Truncate,\n UserDate,\n Pending\n) {\n\n // Module variables.\n /** @var {Number} uniqInstances Count of times this constructor has been called. */\n var uniqInstances = 0;\n\n /** @var {String[]} templateCache - Cache of already loaded template strings */\n var templateCache = {};\n\n /** @var {Promise[]} templatePromises - Cache of already loaded template promises */\n var templatePromises = {};\n\n /** @var {Promise[]} cachePartialPromises - Cache of already loaded template partial promises */\n var cachePartialPromises = {};\n\n /** @var {Object} iconSystem - Object extending core/iconsystem */\n var iconSystem = {};\n\n /** @var {Object[]} loadTemplateBuffer - List of templates to be loaded */\n var loadTemplateBuffer = [];\n\n /** @var {Bool} isLoadingTemplates - Whether templates are currently being loaded */\n var isLoadingTemplates = false;\n\n /** @var {Array} disallowedNestedHelpers - List of helpers that can't be called within other helpers */\n var disallowedNestedHelpers = ['js'];\n\n /**\n * Normalise the provided component such that '', 'moodle', and 'core' are treated consistently.\n *\n * @param {String} component\n * @returns {String}\n */\n var getNormalisedComponent = function(component) {\n if (component) {\n if (component !== 'moodle' && component !== 'core') {\n return component;\n }\n }\n\n return 'core';\n };\n\n /**\n * Search the various caches for a template promise for the given search key.\n * The search key should be in the format //