Merge branch 'MDL-83527-main-v02' of https://github.com/ferranrecio/moodle

This commit is contained in:
Sara Arjona 2025-01-14 09:38:54 +01:00
commit 1627aa8e71
No known key found for this signature in database
10 changed files with 1176 additions and 93 deletions

View File

@ -0,0 +1,10 @@
issueNumber: MDL-83527
notes:
core_courseformat:
- message: >-
Using arrays to define course menu items is deprecated. All course
formats that extend the section or activity control menus
(format_NAME\output\courseformat\content\section\controlmenu or
format_NAME\output\courseformat\cm\section\controlmenu) should return
standard action_menu_link objects instead.
type: deprecated

View File

@ -0,0 +1,7 @@
issueNumber: MDL-83527
notes:
format_topics:
- message: >-
The get_highlight_control in the section controlmenu class is now
deprecated. Use get_section_highlight_item instead
type: deprecated

View File

@ -0,0 +1,7 @@
issueNumber: MDL-83527
notes:
core_course:
- message: >-
course_get_cm_edit_actions is now deprecated. Formats should extend
core_courseformat\output\local\content\cm\controlmenu instead.
type: deprecated

View File

@ -16,14 +16,16 @@
namespace core_courseformat\output\local\content;
use action_menu;
use action_menu_link_secondary;
use core\context\course as context_course;
use core\output\action_menu;
use core\output\action_menu\link_secondary;
use core\output\named_templatable;
use core\output\pix_icon;
use core\output\renderable;
use core\output\renderer_base;
use core_courseformat\base as course_format;
use core_courseformat\output\local\courseformat_named_templatable;
use moodle_url;
use pix_icon;
use renderable;
use core\url;
use section_info;
use cm_info;
use stdClass;
@ -48,12 +50,21 @@ abstract class basecontrolmenu implements named_templatable, renderable {
/** @var cm_info the course module class */
protected $mod;
/** @var stdClass the course instance */
protected stdClass $course;
/** @var context_course the course context */
protected $coursecontext;
/** @var string the menu ID */
protected $menuid;
/** @var action_menu the action menu */
protected $menu;
/** @var url The base URL for the course or the section */
protected url $baseurl;
/**
* Constructor.
*
@ -67,15 +78,18 @@ abstract class basecontrolmenu implements named_templatable, renderable {
$this->section = $section;
$this->mod = $mod;
$this->menuid = $menuid;
$this->course = $format->get_course();
$this->coursecontext = $format->get_context();
$this->baseurl = $format->get_view_url($format->get_sectionnum(), ['navigation' => true]);
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param \renderer_base $output typically, the renderer that's calling this function
* @param renderer_base $output typically, the renderer that's calling this function
* @return null|array data context for a mustache template
*/
public function export_for_template(\renderer_base $output): ?stdClass {
public function export_for_template(renderer_base $output): ?stdClass {
$menu = $this->get_action_menu($output);
if (empty($menu)) {
return new stdClass();
@ -93,10 +107,10 @@ abstract class basecontrolmenu implements named_templatable, renderable {
/**
* Generate the action menu element.
*
* @param \renderer_base $output typically, the renderer that's calling this function
* @param renderer_base $output typically, the renderer that's calling this function
* @return action_menu|null the action menu or null if no action menu is available
*/
public function get_action_menu(\renderer_base $output): ?action_menu {
public function get_action_menu(renderer_base $output): ?action_menu {
if (!empty($this->menu)) {
return $this->menu;
@ -111,10 +125,10 @@ abstract class basecontrolmenu implements named_templatable, renderable {
*
* This method is public in case some block needs to modify the menu before output it.
*
* @param \renderer_base $output typically, the renderer that's calling this function
* @param renderer_base $output typically, the renderer that's calling this function
* @return action_menu|null the action menu
*/
public function get_default_action_menu(\renderer_base $output): ?action_menu {
public function get_default_action_menu(renderer_base $output): ?action_menu {
return null;
}
@ -133,23 +147,57 @@ abstract class basecontrolmenu implements named_templatable, renderable {
$menu->set_kebab_trigger(get_string('edit'));
$menu->attributes['class'] .= ' section-actions';
$menu->attributes['data-sectionid'] = $this->section->id;
foreach ($controls as $value) {
$url = empty($value['url']) ? '' : $value['url'];
$icon = empty($value['icon']) ? '' : $value['icon'];
$name = empty($value['name']) ? '' : $value['name'];
$attr = empty($value['attr']) ? [] : $value['attr'];
$class = empty($value['pixattr']['class']) ? '' : $value['pixattr']['class'];
$al = new action_menu_link_secondary(
new moodle_url($url),
new pix_icon($icon, '', null, ['class' => "smallicon " . $class]),
$name,
$attr
);
$menu->add($al);
foreach ($controls as $item) {
// Actions not available for the user can be null.
if ($item === null) {
continue;
}
// TODO remove this if as part of MDL-83530.
if (is_array($item)) {
// Some third party formats from 4.5 and older can use array to define the action menu items.
$item = $this->normalize_action_menu_link($item);
}
$menu->add($item);
}
return $menu;
}
/**
* Nromalize the action menu item, or return null if it is not possible.
*
* Traditionally, this class uses array to define the action menu items,
* for backward compatibility, this method will normalize the array into
* the correct action_menu_link object.
*
* @todo Remove this method in Moodle 6.0 (MDL-83530).
* @param array|null $itemdata the item data
* @return void
*/
private function normalize_action_menu_link(
array|null $itemdata
): ?link_secondary {
debugging(
"Using arrays as action menu items is deprecated, use a compatible menu item instead.",
DEBUG_DEVELOPER
);
if (empty($itemdata)) {
return null;
}
$url = empty($itemdata['url']) ? '' : $itemdata['url'];
$icon = empty($itemdata['icon']) ? '' : $itemdata['icon'];
$name = empty($itemdata['name']) ? '' : $itemdata['name'];
$attr = empty($itemdata['attr']) ? [] : $itemdata['attr'];
$class = empty($itemdata['pixattr']['class']) ? '' : $itemdata['pixattr']['class'];
return new link_secondary(
url: new url($url),
icon: new pix_icon($icon, '', null, ['class' => "smallicon " . $class]),
text: $name,
attributes: $attr,
);
}
/**
* Generate the edit control items of a section.
*
@ -160,4 +208,33 @@ abstract class basecontrolmenu implements named_templatable, renderable {
public function section_control_items() {
return [];
}
/**
* Adds a new control item after a given control item.
*
* If the control item is not found, the new control item is added at the beginning.
*
* @param array $controls array of edit control items
* @param string $aftername name of the control item after which the new control item will be added
* @param string $newkey key of the new control item
* @param mixed $newcontrol new control item to be added (anything compatible with an action menu or null)
*/
protected function add_control_after(array $controls, string $aftername, string $newkey, mixed $newcontrol): array {
if (!array_key_exists($aftername, $controls)) {
return array_merge([$newkey => $newcontrol], $controls);
}
$newcontrols = [];
$found = false;
foreach ($controls as $keyname => $control) {
$newcontrols[$keyname] = $control;
if ($keyname === $aftername) {
$newcontrols[$newkey] = $newcontrol;
$found = true;
}
}
if (!$found) {
$newcontrols[$newkey] = $newcontrol;
}
return $newcontrols;
}
}

View File

@ -24,12 +24,18 @@
namespace core_courseformat\output\local\content\cm;
use action_menu;
use action_menu_link;
use cm_info;
use core\context\module as context_module;
use core\output\action_menu;
use core\output\action_menu\link;
use core\output\action_menu\link_secondary;
use core\output\action_menu\subpanel;
use core\output\pix_icon;
use core\output\renderer_base;
use core_courseformat\base as course_format;
use core_courseformat\output\local\content\basecontrolmenu;
use core_courseformat\output\local\courseformat_named_templatable;
use core_courseformat\sectiondelegate;
use core\url;
use section_info;
use stdClass;
@ -45,6 +51,15 @@ class controlmenu extends basecontrolmenu {
/** @var array optional display options */
protected $displayoptions;
/** @var context_module|null modcontext the module context if any */
protected ?context_module $modcontext = null;
/** @var bool $canmanageactivities Optimization to know if the user can manage activities */
protected bool $canmanageactivities;
/** @var url $basemodurl the base mod.php url */
protected url $basemodurl;
/**
* Constructor.
*
@ -56,15 +71,24 @@ class controlmenu extends basecontrolmenu {
public function __construct(course_format $format, section_info $section, cm_info $mod, array $displayoptions = []) {
parent::__construct($format, $section, $mod, $mod->id);
$this->displayoptions = $displayoptions;
$this->modcontext = context_module::instance($mod->id);
$this->canmanageactivities = has_capability('moodle/course:manageactivities', $this->modcontext);
$this->basemodurl = new url('/course/mod.php', ['sesskey' => sesskey()]);
$sectionnumreturn = $format->get_sectionnum();
if ($sectionnumreturn !== null) {
$this->basemodurl->param('sr', $sectionnumreturn);
}
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param \renderer_base $output typically, the renderer that's calling this function
* @param renderer_base $output typically, the renderer that's calling this function
* @return stdClass|null data context for a mustache template
*/
public function export_for_template(\renderer_base $output): ?stdClass {
public function export_for_template(renderer_base $output): ?stdClass {
$mod = $this->mod;
@ -96,25 +120,305 @@ class controlmenu extends basecontrolmenu {
* Generate the action menu element.
*
* This method is public in case some block needs to modify the menu before output it.
* @param \renderer_base $output typically, the renderer that's calling this function
* @param renderer_base $output typically, the renderer that's calling this function
* @return action_menu|null the activity action menu
*/
public function get_action_menu(\renderer_base $output): ?action_menu {
public function get_action_menu(renderer_base $output): ?action_menu {
if (!empty($this->menu)) {
return $this->menu;
}
$mod = $this->mod;
// In case module is delegating a section, we should return delegated section action menu.
if ($delegated = $mod->get_delegated_section_info()) {
if ($delegated = $this->mod->get_delegated_section_info()) {
$controlmenuclass = $this->format->get_output_classname('content\\cm\\delegatedcontrolmenu');
$controlmenu = new $controlmenuclass($this->format, $delegated, $mod);
$controlmenu = new $controlmenuclass($this->format, $delegated, $this->mod);
return $controlmenu->get_action_menu($output);
}
// TODO remove this if as part of MDL-83530.
if (!$this->format->supports_components()) {
$this->menu = $this->get_action_menu_legacy($output);
return $this->menu;
}
$controls = $this->get_cm_control_items();
return $this->format_controls($controls);
}
/**
* Generate the edit control items of a course module.
*
* This method uses course_get_cm_edit_actions function to get the cm actions.
* However, format plugins can override the method to add or remove elements
* from the menu.
*
* @return array of edit control items
*/
public function get_cm_control_items(): ?array {
$controls = [];
$controls['update'] = $this->get_cm_edit_item();
$controls['move'] = $this->get_cm_move_item();
$controls['moveright'] = $this->get_cm_moveend_item();
$controls['moveleft'] = $this->get_cm_movestart_item();
$controls['availability'] = $this->get_cm_visibility_item();
$controls['duplicate'] = $this->get_cm_duplicate_item();
$controls['assign'] = $this->get_cm_assign_item();
$controls['groupmode'] = $this->get_cm_groupmode_item();
$controls['delete'] = $this->get_cm_delete_item();
return $controls;
}
/**
* Generates the edit settings item for a course module.
*
* @return link|null The menu item if applicable, otherwise null.
*/
protected function get_cm_edit_item(): ?link {
if (!$this->canmanageactivities) {
return null;
}
$url = new url($this->basemodurl, ['update' => $this->mod->id]);
return new link_secondary(
url: $url,
icon: new pix_icon('i/settings', ''),
text: get_string('editsettings'),
attributes: [
'class' => 'editing_update',
],
);
}
/**
* Generates the move item for a course module.
*
* @return link|null The menu item if applicable, otherwise null.
*/
protected function get_cm_move_item(): ?link {
// Only show the move link if we are not already in the section view page.
if (!$this->canmanageactivities) {
return null;
}
$url = new url($this->basemodurl, ['copy' => $this->mod->id]);
return new link_secondary(
url: $url,
icon: new pix_icon('i/dragdrop', ''),
text: get_string('move'),
attributes: [
// This tool requires ajax and will appear only when the frontend state is ready.
'class' => 'editing_movecm waitstate',
'data-action' => 'moveCm',
'data-id' => $this->mod->id,
],
);
}
/**
* Check if the course module can be indented.
*
* @return bool
*/
protected function can_indent_cm(): bool {
return $this->canmanageactivities
&& !sectiondelegate::has_delegate_class('mod_'.$this->mod->modname)
&& empty($this->displayoptions['disableindentation'])
&& $this->format->uses_indentation();
}
/**
* Generates the move right item for a course module.
*
* @return link|null The menu item if applicable, otherwise null.
*/
protected function get_cm_moveend_item(): ?link {
if (!$this->can_indent_cm() || $this->mod->indent > 0) {
return null;
}
$url = new url($this->basemodurl, ['id' => $this->mod->id, 'indent' => 1]);
$icon = (right_to_left()) ? 't/left' : 't/right';
return new link_secondary(
url: $url,
icon: new pix_icon($icon, ''),
text: get_string('moveright'),
attributes: [
'class' => 'editing_moveright',
'data-action' => 'cmMoveRight',
'data-keepopen' => true,
'data-sectionreturn' => $this->format->get_sectionnum(),
'data-id' => $this->mod->id,
],
);
}
/**
* Generates the move left item for a course module.
*
* @return link|null The menu item if applicable, otherwise null.
*/
protected function get_cm_movestart_item(): ?link {
if (!$this->can_indent_cm() || $this->mod->indent <= 0) {
return null;
}
$url = new url($this->basemodurl, ['id' => $this->mod->id, 'indent' => -1]);
$icon = (right_to_left()) ? 't/right' : 't/left';
return new link_secondary(
url: $url,
icon: new pix_icon($icon, ''),
text: get_string('moveleft'),
attributes: [
'class' => 'editing_moveleft',
'data-action' => 'cmMoveLeft',
'data-keepopen' => true,
'data-sectionreturn' => $this->format->get_sectionnum(),
'data-id' => $this->mod->id,
],
);
}
/**
* Generates the visibility item for a course module.
*
* @return link|null The menu item if applicable, otherwise null.
*/
protected function get_cm_visibility_item(): link_secondary|subpanel|null {
if (!has_capability('moodle/course:activityvisibility', $this->modcontext)) {
return null;
}
$outputclass = $this->format->get_output_classname('content\\cm\\visibility');
/** @var \core_courseformat\output\local\content\cm\visibility $output */
$output = new $outputclass($this->format, $this->section, $this->mod);
return $output->get_menu_item();
}
/**
* Generates the duplicate item for a course module.
*
* @return link|null The menu item if applicable, otherwise null.
*/
protected function get_cm_duplicate_item(): ?link {
if (
!has_all_capabilities(
['moodle/backup:backuptargetimport', 'moodle/restore:restoretargetimport'],
$this->coursecontext
)
|| !plugin_supports('mod', $this->mod->modname, FEATURE_BACKUP_MOODLE2)
|| !course_allowed_module($this->mod->get_course(), $this->mod->modname)
) {
return null;
}
$url = new url($this->basemodurl, ['duplicate' => $this->mod->id]);
return new link_secondary(
url: $url,
icon: new pix_icon('t/copy', ''),
text: get_string('duplicate'),
attributes: [
'class' => 'editing_duplicate',
'data-action' => 'cmDuplicate',
'data-sectionreturn' => $this->format->get_sectionnum(),
'data-id' => $this->mod->id,
],
);
}
/**
* Generates the assign roles item for a course module.
*
* @return link|null The menu item if applicable, otherwise null.
*/
protected function get_cm_assign_item(): ?link {
if (
!has_capability('moodle/role:assign', $this->modcontext)
|| sectiondelegate::has_delegate_class('mod_'.$this->mod->modname)
) {
return null;
}
return new link_secondary(
url: new url('/admin/roles/assign.php', ['contextid' => $this->modcontext->id]),
icon: new pix_icon('t/assignroles', ''),
text: get_string('assignroles', 'role'),
attributes: [
'class' => 'editing_assign',
'data-sectionreturn' => $this->format->get_sectionnum(),
],
);
}
/**
* Generates the group mode item for a course module.
*
* @return subpanel|null The menu item if applicable, otherwise null.
*/
protected function get_cm_groupmode_item(): ?subpanel {
if (
!$this->format->show_groupmode($this->mod)
|| $this->mod->coursegroupmodeforce
) {
return null;
}
$groupmodeclass = $this->format->get_output_classname('content\\cm\\groupmode');
/** @var \core_courseformat\output\local\content\cm\groupmode $groupmode */
$groupmode = new $groupmodeclass($this->format, $this->section, $this->mod);
return new subpanel(
text: get_string('groupmode', 'group'),
subpanel: $groupmode->get_choice_list(),
attributes: ['class' => 'editing_groupmode'],
icon: new pix_icon('t/groupv', '', 'moodle', ['class' => 'iconsmall']),
);
}
/**
* Generates the delete item for a course module.
*
* @return link|null The menu item if applicable, otherwise null.
*/
protected function get_cm_delete_item(): ?link {
if (!$this->canmanageactivities) {
return null;
}
$url = new url($this->basemodurl, ['delete' => $this->mod->id]);
return new link_secondary(
url: $url,
icon: new pix_icon('t/delete', ''),
text: get_string('delete'),
attributes: [
'class' => 'editing_delete text-danger',
'data-action' => 'cmDelete',
'data-sectionreturn' => $this->format->get_sectionnum(),
'data-id' => $this->mod->id,
],
);
}
/**
* Generate the action menu element for old course formats.
*
* This method is public in case some block needs to modify the menu before output it.
*
* @todo Remove this method in Moodle 6.0 (MDL-83530).
* @param \renderer_base $output typically, the renderer that's calling this function
* @return action_menu|null the activity action menu
*/
private function get_action_menu_legacy(\renderer_base $output): ?action_menu {
$mod = $this->mod;
$controls = $this->cm_control_items();
if (empty($controls)) {
@ -133,7 +437,7 @@ class controlmenu extends basecontrolmenu {
$menu->set_owner_selector($ownerselector);
foreach ($controls as $control) {
if ($control instanceof action_menu_link) {
if ($control instanceof link) {
$control->add_class('cm-edit-action');
}
$menu->add($control);
@ -151,9 +455,17 @@ class controlmenu extends basecontrolmenu {
* However, format plugins can override the method to add or remove elements
* from the menu.
*
* @deprecated since Moodle 5.0
* @todo Remove this method in Moodle 6.0 (MDL-83530).
* @return array of edit control items
*/
#[\core\attribute\deprecated(
replacement: 'get_cm_control_items',
since: '5.0',
mdl: 'MDL-83527',
)]
protected function cm_control_items() {
\core\deprecation::emit_deprecation_if_present([self::class, __FUNCTION__]);
$format = $this->format;
$mod = $this->mod;
$sectionreturn = $format->get_sectionnum();

View File

@ -16,13 +16,18 @@
namespace core_courseformat\output\local\content\cm;
use action_menu;
use context_course;
use cm_info;
use core\context\course as context_course;
use core\context\module as context_module;
use core\output\action_menu;
use core\output\action_menu\link;
use core\output\action_menu\link_secondary;
use core\output\renderer_base;
use core_courseformat\base as course_format;
use core_courseformat\output\local\content\basecontrolmenu;
use moodle_url;
use core\output\pix_icon;
use core\url;
use section_info;
use cm_info;
/**
* Base class to render delegated section controls.
@ -32,6 +37,11 @@ use cm_info;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class delegatedcontrolmenu extends basecontrolmenu {
/** @var context_module|null modcontext the module context if any */
protected ?context_module $modcontext = null;
/** @var bool $canmanageactivities Optimization to know if the user can manage activities */
protected bool $canmanageactivities;
/**
* Constructor.
@ -42,6 +52,9 @@ class delegatedcontrolmenu extends basecontrolmenu {
*/
public function __construct(course_format $format, section_info $section, cm_info $mod) {
parent::__construct($format, $section, $mod, $section->id);
$this->modcontext = context_module::instance($mod->id);
$this->canmanageactivities = has_capability('moodle/course:manageactivities', $this->modcontext);
}
/**
@ -49,26 +62,267 @@ class delegatedcontrolmenu extends basecontrolmenu {
*
* This method is public in case some block needs to modify the menu before output it.
*
* @param \renderer_base $output typically, the renderer that's calling this function
* @param renderer_base $output typically, the renderer that's calling this function
* @return action_menu|null the action menu
*/
public function get_default_action_menu(\renderer_base $output): ?action_menu {
public function get_default_action_menu(renderer_base $output): ?action_menu {
$controls = $this->delegated_control_items();
return $this->format_controls($controls);
}
/**
* Generate the edit control items of a section.
*
* @return array of edit control items
*/
public function delegated_control_items() {
// TODO remove this if as part of MDL-83530.
if (!$this->format->supports_components()) {
return $this->delegated_control_items_legacy();
}
$controls = [];
$controls['view'] = $this->get_section_view_item();
$controls['edit'] = $this->get_section_edit_item();
$controls['visibility'] = $this->get_section_visibility_item();
$controls['movesection'] = $this->get_cm_move_item();
$controls['permalink'] = $this->get_section_permalink_item();
$controls['delete'] = $this->get_cm_delete_item();
return $controls;
}
/**
* Retrieves the view item for the section control menu.
*
* @return link|null The menu item if applicable, otherwise null.
*/
protected function get_section_view_item(): ?link {
// Only show the view link if we are not already in the section view page.
if ($this->format->get_sectionid() == $this->section->id) {
return null;
}
return new link_secondary(
url: new url('/course/section.php', ['id' => $this->section->id]),
icon: new pix_icon('i/viewsection', ''),
text: get_string('view'),
attributes: ['class' => 'view'],
);
}
/**
* Retrieves the edit item for the section control menu.
*
* @return link|null The menu item if applicable, otherwise null.
*/
protected function get_section_edit_item(): ?link {
if (!has_capability('moodle/course:update', $this->coursecontext)) {
return null;
}
$url = new url(
'/course/editsection.php',
[
'id' => $this->section->id,
'sr' => $this->section->sectionnum,
]
);
return new link_secondary(
url: $url,
icon: new pix_icon('i/settings', ''),
text: get_string('editsection'),
attributes: ['class' => 'edit'],
);
}
/**
* Generates the move item for a course module.
*
* @return link|null The menu item if applicable, otherwise null.
*/
protected function get_cm_move_item(): ?link {
// Only show the move link if we are not already in the section view page.
if (
!$this->canmanageactivities
|| $this->format->get_sectionid() == $this->section->id
) {
return null;
}
// The move action uses visual elements on the course page.
$url = new url(
'/course/mod.php',
[
'sesskey' => sesskey(),
'copy' => $this->mod->id,
]
);
$sectionnumreturn = $this->format->get_sectionnum();
if ($sectionnumreturn !== null) {
$url->param('sr', $sectionnumreturn);
}
return new link_secondary(
url: $url,
icon: new pix_icon('i/dragdrop', ''),
text: get_string('move'),
attributes: [
// This tool requires ajax and will appear only when the frontend state is ready.
'class' => 'editing_movecm waitstate',
'data-action' => 'moveCm',
'data-id' => $this->mod->id,
],
);
}
/**
* Retrieves the get_section_visibility_menu_item item for the section control menu.
*
* @return link|null The menu item if applicable, otherwise null.
*/
protected function get_section_visibility_item(): ?link {
// To avoid exponential complexity, we only allow subsection visibility actions
// when the parent section is visible.
$parentsection = $this->mod->get_section_info();
if (
$this->section->sectionnum == 0
|| !$parentsection->visible
|| !has_capability('moodle/course:sectionvisibility', $this->coursecontext)
|| !has_capability('moodle/course:activityvisibility', $this->modcontext)
) {
return null;
}
$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);
$icon = 'i/show';
$name = $strhide;
$attributes = [
'class' => 'icon editing_showhide',
'data-sectionreturn' => $sectionreturn,
'data-action' => 'sectionHide',
'data-id' => $this->section->id,
'data-icon' => 'i/show',
'data-swapname' => $strshow,
'data-swapicon' => 'i/hide',
];
} else {
$url->param('show', $this->section->sectionnum);
$icon = 'i/hide';
$name = $strshow;
$attributes = [
'class' => 'editing_showhide',
'data-sectionreturn' => $sectionreturn,
'data-action' => 'sectionShow',
'data-id' => $this->section->id,
'data-icon' => 'i/hide',
'data-swapname' => $strhide,
'data-swapicon' => 'i/show',
];
}
return new link_secondary(
url: $url,
icon: new pix_icon($icon, ''),
text: $name,
attributes: $attributes,
);
}
/**
* Retrieves the permalink item for the section control menu.
*
* @return link|null The menu item if applicable, otherwise null.
*/
protected function get_section_permalink_item(): ?link {
if (!has_any_capability(
[
'moodle/course:movesections',
'moodle/course:update',
'moodle/course:sectionvisibility',
],
$this->coursecontext
)
) {
return null;
}
$url = new url(
'/course/section.php',
['id' => $this->section->id]
);
return new link_secondary(
url: $url,
icon: new pix_icon('i/link', ''),
text: get_string('sectionlink', 'course'),
attributes: [
'class' => 'permalink',
'data-action' => 'permalink',
],
);
}
/**
* Generates the delete item for a course module.
*
* @return link|null The menu item if applicable, otherwise null.
*/
protected function get_cm_delete_item(): ?link {
if (!$this->canmanageactivities) {
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,
],
);
return new link_secondary(
url: $url,
icon: new pix_icon('t/delete', ''),
text: get_string('delete'),
attributes: [
'class' => 'editing_delete text-danger',
'data-action' => 'cmDelete',
'data-sectionreturn' => $this->format->get_sectionnum(),
'data-id' => $this->mod->id,
],
);
}
/**
* Generate the edit control items of a section.
*
* It is not clear this kind of controls are still available in 4.0 so, for now, this
* method is almost a clone of the previous section_control_items from the course/renderer.php.
*
* This method must remain public until the final deprecation of section_edit_control_items.
*
* @deprecated since Moodle 5.0
* @todo Remove this method in Moodle 6.0 (MDL-83530).
* @return array of edit control items
*/
public function delegated_control_items() {
#[\core\attribute\deprecated(
replacement: 'delegated_control_items',
since: '5.0',
mdl: 'MDL-83527',
)]
protected function delegated_control_items_legacy(): array {
global $USER;
\core\deprecation::emit_deprecation_if_present([self::class, __FUNCTION__]);
$format = $this->format;
$section = $this->section;
@ -83,7 +337,7 @@ class delegatedcontrolmenu extends basecontrolmenu {
$baseurl = course_get_url($course, $sectionreturn);
$baseurl->param('sesskey', sesskey());
$cmbaseurl = new moodle_url('/course/mod.php');
$cmbaseurl = new url('/course/mod.php');
$cmbaseurl->param('sesskey', sesskey());
$hasmanageactivities = has_capability('moodle/course:manageactivities', $coursecontext);
@ -94,7 +348,7 @@ class delegatedcontrolmenu extends basecontrolmenu {
// Only show the view link if we are not already in the section view page.
if (!$isheadersection) {
$controls['view'] = [
'url' => new moodle_url('/course/section.php', ['id' => $section->id]),
'url' => new url('/course/section.php', ['id' => $section->id]),
'icon' => 'i/viewsection',
'name' => get_string('view'),
'pixattr' => ['class' => ''],
@ -113,7 +367,7 @@ class delegatedcontrolmenu extends basecontrolmenu {
// Edit settings goes to section settings form.
$controls['edit'] = [
'url' => new moodle_url('/course/editsection.php', $params),
'url' => new url('/course/editsection.php', $params),
'icon' => 'i/settings',
'name' => $streditsection,
'pixattr' => ['class' => ''],
@ -171,7 +425,7 @@ class delegatedcontrolmenu extends basecontrolmenu {
// Move (only for component compatible formats).
if (!$isheadersection && $hasmanageactivities && $usecomponents) {
$controls['move'] = [
'url' => new moodle_url('/course/mod.php', ['copy' => $cm->id]),
'url' => new url('/course/mod.php', ['copy' => $cm->id]),
'icon' => 'i/dragdrop',
'name' => get_string('move'),
'pixattr' => ['class' => ''],
@ -211,7 +465,7 @@ class delegatedcontrolmenu extends basecontrolmenu {
'moodle/course:sectionvisibility',
], $coursecontext)
) {
$sectionlink = new moodle_url(
$sectionlink = new url(
'/course/section.php',
['id' => $section->id]
);

View File

@ -16,11 +16,15 @@
namespace core_courseformat\output\local\content\section;
use action_menu;
use context_course;
use core\context\course as context_course;
use core\output\action_menu;
use core\output\action_menu\link;
use core\output\action_menu\link_secondary;
use core\output\pix_icon;
use core\output\renderer_base;
use core_courseformat\base as course_format;
use core_courseformat\output\local\content\basecontrolmenu;
use moodle_url;
use core\url;
use section_info;
/**
@ -47,10 +51,10 @@ class controlmenu extends basecontrolmenu {
*
* Sections controlled by a plugin will delegate the control menu to the delegated section class.
*
* @param \renderer_base $output typically, the renderer that's calling this function
* @param renderer_base $output typically, the renderer that's calling this function
* @return action_menu|null the section action menu or null if no action menu is available
*/
public function get_action_menu(\renderer_base $output): ?action_menu {
public function get_action_menu(renderer_base $output): ?action_menu {
if (!empty($this->menu)) {
return $this->menu;
@ -68,14 +72,357 @@ class controlmenu extends basecontrolmenu {
*
* This method is public in case some block needs to modify the menu before output it.
*
* @param \renderer_base $output typically, the renderer that's calling this function
* @param renderer_base $output typically, the renderer that's calling this function
* @return action_menu|null the section action menu
*/
public function get_default_action_menu(\renderer_base $output): ?action_menu {
public function get_default_action_menu(renderer_base $output): ?action_menu {
$controls = $this->section_control_items();
return $this->format_controls($controls);
}
/**
* Generate the edit control items of a section.
*
* @return array of edit control items
*/
public function section_control_items() {
// TODO remove this if as part of MDL-83530.
if (!$this->format->supports_components()) {
return $this->section_control_items_legacy();
}
$controls = [];
$controls['view'] = $this->get_section_view_item();
if (!$this->section->is_orphan()) {
$controls['edit'] = $this->get_section_edit_item();
$controls['duplicate'] = $this->get_section_duplicate_item();
$controls['visibility'] = $this->get_section_visibility_item();
$controls['movesection'] = $this->get_section_movesection_item();
$controls['moveup'] = $this->get_section_moveup_item();
$controls['movedown'] = $this->get_section_movedown_item();
$controls['permalink'] = $this->get_section_permalink_item();
}
$controls['delete'] = $this->get_section_delete_item();
return $controls;
}
/**
* Retrieves the view item for the section control menu.
*
* @return link|null The menu item if applicable, otherwise null.
*/
protected function get_section_view_item(): ?link {
// Only show the view link if we are not already in the section view page.
if ($this->format->get_sectionid() == $this->section->id) {
return null;
}
return new link_secondary(
url: new url('/course/section.php', ['id' => $this->section->id]),
icon: new pix_icon('i/viewsection', ''),
text: get_string('view'),
attributes: ['class' => 'view'],
);
}
/**
* Retrieves the edit item for the section control menu.
*
* @return link|null The menu item if applicable, otherwise null.
*/
protected function get_section_edit_item(): ?link {
if (!has_capability('moodle/course:update', $this->coursecontext)) {
return null;
}
$url = new url(
'/course/editsection.php',
[
'id' => $this->section->id,
'sr' => $this->section->sectionnum,
]
);
return new link_secondary(
url: $url,
icon: new pix_icon('i/settings', ''),
text: get_string('editsection'),
attributes: ['class' => 'edit'],
);
}
/**
* Retrieves the duplicate item for the section control menu.
*
* @return link|null The menu item if applicable, otherwise null.
*/
protected function get_section_duplicate_item(): ?link {
if (
$this->section->sectionnum == 0
|| !has_capability('moodle/course:update', $this->coursecontext)
) {
return null;
}
$url = new url(
$this->baseurl,
[
'sectionid' => $this->section->id,
'duplicatesection' => 1,
'sesskey' => sesskey(),
]
);
return new link_secondary(
url: $url,
icon: new pix_icon('t/copy', ''),
text: get_string('duplicate'),
attributes: ['class' => 'duplicate'],
);
}
/**
* Retrieves the get_section_visibility_menu_item item for the section control menu.
*
* @return link|null The menu item if applicable, otherwise null.
*/
protected function get_section_visibility_item(): ?link {
if (
$this->section->sectionnum == 0
|| !has_capability('moodle/course:sectionvisibility', $this->coursecontext)
) {
return null;
}
$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);
$icon = 'i/show';
$name = $strhide;
$attributes = [
'class' => 'icon editing_showhide',
'data-sectionreturn' => $sectionreturn,
'data-action' => 'sectionHide',
'data-id' => $this->section->id,
'data-icon' => 'i/show',
'data-swapname' => $strshow,
'data-swapicon' => 'i/hide',
];
} else {
$url->param('show', $this->section->sectionnum);
$icon = 'i/hide';
$name = $strshow;
$attributes = [
'class' => 'editing_showhide',
'data-sectionreturn' => $sectionreturn,
'data-action' => 'sectionShow',
'data-id' => $this->section->id,
'data-icon' => 'i/hide',
'data-swapname' => $strhide,
'data-swapicon' => 'i/show',
];
}
return new link_secondary(
url: $url,
icon: new pix_icon($icon, ''),
text: $name,
attributes: $attributes,
);
}
/**
* Retrieves the move item for the section control menu.
*
* @return link|null The menu item if applicable, otherwise null.
*/
protected function get_section_movesection_item(): ?link {
if (
$this->section->sectionnum == 0
|| $this->format->get_sectionid()
|| !has_capability('moodle/course:movesections', $this->coursecontext)
) {
return null;
}
$url = new url(
$this->baseurl,
[
'movesection' => $this->section->sectionnum,
'section' => $this->section->sectionnum,
]
);
return new link_secondary(
url: $url,
icon: new pix_icon('i/dragdrop', ''),
text: get_string('move'),
attributes: [
// This tool requires ajax and will appear only when the frontend state is ready.
'class' => 'move waitstate',
'data-action' => 'moveSection',
'data-id' => $this->section->id,
],
);
}
/**
* Retrieves the move up for the section control menu.
*
* This actions only apply to non-component-based formats
* or when javascript is not available.
*
* Note: this action will be removed, do not depend on it for your
* custom formats. For more information, see MDL-83562.
*
* @return link|null The menu item if applicable, otherwise null.
*/
protected function get_section_moveup_item(): ?link {
if (
$this->section->sectionnum <= 1
|| $this->format->get_sectionid()
|| !has_capability('moodle/course:movesections', $this->coursecontext)
) {
return null;
}
$url = new url(
$this->baseurl,
[
'section' => $this->section->sectionnum,
'move' => -1,
'sesskey' => sesskey(),
]
);
return new link_secondary(
url: $url,
icon: new pix_icon('i/up', ''),
text: get_string('moveup'),
attributes: [
// This tool disappears when the state is ready whilenostate.
'class' => 'moveup whilenostate',
],
);
}
/**
* Retrieves the move down for the section control menu.
*
* This actions only apply to non-component-based formats
* or when javascript is not available.
*
* Note: this action will be removed, do not depend on it for your
* custom formats. For more information, see MDL-83562.
*
* @return link|null The menu item if applicable, otherwise null.
*/
protected function get_section_movedown_item(): ?link {
$numsections = $this->format->get_last_section_number();
if (
$this->section->sectionnum == 0
|| $this->section->sectionnum >= $numsections
|| $this->format->get_sectionid()
|| !has_capability('moodle/course:movesections', $this->coursecontext)
) {
return null;
}
$url = new url(
$this->baseurl,
[
'section' => $this->section->sectionnum,
'move' => 1,
'sesskey' => sesskey(),
]
);
return new link_secondary(
url: $url,
icon: new pix_icon('i/down', ''),
text: get_string('movedown'),
attributes: [
// This tool disappears when the state is ready.
'class' => 'movedown whilenostate',
],
);
}
/**
* Retrieves the permalink item for the section control menu.
*
* @return link|null The menu item if applicable, otherwise null.
*/
protected function get_section_permalink_item(): ?link {
if (!has_any_capability(
[
'moodle/course:movesections',
'moodle/course:update',
'moodle/course:sectionvisibility',
],
$this->coursecontext
)
) {
return null;
}
$url = new url(
'/course/section.php',
['id' => $this->section->id]
);
return new link_secondary(
url: $url,
icon: new pix_icon('i/link', ''),
text: get_string('sectionlink', 'course'),
attributes: [
'class' => 'permalink',
'data-action' => 'permalink',
],
);
}
/**
* Retrieves the delete item for the section control menu.
*
* @return link|null The menu item if applicable, otherwise null.
*/
protected function get_section_delete_item(): ?link {
if (!course_can_delete_section($this->format->get_course(), $this->section)) {
return null;
}
$params = [
'id' => $this->section->id,
'delete' => 1,
'sesskey' => sesskey(),
];
$params['sr'] ??= $this->format->get_sectionnum();
$url = new url(
'/course/editsection.php',
$params,
);
return new link_secondary(
url: $url,
icon: new pix_icon('i/delete', ''),
text: get_string('delete'),
attributes: [
'class' => 'editing_delete text-danger',
'data-action' => 'deleteSection',
'data-id' => $this->section->id,
],
);
}
/**
* Generate the edit control items of a section.
*
@ -84,10 +431,18 @@ class controlmenu extends basecontrolmenu {
*
* This method must remain public until the final deprecation of section_edit_control_items.
*
* @deprecated since Moodle 5.0
* @todo Remove this method in Moodle 6.0 (MDL-83530).
* @return array of edit control items
*/
public function section_control_items() {
#[\core\attribute\deprecated(
replacement: 'section_control_items',
since: '5.0',
mdl: 'MDL-83527',
)]
protected function section_control_items_legacy(): array {
global $USER, $PAGE;
\core\deprecation::emit_deprecation_if_present([self::class, __FUNCTION__]);
$format = $this->format;
$section = $this->section;
@ -108,7 +463,7 @@ class controlmenu extends basecontrolmenu {
// Only show the view link if we are not already in the section view page.
if ($PAGE->pagetype !== 'course-view-section-' . $course->format) {
$controls['view'] = [
'url' => new moodle_url('/course/section.php', ['id' => $section->id]),
'url' => new url('/course/section.php', ['id' => $section->id]),
'icon' => 'i/viewsection',
'name' => get_string('view'),
'pixattr' => ['class' => ''],
@ -126,7 +481,7 @@ class controlmenu extends basecontrolmenu {
}
$controls['edit'] = [
'url' => new moodle_url('/course/editsection.php', $params),
'url' => new url('/course/editsection.php', $params),
'icon' => 'i/settings',
'name' => $streditsection,
'pixattr' => ['class' => ''],
@ -259,7 +614,7 @@ class controlmenu extends basecontrolmenu {
if (!is_null($sectionreturn)) {
$params['sr'] = $sectionreturn;
}
$url = new moodle_url(
$url = new url(
'/course/editsection.php',
$params,
);
@ -284,7 +639,7 @@ class controlmenu extends basecontrolmenu {
'moodle/course:sectionvisibility',
], $coursecontext)
) {
$sectionlink = new moodle_url(
$sectionlink = new url(
'/course/section.php',
['id' => $section->id]
);

View File

@ -24,8 +24,11 @@
namespace format_topics\output\courseformat\content\section;
use core\output\action_menu\link as action_menu_link;
use core\output\action_menu\link_secondary as action_menu_link_secondary;
use core\output\pix_icon;
use core_courseformat\output\local\content\section\controlmenu as controlmenu_base;
use moodle_url;
use core\url;
/**
* Base class to render a course section menu.
@ -50,54 +53,33 @@ class controlmenu extends controlmenu_base {
* @return array of edit control items
*/
public function section_control_items() {
$format = $this->format;
$section = $this->section;
$coursecontext = $format->get_context();
$parentcontrols = parent::section_control_items();
if ($section->is_orphan() || !$section->section) {
if ($section->is_orphan() || !$section->sectionnum) {
return $parentcontrols;
}
$controls = [];
if (has_capability('moodle/course:setcurrentsection', $coursecontext)) {
$controls['highlight'] = $this->get_highlight_control();
if (!has_capability('moodle/course:setcurrentsection', $this->coursecontext)) {
return $parentcontrols;
}
// If the edit key exists, we are going to insert our controls after it.
if (array_key_exists("edit", $parentcontrols)) {
$merged = [];
// We can't use splice because we are using associative arrays.
// Step through the array and merge the arrays.
foreach ($parentcontrols as $key => $action) {
$merged[$key] = $action;
if ($key == "edit") {
// If we have come to the edit key, merge these controls here.
$merged = array_merge($merged, $controls);
}
}
return $merged;
} else {
return array_merge($controls, $parentcontrols);
}
return $this->add_control_after($parentcontrols, 'edit', 'highlight', $this->get_section_highlight_item());
}
/**
* Return the course url.
*
* @return moodle_url
* @return url
*/
protected function get_course_url(): moodle_url {
protected function get_course_url(): url {
$format = $this->format;
$section = $this->section;
$course = $format->get_course();
$sectionreturn = $format->get_sectionnum();
if ($sectionreturn) {
$url = course_get_url($course, $section->section);
$url = course_get_url($course, $section->sectionnum);
} else {
$url = course_get_url($course);
}
@ -105,12 +87,77 @@ class controlmenu extends controlmenu_base {
return $url;
}
/**
* Retrieves the view item for the section control menu.
*
* @return action_menu_link|null The menu item if applicable, otherwise null.
*/
protected function get_section_highlight_item(): action_menu_link_secondary {
$format = $this->format;
$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';
$highlighton = get_string('highlight');
$highlightonicon = 'i/marker';
if ($course->marker == $section->sectionnum) { // Show the "light globe" on/off.
$url->param('marker', 0);
$icon = $highlightofficon;
$name = $highlightoff;
$attributes = [
'class' => 'editing_highlight',
'data-action' => 'sectionUnhighlight',
'data-sectionreturn' => $sectionreturn,
'data-id' => $section->id,
'data-icon' => $highlightofficon,
'data-swapname' => $highlighton,
'data-swapicon' => $highlightonicon,
];
} else {
$url->param('marker', $section->section);
$icon = $highlightonicon;
$name = $highlighton;
$attributes = [
'class' => 'editing_highlight',
'data-action' => 'sectionHighlight',
'data-sectionreturn' => $sectionreturn,
'data-id' => $section->id,
'data-icon' => $highlightonicon,
'data-swapname' => $highlightoff,
'data-swapicon' => $highlightofficon,
];
}
return new action_menu_link_secondary(
url: $url,
icon: new pix_icon($icon, ''),
text: $name,
attributes: $attributes,
);
}
/**
* Return the specific section highlight action.
*
* @deprecated since Moodle 5.0
* @todo Remove this method in Moodle 6.0 (MDL-83530).
* @return array the action element.
*/
#[\core\attribute\deprecated(
replacement: 'get_section_highlight_item',
since: '5.0',
mdl: 'MDL-83527',
reason: 'Wrong return type',
)]
protected function get_highlight_control(): array {
\core\deprecation::emit_deprecation_if_present([self::class, __FUNCTION__]);
$format = $this->format;
$section = $this->section;
$course = $format->get_course();

View File

@ -1353,14 +1353,25 @@ function moveto_module($mod, $section, $beforemod=NULL) {
/**
* Returns the list of all editing actions that current user can perform on the module
*
* @deprecated since Moodle 5.0
* @todo Remove this method in Moodle 6.0 (MDL-83530).
*
* @param cm_info $mod The module to produce editing buttons for
* @param int $indent The current indenting (default -1 means no move left-right actions)
* @param int $sr The section to link back to (used for creating the links)
* @return array array of action_link or pix_icon objects
*/
#[\core\attribute\deprecated(
replacement: 'core_courseformat\output\local\content\cm\controlmenu',
since: '5.0',
mdl: 'MDL-83527',
reason: 'Replaced by an output class equivalent.',
)]
function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
global $COURSE, $SITE, $CFG;
\core\deprecation::emit_deprecation_if_present(__FUNCTION__);
static $str;
$coursecontext = context_course::instance($mod->course);

View File

@ -18,6 +18,7 @@ namespace core_question\output;
use action_link;
use renderer_base;
use core_courseformat\output\local\content\cm\controlmenu;
/**
* Create a list of question bank type links to manage their respective instances.
@ -51,7 +52,9 @@ class question_bank_list implements \renderable, \templatable {
$banks = [];
foreach ($this->bankinstances as $instance) {
if (plugin_supports('mod', $instance->cminfo->modname, FEATURE_PUBLISHES_QUESTIONS)) {
$actions = course_get_cm_edit_actions($instance->cminfo);
$format = course_get_format($instance->cminfo->course);
$controlmenu = new controlmenu($format, $instance->cminfo->get_section_info(), $instance->cminfo);
$actions = $controlmenu->get_cm_control_items();
$actionmenu = new \action_menu();
$actionmenu->set_kebab_trigger(get_string('edit'));
$actionmenu->add_secondary_action($actions['update']);