Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,23 @@ jobs:

services:
postgres:
image: postgres:16
image: ${{ matrix.database == 'pgsql' && 'postgres:17' || '' }}
env:
POSTGRES_USER: 'postgres'
POSTGRES_HOST_AUTH_METHOD: 'trust'
ports:
- 5432:5432
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 3

mariadb:
image: mariadb:10
image: ${{ matrix.database == 'mariadb' && 'mariadb:11' || '' }}
env:
MYSQL_USER: 'root'
MYSQL_ALLOW_EMPTY_PASSWORD: "true"
MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: '1'
MYSQL_CHARACTER_SET_SERVER: "utf8mb4"
MYSQL_COLLATION_SERVER: "utf8mb4_unicode_ci"

ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval 10s --health-timeout 5s --health-retries 3
options: --health-cmd="healthcheck.sh --connect --innodb_initialized" --health-interval 10s --health-timeout 5s --health-retries 3

strategy:
fail-fast: false
Expand All @@ -37,6 +36,12 @@ jobs:
- php: '8.4'
moodle-branch: 'main'
database: 'pgsql'
- php: '8.4'
moodle-branch: 'MOODLE_502_STABLE'
database: 'mariadb'
- php: '8.4'
moodle-branch: 'MOODLE_502_STABLE'
database: 'pgsql'
- php: '8.4'
moodle-branch: 'MOODLE_501_STABLE'
database: 'mariadb'
Expand Down
8 changes: 3 additions & 5 deletions classes/external/get_cm.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
use core_external\external_value;
use core_external\external_single_structure;
use core_external\external_function_parameters;
use core_courseformat\output\local\content\cm\completion;
use mod_learningmap\helper;
/**
* Class get_cm
*
Expand Down Expand Up @@ -91,10 +91,8 @@ public static function execute(int $cmid): array {
$PAGE->activityheader->set_description('');
}

$data['completion'] = $OUTPUT->render_from_template(
'core/activity_header',
$PAGE->activityheader->export_for_template($OUTPUT)
);
$activityheaderdata = $PAGE->activityheader->export_for_template($OUTPUT);
$data['completion'] = helper::render_activity_header_for_modal($activityheaderdata);

$data['name'] = format_string($cm->name, true, ['context' => $context]);

Expand Down
7 changes: 3 additions & 4 deletions classes/external/get_learningmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
use external_value;
use invalid_parameter_exception;
use moodle_exception;
use mod_learningmap\helper;
use required_capability_exception;
use restricted_context_exception;

Expand Down Expand Up @@ -104,10 +105,8 @@ public static function execute(int $cmid): array {
$PAGE->set_cm($cminfo);
$PAGE->set_pagelayout('embedded');

$completion = $OUTPUT->render_from_template(
'core/activity_header',
$PAGE->activityheader->export_for_template($OUTPUT)
);
$activityheaderdata = $PAGE->activityheader->export_for_template($OUTPUT);
$completion = helper::render_activity_header_for_modal($activityheaderdata);

return [
'content' => learningmap_get_learningmap($cminfo),
Expand Down
73 changes: 73 additions & 0 deletions classes/helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,79 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class helper {
/**
* Get the current default activity header description without rendering the full header.
*
* Calling activity_header::export_for_template() here would add completion actions as a side effect.
*
* @return string
*/
public static function get_activity_header_description(): string {
global $PAGE;

$layoutoptions = $PAGE->layout_options['activityheader'] ?? [];
if (
empty($layoutoptions['nodescription'])
&& !empty($PAGE->activityrecord->intro)
&& trim($PAGE->activityrecord->intro)
) {
return format_module_intro($PAGE->activityname, $PAGE->activityrecord, $PAGE->cm->id);
}

return '';
}

/**
* Render the activity header for learningmap modals.
*
* Since Moodle 5.2 the manual completion UI is rendered via the activity header.
* With linear navigation (introduced in Moodle 5.3), the interactive toggle may be
* moved to the sticky footer. Modals have no sticky footer, so we inject a fallback
* toggle into the header actions when needed.
*
* @param array $activityheaderdata Exported activity header template data.
* @return string
*/
public static function render_activity_header_for_modal(array $activityheaderdata): string {
global $PAGE, $OUTPUT;

$headerhtml = $OUTPUT->render_from_template('core/activity_header', $activityheaderdata);

$headeractions = $PAGE->get_header_actions();
if (self::should_add_manual_completion_fallback($activityheaderdata, $headeractions)) {
$headeractions[] = $OUTPUT->render_from_template('core_course/completion_manual', $activityheaderdata);
}

if (!empty($headeractions)) {
$headerhtml .= $OUTPUT->render_from_template('mod_learningmap/modal_header_actions', [
'headeractions' => $headeractions,
]);
}

return $headerhtml;
}

/**
* Check whether a manual completion fallback button should be rendered.
*
* @param array $activityheaderdata Exported activity header template data.
* @param array $headeractions Collected page header actions.
* @return bool
*/
private static function should_add_manual_completion_fallback(array $activityheaderdata, array $headeractions): bool {
if (empty($activityheaderdata['showmanualcompletion']) || empty($activityheaderdata['istrackeduser'])) {
return false;
}

foreach ($headeractions as $headeraction) {
if (str_contains($headeraction, 'data-action="toggle-manual-completion"')) {
return false;
}
}

return true;
}

/**
* Returns whether the map should be shown on the course page.
*
Expand Down
7 changes: 4 additions & 3 deletions classes/local/hook_callbacks.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
use core\hook\output\before_http_headers;
use Exception;
use mod_learningmap\cachemanager;
use mod_learningmap\helper;

/**
* Hook callbacks for mod_learningmap.
Expand All @@ -48,7 +49,7 @@ class hook_callbacks {
* @param before_http_headers $beforehttpheadershook the hook object
*/
public static function inject_backlinks_into_activity_header(before_http_headers $beforehttpheadershook): void {
global $OUTPUT, $PAGE;
global $PAGE;

if (defined('LEARNINGMAP_NO_BACKLINK')) {
return;
Expand Down Expand Up @@ -99,8 +100,8 @@ public static function inject_backlinks_into_activity_header(before_http_headers
}

if ($backlinktext) {
$activityheader = $PAGE->activityheader->export_for_template($OUTPUT);
$PAGE->activityheader->set_description(($activityheader['description'] ?? '') . $backlinktext);
$description = helper::get_activity_header_description();
$PAGE->activityheader->set_description($description . $backlinktext);
}
} catch (Exception $e) {
debugging($e->getMessage());
Expand Down
4 changes: 2 additions & 2 deletions lib.php
Original file line number Diff line number Diff line change
Expand Up @@ -440,8 +440,8 @@ function learningmap_before_http_headers() {
}

if ($backlinktext) {
$activityheader = $PAGE->activityheader->export_for_template($OUTPUT);
$PAGE->activityheader->set_description($activityheader['description'] . $backlinktext);
$description = helper::get_activity_header_description();
$PAGE->activityheader->set_description($description . $backlinktext);
}
} catch (Exception $e) {
debugging($e->getMessage());
Expand Down
36 changes: 36 additions & 0 deletions templates/modal_header_actions.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{{!
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 mod_learningmap/modal_header_actions

Renders header actions for learningmap modals.

Context variables required for this template:
* headeractions - Array of pre-rendered header actions.

Example context (json):
{
"headeractions": [
"<button data-action=\"toggle-manual-completion\">Mark as done</button>"
]
}
}}
<div class="header-actions-container mb-3" data-region="header-actions-container">
{{#headeractions}}
<div class="header-action">{{{.}}}</div>
{{/headeractions}}
</div>
39 changes: 39 additions & 0 deletions tests/mod_learningmap_helper_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
*/
#[\PHPUnit\Framework\Attributes\CoversClass(helper::class)]
#[\PHPUnit\Framework\Attributes\CoversMethod(helper::class, 'repair_learningmap_record')]
#[\PHPUnit\Framework\Attributes\CoversMethod(helper::class, 'render_activity_header_for_modal')]
final class mod_learningmap_helper_test extends \advanced_testcase {
/**
* Tests the repair_learningmap_record method.
Expand Down Expand Up @@ -62,4 +63,42 @@ public function test_repair_learningmap_record(): void {
'The learning map record should not have changed after trying to repair it with a non-existing course.'
);
}

/**
* Tests that the manual completion button is present in rendered modal header HTML.
*
* There have been changes to the placement of the completion information/manual completion button
* across < Mooodle 5.2, Moodle 5.2 and Moodle > 5.2, so this test should make sure there's always
* a completion button for all supported moodle versions.
*/
public function test_render_activity_header_for_modal_manual_completion_button_presence(): void {
global $PAGE, $OUTPUT;

$this->resetAfterTest();
set_config('enablecompletion', 1);

$course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
$user = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
$this->setUser($user);

$label = $this->getDataGenerator()->create_module('label', [
'course' => $course->id,
'completion' => COMPLETION_TRACKING_MANUAL,
]);

$modinfo = get_fast_modinfo($course);
$cminfo = $modinfo->get_cm($label->cmid);
$context = \context_module::instance($cminfo->id);

$PAGE->set_url(new \moodle_url('/mod/label/view.php', ['id' => $cminfo->id]));
$PAGE->set_context($context);
$PAGE->set_cm($cminfo, $course);
$PAGE->set_pagelayout('embedded');

$activityheaderdata = $PAGE->activityheader->export_for_template($OUTPUT);
$html = helper::render_activity_header_for_modal($activityheaderdata);

$this->assertStringContainsString('data-action="toggle-manual-completion"', $html);
}
}
2 changes: 1 addition & 1 deletion version.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,5 @@
$plugin->release = '0.9.16';
$plugin->version = 2026021900;
$plugin->requires = 2022112800;
$plugin->supported = [405, 501];
$plugin->supported = [405, 502];
$plugin->maturity = MATURITY_STABLE;
Loading