From 165baf349b47b80173a16b22f5bca670ca291c01 Mon Sep 17 00:00:00 2001 From: "Eloy Lafuente (stronk7)" Date: Fri, 29 Mar 2019 20:44:16 +0100 Subject: [PATCH 01/10] MDL-65218 recycle bin: Course backups (cat bin) to use MODE_AUTOMATED Surely this should have been done as part of MDL-63263, but it wasn't. Better let's do it here so every recycle bin backup is using the very same mode. --- admin/tool/recyclebin/classes/category_bin.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/admin/tool/recyclebin/classes/category_bin.php b/admin/tool/recyclebin/classes/category_bin.php index b08b6a5f5bb..3c80f6b6906 100644 --- a/admin/tool/recyclebin/classes/category_bin.php +++ b/admin/tool/recyclebin/classes/category_bin.php @@ -115,7 +115,7 @@ class category_bin extends base_bin { $course->id, \backup::FORMAT_MOODLE, \backup::INTERACTIVE_NO, - \backup::MODE_GENERAL, + \backup::MODE_AUTOMATED, $user->id ); $controller->execute_plan(); @@ -233,7 +233,7 @@ class category_bin extends base_bin { $tempdir, $course->id, \backup::INTERACTIVE_NO, - \backup::MODE_GENERAL, + \backup::MODE_AUTOMATED, $user->id, \backup::TARGET_NEW_COURSE ); From cdcc3ab5da4cdfa6701cd8136dff5521db7f964f Mon Sep 17 00:00:00 2001 From: "Eloy Lafuente (stronk7)" Date: Fri, 29 Mar 2019 20:49:52 +0100 Subject: [PATCH 02/10] MDL-65218 recycle bin: immunize recycle bin from backup_auto_storage As far as recycle bin is using MODE_AUTOMATED, it observes the backup_auto_storage setting (storing backups @ real location. For recycle bin we want to ensure that backup files are always stored in Moodle file area. In order to achieve that, we hack the setting here via $CFG->forced_plugin_settings, so it won't interfere other operations. See MDL-65218 for more information. This hack will be removed once recycle bin switches to use its own backup mode, with own preferences and 100% appart from MODLE_AUTOMATED. --- admin/tool/recyclebin/classes/category_bin.php | 14 ++++++++++++++ admin/tool/recyclebin/classes/course_bin.php | 16 +++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/admin/tool/recyclebin/classes/category_bin.php b/admin/tool/recyclebin/classes/category_bin.php index 3c80f6b6906..6477c4d25c2 100644 --- a/admin/tool/recyclebin/classes/category_bin.php +++ b/admin/tool/recyclebin/classes/category_bin.php @@ -108,6 +108,16 @@ class category_bin extends base_bin { require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php'); + // As far as recycle bin is using MODE_AUTOMATED, it observes the backup_auto_storage + // setting (storing backups @ real location. For recycle bin we want to ensure that + // backup files are always stored in Moodle file area. In order to achieve that, we + // hack the setting here via $CFG->forced_plugin_settings, so it won't interfere other + // operations. See MDL-65218 for more information. + // This hack will be removed once recycle bin switches to use its own backup mode, with + // own preferences and 100% appart from MODLE_AUTOMATED. + // TODO: Remove this as part of MDL-65228. + $CFG->forced_plugin_settings['backup'] = ['backup_auto_storage' => 0]; + // Backup the course. $user = get_admin(); $controller = new \backup_controller( @@ -120,6 +130,10 @@ class category_bin extends base_bin { ); $controller->execute_plan(); + // We don't need the forced setting anymore, hence unsetting it. + // TODO: Remove this as part of MDL-65228. + unset($CFG->forced_plugin_settings['backup']); + // Grab the result. $result = $controller->get_results(); if (!isset($result['backup_destination'])) { diff --git a/admin/tool/recyclebin/classes/course_bin.php b/admin/tool/recyclebin/classes/course_bin.php index d58d1a77f7c..deec65c1cb2 100644 --- a/admin/tool/recyclebin/classes/course_bin.php +++ b/admin/tool/recyclebin/classes/course_bin.php @@ -112,6 +112,16 @@ class course_bin extends base_bin { return; } + // As far as recycle bin is using MODE_AUTOMATED, it observes the backup_auto_storage + // setting (storing backups @ real location. For recycle bin we want to ensure that + // backup files are always stored in Moodle file area. In order to achieve that, we + // hack the setting here via $CFG->forced_plugin_settings, so it won't interfere other + // operations. See MDL-65218 for more information. + // This hack will be removed once recycle bin switches to use its own backup mode, with + // own preferences and 100% appart from MODLE_AUTOMATED. + // TODO: Remove this as part of MDL-65228. + $CFG->forced_plugin_settings['backup'] = ['backup_auto_storage' => 0]; + // Backup the activity. $user = get_admin(); $controller = new \backup_controller( @@ -124,6 +134,10 @@ class course_bin extends base_bin { ); $controller->execute_plan(); + // We don't need the forced setting anymore, hence unsetting it. + // TODO: Remove this as part of MDL-65228. + unset($CFG->forced_plugin_settings['backup']); + // Grab the result. $result = $controller->get_results(); if (!isset($result['backup_destination'])) { @@ -344,4 +358,4 @@ class course_bin extends base_bin { $context = \context_course::instance($this->_courseid); return has_capability('tool/recyclebin:deleteitems', $context); } -} \ No newline at end of file +} From 49681cd03dae187d2d7e82da2362cf6997643639 Mon Sep 17 00:00:00 2001 From: "Eloy Lafuente (stronk7)" Date: Fri, 29 Mar 2019 21:26:49 +0100 Subject: [PATCH 03/10] MDL-65218 recycle bin: cover immunization via settings provider Both for course and category recycle bins --- .../recyclebin/tests/category_bin_test.php | 112 ++++++++++++++++++ .../tool/recyclebin/tests/course_bin_test.php | 54 ++++++++- 2 files changed, 163 insertions(+), 3 deletions(-) diff --git a/admin/tool/recyclebin/tests/category_bin_test.php b/admin/tool/recyclebin/tests/category_bin_test.php index bada3d63853..0e24c311ca1 100644 --- a/admin/tool/recyclebin/tests/category_bin_test.php +++ b/admin/tool/recyclebin/tests/category_bin_test.php @@ -176,4 +176,116 @@ class tool_recyclebin_category_bin_tests extends advanced_testcase { $course = reset($courses); $this->assertEquals('Test course 2', $course->fullname); } + + /** + * Provider for test_course_restore_with_userdata() and test_course_restore_without_userdata() + * + * Used to verify that recycle bin is immune to various settings. Provides plugin, name, value for + * direct usage with set_config() + */ + public function recycle_bin_settings_provider() { + return [ + 'backup/backup_auto_storage moodle' => [[ + (object)['plugin' => 'backup', 'name' => 'backup_auto_storage', 'value' => 0], + ]], + + 'backup/backup_auto_storage external' => [[ + (object)['plugin' => 'backup', 'name' => 'backup_auto_storage', 'value' => 1], + (object)['plugin' => 'backup', 'name' => 'backup_auto_destination', 'value' => true], + ]], + + 'backup/backup_auto_storage mixed' => [[ + (object)['plugin' => 'backup', 'name' => 'backup_auto_storage', 'value' => 2], + (object)['plugin' => 'backup', 'name' => 'backup_auto_destination', 'value' => true], + ]], + ]; + } + + /** + * Tests that user data is restored when course is restored. + * + * @dataProvider recycle_bin_settings_provider + * @param array $settings array of plugin, name, value stdClass(). + */ + public function test_course_restore_with_userdata($settings) { + global $DB; + + // Force configuration changes from provider. + foreach ($settings as $setting) { + // Need to create a directory for backup_auto_destination. + if ($setting->plugin === 'backup' && $setting->name === 'backup_auto_destination' && $setting->value === true) { + $setting->value = make_request_directory(); + } + set_config($setting->name, $setting->value, $setting->plugin); + } + + // We want user data to be included for this test. + set_config('backup_auto_users', true, 'backup'); + + $student = $this->getDataGenerator()->create_and_enrol($this->course, 'student'); + + // Delete course. + delete_course($this->course, false); + $this->assertFalse($DB->record_exists('course', ['id' => $this->course->id])); + + // Verify there is now a backup @ cat recycle bin file area. + $recyclebin = new \tool_recyclebin\category_bin($this->course->category); + $this->assertEquals(1, count($recyclebin->get_items())); + + // Restore the recycle bin item. + $recyclebin->restore_item(current($recyclebin->get_items())); + + // Get the new course. + $newcourse = $DB->get_record('course', ['shortname' => $this->course->shortname], '*', MUST_EXIST); + + // Check that it was removed from the recycle bin. + $this->assertEquals(0, count($recyclebin->get_items())); + + // Verify that student DOES continue enrolled. + $this->assertTrue(is_enrolled(context_course::instance($newcourse->id), $student->id)); + } + + /** + * Tests that user data is not restored when course is restored. + * + * @dataProvider recycle_bin_settings_provider + * @param array $settings array of plugin, name, value stdClass(). + */ + public function test_course_restore_without_userdata($settings) { + global $DB; + + // Force configuration changes from provider. + foreach ($settings as $setting) { + // Need to create a directory for backup_auto_destination. + if ($setting->plugin === 'backup' && $setting->name === 'backup_auto_destination' && $setting->value === true) { + $setting->value = make_request_directory(); + } + set_config($setting->name, $setting->value, $setting->plugin); + } + + // We want user data to be included for this test. + set_config('backup_auto_users', false, 'backup'); + + $student = $this->getDataGenerator()->create_and_enrol($this->course, 'student'); + + // Delete course. + delete_course($this->course, false); + $this->assertFalse($DB->record_exists('course', ['id' => $this->course->id])); + + // Verify there is now a backup @ cat recycle bin file area. + $recyclebin = new \tool_recyclebin\category_bin($this->course->category); + $this->assertEquals(1, count($recyclebin->get_items())); + + // Restore the recycle bin item. + $recyclebin->restore_item(current($recyclebin->get_items())); + + // Get the new course. + $newcourse = $DB->get_record('course', ['shortname' => $this->course->shortname], '*', MUST_EXIST); + + // Check that it was removed from the recycle bin. + $this->assertEquals(0, count($recyclebin->get_items())); + + // Verify that student DOES NOT continue enrolled. + $this->assertFalse(is_enrolled(context_course::instance($newcourse->id), $student->id)); + } } diff --git a/admin/tool/recyclebin/tests/course_bin_test.php b/admin/tool/recyclebin/tests/course_bin_test.php index 9d993260c36..44f9db6cc06 100644 --- a/admin/tool/recyclebin/tests/course_bin_test.php +++ b/admin/tool/recyclebin/tests/course_bin_test.php @@ -175,9 +175,45 @@ class tool_recyclebin_course_bin_tests extends advanced_testcase { } /** - * Tests that user data is restored when module is restored. + * Provider for test_coursemodule_restore_with_userdata() and test_coursemodule_restore_without_userdata() + * + * Used to verify that recycle bin is immune to various settings. Provides plugin, name, value for + * direct usage with set_config() */ - public function test_coursemodule_restore_with_userdata() { + public function recycle_bin_settings_provider() { + return [ + 'backup/backup_auto_storage moodle' => [[ + (object)['plugin' => 'backup', 'name' => 'backup_auto_storage', 'value' => 0], + ]], + + 'backup/backup_auto_storage external' => [[ + (object)['plugin' => 'backup', 'name' => 'backup_auto_storage', 'value' => 1], + (object)['plugin' => 'backup', 'name' => 'backup_auto_destination', 'value' => true], + ]], + + 'backup/backup_auto_storage mixed' => [[ + (object)['plugin' => 'backup', 'name' => 'backup_auto_storage', 'value' => 2], + (object)['plugin' => 'backup', 'name' => 'backup_auto_destination', 'value' => true], + ]], + ]; + } + + /** + * Tests that user data is restored when module is restored. + * + * @dataProvider recycle_bin_settings_provider + * @param array $settings array of plugin, name, value stdClass(). + */ + public function test_coursemodule_restore_with_userdata($settings) { + // Force configuration changes from provider. + foreach ($settings as $setting) { + // Need to create a directory for backup_auto_destination. + if ($setting->plugin === 'backup' && $setting->name === 'backup_auto_destination' && $setting->value === true) { + $setting->value = make_request_directory(); + } + set_config($setting->name, $setting->value, $setting->plugin); + } + $student = $this->getDataGenerator()->create_and_enrol($this->course, 'student'); $this->setUser($student); @@ -211,8 +247,20 @@ class tool_recyclebin_course_bin_tests extends advanced_testcase { /** * Tests that user data is not restored when module is restored. + * + * @dataProvider recycle_bin_settings_provider + * @param array $settings array of plugin, name, value stdClass(). */ - public function test_coursemodule_restore_without_userdata() { + public function test_coursemodule_restore_without_userdata($settings) { + // Force configuration changes from provider. + foreach ($settings as $setting) { + // Need to create a directory for backup_auto_destination. + if ($setting->plugin === 'backup' && $setting->name === 'backup_auto_destination' && $setting->value === true) { + $setting->value = make_request_directory(); + } + set_config($setting->name, $setting->value, $setting->plugin); + } + $student = $this->getDataGenerator()->create_and_enrol($this->course, 'student'); $this->setUser($student); From 9223d556fb7dfb582b5f855982b3aad31f3d7081 Mon Sep 17 00:00:00 2001 From: Damyon Wiese Date: Mon, 11 Feb 2019 09:34:18 +0800 Subject: [PATCH 04/10] MDL-64819 output: Activity menu icons Remove duplciate alt text from activity menu icons and the links to add new sections. --- course/amd/build/actions.min.js | 2 +- course/amd/src/actions.js | 17 ++++------------- course/format/renderer.php | 29 ++++++++++++++--------------- course/format/topics/renderer.php | 10 ++++------ course/lib.php | 22 +++++++++++----------- course/tests/behat/behat_course.php | 10 +++++----- 6 files changed, 39 insertions(+), 51 deletions(-) diff --git a/course/amd/build/actions.min.js b/course/amd/build/actions.min.js index 2875e390ae2..2227311b6c0 100644 --- a/course/amd/build/actions.min.js +++ b/course/amd/build/actions.min.js @@ -1 +1 @@ -define(["jquery","core/ajax","core/templates","core/notification","core/str","core/url","core/yui","core/modal_factory","core/modal_events","core/key_codes"],function(a,b,c,d,e,f,g,h,i,j){var k={EDITINPROGRESS:"editinprogress",SECTIONDRAGGABLE:"sectiondraggable",EDITINGMOVE:"editing_move"},l={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();a&&(l.SECTIONLI=a)});var m=function(a){var b;return g.use("moodle-course-util",function(c){b=c.Moodle.core_course.util.cm.getId(c.Node(a.get(0)))}),b},n=function(a){var b;return g.use("moodle-course-util",function(c){b=c.Moodle.core_course.util.cm.getName(c.Node(a.get(0)))}),b},o=function(a){a.addClass(k.EDITINPROGRESS);var b=a.find(l.ACTIONAREA).get(0);if(b){var c=M.util.add_spinner(g,g.Node(b));return c.show(),c}return null},p=function(a){a.addClass(k.EDITINPROGRESS);var b=a.find(l.SECTIONACTIONMENU).get(0);if(b){var c=M.util.add_spinner(g,g.Node(b));return c.show(),c}return null},q=function(a){var b=M.util.add_lightbox(g,g.Node(a.get(0)));return b.show(),b},r=function(a,b,c){window.setTimeout(function(){a.removeClass(k.EDITINPROGRESS),b&&b.hide()},c)},s=function(a,b){a&&window.setTimeout(function(){a.hide()},b)},t=function(a,b){if(g.use("moodle-course-coursebase",function(){M.course.coursebase.invoke_function("setup_for_resource","#"+a)}),M.core.actionmenu&&M.core.actionmenu.newDOMNode&&M.core.actionmenu.newDOMNode(g.one("#"+a)),b){var c=g.one("#"+a+" "+l.MENU).one(l.TOGGLE);c&&c.simulate&&c.simulate("click")}},u=function(b,c){var d=a("#"+b),e="[data-action="+c+"]";"groupsseparate"!==c&&"groupsvisible"!==c&&"groupsnone"!==c||(e="[data-action=groupsseparate],[data-action=groupsvisible],[data-action=groupsnone]"),d.find(e).is(":visible")?d.find(e).focus():d.find(l.MENU).find(l.TOGGLE).focus()},v=function(b){var c=a("a:visible"),d=!1,e=null;return c.each(function(){if(a.contains(b[0],this))d=!0;else if(d)return e=this,!1}),e},w=function(c,e,f){var g,h=f.attr("data-keepopen"),i=f.attr("data-action"),j=o(c),k=b.call([{methodname:"core_course_edit_module",args:{id:e,action:i,sectionreturn:f.attr("data-sectionreturn")?f.attr("data-sectionreturn"):0}}],!0);"duplicate"===i&&(g=q(f.closest(l.SECTIONLI))),a.when.apply(a,k).done(function(b){var d=v(c);c.replaceWith(b),a("
"+b+"
").find(l.ACTIVITYLI).each(function(b){t(a(this).attr("id"),h),0===b&&(u(a(this).attr("id"),i),d=null)}),d&&d.focus(),r(c,j,400),s(g,400),c.trigger(a.Event("coursemoduleedited",{ajaxreturn:b,action:i}))}).fail(function(b){r(c,j),s(g);var e=a.Event("coursemoduleeditfailed",{exception:b,action:i});c.trigger(e),e.isDefaultPrevented()||d.exception(b)})},x=function(c,d,e){var f=o(c),g=b.call([{methodname:"core_course_get_module",args:{id:d,sectionreturn:e}}],!0);a.when.apply(a,g).done(function(a){r(c,f,400),C(a)}).fail(function(){r(c,f)})},y=function(a,b){var c=a.attr("class").match(/modtype_([^\s]*)/)[1],f=n(a);e.get_string("pluginname",c).done(function(a){var c={type:a,name:f};e.get_strings([{key:"confirm"},{key:null===f?"deletechecktype":"deletechecktypename",param:c},{key:"yes"},{key:"no"}]).done(function(a){d.confirm(a[0],a[1],a[2],a[3],b)})})},z=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)})},A=function(a,b,f,g,h,i,j){var k=[{key:f,component:g}];return h&&k.push({key:h,component:i}),e.get_strings(k).then(function(d){a.find("span.menu-action-text").html(d[0]),a.attr("title",d[0]);var e="";return h&&(e=d[1],a.attr("title",e)),c.renderPix(b,"core",e)}).then(function(b){a.find(".icon").replaceWith(b),a.attr("data-action",j)})["catch"](d.exception)},B=function(b,c,d,e){var f=c.attr("data-action");if("hide"===f||"show"===f){if("hide"===f?(b.addClass("hidden"),A(c,"i/show","showfromothers","format_"+e,null,null,"show")):(b.removeClass("hidden"),A(c,"i/hide","hidefromothers","format_"+e,null,null,"hide")),void 0!==d.modules)for(var g in d.modules)C(d.modules[g]);void 0!==d.section_availability&&b.find(".section_availability").first().replaceWith(d.section_availability)}else if("setmarker"===f){var h=a(l.SECTIONLI+".current"),i=h.find(l.SECTIONACTIONMENU+" a[data-action=removemarker]");h.removeClass("current"),A(i,"i/marker","highlight","core","markthistopic","core","setmarker"),b.addClass("current"),A(c,"i/marked","highlightoff","core","markedthistopic","core","removemarker")}else"removemarker"===f&&(b.removeClass("current"),A(c,"i/marker","highlight","core","markthistopic","core","setmarker"))},C=function(b){a("
"+b+"
").find(l.ACTIVITYLI).each(function(){var c=a(this).attr("id");a(l.ACTIVITYLI+"#"+c).replaceWith(b),t(c,!1)})},D=function(c,e,f,g){var h=f.attr("data-action"),i=f.attr("data-sectionreturn")?f.attr("data-sectionreturn"):0,j=p(c),k=b.call([{methodname:"core_course_edit_section",args:{id:e,action:h,sectionreturn:i}}],!0),m=q(c);a.when.apply(a,k).done(function(b){var d=a.parseJSON(b);r(c,j),s(m),c.find(l.SECTIONACTIONMENU).find(l.TOGGLE).focus();var e=a.Event("coursesectionedited",{ajaxreturn:d,action:h});c.trigger(e),e.isDefaultPrevented()||B(c,f,d,g)}).fail(function(b){r(c,j),s(m);var e=a.Event("coursesectioneditfailed",{exception:b,action:h});c.trigger(e),e.isDefaultPrevented()||d.exception(b)})};return g.use("moodle-course-coursebase",function(){M.course.coursebase.register_module({set_visibility_resource_ui:function(b){var c=a(b.element.getDOMNode()),d=m(c);if(d){var e=c.find("."+k.EDITINGMOVE).attr("data-sectionreturn");x(c,d,e)}}})}),{initCoursePage:function(b){a("body").on("click keypress",l.ACTIVITYLI+" "+l.ACTIVITYACTION+"[data-action]",function(b){if("keypress"!==b.type||13===b.keyCode){var c=a(this),d=c.closest(l.ACTIVITYLI),e=c.attr("data-action"),f=m(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}f&&(b.preventDefault(),"delete"===e?y(d,function(){w(d,f,c)}):w(d,f,c))}}),a("body").on("click keypress",l.SECTIONLI+" "+l.SECTIONACTIONMENU+"[data-sectionid] a[data-action]",function(c){if("keypress"!==c.type||13===c.keyCode){var d=a(this),e=d.closest(l.SECTIONLI),f=d.closest(l.SECTIONACTIONMENU).attr("data-sectionid");c.preventDefault(),d.attr("data-confirm")?z(d.attr("data-confirm"),function(){D(e,f,d,b)}):D(e,f,d,b)}}),e.get_string("numberweeks").done(function(b){var c=a(l.ADDSECTIONS),d=c.attr("data-add-sections"),e=c.attr("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(){""+parseInt(e.val())===e.val()&&parseInt(e.val())>=1&&(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){a.keyCode===j.enter&&f()})}),b.getRoot().on(i.save,function(a){a.preventDefault(),f()})})})},replaceSectionActionItem:function(a,b,c,d,e,f,g,h){var i=a.find(l.SECTIONACTIONMENU+" "+b);A(i,c,d,e,f,g,h)}}}); \ No newline at end of file +define(["jquery","core/ajax","core/templates","core/notification","core/str","core/url","core/yui","core/modal_factory","core/modal_events","core/key_codes"],function(a,b,c,d,e,f,g,h,i,j){var k={EDITINPROGRESS:"editinprogress",SECTIONDRAGGABLE:"sectiondraggable",EDITINGMOVE:"editing_move"},l={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();a&&(l.SECTIONLI=a)});var m=function(a){var b;return g.use("moodle-course-util",function(c){b=c.Moodle.core_course.util.cm.getId(c.Node(a.get(0)))}),b},n=function(a){var b;return g.use("moodle-course-util",function(c){b=c.Moodle.core_course.util.cm.getName(c.Node(a.get(0)))}),b},o=function(a){a.addClass(k.EDITINPROGRESS);var b=a.find(l.ACTIONAREA).get(0);if(b){var c=M.util.add_spinner(g,g.Node(b));return c.show(),c}return null},p=function(a){a.addClass(k.EDITINPROGRESS);var b=a.find(l.SECTIONACTIONMENU).get(0);if(b){var c=M.util.add_spinner(g,g.Node(b));return c.show(),c}return null},q=function(a){var b=M.util.add_lightbox(g,g.Node(a.get(0)));return b.show(),b},r=function(a,b,c){window.setTimeout(function(){a.removeClass(k.EDITINPROGRESS),b&&b.hide()},c)},s=function(a,b){a&&window.setTimeout(function(){a.hide()},b)},t=function(a,b){if(g.use("moodle-course-coursebase",function(){M.course.coursebase.invoke_function("setup_for_resource","#"+a)}),M.core.actionmenu&&M.core.actionmenu.newDOMNode&&M.core.actionmenu.newDOMNode(g.one("#"+a)),b){var c=g.one("#"+a+" "+l.MENU).one(l.TOGGLE);c&&c.simulate&&c.simulate("click")}},u=function(b,c){var d=a("#"+b),e="[data-action="+c+"]";"groupsseparate"!==c&&"groupsvisible"!==c&&"groupsnone"!==c||(e="[data-action=groupsseparate],[data-action=groupsvisible],[data-action=groupsnone]"),d.find(e).is(":visible")?d.find(e).focus():d.find(l.MENU).find(l.TOGGLE).focus()},v=function(b){var c=a("a:visible"),d=!1,e=null;return c.each(function(){if(a.contains(b[0],this))d=!0;else if(d)return e=this,!1}),e},w=function(c,e,f){var g,h=f.attr("data-keepopen"),i=f.attr("data-action"),j=o(c),k=b.call([{methodname:"core_course_edit_module",args:{id:e,action:i,sectionreturn:f.attr("data-sectionreturn")?f.attr("data-sectionreturn"):0}}],!0);"duplicate"===i&&(g=q(f.closest(l.SECTIONLI))),a.when.apply(a,k).done(function(b){var d=v(c);c.replaceWith(b),a("
"+b+"
").find(l.ACTIVITYLI).each(function(b){t(a(this).attr("id"),h),0===b&&(u(a(this).attr("id"),i),d=null)}),d&&d.focus(),r(c,j,400),s(g,400),c.trigger(a.Event("coursemoduleedited",{ajaxreturn:b,action:i}))}).fail(function(b){r(c,j),s(g);var e=a.Event("coursemoduleeditfailed",{exception:b,action:i});c.trigger(e),e.isDefaultPrevented()||d.exception(b)})},x=function(c,d,e){var f=o(c),g=b.call([{methodname:"core_course_get_module",args:{id:d,sectionreturn:e}}],!0);a.when.apply(a,g).done(function(a){r(c,f,400),C(a)}).fail(function(){r(c,f)})},y=function(a,b){var c=a.attr("class").match(/modtype_([^\s]*)/)[1],f=n(a);e.get_string("pluginname",c).done(function(a){var c={type:a,name:f};e.get_strings([{key:"confirm"},{key:null===f?"deletechecktype":"deletechecktypename",param:c},{key:"yes"},{key:"no"}]).done(function(a){d.confirm(a[0],a[1],a[2],a[3],b)})})},z=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)})},A=function(a,b,f,g,h,i,j){var k=[{key:f,component:g}];return e.get_strings(k).then(function(d){return a.find("span.menu-action-text").html(d[0]),c.renderPix(b,"core")}).then(function(b){a.find(".icon").replaceWith(b),a.attr("data-action",j)})["catch"](d.exception)},B=function(b,c,d,e){var f=c.attr("data-action");if("hide"===f||"show"===f){if("hide"===f?(b.addClass("hidden"),A(c,"i/show","showfromothers","format_"+e,null,null,"show")):(b.removeClass("hidden"),A(c,"i/hide","hidefromothers","format_"+e,null,null,"hide")),void 0!==d.modules)for(var g in d.modules)C(d.modules[g]);void 0!==d.section_availability&&b.find(".section_availability").first().replaceWith(d.section_availability)}else if("setmarker"===f){var h=a(l.SECTIONLI+".current"),i=h.find(l.SECTIONACTIONMENU+" a[data-action=removemarker]");h.removeClass("current"),A(i,"i/marker","highlight","core","markthistopic","core","setmarker"),b.addClass("current"),A(c,"i/marked","highlightoff","core","markedthistopic","core","removemarker")}else"removemarker"===f&&(b.removeClass("current"),A(c,"i/marker","highlight","core","markthistopic","core","setmarker"))},C=function(b){a("
"+b+"
").find(l.ACTIVITYLI).each(function(){var c=a(this).attr("id");a(l.ACTIVITYLI+"#"+c).replaceWith(b),t(c,!1)})},D=function(c,e,f,g){var h=f.attr("data-action"),i=f.attr("data-sectionreturn")?f.attr("data-sectionreturn"):0,j=p(c),k=b.call([{methodname:"core_course_edit_section",args:{id:e,action:h,sectionreturn:i}}],!0),m=q(c);a.when.apply(a,k).done(function(b){var d=a.parseJSON(b);r(c,j),s(m),c.find(l.SECTIONACTIONMENU).find(l.TOGGLE).focus();var e=a.Event("coursesectionedited",{ajaxreturn:d,action:h});c.trigger(e),e.isDefaultPrevented()||B(c,f,d,g)}).fail(function(b){r(c,j),s(m);var e=a.Event("coursesectioneditfailed",{exception:b,action:h});c.trigger(e),e.isDefaultPrevented()||d.exception(b)})};return g.use("moodle-course-coursebase",function(){M.course.coursebase.register_module({set_visibility_resource_ui:function(b){var c=a(b.element.getDOMNode()),d=m(c);if(d){var e=c.find("."+k.EDITINGMOVE).attr("data-sectionreturn");x(c,d,e)}}})}),{initCoursePage:function(b){a("body").on("click keypress",l.ACTIVITYLI+" "+l.ACTIVITYACTION+"[data-action]",function(b){if("keypress"!==b.type||13===b.keyCode){var c=a(this),d=c.closest(l.ACTIVITYLI),e=c.attr("data-action"),f=m(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}f&&(b.preventDefault(),"delete"===e?y(d,function(){w(d,f,c)}):w(d,f,c))}}),a("body").on("click keypress",l.SECTIONLI+" "+l.SECTIONACTIONMENU+"[data-sectionid] a[data-action]",function(c){if("keypress"!==c.type||13===c.keyCode){var d=a(this),e=d.closest(l.SECTIONLI),f=d.closest(l.SECTIONACTIONMENU).attr("data-sectionid");c.preventDefault(),d.attr("data-confirm")?z(d.attr("data-confirm"),function(){D(e,f,d,b)}):D(e,f,d,b)}}),e.get_string("numberweeks").done(function(b){var c=a(l.ADDSECTIONS),d=c.attr("data-add-sections"),e=c.attr("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(){""+parseInt(e.val())===e.val()&&parseInt(e.val())>=1&&(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){a.keyCode===j.enter&&f()})}),b.getRoot().on(i.save,function(a){a.preventDefault(),f()})})})},replaceSectionActionItem:function(a,b,c,d,e,f,g,h){var i=a.find(l.SECTIONACTIONMENU+" "+b);A(i,c,d,e,f,g,h)}}}); \ No newline at end of file diff --git a/course/amd/src/actions.js b/course/amd/src/actions.js index 76d42320d6d..b9910bba41c 100644 --- a/course/amd/src/actions.js +++ b/course/amd/src/actions.js @@ -352,30 +352,21 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str' * @param {String} image new image name ("i/show", "i/hide", etc.) * @param {String} stringname new string for the action menu item * @param {String} stringcomponent - * @param {String} titlestr string for "title" attribute (if different from stringname) - * @param {String} titlecomponent + * @param {String} titlestr not used + * @param {String} titlecomponent not used * @param {String} newaction new value for data-action attribute of the link * @return {Promise} promise which is resolved when the replacement has completed */ var replaceActionItem = function(actionitem, image, stringname, stringcomponent, titlestr, titlecomponent, newaction) { - var stringRequests = [{key: stringname, component: stringcomponent}]; - if (titlestr) { - stringRequests.push({key: titlestr, component: titlecomponent}); - } + // Do not provide an icon with duplicate, different text to the menu item. return str.get_strings(stringRequests).then(function(strings) { actionitem.find('span.menu-action-text').html(strings[0]); - actionitem.attr('title', strings[0]); - var title = ''; - if (titlestr) { - title = strings[1]; - actionitem.attr('title', title); - } - return templates.renderPix(image, 'core', title); + return templates.renderPix(image, 'core'); }).then(function(pixhtml) { actionitem.find('.icon').replaceWith(pixhtml); actionitem.attr('data-action', newaction); diff --git a/course/format/renderer.php b/course/format/renderer.php index 45ff162e6b4..2f94041f734 100644 --- a/course/format/renderer.php +++ b/course/format/renderer.php @@ -117,10 +117,9 @@ abstract class format_section_renderer_base extends plugin_renderer_base { $name = empty($value['name']) ? '' : $value['name']; $attr = empty($value['attr']) ? array() : $value['attr']; $class = empty($value['pixattr']['class']) ? '' : $value['pixattr']['class']; - $alt = empty($value['pixattr']['alt']) ? '' : $value['pixattr']['alt']; $al = new action_menu_link_secondary( new moodle_url($url), - new pix_icon($icon, $alt, null, array('class' => "smallicon " . $class)), + new pix_icon($icon, '', null, array('class' => "smallicon " . $class)), $name, $attr ); @@ -328,8 +327,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base { 'url' => new moodle_url('/course/editsection.php', array('id' => $section->id, 'sr' => $sectionreturn)), 'icon' => 'i/settings', 'name' => $streditsection, - 'pixattr' => array('class' => '', 'alt' => $streditsection), - 'attr' => array('class' => 'icon edit', 'title' => $streditsection)); + 'pixattr' => array('class' => ''), + 'attr' => array('class' => 'icon edit')); } if ($section->section) { @@ -343,8 +342,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base { 'url' => $url, 'icon' => 'i/hide', 'name' => $strhidefromothers, - 'pixattr' => array('class' => '', 'alt' => $strhidefromothers), - 'attr' => array('class' => 'icon editing_showhide', 'title' => $strhidefromothers, + 'pixattr' => array('class' => ''), + 'attr' => array('class' => 'icon editing_showhide', 'data-sectionreturn' => $sectionreturn, 'data-action' => 'hide')); } else { $strshowfromothers = get_string('showfromothers', 'format_'.$course->format); @@ -353,8 +352,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base { 'url' => $url, 'icon' => 'i/show', 'name' => $strshowfromothers, - 'pixattr' => array('class' => '', 'alt' => $strshowfromothers), - 'attr' => array('class' => 'icon editing_showhide', 'title' => $strshowfromothers, + 'pixattr' => array('class' => ''), + 'attr' => array('class' => 'icon editing_showhide', 'data-sectionreturn' => $sectionreturn, 'data-action' => 'show')); } } @@ -370,8 +369,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base { 'url' => $url, 'icon' => 'i/up', 'name' => $strmoveup, - 'pixattr' => array('class' => '', 'alt' => $strmoveup), - 'attr' => array('class' => 'icon moveup', 'title' => $strmoveup)); + 'pixattr' => array('class' => ''), + 'attr' => array('class' => 'icon moveup')); } $url = clone($baseurl); @@ -383,8 +382,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base { 'url' => $url, 'icon' => 'i/down', 'name' => $strmovedown, - 'pixattr' => array('class' => '', 'alt' => $strmovedown), - 'attr' => array('class' => 'icon movedown', 'title' => $strmovedown)); + 'pixattr' => array('class' => ''), + 'attr' => array('class' => 'icon movedown')); } } } @@ -405,8 +404,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base { 'url' => $url, 'icon' => 'i/delete', 'name' => $strdelete, - 'pixattr' => array('class' => '', 'alt' => $strdelete), - 'attr' => array('class' => 'icon editing_delete', 'title' => $strdelete)); + 'pixattr' => array('class' => ''), + 'attr' => array('class' => 'icon editing_delete')); } } @@ -1009,7 +1008,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base { if ($sectionreturn !== null) { $url->param('sectionreturn', $sectionreturn); } - $icon = $this->output->pix_icon('t/add', $straddsections); + $icon = $this->output->pix_icon('t/add', ''); $newsections = $maxsections - $lastsection; echo html_writer::link($url, $icon . $straddsections, array('class' => 'add-sections', 'data-add-sections' => $straddsections, 'new-sections' => $newsections)); diff --git a/course/format/topics/renderer.php b/course/format/topics/renderer.php index 248c73a5cdb..1efad308bd5 100644 --- a/course/format/topics/renderer.php +++ b/course/format/topics/renderer.php @@ -123,21 +123,19 @@ class format_topics_renderer extends format_section_renderer_base { if ($section->section && has_capability('moodle/course:setcurrentsection', $coursecontext)) { if ($course->marker == $section->section) { // Show the "light globe" on/off. $url->param('marker', 0); - $markedthistopic = get_string('markedthistopic'); $highlightoff = get_string('highlightoff'); $controls['highlight'] = array('url' => $url, "icon" => 'i/marked', 'name' => $highlightoff, - 'pixattr' => array('class' => '', 'alt' => $markedthistopic), - 'attr' => array('class' => 'editing_highlight', 'title' => $markedthistopic, + 'pixattr' => array('class' => ''), + 'attr' => array('class' => 'editing_highlight', 'data-action' => 'removemarker')); } else { $url->param('marker', $section->section); - $markthistopic = get_string('markthistopic'); $highlight = get_string('highlight'); $controls['highlight'] = array('url' => $url, "icon" => 'i/marker', 'name' => $highlight, - 'pixattr' => array('class' => '', 'alt' => $markthistopic), - 'attr' => array('class' => 'editing_highlight', 'title' => $markthistopic, + 'pixattr' => array('class' => ''), + 'attr' => array('class' => 'editing_highlight', 'data-action' => 'setmarker')); } } diff --git a/course/lib.php b/course/lib.php index d0cb0b39a93..2294e71c862 100644 --- a/course/lib.php +++ b/course/lib.php @@ -1891,7 +1891,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) { if ($hasmanageactivities) { $actions['update'] = new action_menu_link_secondary( new moodle_url($baseurl, array('update' => $mod->id)), - new pix_icon('t/edit', $str->editsettings, 'moodle', array('class' => 'iconsmall', 'title' => '')), + new pix_icon('t/edit', '', 'moodle', array('class' => 'iconsmall')), $str->editsettings, array('class' => 'editing_update', 'data-action' => 'update') ); @@ -1917,7 +1917,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) { } $actions['moveright'] = new action_menu_link_secondary( new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '1')), - new pix_icon($rightarrow, $str->moveright, 'moodle', array('class' => 'iconsmall', 'title' => '')), + new pix_icon($rightarrow, '', 'moodle', array('class' => 'iconsmall')), $str->moveright, array('class' => 'editing_moveright ' . $enabledclass, 'data-action' => 'moveright', 'data-keepopen' => true, 'data-sectionreturn' => $sr) @@ -1930,7 +1930,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) { } $actions['moveleft'] = new action_menu_link_secondary( new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '-1')), - new pix_icon($leftarrow, $str->moveleft, 'moodle', array('class' => 'iconsmall', 'title' => '')), + new pix_icon($leftarrow, '', 'moodle', array('class' => 'iconsmall')), $str->moveleft, array('class' => 'editing_moveleft ' . $enabledclass, 'data-action' => 'moveleft', 'data-keepopen' => true, 'data-sectionreturn' => $sr) @@ -1953,7 +1953,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) { if ($displayedoncoursepage) { $actions['hide'] = new action_menu_link_secondary( new moodle_url($baseurl, array('hide' => $mod->id)), - new pix_icon('t/hide', $str->modhide, 'moodle', array('class' => 'iconsmall', 'title' => '')), + new pix_icon('t/hide', '', 'moodle', array('class' => 'iconsmall')), $str->modhide, array('class' => 'editing_hide', 'data-action' => 'hide') ); @@ -1961,7 +1961,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) { // Offer to "show" only if the section is visible. $actions['show'] = new action_menu_link_secondary( new moodle_url($baseurl, array('show' => $mod->id)), - new pix_icon('t/show', $str->modshow, 'moodle', array('class' => 'iconsmall', 'title' => '')), + new pix_icon('t/show', '', 'moodle', array('class' => 'iconsmall')), $str->modshow, array('class' => 'editing_show', 'data-action' => 'show') ); @@ -1971,7 +1971,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) { // When making the "stealth" module unavailable we perform the same action as hiding the visible module. $actions['hide'] = new action_menu_link_secondary( new moodle_url($baseurl, array('hide' => $mod->id)), - new pix_icon('t/unblock', $str->makeunavailable, 'moodle', array('class' => 'iconsmall', 'title' => '')), + new pix_icon('t/unblock', '', 'moodle', array('class' => 'iconsmall')), $str->makeunavailable, array('class' => 'editing_makeunavailable', 'data-action' => 'hide', 'data-sectionreturn' => $sr) ); @@ -1982,7 +1982,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) { $action = $sectionvisible ? 'stealth' : 'show'; $actions[$action] = new action_menu_link_secondary( new moodle_url($baseurl, array($action => $mod->id)), - new pix_icon('t/block', $str->makeavailable, 'moodle', array('class' => 'iconsmall', 'title' => '')), + new pix_icon('t/block', '', 'moodle', array('class' => 'iconsmall')), $str->makeavailable, array('class' => 'editing_makeavailable', 'data-action' => $action, 'data-sectionreturn' => $sr) ); @@ -1995,7 +1995,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) { course_allowed_module($mod->get_course(), $mod->modname)) { $actions['duplicate'] = new action_menu_link_secondary( new moodle_url($baseurl, array('duplicate' => $mod->id)), - new pix_icon('t/copy', $str->duplicate, 'moodle', array('class' => 'iconsmall', 'title' => '')), + new pix_icon('t/copy', '', 'moodle', array('class' => 'iconsmall')), $str->duplicate, array('class' => 'editing_duplicate', 'data-action' => 'duplicate', 'data-sectionreturn' => $sr) ); @@ -2026,7 +2026,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) { $actions[$actionname] = new action_menu_link_primary( new moodle_url($baseurl, array('id' => $mod->id, 'groupmode' => $nextgroupmode)), - new pix_icon($groupimage, $grouptitle, 'moodle', array('class' => 'iconsmall')), + new pix_icon($groupimage, '', 'moodle', array('class' => 'iconsmall')), $grouptitle, array('class' => 'editing_'. $actionname, 'data-action' => $nextactionname, 'aria-live' => 'assertive', 'data-sectionreturn' => $sr) @@ -2040,7 +2040,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) { if (has_capability('moodle/role:assign', $modcontext)){ $actions['assign'] = new action_menu_link_secondary( new moodle_url('/admin/roles/assign.php', array('contextid' => $modcontext->id)), - new pix_icon('t/assignroles', $str->assign, 'moodle', array('class' => 'iconsmall', 'title' => '')), + new pix_icon('t/assignroles', '', 'moodle', array('class' => 'iconsmall')), $str->assign, array('class' => 'editing_assign', 'data-action' => 'assignroles', 'data-sectionreturn' => $sr) ); @@ -2050,7 +2050,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) { if ($hasmanageactivities) { $actions['delete'] = new action_menu_link_secondary( new moodle_url($baseurl, array('delete' => $mod->id)), - new pix_icon('t/delete', $str->delete, 'moodle', array('class' => 'iconsmall', 'title' => '')), + new pix_icon('t/delete', '', 'moodle', array('class' => 'iconsmall')), $str->delete, array('class' => 'editing_delete', 'data-action' => 'delete', 'data-sectionreturn' => $sr) ); diff --git a/course/tests/behat/behat_course.php b/course/tests/behat/behat_course.php index 732ae95bbb8..4c2379ab666 100644 --- a/course/tests/behat/behat_course.php +++ b/course/tests/behat/behat_course.php @@ -304,7 +304,7 @@ class behat_course extends behat_base { // Click on highlight topic link. $this->execute('behat_general::i_click_on_in_the', - array(get_string('markthistopic'), "link", $this->escape($xpath), "xpath_element") + array(get_string('highlight'), "link", $this->escape($xpath), "xpath_element") ); } @@ -326,7 +326,7 @@ class behat_course extends behat_base { // Click on un-highlight topic link. $this->execute('behat_general::i_click_on_in_the', - array(get_string('markedthistopic'), "link", $this->escape($xpath), "xpath_element") + array(get_string('highlightoff'), "link", $this->escape($xpath), "xpath_element") ); } @@ -444,7 +444,7 @@ class behat_course extends behat_base { $xpath = $this->section_exists($sectionnumber); // The important checking, we can not check the img. - $this->execute('behat_general::should_exist_in_the', ['This topic is highlighted as the current topic', 'icon', $xpath, 'xpath_element']); + $this->execute('behat_general::should_exist_in_the', ['Remove highlight', 'link', $xpath, 'xpath_element']); } /** @@ -1165,9 +1165,9 @@ class behat_course extends behat_base { // Checking the show button alt text and show icon. $showtext = get_string('showfromothers', $courseformat); - $linkxpath = $xpath . "/descendant::a[@title=" . behat_context_helper::escape($showtext) . "]"; + $linkxpath = $xpath . "//a[*[contains(text(), " . behat_context_helper::escape($showtext) . ")]]"; - $exception = new ElementNotFoundException($this->getSession(), 'Show section link '); + $exception = new ElementNotFoundException($this->getSession(), 'Show section link'); // Returing the link so both Non-JS and JS browsers can interact with it. return $this->find('xpath', $linkxpath, $exception); From fadc44f298a89243ddfe850ea5c4af5b9bb2cf0e Mon Sep 17 00:00:00 2001 From: Damyon Wiese Date: Mon, 8 Apr 2019 15:39:02 +0800 Subject: [PATCH 05/10] MDL-64819 output: Hide icons with no title When output from javascript they should match what gets output from php. --- lib/amd/build/icon_system_fontawesome.min.js | 2 +- lib/amd/build/icon_system_standard.min.js | 2 +- lib/amd/src/icon_system_fontawesome.js | 4 ++++ lib/amd/src/icon_system_standard.js | 4 ++++ 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/amd/build/icon_system_fontawesome.min.js b/lib/amd/build/icon_system_fontawesome.min.js index 2a6fd4019da..9b9bcf3f158 100644 --- a/lib/amd/build/icon_system_fontawesome.min.js +++ b/lib/amd/build/icon_system_fontawesome.min.js @@ -1 +1 @@ -define(["core/icon_system","jquery","core/ajax","core/mustache","core/localstorage","core/url"],function(a,b,c,d,e,f){var g=null,h=null,i=function(){a.apply(this,arguments)};return i.prototype=Object.create(a.prototype),i.prototype.init=function(){if(g)return b.when(this);var a=e.get("core/iconmap-fontawesome");return a&&(a=JSON.parse(a)),a?(g=a,b.when(this)):(null===h&&(h=c.call([{methodname:"core_output_load_fontawesome_icon_map",args:[]}],!0,!1)[0]),h.then(function(a){return g={},b.each(a,function(a,b){g[b.component+"/"+b.pix]=b.to}),e.set("core/iconmap-fontawesome",JSON.stringify(g)),this}.bind(this)))},i.prototype.renderIcon=function(a,b,c,e){var h=g[b+"/"+a],i=!1;if("undefined"==typeof h){var j=f.imageUrl(a,b);i={attributes:[{name:"src",value:j},{name:"alt",value:c},{name:"title",value:c}]}}var k={key:h,title:c,alt:c,unmappedIcon:i},l=d.render(e,k);return l.trim()},i.prototype.getTemplateName=function(){return"core/pix_icon_fontawesome"},i}); \ No newline at end of file +define(["core/icon_system","jquery","core/ajax","core/mustache","core/localstorage","core/url"],function(a,b,c,d,e,f){var g=null,h=null,i=function(){a.apply(this,arguments)};return i.prototype=Object.create(a.prototype),i.prototype.init=function(){if(g)return b.when(this);var a=e.get("core/iconmap-fontawesome");return a&&(a=JSON.parse(a)),a?(g=a,b.when(this)):(null===h&&(h=c.call([{methodname:"core_output_load_fontawesome_icon_map",args:[]}],!0,!1)[0]),h.then(function(a){return g={},b.each(a,function(a,b){g[b.component+"/"+b.pix]=b.to}),e.set("core/iconmap-fontawesome",JSON.stringify(g)),this}.bind(this)))},i.prototype.renderIcon=function(a,b,c,e){var h=g[b+"/"+a],i=!1;if("undefined"==typeof h){var j=f.imageUrl(a,b);i={attributes:[{name:"src",value:j},{name:"alt",value:c},{name:"title",value:c}]}}var k={key:h,title:c,alt:c,unmappedIcon:i};"undefined"!=typeof c&&""!==c||(k["aria-hidden"]=!0);var l=d.render(e,k);return l.trim()},i.prototype.getTemplateName=function(){return"core/pix_icon_fontawesome"},i}); \ No newline at end of file diff --git a/lib/amd/build/icon_system_standard.min.js b/lib/amd/build/icon_system_standard.min.js index d165a79845c..4591141c2a9 100644 --- a/lib/amd/build/icon_system_standard.min.js +++ b/lib/amd/build/icon_system_standard.min.js @@ -1 +1 @@ -define(["core/icon_system","core/url","core/mustache"],function(a,b,c){var d=function(){a.apply(this,arguments)};return d.prototype=Object.create(a.prototype),d.prototype.renderIcon=function(a,d,e,f){var g=b.imageUrl(a,d),h={attributes:[{name:"src",value:g},{name:"alt",value:e},{name:"title",value:e}]},i=c.render(f,h);return i.trim()},d.prototype.getTemplateName=function(){return"core/pix_icon"},d}); \ No newline at end of file +define(["core/icon_system","core/url","core/mustache"],function(a,b,c){var d=function(){a.apply(this,arguments)};return d.prototype=Object.create(a.prototype),d.prototype.renderIcon=function(a,d,e,f){var g=b.imageUrl(a,d),h={attributes:[{name:"src",value:g},{name:"alt",value:e},{name:"title",value:e}]};"undefined"!=typeof e&&""!=e||h.attributes.push({name:"aria-hidden",value:"true"});var i=c.render(f,h);return i.trim()},d.prototype.getTemplateName=function(){return"core/pix_icon"},d}); \ No newline at end of file diff --git a/lib/amd/src/icon_system_fontawesome.js b/lib/amd/src/icon_system_fontawesome.js index 970b0b95911..5ef205ac2fc 100644 --- a/lib/amd/src/icon_system_fontawesome.js +++ b/lib/amd/src/icon_system_fontawesome.js @@ -105,6 +105,10 @@ define(['core/icon_system', 'jquery', 'core/ajax', 'core/mustache', 'core/locals unmappedIcon: unmappedIcon }; + if (typeof title === "undefined" || title === '') { + context['aria-hidden'] = true; + } + var result = Mustache.render(template, context); return result.trim(); }; diff --git a/lib/amd/src/icon_system_standard.js b/lib/amd/src/icon_system_standard.js index 516245474d9..a1bce77a6ab 100644 --- a/lib/amd/src/icon_system_standard.js +++ b/lib/amd/src/icon_system_standard.js @@ -52,6 +52,10 @@ define(['core/icon_system', 'core/url', 'core/mustache'], {name: 'title', value: title} ] }; + if (typeof title === "undefined" || title == "") { + templatecontext.attributes.push({name: 'aria-hidden', value: 'true'}); + } + var result = Mustache.render(template, templatecontext); return result.trim(); }; From 018aecdeb51913d7b975e7d46573bbeced45de35 Mon Sep 17 00:00:00 2001 From: Amaia Anabitarte Date: Tue, 5 Mar 2019 21:43:08 +0100 Subject: [PATCH 06/10] MDL-64669 core_message: Behat tests for send messages --- .../tests/behat/message_send_messages.feature | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 message/tests/behat/message_send_messages.feature diff --git a/message/tests/behat/message_send_messages.feature b/message/tests/behat/message_send_messages.feature new file mode 100644 index 00000000000..4a566b38981 --- /dev/null +++ b/message/tests/behat/message_send_messages.feature @@ -0,0 +1,84 @@ +@core @core_message @javascript +Feature: Message send messages + In order to communicate with fellow users + As a user + I need to be able to send a message + + Background: + Given the following "courses" exist: + | fullname | shortname | category | groupmode | + | Course 1 | C1 | 0 | 1 | + And the following "users" exist: + | username | firstname | lastname | email | + | student1 | Student | 1 | student1@example.com | + | student2 | Student | 2 | student2@example.com | + And the following "course enrolments" exist: + | user | course | role | + | student1 | C1 | student | + | student2 | C1 | student | + And the following "groups" exist: + | name | course | idnumber | enablemessaging | + | Group 1 | C1 | G1 | 1 | + And the following "group members" exist: + | user | group | + | student1 | G1 | + | student2 | G1 | + And the following config values are set as admin: + | messaging | 1 | + + Scenario: Send a message to a group conversation + Given I log in as "student1" + And I open messaging + And "Group 1" "group_message" should exist + And I select "Group 1" conversation in messaging + When I send "Hi!" message in the message area + Then I should see "Hi!" in the "Group 1" "group_message_conversation" + And I should see "##today##j F##" in the "Group 1" "group_message_conversation" + And I log out + And I log in as "student2" + And I open messaging + And "Group 1" "group_message" should exist + And I select "Group 1" conversation in messaging + And I should see "Hi!" in the "Group 1" "group_message_conversation" + + Scenario: Send a message to a starred conversation + Given I log in as "student1" + When I open messaging + Then "Group 1" "group_message" should exist + And I select "Group 1" conversation in the "group-messages" conversations list + And I open contact menu + And I click on "Star" "link" in the "//div[@data-region='header-container']" "xpath_element" + And I go back in "view-conversation" message drawer + And I open the "Starred" conversations list + And I should see "Group 1" + And I select "Group 1" conversation in the "favourites" conversations list + And I send "Hi!" message in the message area + And I should see "Hi!" in the "Group 1" "group_message_conversation" + And I should see "##today##j F##" in the "Group 1" "group_message_conversation" + And I go back in "view-conversation" message drawer + And I open the "Group" conversations list + And I should not see "Group 1" in the "Group" "group_message_tab" + + Scenario: Send a message to a private conversation via contact tab + Given the following "message contacts" exist: + | user | contact | + | student1 | student2 | + And I log in as "student1" + And I open messaging + And I click on "Contacts" "link" + And I click on "Student 2" "link" in the "//*[@data-section='contacts']" "xpath_element" + When I send "Hi!" message in the message area + Then I should see "Hi!" in the "Student 2" "group_message_conversation" + And I should see "##today##j F##" in the "Student 2" "group_message_conversation" + + Scenario: Try to send a message to a private conversation is not contact but you are allowed to send a message + Given I log in as "student1" + And I open messaging + When I send "Hi!" message to "Student 2" user + Then I should see "Hi!" in the "Student 2" "group_message_conversation" + And I should see "##today##j F##" in the "Student 2" "group_message_conversation" + And I log out + And I log in as "student2" + And I open messaging + And I select "Student 1" conversation in messaging + And I should see "Hi!" in the "Student 1" "group_message_conversation" \ No newline at end of file From 1118af2a3b599bde4ab12bf8d7a22020f66f7e0e Mon Sep 17 00:00:00 2001 From: Amaia Anabitarte Date: Tue, 5 Mar 2019 21:42:32 +0100 Subject: [PATCH 07/10] MDL-64669 core_message: New steps and selectors for messaging --- lib/behat/classes/partial_named_selector.php | 6 +++- message/tests/behat/behat_message.php | 30 +++++++++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/lib/behat/classes/partial_named_selector.php b/lib/behat/classes/partial_named_selector.php index 91757840ec5..26fa673fe89 100644 --- a/lib/behat/classes/partial_named_selector.php +++ b/lib/behat/classes/partial_named_selector.php @@ -89,6 +89,7 @@ class behat_partial_named_selector extends \Behat\Mink\Selector\PartialNamedSele 'file' => 'file', 'filemanager' => 'filemanager', 'group_message' => 'group_message', + 'group_message_conversation' => 'group_message_conversation', 'group_message_header' => 'group_message_header', 'group_message_member' => 'group_message_member', 'group_message_tab' => 'group_message_tab', @@ -154,7 +155,10 @@ XPATH , 'group_message' => << << << << <<execute("behat_general::i_click_on", [get_string('togglemessagemenu', 'core_message'), 'link']); } + /** + * Open the messaging conversation list. + * + * @Given /^I open the "(?P(?:[^"]|\\")*)" conversations list/ + * @param string $tab + */ + public function i_open_the_conversations_list(string $tab) { + $this->execute('behat_general::i_click_on', [ + $this->escape($tab), + 'group_message_tab' + ]); + } + /** * Open the messaging UI. * @@ -153,7 +166,7 @@ class behat_message extends behat_base { array("//textarea[@data-region='send-message-txt']", $this->escape($messagecontent)) ); - $this->execute("behat_forms::press_button", get_string('send', 'message')); + $this->execute("behat_forms::press_button", get_string('sendmessage', 'message')); } /** @@ -204,4 +217,19 @@ class behat_message extends behat_base { ) ); } + + /** + * Select a user in a specific messaging UI conversations list. + * + * @Given /^I select "(?P(?:[^"]|\\")*)" conversation in the "(?P(?:[^"]|\\")*)" conversations list$/ + * @param string $convname + * @param string $listname + */ + public function i_select_conversation_in_the_conversations_list(string $convname, string $listname) { + $xpath = '//*[@data-region="message-drawer"]//div[@data-region="view-overview-'. + $this->escape($listname). + '"]//*[@data-conversation-id]//img[contains(@alt,"'. + $this->escape($convname).'")]'; + $this->execute('behat_general::i_click_on', array($xpath, 'xpath_element')); + } } From a64fdeacd8af39ba84969f67ec6a55910956e2e4 Mon Sep 17 00:00:00 2001 From: Amaia Anabitarte Date: Fri, 5 Apr 2019 23:39:05 +0200 Subject: [PATCH 08/10] MDL-64669 core_message: Waiting for buttons to hide before login out --- message/tests/behat/message_drawer_manage_contacts.feature | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/message/tests/behat/message_drawer_manage_contacts.feature b/message/tests/behat/message_drawer_manage_contacts.feature index 07190be7228..b129e24dbf3 100644 --- a/message/tests/behat/message_drawer_manage_contacts.feature +++ b/message/tests/behat/message_drawer_manage_contacts.feature @@ -31,6 +31,7 @@ Feature: Manage contacts And I open contact menu And I click on "Add to contacts" "link" And I click on "Add" "button" + And I should see "Contact request sent" And I log out And I log in as "student4" Then I should see "2" in the "//*[@data-region='count-container']" "xpath_element" @@ -41,6 +42,7 @@ Feature: Manage contacts And I click on "Student 1 Would like to contact you" "link" Then I should see "Accept and add to contacts" And I click on "Accept and add to contacts" "link_or_button" + And I should not see "Accept and add to contacts" And I log out And I log in as "student1" And I open messaging @@ -54,6 +56,7 @@ Feature: Manage contacts And I open contact menu And I click on "Add to contacts" "link" And I click on "Add" "button" + And I should see "Contact request sent" And I log out And I log in as "student3" Then I should see "1" in the "//*[@data-region='count-container']" "xpath_element" @@ -64,6 +67,7 @@ Feature: Manage contacts And I click on "Student 1 Would like to contact you" "link" Then I should see "Accept and add to contacts" And I click on "Decline" "link_or_button" + And I should not see "Accept and add to contacts" And I open contact menu Then I should see "Add to contacts" in the "//div[@data-region='header-container']" "xpath_element" From 550c3afcd882314a99f4e6354ce714f9f8cd5b14 Mon Sep 17 00:00:00 2001 From: Sara Arjona Date: Tue, 9 Apr 2019 14:06:21 +0200 Subject: [PATCH 09/10] MDL-64819 course: fix behat errors with the move icon Some errors have been detected when using Clean + Goutte. --- course/tests/behat/behat_course.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/course/tests/behat/behat_course.php b/course/tests/behat/behat_course.php index 4c2379ab666..7239e1a5fd7 100644 --- a/course/tests/behat/behat_course.php +++ b/course/tests/behat/behat_course.php @@ -775,11 +775,10 @@ class behat_course extends behat_base { // Ensure the destination is valid. $sectionxpath = $this->section_exists($sectionnumber); - $activitynode = $this->get_activity_element('Move', 'icon', $activityname); - // JS enabled. if ($this->running_javascript()) { + $activitynode = $this->get_activity_element('Move', 'icon', $activityname); $destinationxpath = $sectionxpath . "/descendant::ul[contains(concat(' ', normalize-space(@class), ' '), ' yui3-dd-drop ')]"; $this->execute("behat_general::i_drag_and_i_drop_it_in", From dcaa783741fe6fc23c8f69704ecc420ec4327e7f Mon Sep 17 00:00:00 2001 From: "Eloy Lafuente (stronk7)" Date: Tue, 9 Apr 2019 20:37:08 +0200 Subject: [PATCH 10/10] weekly release 3.6.3+ --- version.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.php b/version.php index 9bf2ce21af7..1a926fc2cb8 100644 --- a/version.php +++ b/version.php @@ -29,11 +29,11 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2018120303.08; // 20181203 = branching date YYYYMMDD - do not modify! +$version = 2018120303.09; // 20181203 = branching date YYYYMMDD - do not modify! // RR = release increments - 00 in DEV branches. // .XX = incremental changes. -$release = '3.6.3+ (Build: 20190406)'; // Human-friendly version name +$release = '3.6.3+ (Build: 20190410)'; // Human-friendly version name $branch = '36'; // This version's branch. $maturity = MATURITY_STABLE; // This version's maturity level.