diff --git a/lang/en/moodle.php b/lang/en/moodle.php index 4ca3c691cc4..10e37273878 100644 --- a/lang/en/moodle.php +++ b/lang/en/moodle.php @@ -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'; diff --git a/lib/classes/output/activity_header.php b/lib/classes/output/activity_header.php new file mode 100644 index 00000000000..af1bd2bf904 --- /dev/null +++ b/lib/classes/output/activity_header.php @@ -0,0 +1,184 @@ +. + +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('/]*>([.\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, + ]; + } +} diff --git a/lib/outputlib.php b/lib/outputlib.php index aecd3f47db6..a99c7cdca6a 100644 --- a/lib/outputlib.php +++ b/lib/outputlib.php @@ -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)) { diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php index 534bbfcf969..8d55c21e5ab 100644 --- a/lib/outputrenderers.php +++ b/lib/outputrenderers.php @@ -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; } /** diff --git a/lib/pagelib.php b/lib/pagelib.php index 189ece5d763..eb7a00117f0 100644 --- a/lib/pagelib.php +++ b/lib/pagelib.php @@ -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 diff --git a/lib/templates/activity_header.mustache b/lib/templates/activity_header.mustache new file mode 100644 index 00000000000..f26224b9508 --- /dev/null +++ b/lib/templates/activity_header.mustache @@ -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 . +}} +{{! + @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": "
Some activity completion criteria
", + "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" + } + } +}} + +{{#title}} +

{{{title}}}

+{{/title}} +{{#completion}} + {{#str}} overallaggregation, completion {{/str}} + {{{completion}}} +{{/completion}} +{{#description}} +
+ {{{description}}} +
+{{/description}} +{{#additional_items}} + +{{/additional_items}} diff --git a/theme/boost/config.php b/theme/boost/config.php index ac1c0edbb33..bb67a323795 100644 --- a/theme/boost/config.php +++ b/theme/boost/config.php @@ -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 +]; diff --git a/theme/boost/layout/columns1.php b/theme/boost/layout/columns1.php index d5b041211d7..44d07b53d01 100644 --- a/theme/boost/layout/columns1.php +++ b/theme/boost/layout/columns1.php @@ -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); diff --git a/theme/boost/layout/columns2.php b/theme/boost/layout/columns2.php index 9c34f59575f..57eee18e079 100644 --- a/theme/boost/layout/columns2.php +++ b/theme/boost/layout/columns2.php @@ -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(); diff --git a/theme/boost/layout/drawers.php b/theme/boost/layout/drawers.php index aa74077bbff..17e2e124902 100644 --- a/theme/boost/layout/drawers.php +++ b/theme/boost/layout/drawers.php @@ -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; diff --git a/theme/boost/templates/columns1.mustache b/theme/boost/templates/columns1.mustache index 51fb23efedb..d2965a73b71 100644 --- a/theme/boost/templates/columns1.mustache +++ b/theme/boost/templates/columns1.mustache @@ -50,6 +50,9 @@
{{{ output.course_content_header }}} + {{#headercontent}} + {{> core/activity_header }} + {{/headercontent}} {{{ output.main_content }}} {{{ output.activity_navigation }}} {{{ output.course_content_footer }}} diff --git a/theme/boost/templates/columns2.mustache b/theme/boost/templates/columns2.mustache index 8aa1c386b3d..64ce7ba797a 100644 --- a/theme/boost/templates/columns2.mustache +++ b/theme/boost/templates/columns2.mustache @@ -79,6 +79,9 @@
{{/hasregionmainsettingsmenu}} {{{ output.course_content_header }}} + {{#headercontent}} + {{> core/activity_header }} + {{/headercontent}} {{#overflow}} {{> core/url_select}} {{/overflow}} diff --git a/theme/boost/templates/drawers.mustache b/theme/boost/templates/drawers.mustache index c6a4c3f20fd..4b92080a340 100644 --- a/theme/boost/templates/drawers.mustache +++ b/theme/boost/templates/drawers.mustache @@ -140,6 +140,9 @@
{{/hasregionmainsettingsmenu}} {{{ output.course_content_header }}} + {{#headercontent}} + {{> core/activity_header }} + {{/headercontent}} {{#overflow}} {{> core/url_select}} {{/overflow}} diff --git a/theme/classic/config.php b/theme/classic/config.php index da07493f375..0f6aff0fd3b 100644 --- a/theme/classic/config.php +++ b/theme/classic/config.php @@ -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( diff --git a/theme/classic/layout/columns.php b/theme/classic/layout/columns.php index 61269028113..2d66771e795 100644 --- a/theme/classic/layout/columns.php +++ b/theme/classic/layout/columns.php @@ -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); diff --git a/theme/classic/layout/contentonly.php b/theme/classic/layout/contentonly.php index 253b3da76f8..de7f8f0bd2b 100644 --- a/theme/classic/layout/contentonly.php +++ b/theme/classic/layout/contentonly.php @@ -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); diff --git a/theme/classic/templates/columns.mustache b/theme/classic/templates/columns.mustache index 87dd7774cf3..36b54558b05 100644 --- a/theme/classic/templates/columns.mustache +++ b/theme/classic/templates/columns.mustache @@ -71,6 +71,9 @@
{{/hasregionmainsettingsmenu}} {{{ output.course_content_header }}} + {{#headercontent}} + {{> core/activity_header }} + {{/headercontent}} {{{ output.main_content }}} {{{ output.activity_navigation }}} {{{ output.course_content_footer }}} diff --git a/theme/classic/templates/contentonly.mustache b/theme/classic/templates/contentonly.mustache index 2e4e1f7bb38..b4045d64225 100644 --- a/theme/classic/templates/contentonly.mustache +++ b/theme/classic/templates/contentonly.mustache @@ -56,6 +56,9 @@
{{{ output.course_content_header }}} + {{#headercontent}} + {{> core/activity_header }} + {{/headercontent}} {{{ output.main_content }}} {{{ output.activity_navigation }}} {{{ output.course_content_footer }}} diff --git a/version.php b/version.php index 9bf293d2fbb..42c73d2078d 100644 --- a/version.php +++ b/version.php @@ -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