MDL-72413 core: Introduce new header component to render standard items

New activity header to render common module items in a centralised location.
This commit is contained in:
Peter Dias
2021-12-13 09:49:34 +08:00
parent 222c8cc77c
commit a2d8ac6eea
19 changed files with 381 additions and 15 deletions
+1
View File
@@ -60,6 +60,7 @@ $string['addedtogroupnot'] = 'Not added to group "{$a}"';
$string['addedtogroupnotenrolled'] = 'Not added to group "{$a}", because not enrolled in course';
$string['addfilehere'] = 'Add file(s) here';
$string['addinganew'] = 'Adding a new {$a}';
$string['additionalcustomnav'] = 'Additional custom navigation';
$string['addnew'] = 'Add a new {$a}';
$string['addinganewto'] = 'Adding a new {$a->what} to {$a->to}';
$string['addingdatatoexisting'] = 'Adding data to existing';
+184
View File
@@ -0,0 +1,184 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core\output;
use moodle_page;
use renderer_base;
use url_select;
/**
* Data structure representing standard components displayed on the activity header.
*
* Consists of title, header, description. In addition, additional_items can be provided which is a url_select
*
* @copyright 2021 Peter
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 4.0
* @package core
* @category output
*/
class activity_header implements \renderable, \templatable {
/** @var moodle_page $page The current page we are looking at */
protected $page;
/** @var string $title The title to be displayed in the header. Defaults to activityrecord name. */
protected $title;
/** @var string $description The description to be displayed. Defaults to activityrecord intro. */
protected $description;
/** @var \stdClass $user The user we are dealing with */
protected $user;
/** @var url_select $additionalnavitems Any additional custom navigation elements to be injected into template. */
protected $additionalnavitems;
/** @var bool $hidecompletion Whether to show completion criteria, if available, or not */
protected $hidecompletion;
/** @var bool $hideoverflow Whether to show the overflow data or not */
protected $hideoverflow;
/** @var bool $hideheader Whether or not to show the header */
protected $hideheader;
/**
* Constructor for activity_header
*
* @param moodle_page $page
* @param \stdClass $user
*/
public function __construct(moodle_page $page, \stdClass $user) {
$this->page = $page;
$this->user = $user;
$pageoptions = $this->page->theme->activityheaderconfig ?? [];
$layoutoptions = $this->page->layout_options['activityheader'] ?? [];
// Do a basic setup for the header based on theme/page options.
if ($page->activityrecord) {
if (empty($pageoptions['notitle']) && empty($layoutoptions['notitle'])) {
$this->title = format_string($page->activityrecord->name);
}
if (empty($layoutoptions['nodescription']) && $page->activityrecord->intro && trim($page->activityrecord->intro)) {
$this->description = format_module_intro($this->page->activityname, $page->activityrecord, $page->cm->id);
}
}
$this->hidecompletion = !empty($layoutoptions['nocompletion']);
$this->hideoverflow = false;
$this->hideheader = false;
}
/**
* Checks if the theme has specified titles to be displayed.
*
* @return bool
*/
public function is_title_allowed(): bool {
return empty($this->page->theme->activityheaderconfig['notitle']);
}
/**
* Bulk set class member variables. Only updates variables which have corresponding setters
*
* @param array $config
*/
public function set_attrs(array $config): void {
foreach ($config as $key => $value) {
if (method_exists($this, "set_$key")) {
$this->{"set_$key"}($value);
}
}
}
/**
* Sets the hidecompletion class member variable
*
* @param bool $value
*/
public function set_hidecompletion(bool $value): void {
$this->hidecompletion = $value;
}
/**
* Sets the additionalnavitems class member variable
*
* @param url_select $value
*/
public function set_additionalnavitems(url_select $value): void {
$this->additionalnavitems = $value;
}
/**
* Sets the hideoverflow class member variable
*
* @param bool $value
*/
public function set_hideoverflow(bool $value): void {
$this->hideoverflow = $value;
}
/**
* Sets the title class member variable.
*
* @param string $value
*/
public function set_title(string $value): void {
$this->title = preg_replace('/<h2[^>]*>([.\s\S]*)<\/h2>/', '$1', $value);
}
/**
* Sets the description class member variable
*
* @param string $value
*/
public function set_description(string $value): void {
$this->description = $value;
}
/**
* Disable the activity header completely. Use this if the page has some custom content, headings to be displayed.
*/
public function disable(): void {
$this->hideheader = true;
}
/**
* Export items to be rendered with a template.
*
* @param renderer_base $output
* @return array
*/
public function export_for_template(renderer_base $output): array {
// Don't need to show anything if not displaying within an activity context.
if (!$this->page->activityrecord) {
return [];
}
// If within an activity context but requesting to hide the header,
// then just trigger the render for maincontent div.
if ($this->hideheader) {
return ['title' => ''];
}
$completion = null;
if (!$this->hidecompletion) {
$completiondetails = \core_completion\cm_completion_details::get_instance($this->page->cm, $this->user->id);
$activitydates = \core\activity_dates::get_dates_for_module($this->page->cm, $this->user->id);
$completion = $output->activity_information($this->page->cm, $completiondetails, $activitydates);
}
return [
'title' => $this->title,
'description' => $this->description,
'completion' => $completion,
'additional_items' => $this->hideoverflow ? '' : $this->additionalnavitems,
];
}
}
+1 -1
View File
@@ -760,7 +760,7 @@ class theme_config {
'rendererfactory', 'csspostprocess', 'editor_sheets', 'editor_scss', 'rarrow', 'larrow', 'uarrow', 'darrow',
'hidefromselector', 'doctype', 'yuicssmodules', 'blockrtlmanipulations', 'blockrendermethod',
'scss', 'extrascsscallback', 'prescsscallback', 'csstreepostprocessor', 'addblockposition',
'iconsystem', 'precompiledcsscallback', 'haseditswitch', 'usescourseindex');
'iconsystem', 'precompiledcsscallback', 'haseditswitch', 'usescourseindex', 'activityheaderconfig');
foreach ($config as $key=>$value) {
if (in_array($key, $configurable)) {
+5 -1
View File
@@ -1402,7 +1402,11 @@ class core_renderer extends renderer_base {
$this->opencontainers->push('header/footer', $footer);
$this->page->set_state(moodle_page::STATE_IN_BODY);
return $header . $this->skip_link_target('maincontent');
// If an activity record has been set, activity_header will handle this.
if (!$this->page->activityrecord || !empty($this->page->layout_options['noactivityheader'])) {
$header .= $this->skip_link_target('maincontent');
}
return $header;
}
/**
+23
View File
@@ -403,6 +403,11 @@ class moodle_page {
*/
protected $_activenodeprimary = null;
/**
* @var \core\output\activity_header The default activity header for standardised.
*/
protected $_activityheader;
/**
* Force the settings menu to be displayed on this page. This will only force the
* settings menu on an activity / resource page that is being displayed on a theme that
@@ -823,6 +828,24 @@ class moodle_page {
return $this->_flatnav;
}
/**
* Returns the activity header object
* @return secondary
*/
protected function magic_get_activityheader() {
global $USER;
if ($this->_activityheader === null) {
$class = 'core\output\activity_header';
// Try and load a custom class first.
if (class_exists("mod_{$this->activityname}\\output\\activity_header")) {
$class = "mod_{$this->activityname}\\output\\activity_header";
}
$this->_activityheader = new $class($this, $USER);
}
return $this->_activityheader;
}
/**
* Returns the secondary navigation object
* @return secondary
+86
View File
@@ -0,0 +1,86 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template core/activity_header
Activity header template.
Context variables required for this template:
* title - The title of the activity module
* description - The intro for the module
* completion - The completion info if available for the the module as acquired via the activity_information method
* additional_items - Any additional URL select navigation that needs to show up in the header
Example context (json):
{
"title": "Assignment 1",
"description": "The assignment does something",
"completion": "<div class='activitycompletion'>Some activity completion criteria</div>",
"additional_items": {
"id": "url_select_test",
"action": "https://example.com/post",
"formid": "url_select_form",
"sesskey": "sesskey",
"label": "core/url_select",
"helpicon": {
"title": "Help with something",
"text": "Help with something",
"url": "http://example.org/help",
"linktext": "",
"icon":{
"extraclasses": "iconhelp",
"attributes": [
{"name": "src", "value": "../../../pix/help.svg"},
{"name": "alt", "value": "Help icon"}
]
}
},
"showbutton": "Go",
"options": [{
"name": "Group 1", "isgroup": true, "options":
[
{"name": "Item 1", "isgroup": false, "value": "1"},
{"name": "Item 2", "isgroup": false, "value": "2"}
]},
{"name": "Group 2", "isgroup": true, "options":
[
{"name": "Item 3", "isgroup": false, "value": "3"},
{"name": "Item 4", "isgroup": false, "value": "4"}
]}],
"disabled": false,
"title": "Some cool title"
}
}
}}
<span id="maincontent"></span>
{{#title}}
<h2>{{{title}}}</h2>
{{/title}}
{{#completion}}
<span class="sr-only">{{#str}} overallaggregation, completion {{/str}}</span>
{{{completion}}}
{{/completion}}
{{#description}}
<div class="box py-3 generalbox" id="intro">
{{{description}}}
</div>
{{/description}}
{{#additional_items}}
<nav aria-label="{{#str}} additionalcustomnav, core {{/str}}">
{{> core/url_select}}
</nav>
{{/additional_items}}
+22 -4
View File
@@ -102,17 +102,31 @@ $THEME->layouts = [
'options' => array('langmenu' => true),
),
// Pages that appear in pop-up windows - no navigation, no blocks, no header.
// Pages that appear in pop-up windows - no navigation, no blocks, no header and bare activity header.
'popup' => array(
'file' => 'columns1.php',
'regions' => array(),
'options' => array('nofooter' => true, 'nonavbar' => true),
'options' => array(
'nofooter' => true,
'nonavbar' => true,
'activityheader' => [
'notitle' => true,
'nocompletion' => true,
'nodescription' => true
]
)
),
// No blocks and minimal footer - used for legacy frame layouts only!
'frametop' => array(
'file' => 'columns1.php',
'regions' => array(),
'options' => array('nofooter' => true, 'nocoursefooter' => true),
'options' => array(
'nofooter' => true,
'nocoursefooter' => true,
'activityheader' => [
'nocompletion' => true
]
),
),
// Embeded pages, like iframe/object embeded in moodleform - it needs as much space as possible.
'embedded' => array(
@@ -131,7 +145,7 @@ $THEME->layouts = [
'print' => array(
'file' => 'columns1.php',
'regions' => array(),
'options' => array('nofooter' => true, 'nonavbar' => false),
'options' => array('nofooter' => true, 'nonavbar' => false, 'noactivityheader' => true),
),
// The pagelayout used when a redirection is occuring.
'redirect' => array(
@@ -164,3 +178,7 @@ $THEME->addblockposition = BLOCK_ADDBLOCK_POSITION_FLATNAV;
$THEME->iconsystem = \core\output\icon_system::FONTAWESOME;
$THEME->haseditswitch = true;
$THEME->usescourseindex = true;
// By default, all boost theme do not need their titles displayed.
$THEME->activityheaderconfig = [
'notitle' => true
];
+7 -2
View File
@@ -25,12 +25,17 @@
defined('MOODLE_INTERNAL') || die();
$bodyattributes = $OUTPUT->body_attributes([]);
$templatecontext = [
'sitename' => format_string($SITE->shortname, true, ['context' => context_course::instance(SITEID), "escape" => false]),
'output' => $OUTPUT,
'bodyattributes' => $bodyattributes
'bodyattributes' => $bodyattributes,
];
if (empty($PAGE->layout_options['noactivityheader'])) {
$header = $PAGE->activityheader;
$renderer = $PAGE->get_renderer('core');
$templatecontext['headercontent'] = $header->export_for_template($renderer);
}
echo $OUTPUT->render_from_template('theme_boost/columns1', $templatecontext);
+5 -1
View File
@@ -71,6 +71,9 @@ $buildregionmainsettings = !$PAGE->include_region_main_settings_in_header_action
// If the settings menu will be included in the header then don't add it here.
$regionmainsettingsmenu = $buildregionmainsettings ? $OUTPUT->region_main_settings_menu() : false;
$header = $PAGE->activityheader;
$headercontent = $header->export_for_template($renderer);
$templatecontext = [
'sitename' => format_string($SITE->shortname, true, ['context' => context_course::instance(SITEID), "escape" => false]),
'output' => $OUTPUT,
@@ -84,7 +87,8 @@ $templatecontext = [
'langmenu' => $primarymenu['lang'],
'regionmainsettingsmenu' => $regionmainsettingsmenu,
'hasregionmainsettingsmenu' => !empty($regionmainsettingsmenu),
'overflow' => $overflow
'headercontent' => $headercontent,
'overflow' => $overflow,
];
$nav = $PAGE->flatnav;
$templatecontext['firstcollectionlabel'] = $nav->get_collectionlabel();
+5 -1
View File
@@ -96,6 +96,9 @@ $buildregionmainsettings = !$PAGE->include_region_main_settings_in_header_action
// If the settings menu will be included in the header then don't add it here.
$regionmainsettingsmenu = $buildregionmainsettings ? $OUTPUT->region_main_settings_menu() : false;
$header = $PAGE->activityheader;
$headercontent = $header->export_for_template($renderer);
$templatecontext = [
'sitename' => format_string($SITE->shortname, true, ['context' => context_course::instance(SITEID), "escape" => false]),
'output' => $OUTPUT,
@@ -113,7 +116,8 @@ $templatecontext = [
'forceblockdraweropen' => $forceblockdraweropen,
'regionmainsettingsmenu' => $regionmainsettingsmenu,
'hasregionmainsettingsmenu' => !empty($regionmainsettingsmenu),
'overflow' => $overflow
'overflow' => $overflow,
'headercontent' => $headercontent,
];
$nav = $PAGE->flatnav;
+3
View File
@@ -50,6 +50,9 @@
<div id="region-main-box" class="col-12">
<section id="region-main" aria-label="{{#str}}content{{/str}}">
{{{ output.course_content_header }}}
{{#headercontent}}
{{> core/activity_header }}
{{/headercontent}}
{{{ output.main_content }}}
{{{ output.activity_navigation }}}
{{{ output.course_content_footer }}}
+3
View File
@@ -79,6 +79,9 @@
<div class="region_main_settings_menu_proxy"></div>
{{/hasregionmainsettingsmenu}}
{{{ output.course_content_header }}}
{{#headercontent}}
{{> core/activity_header }}
{{/headercontent}}
{{#overflow}}
{{> core/url_select}}
{{/overflow}}
+3
View File
@@ -140,6 +140,9 @@
<div class="region_main_settings_menu_proxy"></div>
{{/hasregionmainsettingsmenu}}
{{{ output.course_content_header }}}
{{#headercontent}}
{{> core/activity_header }}
{{/headercontent}}
{{#overflow}}
{{> core/url_select}}
{{/overflow}}
+16 -3
View File
@@ -101,13 +101,26 @@ $THEME->layouts = [
'popup' => array(
'file' => 'contentonly.php',
'regions' => array(),
'options' => array('nofooter' => true, 'nonavbar' => true),
'options' => array(
'nofooter' => true,
'nonavbar' => true,
'activityheader' => [
'notitle' => true,
'nocompletion' => true,
'nodescription' => true
]
),
),
// No blocks and minimal footer - used for legacy frame layouts only!
'frametop' => array(
'file' => 'contentonly.php',
'regions' => array(),
'options' => array('nofooter' => true, 'nocoursefooter' => true),
'options' => array(
'nofooter' => true,
'nocoursefooter' => true,
'activityheader' => [
'nocompletion' => true
]),
),
// Embeded pages, like iframe/object embeded in moodleform - it needs as much space as possible.
'embedded' => array(
@@ -127,7 +140,7 @@ $THEME->layouts = [
'print' => array(
'file' => 'contentonly.php',
'regions' => array(),
'options' => array('nofooter' => true, 'nonavbar' => false),
'options' => array('nofooter' => true, 'nonavbar' => false, 'noactivityheader' => true),
),
// The pagelayout used when a redirection is occuring.
'redirect' => array(
+5 -1
View File
@@ -31,6 +31,9 @@ $blockspost = $OUTPUT->blocks('side-post');
$hassidepre = $PAGE->blocks->region_has_content('side-pre', $OUTPUT);
$hassidepost = $PAGE->blocks->region_has_content('side-post', $OUTPUT);
$PAGE->has_secondary_navigation_setter(false);
$renderer = $PAGE->get_renderer('core');
$header = $PAGE->activityheader;
$headercontent = $header->export_for_template($renderer);
$templatecontext = [
'sitename' => format_string($SITE->shortname, true, ['context' => context_course::instance(SITEID), "escape" => false]),
@@ -39,7 +42,8 @@ $templatecontext = [
'sidepostblocks' => $blockspost,
'haspreblocks' => $hassidepre,
'haspostblocks' => $hassidepost,
'bodyattributes' => $bodyattributes
'bodyattributes' => $bodyattributes,
'headercontent' => $headercontent,
];
echo $OUTPUT->render_from_template('theme_classic/columns', $templatecontext);
+5
View File
@@ -31,6 +31,11 @@ $templatecontext = [
'output' => $OUTPUT,
'bodyattributes' => $bodyattributes
];
if (empty($PAGE->layout_options['noactivityheader'])) {
$header = $PAGE->activityheader;
$renderer = $PAGE->get_renderer('core');
$templatecontext['headercontent'] = $header->export_for_template($renderer);
}
echo $OUTPUT->render_from_template('theme_classic/contentonly', $templatecontext);
+3
View File
@@ -71,6 +71,9 @@
<div class="region_main_settings_menu_proxy"></div>
{{/hasregionmainsettingsmenu}}
{{{ output.course_content_header }}}
{{#headercontent}}
{{> core/activity_header }}
{{/headercontent}}
{{{ output.main_content }}}
{{{ output.activity_navigation }}}
{{{ output.course_content_footer }}}
@@ -56,6 +56,9 @@
<div id="region-main-box" class="col-12">
<section id="region-main" aria-label="{{#str}}content{{/str}}">
{{{ output.course_content_header }}}
{{#headercontent}}
{{> core/activity_header }}
{{/headercontent}}
{{{ output.main_content }}}
{{{ output.activity_navigation }}}
{{{ output.course_content_footer }}}
+1 -1
View File
@@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
$version = 2021121000.00; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2021121000.01; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
$release = '4.0dev+ (Build: 20211210)'; // Human-friendly version name