Merge branch 'MDL-87662-main' of https://github.com/sarjona/moodle

This commit is contained in:
Huong Nguyen
2026-02-11 11:53:44 +07:00
12 changed files with 175 additions and 19 deletions
@@ -0,0 +1,12 @@
issueNumber: MDL-87662
notes:
theme:
- message: >-
The manual completion button has been moved to the activity header
to improve visibility and proximity to the activity name. A new
theme layout option, `completioninheader`, has been introduced to
control this behaviour and is enabled by default. Themes that set
completioninheader to false must manually override the relevant
template (such as `activity_header` or `activity_info`) to ensure
the completion information is displayed correctly.
type: improved
@@ -302,7 +302,7 @@ class behat_completion extends behat_base {
* @param string $activityname The activity name.
*/
public function the_manual_completion_button_for_activity_should_be_disabled(string $activityname): void {
$selector = "div[data-region='activity-information'][data-activityname='$activityname'] button";
$selector = "button[data-activityname='$activityname'][data-completion='manual']";
$params = [$selector, "css_element"];
$this->execute("behat_general::the_element_should_be_disabled", $params);
@@ -315,7 +315,7 @@ class behat_completion extends behat_base {
* @param string $activityname The activity name.
*/
public function the_manual_completion_button_for_activity_should_not_exist(string $activityname): void {
$selector = "div[data-region=activity-information][data-activityname='$activityname'] button";
$selector = "button[data-activityname='$activityname'][data-completion='manual']";
$params = [$selector, "css_element"];
$this->execute('behat_general::should_not_exist', $params);
@@ -328,7 +328,7 @@ class behat_completion extends behat_base {
* @param string $activityname The activity name.
*/
public function the_manual_completion_button_for_activity_should_exist(string $activityname): void {
$selector = "div[data-region=activity-information][data-activityname='$activityname'] button";
$selector = "button[data-activityname='$activityname'][data-completion='manual']";
$params = [$selector, "css_element"];
$this->execute('behat_general::should_exist', $params);
@@ -53,20 +53,15 @@
{{#hascompletion}}
{{#uservisible}}
<div class="completion-info" data-region="completion-info">
{{#isautomatic}}
{{#isautomatic}}
<div class="completion-info" data-region="completion-info">
<div class="automatic-completion-conditions" data-region ="completionrequirements" role="list" aria-label="{{#str}}completionrequirements, core_course, {{activityname}}{{/str}}">
{{#completiondetails}}
{{> core_course/completion_automatic }}
{{/completiondetails}}
</div>
{{/isautomatic}}
{{^isautomatic}}
{{#showmanualcompletion}}
{{> core_course/completion_manual }}
{{/showmanualcompletion}}
{{/isautomatic}}
</div>
</div>
{{/isautomatic}}
{{/uservisible}}
{{/hascompletion}}
@@ -35,6 +35,7 @@
data-toggletype="manual:undo"
data-cmid="{{cmid}}"
data-activityname="{{activityname}}"
data-completion="manual"
data-withavailability="{{withavailability}}" {{!
}}{{#accessibledescription}}{{!
}}title="{{.}}" {{!
@@ -55,6 +56,7 @@
data-toggletype="manual:mark-done"
data-cmid="{{cmid}}"
data-activityname="{{activityname}}"
data-completion="manual"
data-withavailability="{{withavailability}}" {{!
}}{{#accessibledescription}}{{!
}}title="{{.}}" {{!
@@ -72,6 +74,8 @@
{{^istrackeduser}}
<button
class="btn btn-subtle-body {{^normalbutton}}btn-sm{{/normalbutton}} text-nowrap"
data-activityname="{{activityname}}"
data-completion="manual"
disabled
>
{{#str}} completion_manual:markdone, core_course {{/str}}
+37 -2
View File
@@ -187,6 +187,7 @@ class activity_header implements renderable, templatable {
}
$activityinfo = null;
$activitycompletiondata = [];
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);
@@ -197,6 +198,14 @@ class activity_header implements renderable, templatable {
$activitydatesdata = (array) $activitydates->export_for_template($output);
$data = array_merge($activitycompletiondata, $activitydatesdata);
if (
!empty($data)
&& !empty($data['uservisible'])
&& !empty($data['showmanualcompletion'])
) {
$this->add_manual_completion_to_page_header($output, $data);
}
$activityinfo = $output->render_from_template('core_course/activity_info', $data);
}
@@ -213,12 +222,38 @@ class activity_header implements renderable, templatable {
$additionalitems = $this->additionalnavitems->export_for_template($output);
}
return [
return array_merge([
'title' => $this->title,
'description' => $this->description,
'completion' => $activityinfo,
'additional_items' => $additionalitems,
];
], $activitycompletiondata);
}
/**
* Adds the manual completion component to the page header actions.
*
* @param renderer_base $output
* @param array $data the template data for the completion component
* @return bool if the completion was added
*/
private function add_manual_completion_to_page_header(renderer_base $output, array $data): bool {
// Some themes may not use completion in the header, so we check first.
$showcompletion = $this->page?->layout_options['completioninheader'] ?? true;
if (!$showcompletion) {
return false;
}
$this->page->add_header_action(
$output->render_from_template('core_course/completion_manual', $data)
);
// Also init the heading component to manage feature like manual completion button display.
$this->page->requires->js_call_amd(
'core_courseformat/local/content/activity_header',
'init',
["[data-for='page-heading']"],
);
return true;
}
/**
+2 -2
View File
@@ -29,7 +29,7 @@
"welcomemessage": "welcomemessage"
}
}}
<header id="page-header" class="header-maxwidth d-print-none">
<header id="page-header" class="header-maxwidth d-print-none" data-for="page-heading">
<div class="w-100">
<div class="d-flex flex-wrap">
{{#hasnavbar}}
@@ -62,4 +62,4 @@
</div>
</div>
</div>
</header>
</header>
+1 -1
View File
@@ -48,7 +48,7 @@
<div id="page" class="container-fluid">
{{! Secured full header }}
<header id="page-header" class="row">
<header id="page-header" class="row" data-for="page-heading">
<div class="col-12 py-3">
<div class="page-context-header">
<div class="page-header-headings">
+1
View File
@@ -58,6 +58,7 @@ $THEME->layouts = [
'file' => 'columns.php',
'regions' => array('side-pre', 'side-post'),
'defaultregion' => 'side-pre',
'options' => ['completioninheader' => false],
),
// The site home page.
'frontpage' => array(
@@ -47,3 +47,11 @@
white-space: pre-wrap;
}
}
// Manual completion button in activity header
.header-group {
display: flex;
justify-content: space-between;
align-items: center;
gap: 20px;
}
@@ -0,0 +1,101 @@
{{!
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": "",
"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>
<div class="header-group">
<div class="activity-title">
{{#title}}
<h2>{{{title}}}</h2>
{{/title}}
</div>
{{^isautomatic}}
{{#showmanualcompletion}}
<div class="header-actions">
{{> core_course/completion_manual }}
</div>
{{/showmanualcompletion}}
{{/isautomatic}}
</div>
<div class="activity-header" data-for="page-activity-header">{{!
}}{{#completion}}
<span class="visually-hidden">{{#str}} overallaggregation, completion {{/str}}</span>
{{{completion}}}
{{/completion}}
{{#description}}
<div class="activity-description" id="intro">
{{{description}}}
</div>
{{/description}}{{!
}}</div>
{{#additional_items}}
<nav aria-label="{{#str}} additionalcustomnav, core {{/str}}">
{{> core/url_select}}
</nav>
{{/additional_items}}
@@ -28,7 +28,7 @@
"welcomemessage": "welcomemessage"
}
}}
<header id="page-header" class="row">
<header id="page-header" class="row" data-for="page-heading">
<div class="col-12 pt-3 pb-3">
<div class="card {{^contextheader}}border-0 bg-transparent{{/contextheader}}">
<div class="card-body {{^contextheader}}p-2{{/contextheader}}">
@@ -59,7 +59,7 @@
<div id="page" class="container-fluid">
{{! Secured full header }}
<header id="page-header" class="row">
<header id="page-header" class="row" data-for="page-heading">
<div class="col-12 py-3">
<div class="page-context-header">
<div class="page-header-headings">