MDL-80203 courseformat: Set action menu for delegated sections
Co-authored by: Ferran Recio <[email protected]>
This commit is contained in:
@@ -46,6 +46,9 @@ class content implements named_templatable, renderable {
|
||||
/** @var string section selector class name */
|
||||
protected $sectionselectorclass;
|
||||
|
||||
/** @var string the section control menu class */
|
||||
protected $sectioncontrolmenuclass;
|
||||
|
||||
/** @var string bulk editor bar toolbox */
|
||||
protected $bulkedittoolsclass;
|
||||
|
||||
@@ -66,13 +69,14 @@ class content implements named_templatable, renderable {
|
||||
$this->sectionnavigationclass = $format->get_output_classname('content\\sectionnavigation');
|
||||
$this->sectionselectorclass = $format->get_output_classname('content\\sectionselector');
|
||||
$this->bulkedittoolsclass = $format->get_output_classname('content\\bulkedittools');
|
||||
$this->sectioncontrolmenuclass = $format->get_output_classname('content\\section\\controlmenu');
|
||||
}
|
||||
|
||||
/**
|
||||
* Export this data so it can be used as the context for a mustache template (core/inplace_editable).
|
||||
*
|
||||
* @param renderer_base $output typically, the renderer that's calling this function
|
||||
* @return stdClass data context for a mustache template
|
||||
* @param \renderer_base $output typically, the renderer that's calling this function
|
||||
* @return \stdClass data context for a mustache template
|
||||
*/
|
||||
public function export_for_template(\renderer_base $output) {
|
||||
global $PAGE;
|
||||
@@ -117,6 +121,24 @@ class content implements named_templatable, renderable {
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the action menu for the page header of the local content section.
|
||||
*
|
||||
* @param \renderer_base $output The renderer object used for rendering the action menu.
|
||||
* @return string|null The rendered action menu HTML, null if page no action menu is available.
|
||||
*/
|
||||
public function get_page_header_action(\renderer_base $output): ?string {
|
||||
$sectionid = $this->format->get_sectionid();
|
||||
if ($sectionid !== null) {
|
||||
$modinfo = $this->format->get_modinfo();
|
||||
$sectioninfo = $modinfo->get_section_info_by_id($sectionid);
|
||||
/** @var \core_courseformat\output\local\content\section\controlmenu */
|
||||
$controlmenu = new $this->sectioncontrolmenuclass($this->format, $sectioninfo);
|
||||
return $output->render($controlmenu->get_action_menu($output));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export sections array data.
|
||||
*
|
||||
|
||||
@@ -299,7 +299,8 @@ class section implements named_templatable, renderable {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($this->hidecontrols)) {
|
||||
// In a single section page the control menu is located in the page header.
|
||||
if (empty($this->hidecontrols) && $this->format->get_sectionid() != $this->section->id) {
|
||||
$controlmenu = new $this->controlmenuclass($this->format, $this->section);
|
||||
$data->controlmenu = $controlmenu->export_for_template($output);
|
||||
}
|
||||
|
||||
@@ -71,15 +71,50 @@ class controlmenu implements named_templatable, renderable {
|
||||
* @return array data context for a mustache template
|
||||
*/
|
||||
public function export_for_template(\renderer_base $output): stdClass {
|
||||
|
||||
$section = $this->section;
|
||||
|
||||
$controls = $this->section_control_items();
|
||||
|
||||
if (empty($controls)) {
|
||||
$menu = $this->get_action_menu($output);
|
||||
if (empty($menu)) {
|
||||
return new stdClass();
|
||||
}
|
||||
|
||||
$data = (object)[
|
||||
'menu' => $output->render($menu),
|
||||
'hasmenu' => true,
|
||||
'id' => $this->section->id,
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the action menu element depending on the section.
|
||||
*
|
||||
* Sections controlled by a plugin will delegate the control menu to the plugin.
|
||||
*
|
||||
* @param \renderer_base $output typically, the renderer that's calling this function
|
||||
* @return action_menu|null the activity action menu or null if no action menu is available
|
||||
*/
|
||||
public function get_action_menu(\renderer_base $output): ?action_menu {
|
||||
$sectiondelegate = $this->section->get_component_instance();
|
||||
if ($sectiondelegate) {
|
||||
return $sectiondelegate->get_section_action_menu($this->format, $this, $output);
|
||||
}
|
||||
return $this->get_default_action_menu($output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the default section action menu.
|
||||
*
|
||||
* 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
|
||||
* @return action_menu|null the activity action menu
|
||||
*/
|
||||
public function get_default_action_menu(\renderer_base $output): ?action_menu {
|
||||
$controls = $this->section_control_items();
|
||||
if (empty($controls)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Convert control array into an action_menu.
|
||||
$menu = new action_menu();
|
||||
$menu->set_kebab_trigger(get_string('edit'));
|
||||
@@ -98,14 +133,7 @@ class controlmenu implements named_templatable, renderable {
|
||||
);
|
||||
$menu->add($al);
|
||||
}
|
||||
|
||||
$data = (object)[
|
||||
'menu' => $output->render($menu),
|
||||
'hasmenu' => true,
|
||||
'id' => $section->id,
|
||||
];
|
||||
|
||||
return $data;
|
||||
return $menu;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,8 +16,12 @@
|
||||
|
||||
namespace core_courseformat;
|
||||
|
||||
use action_menu;
|
||||
use renderer_base;
|
||||
use section_info;
|
||||
use core_courseformat\stateupdates;
|
||||
use core_courseformat\output\local\content\section\controlmenu;
|
||||
use core_courseformat\base as course_format;
|
||||
|
||||
/**
|
||||
* Section delegate base class.
|
||||
@@ -104,4 +108,20 @@ abstract class sectiondelegate {
|
||||
public function put_section_state_extra_updates(section_info $section, stateupdates $updates): void {
|
||||
// By default, do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow delegate plugin to modify the available section menu.
|
||||
*
|
||||
* @param course_format $format The course format instance.
|
||||
* @param controlmenu $controlmenu The control menu instance.
|
||||
* @param renderer_base $output The renderer instance.
|
||||
* @return action_menu|null The new action menu with the list of edit control items or null if no action menu is available.
|
||||
*/
|
||||
public function get_section_action_menu(
|
||||
course_format $format,
|
||||
controlmenu $controlmenu,
|
||||
renderer_base $output,
|
||||
): ?action_menu {
|
||||
return $controlmenu->get_default_action_menu($output);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,10 +16,12 @@
|
||||
|
||||
namespace core_courseformat;
|
||||
|
||||
use test_component\courseformat\sectiondelegate as testsectiondelegate;
|
||||
|
||||
/**
|
||||
* Section delaegate tests.
|
||||
* Section delegate tests.
|
||||
*
|
||||
* @package core_course
|
||||
* @package core_courseformat
|
||||
* @copyright 2023 Ferran Recio <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \core_courseformat\sectiondelegate
|
||||
@@ -73,9 +75,70 @@ class sectiondelegate_test extends \advanced_testcase {
|
||||
$this->assertNull(sectiondelegate::instance($sectioninfos[3]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test has_delegate_class().
|
||||
*
|
||||
* @covers ::has_delegate_class
|
||||
*/
|
||||
public function test_has_delegate_class(): void {
|
||||
$this->assertFalse(sectiondelegate::has_delegate_class('missing_component'));
|
||||
$this->assertFalse(sectiondelegate::has_delegate_class('mod_label'));
|
||||
$this->assertTrue(sectiondelegate::has_delegate_class('test_component'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_section_action_menu().
|
||||
*
|
||||
* @covers ::get_section_action_menu
|
||||
*/
|
||||
public function test_get_section_action_menu(): void {
|
||||
global $DB, $PAGE;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course(['format' => 'topics', 'numsections' => 1]);
|
||||
|
||||
$sectioninfo = formatactions::section($course)->create_delegated('test_component', 1);
|
||||
|
||||
/** @var testsectiondelegate */
|
||||
$delegated = $sectioninfo->get_component_instance();
|
||||
|
||||
$format = course_get_format($course);
|
||||
|
||||
$outputclass = $format->get_output_classname('content\\section\\controlmenu');
|
||||
/** @var \core_courseformat\output\local\content\section\controlmenu */
|
||||
$controlmenu = new $outputclass($format, $sectioninfo);
|
||||
$renderer = $PAGE->get_renderer('format_' . $course->format);
|
||||
$sectionmenu = $controlmenu->get_action_menu($renderer);
|
||||
|
||||
// When the delegate class returns the same action menu, calculated from the given $controlmenu.
|
||||
$result = $delegated->get_section_action_menu($format, $controlmenu, $renderer);
|
||||
// The $result and $sectionmenu are the same but can't be compared directly because they have different ids.
|
||||
$this->assertEquals(
|
||||
count($result->get_primary_actions()),
|
||||
count($sectionmenu->get_primary_actions()),
|
||||
);
|
||||
$this->assertEquals(
|
||||
count($result->get_secondary_actions()),
|
||||
count($sectionmenu->get_secondary_actions())
|
||||
);
|
||||
$this->assertEquals(
|
||||
$result->get_secondary_actions()[0]->url,
|
||||
$sectionmenu->get_secondary_actions()[0]->url
|
||||
);
|
||||
|
||||
// When the delegated class returns an empty action menu.
|
||||
$delegated->set_section_action_menu(testsectiondelegate::MENUEMPTY);
|
||||
$result = $delegated->get_section_action_menu($format, $controlmenu, $renderer);
|
||||
// The $result and $sectionmenu are different.
|
||||
$this->assertNotEquals(
|
||||
count($result->get_secondary_actions()),
|
||||
count($sectionmenu->get_secondary_actions())
|
||||
);
|
||||
|
||||
// When the delegated class return a null action menu.
|
||||
$delegated->set_section_action_menu(null);
|
||||
$result = $delegated->get_section_action_menu($format, $controlmenu, $renderer);
|
||||
$this->assertNull($result);
|
||||
}
|
||||
}
|
||||
|
||||
+8
-8
@@ -142,13 +142,15 @@ if (!empty($bulkbutton)) {
|
||||
$PAGE->add_header_action($bulkbutton);
|
||||
}
|
||||
|
||||
$outputclass = $format->get_output_classname('content');
|
||||
/** @var \core_courseformat\output\local\content */
|
||||
$sectionoutput = new $outputclass($format);
|
||||
|
||||
// Add to the header the control menu for the section.
|
||||
if ($format->show_editor()) {
|
||||
$sectionclass = new \core_courseformat\output\local\content\section($format, $sectioninfo);
|
||||
$renderable = $sectionclass->export_for_template($renderer);
|
||||
if (property_exists($renderable->controlmenu, 'menu')) {
|
||||
$controlmenuhtml = $renderable->controlmenu->menu;
|
||||
$PAGE->add_header_action($controlmenuhtml);
|
||||
$menu = $sectionoutput->get_page_header_action($renderer);
|
||||
if ($menu) {
|
||||
$PAGE->add_header_action($menu);
|
||||
}
|
||||
$sectionheading = $OUTPUT->container(
|
||||
$OUTPUT->render($format->inplace_editable_render_section_name($sectioninfo, false)),
|
||||
@@ -187,9 +189,7 @@ echo $renderer->container_start('course-content');
|
||||
// Include course AJAX.
|
||||
include_course_ajax($course, $modinfo->get_used_module_names());
|
||||
|
||||
$outputclass = $format->get_output_classname('content');
|
||||
$widget = new $outputclass($format);
|
||||
echo $renderer->render($widget);
|
||||
echo $renderer->render($sectionoutput);
|
||||
|
||||
// Include course format javascript files.
|
||||
$jsfiles = $format->get_required_jsfiles();
|
||||
|
||||
+62
@@ -16,9 +16,13 @@
|
||||
|
||||
namespace test_component\courseformat;
|
||||
|
||||
use core_courseformat\base as course_format;
|
||||
use core_courseformat\output\local\content\section\controlmenu;
|
||||
use core_courseformat\sectiondelegate as sectiondelegatebase;
|
||||
use core_courseformat\stateupdates;
|
||||
use section_info;
|
||||
use renderer_base;
|
||||
use action_menu;
|
||||
|
||||
/**
|
||||
* Test class for section delegate.
|
||||
@@ -28,6 +32,22 @@ use section_info;
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class sectiondelegate extends sectiondelegatebase {
|
||||
|
||||
/** @var string force the default parent action menu. */
|
||||
public const MENUPARENT = 'parent';
|
||||
|
||||
/** @var string force an empty action menu. */
|
||||
public const MENUEMPTY = 'empty';
|
||||
|
||||
/** @var string force a null action menu. */
|
||||
public const MENUNULL = 'null';
|
||||
|
||||
/**
|
||||
* @var string|null Status to define which action menu to return when calling get_section_action_menu().
|
||||
* Alternatively, different testing classes could be created, but it wasn't worth it for this case.
|
||||
*/
|
||||
protected ?string $actionmenustatus = self::MENUPARENT;
|
||||
|
||||
/**
|
||||
* Test method to fake preprocesses the section name by appending a suffix to it.
|
||||
*
|
||||
@@ -52,4 +72,46 @@ class sectiondelegate extends sectiondelegatebase {
|
||||
public function put_section_state_extra_updates(section_info $section, stateupdates $updates): void {
|
||||
$updates->add_cm_put($section->itemid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to change the behaviour of the get_section_action_menu(), for testing purposes, to return a different action menu
|
||||
* based on the value of $actionmenustatus. For instance:
|
||||
* - 'parent' returns the parent action menu.
|
||||
* - 'empty' returns an empty action menu.
|
||||
* - 'null' or null returns null action menu.
|
||||
*
|
||||
* @param string|null $actionmenustatus The status of the action menu.
|
||||
*/
|
||||
public function set_section_action_menu(
|
||||
?string $actionmenustatus,
|
||||
) {
|
||||
$this->actionmenustatus = $actionmenustatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow delegate plugin to modify the available section menu.
|
||||
* By default, it returns the parent action menu.
|
||||
*
|
||||
* @param course_format $format The course format instance.
|
||||
* @param controlmenu $controlmenu The control menu instance.
|
||||
* @param renderer_base $output The renderer instance.
|
||||
* @return action_menu|null The new action menu with the list of edit control items or null if no action menu is available.
|
||||
*/
|
||||
public function get_section_action_menu(
|
||||
course_format $format,
|
||||
controlmenu $controlmenu,
|
||||
renderer_base $output,
|
||||
): ?action_menu {
|
||||
switch ($this->actionmenustatus) {
|
||||
case self::MENUPARENT:
|
||||
return parent::get_section_action_menu($format, $controlmenu, $output);
|
||||
|
||||
case self::MENUEMPTY:
|
||||
return new action_menu();
|
||||
|
||||
case self::MENUNULL:
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user