diff --git a/.upgradenotes/MDL-82767-2024103011040897.yml b/.upgradenotes/MDL-82767-2024103011040897.yml new file mode 100644 index 00000000000..18b0ae71692 --- /dev/null +++ b/.upgradenotes/MDL-82767-2024103011040897.yml @@ -0,0 +1,11 @@ +issueNumber: MDL-82767 +notes: + core_courseformat: + - message: >- + A new course/format/update.php url is added as a non-ajax alternative to + the core_courseformat_course_update webservice + type: improved + - message: >- + The core_courseformat\base::get_non_ajax_cm_action_url is now + deprecated. Use get_update_url instead. + type: deprecated diff --git a/.upgradenotes/MDL-82767-2024103011142554.yml b/.upgradenotes/MDL-82767-2024103011142554.yml new file mode 100644 index 00000000000..eb0188a369d --- /dev/null +++ b/.upgradenotes/MDL-82767-2024103011142554.yml @@ -0,0 +1,9 @@ +issueNumber: MDL-82767 +notes: + format_topics: + - message: >- + In format topics, the section controlmenu class deprecates the + get_course_url method. This may affect formats extending the topics + format and adding extra items to the section menu. Use + $this->format->get_update_url instead. + type: deprecated diff --git a/.upgradenotes/MDL-82767-2024103011171091.yml b/.upgradenotes/MDL-82767-2024103011171091.yml new file mode 100644 index 00000000000..86275e54298 --- /dev/null +++ b/.upgradenotes/MDL-82767-2024103011171091.yml @@ -0,0 +1,11 @@ +issueNumber: MDL-82767 +notes: + core_courseformat: + - message: >- + Many get actions from course/view.php and course/mod.php are now + deprecated. Use the new course/format/update.php instead to replace all + direct edit urls in your code. The affected actions are: indent, + duplicate, hide, show, stealth, delete, groupmode and marker (highlight). + The course/format/updates.php uses the same parameters as the + core_courseformat_course_update webservice + type: deprecated diff --git a/.upgradenotes/MDL-82767-2025012208284812.yml b/.upgradenotes/MDL-82767-2025012208284812.yml new file mode 100644 index 00000000000..7c22b3deab0 --- /dev/null +++ b/.upgradenotes/MDL-82767-2025012208284812.yml @@ -0,0 +1,10 @@ +issueNumber: MDL-82767 +notes: + core_courseformat: + - message: >- + From now on, deleting an activity without Ajax will be consistent with + deleting an activity using Ajax. This ensures that all activity deletions + will use the recycle bin and avoid code duplication. If your format uses + the old non-Ajax method to bypass the recycle bin it won't work anymore + as the non-Ajax deletions are now handled in course/format/update.php. + type: changed diff --git a/admin/tool/recyclebin/tests/behat/basic_functionality.feature b/admin/tool/recyclebin/tests/behat/basic_functionality.feature index f7974b156d3..ca888ef1069 100644 --- a/admin/tool/recyclebin/tests/behat/basic_functionality.feature +++ b/admin/tool/recyclebin/tests/behat/basic_functionality.feature @@ -49,6 +49,7 @@ Feature: Basic recycle bin functionality Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I delete "Test assign 1" activity + And I run all adhoc tasks When I navigate to "Recycle bin" in current page administration Then I should see "Test assign 1" And I should see "Contents will be permanently deleted after 7 days" diff --git a/course/format/classes/base.php b/course/format/classes/base.php index bfb71e6010e..9c57fc0c94a 100644 --- a/course/format/classes/base.php +++ b/course/format/classes/base.php @@ -33,8 +33,8 @@ use html_writer; use section_info; use context_course; use editsection_form; -use moodle_exception; -use coding_exception; +use core\exception\moodle_exception; +use core\exception\coding_exception; use moodle_url; use lang_string; use core_external\external_api; @@ -387,6 +387,37 @@ abstract class base { return $this->modinfo; } + /** + * Return a format state updates instance. + */ + public function get_stateupdates_instance(): \core_courseformat\stateupdates { + $defaultupdatesclass = 'core_courseformat\\stateupdates'; + $updatesclass = 'format_' . $this->format . '\\courseformat\\stateupdates'; + if (!class_exists($updatesclass)) { + $updatesclass = $defaultupdatesclass; + } + + $updates = new $updatesclass($this); + if (!is_a($updates, $defaultupdatesclass)) { + throw new coding_exception("The \"$updatesclass\" class must extend \"$defaultupdatesclass\""); + } + + return $updates; + } + + /** + * Return a format state actions instance. + * @return \core_courseformat\stateactions + */ + public function get_stateactions_instance(): \core_courseformat\stateactions { + // Get the actions class from the course format. + $actionsclass = 'format_'. $this->format.'\\courseformat\\stateactions'; + if (!class_exists($actionsclass)) { + $actionsclass = 'core_courseformat\\stateactions'; + } + return new $actionsclass(); + } + /** * Method used in the rendered and during backup instead of legacy 'numsections' * @@ -886,7 +917,7 @@ abstract class base { * of the view script, it is not enough to change just this function. Do not forget * to add proper redirection. * - * @param int|stdClass|section_info $section Section object from database or just field course_sections.section + * @param int|stdClass|section_info|null $section Section object from database or just field course_sections.section * if null the course view page is returned * @param array $options options for view URL. At the moment core uses: * 'navigation' (bool) if true and section not empty, the function returns section page; otherwise, it returns course page. @@ -925,6 +956,51 @@ abstract class base { return $url; } + /** + * The URL to update the course format. + * + * If no section is specified, the update will redirect to the general course page. + * + * @param string $action action name the reactive action + * @param array $ids list of ids to update + * @param int|null $targetsectionid optional target section id + * @param int|null $targetcmid optional target cm id + * @param moodle_url|null $returnurl optional custom return url + * @return moodle_url + */ + public function get_update_url( + string $action, + array $ids = [], + ?int $targetsectionid = null, + ?int $targetcmid = null, + ?moodle_url $returnurl = null + ): moodle_url { + $params = [ + 'courseid' => $this->get_courseid(), + 'sesskey' => sesskey(), + 'action' => $action, + ]; + + if (count($ids) === 1) { + $params['id'] = reset($ids); + } else { + foreach ($ids as $key => $id) { + $params["ids[]"] = $id; + } + } + + if ($targetsectionid) { + $params['sectionid'] = $targetsectionid; + } + if ($targetcmid) { + $params['cmid'] = $targetcmid; + } + if ($returnurl) { + $params['returnurl'] = $returnurl->out_as_local_url(); + } + return new moodle_url('/course/format/update.php', $params); + } + /** * Return the old non-ajax activity action url. * @@ -932,30 +1008,36 @@ abstract class base { * so we must translate to an old non-ajax url while non-ajax * course editing is still supported. * + * @deprecated since Moodle 5.0 + * @todo Remove this method in Moodle 6.0 (MDL-83530). + * * @param string $action action name the reactive action * @param cm_info $cm course module * @return moodle_url */ + #[\core\attribute\deprecated( + replacement: 'core_courseformat\base::get_update_url', + since: '5.0', + mdl: 'MDL-82767', + )] public function get_non_ajax_cm_action_url(string $action, cm_info $cm): moodle_url { $nonajaxactions = [ - 'cmDelete' => 'delete', - 'cmDuplicate' => 'duplicate', - 'cmHide' => 'hide', - 'cmShow' => 'show', - 'cmStealth' => 'stealth', + 'cmDelete' => 'cm_delete', + 'cmDuplicate' => 'cm_duplicate', + 'cmHide' => 'cm_hide', + 'cmShow' => 'cm_show', + 'cmStealth' => 'cm_stealth', ]; if (!isset($nonajaxactions[$action])) { throw new coding_exception('Unknown activity action: ' . $action); } + \core\deprecation::emit_deprecation_if_present([$this, __FUNCTION__]); $nonajaxaction = $nonajaxactions[$action]; - $nonajaxurl = new moodle_url( - '/course/mod.php', - ['sesskey' => sesskey(), $nonajaxaction => $cm->id] + return $this->get_update_url( + action: $nonajaxaction, + ids: [$cm->id], + returnurl: $this->get_view_url($this->get_sectionnum(), ['navigation' => true]), ); - if (!is_null($this->get_sectionid())) { - $nonajaxurl->param('sr', $this->get_sectionnum()); - } - return $nonajaxurl; } /** diff --git a/course/format/classes/external/update_course.php b/course/format/classes/external/update_course.php index 7552484774f..134c050f129 100644 --- a/course/format/classes/external/update_course.php +++ b/course/format/classes/external/update_course.php @@ -16,12 +16,11 @@ namespace core_courseformat\external; +use core\exception\moodle_exception; use core_external\external_api; use core_external\external_function_parameters; use core_external\external_multiple_structure; use core_external\external_value; -use moodle_exception; -use coding_exception; use context_course; use core_courseformat\base as course_format; @@ -106,32 +105,16 @@ class update_course extends external_api { self::validate_context(context_course::instance($courseid)); - $courseformat = course_get_format($courseid); + $format = course_get_format($courseid); - // Create a course changes tracker object. - $defaultupdatesclass = 'core_courseformat\\stateupdates'; - $updatesclass = 'format_' . $courseformat->get_format() . '\\courseformat\\stateupdates'; - if (!class_exists($updatesclass)) { - $updatesclass = $defaultupdatesclass; - } - $updates = new $updatesclass($courseformat); - - if (!is_a($updates, $defaultupdatesclass)) { - throw new coding_exception("The \"$updatesclass\" class must extend \"$defaultupdatesclass\""); - } - - // Get the actions class from the course format. - $actionsclass = 'format_'. $courseformat->get_format().'\\courseformat\\stateactions'; - if (!class_exists($actionsclass)) { - $actionsclass = 'core_courseformat\\stateactions'; - } - $actions = new $actionsclass(); + $updates = $format->get_stateupdates_instance(); + $actions = $format->get_stateactions_instance(); if (!is_callable([$actions, $action])) { throw new moodle_exception("Invalid course state action $action in ".get_class($actions)); } - $course = $courseformat->get_course(); + $course = $format->get_course(); // Execute the action. $actions->$action($updates, $course, $ids, $targetsectionid, $targetcmid); diff --git a/course/format/classes/output/local/content/basecontrolmenu.php b/course/format/classes/output/local/content/basecontrolmenu.php index 64924175525..b045852d2b6 100644 --- a/course/format/classes/output/local/content/basecontrolmenu.php +++ b/course/format/classes/output/local/content/basecontrolmenu.php @@ -83,6 +83,15 @@ abstract class basecontrolmenu implements named_templatable, renderable { $this->baseurl = $format->get_view_url($format->get_sectionnum(), ['navigation' => true]); } + /** + * Change the default base URL to return after each action. + * + * @param url $baseurl + */ + public function set_baseurl(url $baseurl) { + $this->baseurl = $baseurl; + } + /** * Export this data so it can be used as the context for a mustache template. * diff --git a/course/format/classes/output/local/content/cm/controlmenu.php b/course/format/classes/output/local/content/cm/controlmenu.php index c734e7a4b01..b08374d08c1 100644 --- a/course/format/classes/output/local/content/cm/controlmenu.php +++ b/course/format/classes/output/local/content/cm/controlmenu.php @@ -241,7 +241,11 @@ class controlmenu extends basecontrolmenu { return null; } - $url = new url($this->basemodurl, ['id' => $this->mod->id, 'indent' => 1]); + $url = $this->format->get_update_url( + action: 'cm_moveright', + ids: [$this->mod->id], + returnurl: $this->baseurl, + ); $icon = (right_to_left()) ? 't/left' : 't/right'; @@ -269,7 +273,11 @@ class controlmenu extends basecontrolmenu { return null; } - $url = new url($this->basemodurl, ['id' => $this->mod->id, 'indent' => -1]); + $url = $this->format->get_update_url( + action: 'cm_moveleft', + ids: [$this->mod->id], + returnurl: $this->baseurl, + ); $icon = (right_to_left()) ? 't/right' : 't/left'; @@ -319,7 +327,11 @@ class controlmenu extends basecontrolmenu { return null; } - $url = new url($this->basemodurl, ['duplicate' => $this->mod->id]); + $url = $this->format->get_update_url( + action: 'cm_duplicate', + ids: [$this->mod->id], + returnurl: $this->baseurl, + ); return new link_secondary( url: $url, @@ -392,7 +404,11 @@ class controlmenu extends basecontrolmenu { return null; } - $url = new url($this->basemodurl, ['delete' => $this->mod->id]); + $url = $this->format->get_update_url( + action: 'cm_delete', + ids: [$this->mod->id], + returnurl: $this->baseurl, + ); return new link_secondary( url: $url, diff --git a/course/format/classes/output/local/content/cm/delegatedcontrolmenu.php b/course/format/classes/output/local/content/cm/delegatedcontrolmenu.php index 4c1c4a3bc30..215fc3f40f4 100644 --- a/course/format/classes/output/local/content/cm/delegatedcontrolmenu.php +++ b/course/format/classes/output/local/content/cm/delegatedcontrolmenu.php @@ -198,13 +198,12 @@ class delegatedcontrolmenu extends basecontrolmenu { } $sectionreturn = $this->format->get_sectionnum(); - $url = clone ($this->baseurl); $strhide = get_string('hide'); $strshow = get_string('show'); if ($this->section->visible) { - $url->param('hide', $this->section->sectionnum); + $action = 'section_hide'; $icon = 'i/show'; $name = $strhide; $attributes = [ @@ -217,7 +216,7 @@ class delegatedcontrolmenu extends basecontrolmenu { 'data-swapicon' => 'i/hide', ]; } else { - $url->param('show', $this->section->sectionnum); + $action = 'section_show'; $icon = 'i/hide'; $name = $strshow; $attributes = [ @@ -231,6 +230,12 @@ class delegatedcontrolmenu extends basecontrolmenu { ]; } + $url = $this->format->get_update_url( + action: $action, + ids: [$this->section->id], + returnurl: $this->baseurl, + ); + return new link_secondary( url: $url, icon: new pix_icon($icon, ''), @@ -282,14 +287,10 @@ class delegatedcontrolmenu extends basecontrolmenu { return null; } - // Removing a delegated section without ajax returns to the parent section. - $url = new url( - '/course/mod.php', - [ - 'sesskey' => sesskey(), - 'delete' => $this->mod->id, - 'sr' => $this->mod->sectionnum, - ], + $url = $this->format->get_update_url( + action: 'cm_delete', + ids: [$this->mod->id], + returnurl: $this->baseurl, ); return new link_secondary( diff --git a/course/format/classes/output/local/content/cm/groupmode.php b/course/format/classes/output/local/content/cm/groupmode.php index 0677114935c..e12705bb138 100644 --- a/course/format/classes/output/local/content/cm/groupmode.php +++ b/course/format/classes/output/local/content/cm/groupmode.php @@ -158,17 +158,17 @@ class groupmode implements named_templatable, renderable { $choice->add_option( NOGROUPS, get_string('groupsnone', 'group'), - $this->get_option_data(null, 'cmNoGroups', $this->mod->id) + $this->get_option_data(null, 'cmNoGroups', 'cm_nogroups') ); $choice->add_option( SEPARATEGROUPS, get_string('groupsseparate', 'group'), - $this->get_option_data('groupsseparate', 'cmSeparateGroups', $this->mod->id) + $this->get_option_data('groupsseparate', 'cmSeparateGroups', 'cm_separategroups') ); $choice->add_option( VISIBLEGROUPS, get_string('groupsvisible', 'group'), - $this->get_option_data('groupsvisible', 'cmVisibleGroups', $this->mod->id) + $this->get_option_data('groupsvisible', 'cmVisibleGroups', 'cm_visiblegroups') ); $choice->set_selected_value($this->mod->effectivegroupmode); return $choice; @@ -177,18 +177,26 @@ class groupmode implements named_templatable, renderable { /** * Get the data for the option. * @param string|null $name the name of the option - * @param string $action the state action of the option - * @param int $id the id of the module + * @param string $mutation the mutation name + * @param string $stateaction the state action name * @return array */ - private function get_option_data(?string $name, string $action, int $id): array { + private function get_option_data(?string $name, string $mutation, string $stateaction): array { + $format = $this->format; + $nonajaxurl = $format->get_update_url( + action: $stateaction, + ids: [$this->mod->id], + returnurl: $format->get_view_url($format->get_sectionnum(), ['navigation' => true]), + ); + return [ 'description' => ($name) ? get_string("groupmode_{$name}_help", 'group') : null, // The dropdown icons are decorative, so we don't need to provide alt text. - 'icon' => $this->get_action_icon($action), + 'icon' => $this->get_action_icon($mutation), + 'url' => $nonajaxurl, 'extras' => [ - 'data-id' => $id, - 'data-action' => $action, + 'data-id' => $this->mod->id, + 'data-action' => $mutation, ] ]; } diff --git a/course/format/classes/output/local/content/cm/visibility.php b/course/format/classes/output/local/content/cm/visibility.php index b791d608dbc..d171422bd26 100644 --- a/course/format/classes/output/local/content/cm/visibility.php +++ b/course/format/classes/output/local/content/cm/visibility.php @@ -236,20 +236,20 @@ class visibility implements named_templatable, renderable { $choice->add_option( 'show', get_string("availability_{$label}", 'core_courseformat'), - $this->get_option_data($label, 'cmShow') + $this->get_option_data($label, 'cmShow', 'cm_show') ); } $choice->add_option( 'hide', get_string('availability_hide', 'core_courseformat'), - $this->get_option_data('hide', 'cmHide') + $this->get_option_data('hide', 'cmHide', 'cm_hide') ); if ($CFG->allowstealth && $this->format->allow_stealth_module_visibility($this->mod, $this->section)) { $choice->add_option( 'stealth', get_string('availability_stealth', 'core_courseformat'), - $this->get_option_data('stealth', 'cmStealth') + $this->get_option_data('stealth', 'cmStealth', 'cm_stealth') ); } return $choice; @@ -258,19 +258,25 @@ class visibility implements named_templatable, renderable { /** * Get the data for the option. * @param string $name the name of the option - * @param string $action the state action of the option + * @param string $mutation the mutation name + * @param string $stateaction the state action name * @return array */ - private function get_option_data(string $name, string $action): array { + private function get_option_data(string $name, string $mutation, string $stateaction): array { + $format = $this->format; + $nonajaxurl = $format->get_update_url( + action: $stateaction, + ids: [$this->mod->id], + returnurl: $format->get_view_url($format->get_sectionnum(), ['navigation' => true]), + ); + return [ 'description' => get_string("availability_{$name}_help", 'core_courseformat'), 'icon' => $this->get_icon($name), - // Non-ajax behat is not smart enough to discrimante hidden links - // so we need to keep providing the non-ajax links. - 'url' => $this->format->get_non_ajax_cm_action_url($action, $this->mod), + 'url' => $nonajaxurl, 'extras' => [ 'data-id' => $this->mod->id, - 'data-action' => $action, + 'data-action' => $mutation, ] ]; } diff --git a/course/format/classes/output/local/content/section/controlmenu.php b/course/format/classes/output/local/content/section/controlmenu.php index d580bf2a11b..1a069a50ecd 100644 --- a/course/format/classes/output/local/content/section/controlmenu.php +++ b/course/format/classes/output/local/content/section/controlmenu.php @@ -198,13 +198,11 @@ class controlmenu extends basecontrolmenu { } $sectionreturn = $this->format->get_sectionnum(); - $url = new url($this->baseurl, ['sesskey' => sesskey()]); - $strhide = get_string('hide'); $strshow = get_string('show'); if ($this->section->visible) { - $url->param('hide', $this->section->sectionnum); + $stateaction = 'section_hide'; $icon = 'i/show'; $name = $strhide; $attributes = [ @@ -217,7 +215,7 @@ class controlmenu extends basecontrolmenu { 'data-swapicon' => 'i/hide', ]; } else { - $url->param('show', $this->section->sectionnum); + $stateaction = 'section_show'; $icon = 'i/hide'; $name = $strshow; $attributes = [ @@ -231,6 +229,12 @@ class controlmenu extends basecontrolmenu { ]; } + $url = $this->format->get_update_url( + action: $stateaction, + ids: [$this->section->id], + returnurl: $this->baseurl, + ); + return new link_secondary( url: $url, icon: new pix_icon($icon, ''), @@ -400,16 +404,10 @@ class controlmenu extends basecontrolmenu { return null; } - $params = [ - 'id' => $this->section->id, - 'delete' => 1, - 'sesskey' => sesskey(), - ]; - $params['sr'] ??= $this->format->get_sectionnum(); - - $url = new url( - '/course/editsection.php', - $params, + $url = $this->format->get_update_url( + action: 'section_delete', + ids: [$this->section->id], + returnurl: $this->baseurl, ); return new link_secondary( url: $url, diff --git a/course/format/classes/output/local/content/section/visibility.php b/course/format/classes/output/local/content/section/visibility.php index 9c9d965597a..77f210039cd 100644 --- a/course/format/classes/output/local/content/section/visibility.php +++ b/course/format/classes/output/local/content/section/visibility.php @@ -119,12 +119,12 @@ class visibility implements named_templatable, renderable { $choice->add_option( 'show', get_string('availability_show', 'core_courseformat'), - $this->get_option_data('show', 'sectionShow') + $this->get_option_data('show', 'sectionShow', 'section_show') ); $choice->add_option( 'hide', get_string('availability_hide', 'core_courseformat'), - $this->get_option_data('hide', 'sectionHide') + $this->get_option_data('hide', 'sectionHide', 'section_hide') ); $choice->set_selected_value('hide'); @@ -140,23 +140,25 @@ class visibility implements named_templatable, renderable { * Get the data for the option. * * @param string $name the name of the option - * @param string $action the state action of the option + * @param string $mutation the mutation name + * @param string $stateaction the state action name * @return array */ - private function get_option_data(string $name, string $action): array { - $baseurl = course_get_url($this->section->course, $this->section); - $baseurl->param('sesskey', sesskey()); - $baseurl->param($action, $this->section->section); + private function get_option_data(string $name, string $mutation, string $stateaction): array { + $format = $this->format; + $nonajaxurl = $format->get_update_url( + action: $stateaction, + ids: [$this->section->id], + returnurl: $format->get_view_url($format->get_sectionnum(), ['navigation' => true]), + ); return [ 'description' => get_string("availability_{$name}_help", 'core_courseformat'), 'icon' => $this->get_icon($name), - // Non-ajax behat is not smart enough to discrimante hidden links - // so we need to keep providing the non-ajax links. - 'url' => $baseurl, + 'url' => $nonajaxurl, 'extras' => [ 'data-id' => $this->section->id, - 'data-action' => $action, + 'data-action' => $mutation, ], ]; } diff --git a/course/format/classes/output/local/courseupdate.php b/course/format/classes/output/local/courseupdate.php new file mode 100644 index 00000000000..e5d932f5271 --- /dev/null +++ b/course/format/classes/output/local/courseupdate.php @@ -0,0 +1,206 @@ +. + +namespace core_courseformat\output\local; + +use core_courseformat\base as course_format; +use core\output\renderer_base; +use core\output\single_button; +use core\url; +use stdClass; + +/** + * Support UIs for non-ajax course updates alternatives. + * + * This class is used from course/format/update.php to provide confirmation + * dialogs for specific actions that require user confirmation. + * + * All protected methods has the same parameters as the core_courseformat\stateactions + * even if they are not used for a specific action. + * + * @package core_courseformat + * @copyright 2024 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class courseupdate { + use courseformat_named_templatable; + + /** + * Constructor. + * + * @param course_format $format the course format class. + * @param url $actionurl the current action url. + * @param url $returnurl the return url if the user cancel the action. + */ + public function __construct( + /** @var course_format the course format class */ + protected course_format $format, + /** @var url the current action url */ + protected url $actionurl, + /** @var url the return url if the user cancel the action */ + protected url $returnurl, + ) { + } + + /** + * Check if a specific action requires confirmation. + * + * Format plugins can override this method to provide confirmation + * dialogs for specific actions. + * + * @param string $action the action name + * @return bool + */ + public function is_confirmation_required( + string $action, + ): bool { + $methodname = $action . '_confirmation_dialog'; + return method_exists($this, $methodname); + } + + /** + * Get the confirmation dialog for a specific action. + * + * Format plugins can override this method to provide confirmation + * dialogs for specific actions. + * + * @param renderer_base $output the course renderer + * @param stdClass $course + * @param string $action the state action name to execute + * @param array $ids the section or cm ids. + * @param int|null $targetsectionid the optional target section id + * @param int|null $targetcmid the optional target cm id + * @return string the HTML output + */ + public function get_confirmation_dialog( + renderer_base $output, + stdClass $course, + string $action, + array $ids = [], + ?int $targetsectionid = null, + ?int $targetcmid = null, + ): string { + $methodname = $action . '_confirmation_dialog'; + if (method_exists($this, $methodname)) { + return $this->$methodname( + output: $output, + course: $course, + ids: $ids, + targetsectionid: $targetsectionid, + targetcmid: $targetcmid, + ); + } + return ''; + } + + /** + * Render the section delete confirmation dialog. + * + * @param renderer_base $output the course renderer + * @param stdClass $course + * @param array $ids the action ids. + * @param int|null $targetsectionid the target section id (not used) + * @param int|null $targetcmid the target cm id (not used) + * @return string the HTML output + */ + protected function section_delete_confirmation_dialog( + renderer_base $output, + stdClass $course, + array $ids = [], + ?int $targetsectionid = null, + ?int $targetcmid = null, + ): string { + if (count($ids) == 1) { + $modinfo = $this->format->get_modinfo(); + $section = $modinfo->get_section_info_by_id($ids[0]); + $title = get_string('sectiondelete_title', 'core_courseformat'); + $message = get_string( + 'sectiondelete_info', + 'core_courseformat', + ['name' => $this->format->get_section_name($section)] + ); + } else { + $title = get_string('sectionsdelete_title', 'core_courseformat'); + $message = get_string('sectionsdelete_info', 'core_courseformat', ['count' => count($ids)]); + } + + return $output->confirm( + message: $message, + cancel: $this->returnurl, + continue: new url($this->actionurl, ['confirm' => 1]), + displayoptions: [ + 'confirmtitle' => $title, + 'type' => single_button::BUTTON_DANGER, + 'continuestr' => get_string('delete'), + ] + ); + } + + /** + * Render the cm delete confirmation dialog. + * + * @param renderer_base $output the course renderer + * @param stdClass $course + * @param array $ids the action ids. + * @param int|null $targetsectionid the target section id (not used) + * @param int|null $targetcmid the target cm id (not used) + * @return string the HTML output + */ + protected function cm_delete_confirmation_dialog( + renderer_base $output, + stdClass $course, + array $ids = [], + ?int $targetsectionid = null, + ?int $targetcmid = null, + ): string { + + if (count($ids) == 1) { + $modinfo = $this->format->get_modinfo(); + $cm = $modinfo->get_cm($ids[0]); + + if ($cm->get_delegated_section_info()) { + $title = get_string('cmdelete_subsectiontitle', 'core_courseformat'); + $meesagestr = 'sectiondelete_info'; + } else { + $title = get_string('cmdelete_title', 'core_courseformat'); + $meesagestr = 'cmdelete_info'; + } + + $message = get_string( + $meesagestr, + 'core_courseformat', + (object) [ + 'type' => get_string('pluginname', 'mod_' . $cm->modname), + 'name' => $cm->name, + ], + ); + } else { + $title = get_string('cmsdelete_title', 'core_courseformat'); + $message = get_string('cmsdelete_info', 'core_courseformat', ['count' => count($ids)]); + } + + return $output->confirm( + message: $message, + cancel: $this->returnurl, + continue: new url($this->actionurl, ['confirm' => 1]), + displayoptions: [ + 'confirmtitle' => $title, + 'type' => single_button::BUTTON_DANGER, + 'continuestr' => get_string('delete'), + ] + ); + } +} diff --git a/course/format/tests/base_test.php b/course/format/tests/base_test.php index 1b4a835e889..2911ad3d1bd 100644 --- a/course/format/tests/base_test.php +++ b/course/format/tests/base_test.php @@ -790,7 +790,10 @@ final class base_test extends advanced_testcase { $this->expectException(\coding_exception::class); } $result = $format->get_non_ajax_cm_action_url($action, $cminfo); - $this->assertEquals($assign0->cmid, $result->param($expectedparam)); + if (!$exception) { + $this->assertDebuggingCalled(); + } + $this->assertEquals($assign0->cmid, $result->param('id')); } /** diff --git a/course/format/tests/behat/activity_nonajax_edit.feature b/course/format/tests/behat/activity_nonajax_edit.feature new file mode 100644 index 00000000000..39ee09f0480 --- /dev/null +++ b/course/format/tests/behat/activity_nonajax_edit.feature @@ -0,0 +1,108 @@ +@core @core_courseformat +Feature: Validate some activity editing has a non-ajax alternative + In order to edit the course activities faster + As a teacher + I need to be able use some edit tools without ajax. + + Background: + Given the following "course" exists: + | fullname | Course 1 | + | shortname | C1 | + | category | 0 | + | numsections | 3 | + | initsections | 1 | + And the following "activities" exist: + | activity | name | intro | course | idnumber | section | + | assign | Activity sample 1 | Test assignment description | C1 | sample1 | 1 | + And the following "users" exist: + | username | firstname | lastname | email | + | teacher1 | Teacher | 1 | teacher1@example.com | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + + Scenario: Activity settings can be accessed without ajax + Given I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + When I click on "Edit settings" "link" in the "Activity sample 1" "activity" + Then I should see "Assignment name" + And I set the field "Assignment name" to "New name" + And I press "Save and return to course" + And I should see "New name" + + Scenario: Indent an activity can be done without ajax + Given I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + And I should not see "Move left" + When I click on "Move right" "link" in the "Activity sample 1" "activity" + Then I should see "Move left" + And I should not see "Move right" + And I click on "Move left" "link" in the "Activity sample 1" "activity" + And I should not see "Move left" + And I should see "Move right" + + Scenario: Hide and show an activity can be done without ajax + Given I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + And I should not see "Show" in the ".cm_action_menu" "css_element" + When I click on "Hide" "link" in the "Activity sample 1" "activity" + Then I should see "Show" in the ".cm_action_menu" "css_element" + And I should not see "Hide" in the ".cm_action_menu" "css_element" + And I click on "Show" "link" in the "Activity sample 1" "activity" + And I should not see "Show" in the ".cm_action_menu" "css_element" + And I should see "Hide" in the ".cm_action_menu" "css_element" + + Scenario: Activity visibility with stealth option can be changed without ajax + Given the following config values are set as admin: + | allowstealth | 1 | + And I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + And I should see "Show on course page" + And I should see "Hide on course page" + And I should see "Make available but don't show on course page" + And ".activity-badges" "css_element" should not exist + When I click on "Hide on course page" "link" in the "Activity sample 1" "activity" + Then I should see "Hidden from students" in the "Activity sample 1" "core_courseformat > Activity visibility" + And I should not see "Available but not shown on course page" in the "Activity sample 1" "core_courseformat > Activity visibility" + And I click on "Make available but don't show on course page" "link" in the "Activity sample 1" "activity" + And I should not see "Hidden from students" in the "Activity sample 1" "core_courseformat > Activity visibility" + And I should see "Available but not shown on course page" in the "Activity sample 1" "core_courseformat > Activity visibility" + And I click on "Show on course page" "link" in the "Activity sample 1" "activity" + And ".activity-badges" "css_element" should not exist + + Scenario: Duplicate activity can be done without ajax + Given I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + When I click on "Duplicate" "link" in the "Activity sample 1" "activity" + Then I should see "Activity sample 1 (copy)" + + Scenario: Delete activity can be done without ajax + Given I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + When I click on "Delete" "link" in the "Activity sample 1" "activity" + And I should see "Delete activity?" + And I should see "This will delete Activity sample 1 and any user data it contains" + And I click on "Delete" "button" + Then I should not see "Activity sample 1" + + Scenario: The activity groupmode can be changed without ajax + Given the following "groups" exist: + | name | course | idnumber | + | G1 | C1 | GI1 | + And I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + And "No groups" "icon" should exist in the "Activity sample 1" "activity" + And "Visible groups" "icon" should not exist in the "Activity sample 1" "activity" + And "Separate groups" "icon" should not exist in the "Activity sample 1" "activity" + When I click on "Separate groups" "link" in the "Activity sample 1" "activity" + And "No groups" "icon" should not exist in the "Activity sample 1" "activity" + And "Visible groups" "icon" should not exist in the "Activity sample 1" "activity" + And "Separate groups" "icon" should exist in the "Activity sample 1" "activity" + And I click on "Visible groups" "link" in the "Activity sample 1" "activity" + And "No groups" "icon" should not exist in the "Activity sample 1" "activity" + And "Visible groups" "icon" should exist in the "Activity sample 1" "activity" + And "Separate groups" "icon" should not exist in the "Activity sample 1" "activity" + And I click on "No groups" "link" in the "Activity sample 1" "activity" + And "No groups" "icon" should exist in the "Activity sample 1" "activity" + And "Visible groups" "icon" should not exist in the "Activity sample 1" "activity" + And "Separate groups" "icon" should not exist in the "Activity sample 1" "activity" diff --git a/course/format/tests/behat/section_nonajax_edit.feature b/course/format/tests/behat/section_nonajax_edit.feature new file mode 100644 index 00000000000..8d62258540e --- /dev/null +++ b/course/format/tests/behat/section_nonajax_edit.feature @@ -0,0 +1,51 @@ +@core @core_courseformat +Feature: Validate some section editing has a non-ajax alternative + In order to edit the course sections faster + As a teacher + I need to be able use some edit tools without ajax. + + Background: + Given the following "course" exists: + | fullname | Course 1 | + | shortname | C1 | + | category | 0 | + | numsections | 3 | + | initsections | 1 | + And the following "activities" exist: + | activity | name | intro | course | idnumber | section | + | assign | Activity sample 1 | Test assignment description | C1 | sample1 | 1 | + And the following "users" exist: + | username | firstname | lastname | email | + | teacher1 | Teacher | 1 | teacher1@example.com | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + + Scenario: Section settings can be accessed without ajax + Given I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + When I click on "Edit settings" "link" in the "Section 1" "core_courseformat > Section actions menu" + Then I should see "Section name" + And I set the field "Section name" to "New name" + And I press "Save changes" + And I should see "New name" + + Scenario: Hide and show a section can be done without ajax + Given I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + And I should not see "Show" in the "Section 1" "core_courseformat > Section actions menu" + When I click on "Hide" "link" in the "Section 1" "core_courseformat > Section actions menu" + Then I should see "Show" in the "Section 1" "core_courseformat > Section actions menu" + And I should not see "Hide" in the "Section 1" "core_courseformat > Section actions menu" + And I click on "Show" "link" in the "Section 1" "core_courseformat > Section actions menu" + And I should not see "Show" in the "Section 1" "core_courseformat > Section actions menu" + And I should see "Hide" in the "Section 1" "core_courseformat > Section actions menu" + + Scenario: Delete a section can be done without ajax + Given I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + When I click on "Delete" "link" in the "Section 1" "core_courseformat > Section actions menu" + Then I should see "Delete section?" + And I should see "This will delete Section 1 and all the activities it contains." + And I click on "Delete" "button" + And I should not see "Section 1" diff --git a/course/format/topics/classes/output/courseformat/content/section/controlmenu.php b/course/format/topics/classes/output/courseformat/content/section/controlmenu.php index dcc8a4493e3..b3918bbf3c9 100644 --- a/course/format/topics/classes/output/courseformat/content/section/controlmenu.php +++ b/course/format/topics/classes/output/courseformat/content/section/controlmenu.php @@ -72,7 +72,13 @@ class controlmenu extends controlmenu_base { * * @return url */ + #[\core\attribute\deprecated( + since: '5.0', + mdl: 'MDL-82767', + reason: 'Not used anymore, use $this->format->get_update_url instead', + )] protected function get_course_url(): url { + \core\deprecation::emit_deprecation_if_present([self::class, __FUNCTION__]); $format = $this->format; $section = $this->section; $course = $format->get_course(); @@ -97,10 +103,6 @@ class controlmenu extends controlmenu_base { $section = $this->section; $course = $format->get_course(); $sectionreturn = $format->get_sectionnum(); - $url = $this->get_course_url(); - if (!is_null($sectionreturn)) { - $url->param('sectionid', $format->get_sectionid()); - } $highlightoff = get_string('highlightoff'); $highlightofficon = 'i/marked'; @@ -109,7 +111,7 @@ class controlmenu extends controlmenu_base { $highlightonicon = 'i/marker'; if ($course->marker == $section->sectionnum) { // Show the "light globe" on/off. - $url->param('marker', 0); + $action = 'section_unhighlight'; $icon = $highlightofficon; $name = $highlightoff; $attributes = [ @@ -122,7 +124,7 @@ class controlmenu extends controlmenu_base { 'data-swapicon' => $highlightonicon, ]; } else { - $url->param('marker', $section->section); + $action = 'section_highlight'; $icon = $highlightonicon; $name = $highlighton; $attributes = [ @@ -135,6 +137,13 @@ class controlmenu extends controlmenu_base { 'data-swapicon' => $highlightofficon, ]; } + + $url = $this->format->get_update_url( + action: $action, + ids: [$section->id], + returnurl: $this->baseurl, + ); + return new action_menu_link_secondary( url: $url, icon: new pix_icon($icon, ''), diff --git a/course/format/topics/tests/behat/edit_delete_sections.feature b/course/format/topics/tests/behat/edit_delete_sections.feature index 2626dba4234..a9861ff8086 100644 --- a/course/format/topics/tests/behat/edit_delete_sections.feature +++ b/course/format/topics/tests/behat/edit_delete_sections.feature @@ -70,7 +70,7 @@ Feature: Sections can be edited and deleted in custom sections format Scenario: Deleting the last section in custom sections format Given I am on "Course 1" course homepage with editing mode on When I delete section "5" - Then I should see "Are you absolutely sure you want to completely delete \"Section 5\" and all the activities it contains?" + Then I should see "This will delete Section 5 and all the activities it contains." And I press "Delete" And I should not see "Section 5" And I should see "Section 4" diff --git a/course/format/topics/tests/behat/highlight_sections.feature b/course/format/topics/tests/behat/highlight_sections.feature index 0a33f417769..8c5f1dc2863 100644 --- a/course/format/topics/tests/behat/highlight_sections.feature +++ b/course/format/topics/tests/behat/highlight_sections.feature @@ -51,3 +51,11 @@ Feature: Sections can be highlighted When I open section "3" edit menu And I click on "Unhighlight" "link" in the "Section 3" "section" Then I should not see "Highlighted" in the "Section 3" "section" + + Scenario: Highlight and unhighlight a section can be done without ajax + # Without javascript hidden elements cannot be detected with a simple I should see step. + Given ".section.current" "css_element" should not exist + When I click on "Highlight" "link" in the "Section 2" "core_courseformat > Section actions menu" + Then ".section.current[data-number='2']" "css_element" should exist + And I click on "Unhighlight" "link" in the "Section 2" "core_courseformat > Section actions menu" + And ".section.current" "css_element" should not exist diff --git a/course/format/update.php b/course/format/update.php new file mode 100644 index 00000000000..0de542bf962 --- /dev/null +++ b/course/format/update.php @@ -0,0 +1,127 @@ +. + +/** + * Execute an update action on a course format and structure. + * + * @package core_courseformat + * @copyright 2024 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once('../../config.php'); +require_once($CFG->dirroot . '/course/lib.php'); + +use core\url; +use core\exception\moodle_exception; +use core_courseformat\base as course_format; + +$action = required_param('action', PARAM_ALPHANUMEXT); +$courseid = required_param('courseid', PARAM_INT); +$targetsectionid = optional_param('targetsectionid', null, PARAM_INT); +$targetcmid = optional_param('targetcmid', null, PARAM_INT); +$confirm = optional_param('confirm', false, PARAM_BOOL); + +$returnurl = optional_param('returnurl', null, PARAM_LOCALURL); + + +// All state updates are designed to be batch compatible. However, we also +// accept single id values for simplicity. +$ids = optional_param_array('ids', [], PARAM_INT); +if (empty($ids)) { + $ids = [required_param('id', PARAM_INT)]; +} +if (empty($ids)) { + throw new moodle_exception('missingparam', '', '', 'ids'); +} + +$format = course_get_format($courseid); +$course = $format->get_course(); + +if ($returnurl === null) { + $returnurl = new url('/course/view.php', ['id' => $course->id]); +} + +// Normalize the return URL. +$returnurl = new moodle_url($returnurl); + +$currenturl = new moodle_url( + '/course/format/update.php', + [ + 'action' => $action, + 'courseid' => $courseid, + 'targetsectionid' => $targetsectionid, + 'targetcmid' => $targetcmid, + 'returnurl' => $returnurl, + 'sesskey' => sesskey(), + ] +); +foreach ($ids as $key => $id) { + $currenturl->param("ids[]", $id); +} + +require_sesskey(); + +$PAGE->set_url($currenturl); +$PAGE->set_context($format->get_context()); +$PAGE->set_pagelayout('course'); +$PAGE->add_body_class('limitedwidth'); +$PAGE->set_heading($course->fullname); + +require_login($course); +require_all_capabilities( + ['moodle/course:update', 'moodle/course:sectionvisibility', 'moodle/course:activityvisibility'], + $format->get_context(), +); + +// Some actions may require a confirmation dialog. +$actionuiclass = $format->get_output_classname('courseupdate'); +/** @var core_courseformat\output\local\courseupdate $actionui */ +$actionui = new $actionuiclass($format, $currenturl, $returnurl); + +if ( + !$confirm + && $actionui->is_confirmation_required($action) +) { + /** @var \core_course_renderer $renderer */ + $renderer = $format->get_renderer($PAGE); + echo $renderer->header(); + echo $actionui->get_confirmation_dialog( + output: $renderer, + course: $course, + action: $action, + ids: $ids, + targetsectionid: $targetsectionid, + targetcmid: $targetcmid, + ); + echo $renderer->footer(); + die; +} + +$updates = $format->get_stateupdates_instance(); +$actions = $format->get_stateactions_instance(); + +if (!is_callable([$actions, $action])) { + throw new moodle_exception("Invalid course state action $action in ".get_class($actions)); +} + +// Execute the action. +$actions->$action($updates, $course, $ids, $targetsectionid, $targetcmid); + +// Any state action mark the state cache as dirty. +course_format::session_cache_reset($course); + +redirect($returnurl); diff --git a/course/format/weeks/tests/behat/edit_delete_sections.feature b/course/format/weeks/tests/behat/edit_delete_sections.feature index 96061da9948..cfe94575b09 100644 --- a/course/format/weeks/tests/behat/edit_delete_sections.feature +++ b/course/format/weeks/tests/behat/edit_delete_sections.feature @@ -63,7 +63,7 @@ Feature: Sections can be edited and deleted in weekly sections format Scenario: Deleting the last section in weeks format Given I should see "29 May - 4 June" in the "29 May - 4 June" "section" When I delete section "5" - Then I should see "Are you absolutely sure you want to completely delete \"29 May - 4 June\" and all the activities it contains?" + Then I should see "This will delete 29 May - 4 June and all the activities it contains." And I press "Delete" And I should not see "29 May - 4 June" And I should see "22 May - 28 May" diff --git a/course/mod.php b/course/mod.php index 1ae4446f700..258946e8964 100644 --- a/course/mod.php +++ b/course/mod.php @@ -29,20 +29,20 @@ require_once("lib.php"); $sectionreturn = optional_param('sr', null, PARAM_INT); $add = optional_param('add', '', PARAM_ALPHANUM); $type = optional_param('type', '', PARAM_ALPHA); -$indent = optional_param('indent', 0, PARAM_INT); +$indent = optional_param('indent', 0, PARAM_INT); // TODO remove this param as part of MDL-83530. $update = optional_param('update', 0, PARAM_INT); -$duplicate = optional_param('duplicate', 0, PARAM_INT); -$hide = optional_param('hide', 0, PARAM_INT); -$stealth = optional_param('stealth', 0, PARAM_INT); -$show = optional_param('show', 0, PARAM_INT); +$duplicate = optional_param('duplicate', 0, PARAM_INT); // TODO remove this param as part of MDL-83530. +$hide = optional_param('hide', 0, PARAM_INT); // TODO remove this param as part of MDL-83530. +$stealth = optional_param('stealth', 0, PARAM_INT); // TODO remove this param as part of MDL-83530. +$show = optional_param('show', 0, PARAM_INT); // TODO remove this param as part of MDL-83530. $copy = optional_param('copy', 0, PARAM_INT); $moveto = optional_param('moveto', 0, PARAM_INT); $movetosection = optional_param('movetosection', 0, PARAM_INT); -$delete = optional_param('delete', 0, PARAM_INT); +$delete = optional_param('delete', 0, PARAM_INT); // TODO remove this param as part of MDL-83530. $course = optional_param('course', 0, PARAM_INT); -$groupmode = optional_param('groupmode', -1, PARAM_INT); +$groupmode = optional_param('groupmode', -1, PARAM_INT); // TODO remove this param as part of MDL-83530. $cancelcopy = optional_param('cancelcopy', 0, PARAM_BOOL); -$confirm = optional_param('confirm', 0, PARAM_BOOL); +$confirm = optional_param('confirm', 0, PARAM_BOOL); // TODO remove this param as part of MDL-83530. // This page should always redirect $url = new moodle_url('/course/mod.php'); @@ -118,6 +118,11 @@ if (!empty($add)) { ) ); } else if (!empty($duplicate) and confirm_sesskey()) { + // TODO remove this else if as part of MDL-83530. + debugging( + 'The duplicate param is deprecated. Please use action cm_duplicate in course/format/update.php instead.', + DEBUG_DEVELOPER + ); $cm = get_coursemodule_from_id('', $duplicate, 0, true, MUST_EXIST); $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST); @@ -130,6 +135,11 @@ if (!empty($add)) { redirect(course_get_url($course, $cm->sectionnum, $urloptions)); } else if (!empty($delete)) { + // TODO remove this else if as part of MDL-83530. + debugging( + 'The delete param is deprecated. Please use action cm_delete in course/format/update.php instead.', + DEBUG_DEVELOPER + ); $cm = get_coursemodule_from_id('', $delete, 0, true, MUST_EXIST); $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST); @@ -220,6 +230,11 @@ if ((!empty($movetosection) or !empty($moveto)) and confirm_sesskey()) { redirect(course_get_url($course, $section->section, $urloptions)); } else if (!empty($indent) and confirm_sesskey()) { + // TODO remove this else if as part of MDL-83530. + debugging( + 'The indent param deprecated. Please use action cm_moveleft and cm_moveright in course/format/update.php instead.', + DEBUG_DEVELOPER + ); $id = required_param('id', PARAM_INT); $cm = get_coursemodule_from_id('', $id, 0, true, MUST_EXIST); @@ -245,6 +260,11 @@ if ((!empty($movetosection) or !empty($moveto)) and confirm_sesskey()) { redirect(course_get_url($course, $cm->sectionnum, $urloptions)); } else if (!empty($hide) and confirm_sesskey()) { + // TODO remove this else if as part of MDL-83530. + debugging( + 'The hide param deprecated. Please use action cm_hide in course/format/update.php instead.', + DEBUG_DEVELOPER + ); $cm = get_coursemodule_from_id('', $hide, 0, true, MUST_EXIST); $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST); @@ -259,6 +279,11 @@ if ((!empty($movetosection) or !empty($moveto)) and confirm_sesskey()) { redirect(course_get_url($course, $cm->sectionnum, $urloptions)); } else if (!empty($stealth) and confirm_sesskey()) { + // TODO remove this else if as part of MDL-83530. + debugging( + 'The stealth param deprecated. Please use action cm_stealth in course/format/update.php instead.', + DEBUG_DEVELOPER + ); list($course, $cm) = get_course_and_cm_from_cmid($stealth); require_login($course, false, $cm); require_capability('moodle/course:activityvisibility', $cm->context); @@ -269,6 +294,11 @@ if ((!empty($movetosection) or !empty($moveto)) and confirm_sesskey()) { redirect(course_get_url($course, $cm->sectionnum, array('sr' => $sectionreturn))); } else if (!empty($show) and confirm_sesskey()) { + // TODO remove this else if as part of MDL-83530. + debugging( + 'The show param deprecated. Please use action cm_show in course/format/update.php instead.', + DEBUG_DEVELOPER + ); list($course, $cm) = get_course_and_cm_from_cmid($show); require_login($course, false, $cm); require_capability('moodle/course:activityvisibility', $cm->context); @@ -280,6 +310,11 @@ if ((!empty($movetosection) or !empty($moveto)) and confirm_sesskey()) { redirect(course_get_url($course, $section->section, $urloptions)); } else if ($groupmode > -1 and confirm_sesskey()) { + // TODO remove this else if as part of MDL-83530. + debugging( + 'The groupmode param deprecated. Please use the group mode actions in course/format/update.php instead.', + DEBUG_DEVELOPER + ); $id = required_param('id', PARAM_INT); $cm = get_coursemodule_from_id('', $id, 0, true, MUST_EXIST); diff --git a/course/modduplicate.php b/course/modduplicate.php deleted file mode 100644 index d39a34a208f..00000000000 --- a/course/modduplicate.php +++ /dev/null @@ -1,45 +0,0 @@ -. - -/** - * Duplicates a given course module - * - * The script backups and restores a single activity as if it was imported - * from the same course, using the default import settings. The newly created - * copy of the activity is then moved right below the original one. - * - * @package core - * @subpackage course - * @deprecated Moodle 2.8 MDL-46428 - Now redirects to mod.php. - * @copyright 2011 David Mudrak - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -require_once(__DIR__ . '/../config.php'); - -$cmid = required_param('cmid', PARAM_INT); -$courseid = required_param('course', PARAM_INT); -$sectionreturn = optional_param('sr', null, PARAM_INT); - -require_sesskey(); - -debugging('Please use moodle_url(\'/course/mod.php\', array(\'duplicate\' => $cmid - , \'id\' => $courseid, \'sesskey\' => sesskey(), \'sr\' => $sectionreturn))) - instead of new moodle_url(\'/course/modduplicate.php\', array(\'cmid\' => $cmid - , \'course\' => $courseid, \'sr\' => $sectionreturn))', DEBUG_DEVELOPER); - -redirect(new moodle_url('/course/mod.php', array('duplicate' => $cmid, 'id' => $courseid, - 'sesskey' => sesskey(), 'sr' => $sectionreturn))); diff --git a/course/tests/behat/behat_course.php b/course/tests/behat/behat_course.php index ac689614ed1..3c2c1a9f5cf 100644 --- a/course/tests/behat/behat_course.php +++ b/course/tests/behat/behat_course.php @@ -1161,7 +1161,7 @@ class behat_course extends behat_base { ] ); } else { - $this->execute("behat_forms::press_button", get_string('yes')); + $this->execute("behat_forms::press_button", get_string('delete')); } return $steps; diff --git a/course/view.php b/course/view.php index 1980bbd5353..2e8877a30d8 100644 --- a/course/view.php +++ b/course/view.php @@ -31,15 +31,15 @@ redirect_if_major_upgrade_required(); $id = optional_param('id', 0, PARAM_INT); $name = optional_param('name', '', PARAM_TEXT); $edit = optional_param('edit', -1, PARAM_BOOL); -$hide = optional_param('hide', 0, PARAM_INT); -$show = optional_param('show', 0, PARAM_INT); +$hide = optional_param('hide', 0, PARAM_INT); // TODO remove this param as part of MDL-83530. +$show = optional_param('show', 0, PARAM_INT); // TODO remove this param as part of MDL-83530. $duplicatesection = optional_param('duplicatesection', 0, PARAM_INT); $idnumber = optional_param('idnumber', '', PARAM_RAW); $sectionid = optional_param('sectionid', 0, PARAM_INT); $section = optional_param('section', null, PARAM_INT); $expandsection = optional_param('expandsection', -1, PARAM_INT); $move = optional_param('move', 0, PARAM_INT); -$marker = optional_param('marker', -1 , PARAM_INT); +$marker = optional_param('marker', -1 , PARAM_INT); // TODO remove this param as part of MDL-83530. $switchrole = optional_param('switchrole', -1, PARAM_INT); // Deprecated, use course/switchrole.php instead. $return = optional_param('return', 0, PARAM_LOCALURL); @@ -193,8 +193,13 @@ if ($PAGE->user_allowed_editing()) { } } + // TODO remove this if as part of MDL-83530. if (has_capability('moodle/course:sectionvisibility', $context)) { if ($hide && confirm_sesskey()) { + debugging( + 'The hide param in course view is deprecated. Please use course/format/update.php instead.', + DEBUG_DEVELOPER + ); set_section_visible($course->id, $hide, '0'); if ($sectionid) { redirect(course_get_url($course, $section, ['navigation' => true])); @@ -204,6 +209,10 @@ if ($PAGE->user_allowed_editing()) { } if ($show && confirm_sesskey()) { + debugging( + 'The show param in course view is deprecated. Please use course/format/update.php instead.', + DEBUG_DEVELOPER + ); set_section_visible($course->id, $show, '1'); if ($sectionid) { redirect(course_get_url($course, $section, ['navigation' => true])); @@ -213,7 +222,12 @@ if ($PAGE->user_allowed_editing()) { } } + // TODO remove this if as part of MDL-83530. if ($marker >= 0 && confirm_sesskey()) { + debugging( + 'The marker param in course view is deprecated. Please use course/format/update.php instead.', + DEBUG_DEVELOPER + ); course_set_marker($course->id, $marker); if ($sectionid) { redirect(course_get_url($course, $section, ['navigation' => true])); diff --git a/mod/feedback/tests/behat/coursemapping.feature b/mod/feedback/tests/behat/coursemapping.feature index daa0157cc64..ff6b87d0e87 100644 --- a/mod/feedback/tests/behat/coursemapping.feature +++ b/mod/feedback/tests/behat/coursemapping.feature @@ -228,7 +228,7 @@ Feature: Mapping courses in a feedback And I add the "Feedback" block And I add the "Main menu" block And I click on "Delete" "link" in the "Course feedback" activity - And I press "Yes" + And I press "Delete" And I turn editing mode off And I am on site homepage Then "Feedback" "block" should not exist diff --git a/question/classes/output/question_bank_list.php b/question/classes/output/question_bank_list.php index b4b6c01b020..56a81d42b59 100644 --- a/question/classes/output/question_bank_list.php +++ b/question/classes/output/question_bank_list.php @@ -19,6 +19,7 @@ namespace core_question\output; use action_link; use renderer_base; use core_courseformat\output\local\content\cm\controlmenu; +use core_question\local\bank\question_bank_helper; /** * Create a list of question bank type links to manage their respective instances. @@ -52,8 +53,10 @@ class question_bank_list implements \renderable, \templatable { $banks = []; foreach ($this->bankinstances as $instance) { if (plugin_supports('mod', $instance->cminfo->modname, FEATURE_PUBLISHES_QUESTIONS)) { + $returnurl = question_bank_helper::get_url_for_qbank_list($instance->cminfo->course); $format = course_get_format($instance->cminfo->course); $controlmenu = new controlmenu($format, $instance->cminfo->get_section_info(), $instance->cminfo); + $controlmenu->set_baseurl($returnurl); $actions = $controlmenu->get_cm_control_items(); $actionmenu = new \action_menu(); $actionmenu->set_kebab_trigger(get_string('edit')); diff --git a/question/tests/behat/bank_manage.feature b/question/tests/behat/bank_manage.feature index 55eec64f4ad..29978dac746 100644 --- a/question/tests/behat/bank_manage.feature +++ b/question/tests/behat/bank_manage.feature @@ -53,6 +53,6 @@ Feature: Manage question banks When I navigate to "Question banks" in current page administration And I open the action menu in "bank1" "list_item" And I choose "Delete" in the open action menu - And I click on "Yes" "button" in the "Confirm" "dialogue" + And I click on "Delete" "button" Then I should not see "bank1" But I should see "bank2" diff --git a/report/log/tests/behat/filter_log_actions.feature b/report/log/tests/behat/filter_log_actions.feature index fe4224eedac..08deea16be6 100644 --- a/report/log/tests/behat/filter_log_actions.feature +++ b/report/log/tests/behat/filter_log_actions.feature @@ -26,6 +26,7 @@ Feature: In a report, admin can filter log data by action # Delete Action. And I delete "Test assignment 1" activity And I log out + And I trigger cron Scenario: View only create actions. Given I log in as "admin"