diff --git a/admin/classes/local/entities/task_log.php b/admin/classes/local/entities/task_log.php
index 059f1196b64..41e7dc26f10 100644
--- a/admin/classes/local/entities/task_log.php
+++ b/admin/classes/local/entities/task_log.php
@@ -107,15 +107,15 @@ class task_log extends base {
->set_type(column::TYPE_TEXT)
->add_field("$tablealias.classname")
->set_is_sortable(true)
- ->add_callback(static function(string $value): string {
+ ->add_callback(static function(string $classname): string {
$output = '';
- if (class_exists($value)) {
- $task = new $value;
- if ($task instanceof \core\task\scheduled_task) {
+ if (class_exists($classname)) {
+ $task = new $classname;
+ if ($task instanceof \core\task\task_base) {
$output = $task->get_name();
}
}
- $output .= \html_writer::tag('div', "\\{$value}", [
+ $output .= \html_writer::tag('div', "\\{$classname}", [
'class' => 'task-class',
]);
return $output;
diff --git a/admin/renderer.php b/admin/renderer.php
index 57d4f98c76f..0c66010ed73 100644
--- a/admin/renderer.php
+++ b/admin/renderer.php
@@ -2080,7 +2080,7 @@ class core_admin_renderer extends plugin_renderer_base {
$errorline = true;
} else {
if ($status) { //Handle ok result (ok)
- $status = get_string('ok');
+ $status = get_string('statusok');
} else {
if ($environment_result->getLevel() == 'optional') {//Handle check result (warning)
$status = get_string('check');
diff --git a/admin/tool/componentlibrary/thirdpartylibs.xml b/admin/tool/componentlibrary/thirdpartylibs.xml
index 6309cf294e2..87cc1c5be53 100644
--- a/admin/tool/componentlibrary/thirdpartylibs.xml
+++ b/admin/tool/componentlibrary/thirdpartylibs.xml
@@ -3,8 +3,9 @@
amd/src/lunr.js
lunr - http://lunrjs.com
- MIT
+ Provide a great search experience without the need for external, server-side, search services.
2.3.8
-
+ MIT
+ https://github.com/olivernn/lunr.js
diff --git a/admin/tool/mobile/classes/api.php b/admin/tool/mobile/classes/api.php
index 949309f6819..d70d3a404cc 100644
--- a/admin/tool/mobile/classes/api.php
+++ b/admin/tool/mobile/classes/api.php
@@ -344,6 +344,10 @@ class api {
$settings->enabledashboard = $CFG->enabledashboard;
}
+ if (empty($section) || $section === 'themesettings') {
+ $settings->customusermenuitems = $CFG->customusermenuitems;
+ }
+
return $settings;
}
diff --git a/admin/tool/mobile/tests/externallib_test.php b/admin/tool/mobile/tests/externallib_test.php
index a3af5eec152..b6d15d2fae6 100644
--- a/admin/tool/mobile/tests/externallib_test.php
+++ b/admin/tool/mobile/tests/externallib_test.php
@@ -234,6 +234,7 @@ class externallib_test extends externallib_advanced_testcase {
$expected[] = ['name' => 'coursegraceperiodbefore', 'value' => $CFG->coursegraceperiodbefore];
$expected[] = ['name' => 'enabledashboard', 'value' => $CFG->enabledashboard];
+ $expected[] = ['name' => 'customusermenuitems', 'value' => $CFG->customusermenuitems];
$this->assertCount(0, $result['warnings']);
$this->assertEquals($expected, $result['settings']);
diff --git a/admin/tool/mobile/upgrade.txt b/admin/tool/mobile/upgrade.txt
index 3328072493d..9495d7ac1cd 100644
--- a/admin/tool/mobile/upgrade.txt
+++ b/admin/tool/mobile/upgrade.txt
@@ -1,6 +1,10 @@
This files describes changes in tool_mobile code.
Information provided here is intended especially for developers.
+=== 4.1 ===
+
+* External function tool_mobile::get_config now returns the customusermenuitems setting.
+
=== 4.0 ===
* The function tool_mobile\api::get_qrlogin_key() now requires as parameter an object with all the mobile plugin settings.
diff --git a/admin/tool/policy/thirdpartylibs.xml b/admin/tool/policy/thirdpartylibs.xml
index bcc98f2a56e..715096ef216 100644
--- a/admin/tool/policy/thirdpartylibs.xml
+++ b/admin/tool/policy/thirdpartylibs.xml
@@ -3,8 +3,13 @@
amd/src/jquery-eu-cookie-law-popup.js
jQuery EU Cookie Law popups
- MIT
+ An easy-to-install jQuery plugin to create EU Cookie Law popups and for GDPR compliance.
1.1.3
-
+ MIT
+ https://github.com/wimagguc/jquery-eu-cookie-law-popup
+
+ 2015 Richard Dancsi
+
+
diff --git a/auth/cas/thirdpartylibs.xml b/auth/cas/thirdpartylibs.xml
index 8a0579cd424..49403c1d7e8 100644
--- a/auth/cas/thirdpartylibs.xml
+++ b/auth/cas/thirdpartylibs.xml
@@ -3,8 +3,13 @@
CAS
CAS
- Apache
+ phpCAS library to support CAS authentication plugin.
1.4.0
+ Apache
2.0
+ https://github.com/apereo/phpCAS
+
+ 2007-2020, Apereo Foundation
+
diff --git a/backup/util/helper/backup_cron_helper.class.php b/backup/util/helper/backup_cron_helper.class.php
index f21631d182e..ff7610398b9 100644
--- a/backup/util/helper/backup_cron_helper.class.php
+++ b/backup/util/helper/backup_cron_helper.class.php
@@ -202,7 +202,7 @@ abstract class backup_cron_automated_helper {
$message .= get_string('summary') . "\n";
$message .= "==================================================\n";
$message .= ' ' . get_string('courses') . ': ' . array_sum($count) . "\n";
- $message .= ' ' . get_string('ok') . ': ' . $count[self::BACKUP_STATUS_OK] . "\n";
+ $message .= ' ' . get_string('statusok') . ': ' . $count[self::BACKUP_STATUS_OK] . "\n";
$message .= ' ' . get_string('skipped') . ': ' . $count[self::BACKUP_STATUS_SKIPPED] . "\n";
$message .= ' ' . get_string('error') . ': ' . $count[self::BACKUP_STATUS_ERROR] . "\n";
$message .= ' ' . get_string('unfinished') . ': ' . $count[self::BACKUP_STATUS_UNFINISHED] . "\n";
diff --git a/backup/util/ui/tests/behat/restore_moodle2_courses.feature b/backup/util/ui/tests/behat/restore_moodle2_courses.feature
index 949192f6c6e..f14bf80cfe8 100644
--- a/backup/util/ui/tests/behat/restore_moodle2_courses.feature
+++ b/backup/util/ui/tests/behat/restore_moodle2_courses.feature
@@ -12,9 +12,10 @@ Feature: Restore Moodle 2 course backups
| Course 3 | C3 | 0 | topics | 2 | 0 |
| Course 4 | C4 | 0 | topics | 20 | 0 |
And the following "activities" exist:
- | activity | course | idnumber | name | intro | section |
- | assign | C3 | assign1 | Test assign name | Assign description | 1 |
- | data | C3 | data1 | Test database name | Database description | 2 |
+ | activity | course | idnumber | name | intro | section | externalurl |
+ | assign | C3 | assign1 | Test assign name | Assign description | 1 | |
+ | data | C3 | data1 | Test database name | Database description | 2 | |
+ | url | C1 | url1 | Test URL name | Test URL description | 3 | http://www.moodle.org |
And I log in as "admin"
And I am on "Course 1" course homepage with editing mode on
And I add a "Forum" to section "1" and I fill the form with:
@@ -108,11 +109,7 @@ Feature: Restore Moodle 2 course backups
@javascript
Scenario: Restore a backup in an existing course retaining the backup course settings
- Given I add a "URL" to section "3" and I fill the form with:
- | Name | Test URL name |
- | Description | Test URL description |
- | id_externalurl | http://www.moodle.org |
- And I hide section "3"
+ Given I hide section "3"
And I hide section "7"
When I backup "Course 1" course using this options:
| Confirmation | Filename | test_backup.mbz |
@@ -134,11 +131,7 @@ Feature: Restore Moodle 2 course backups
@javascript
Scenario: Restore a backup in an existing course keeping the target course settings
- Given I add a "URL" to section "3" and I fill the form with:
- | Name | Test URL name |
- | Description | Test URL description |
- | id_externalurl | http://www.moodle.org |
- And I hide section "3"
+ Given I hide section "3"
And I hide section "7"
When I backup "Course 1" course using this options:
| Confirmation | Filename | test_backup.mbz |
@@ -160,11 +153,7 @@ Feature: Restore Moodle 2 course backups
@javascript
Scenario: Restore a backup in an existing course deleting contents and retaining the backup course settings
- Given I add a "URL" to section "3" and I fill the form with:
- | Name | Test URL name |
- | Description | Test URL description |
- | id_externalurl | http://www.moodle.org |
- And I hide section "3"
+ Given I hide section "3"
And I hide section "7"
When I backup "Course 1" course using this options:
| Initial | Include enrolled users | 0 |
@@ -188,11 +177,7 @@ Feature: Restore Moodle 2 course backups
@javascript
Scenario: Restore a backup in an existing course deleting contents and keeping the current course settings
- Given I add a "URL" to section "3" and I fill the form with:
- | Name | Test URL name |
- | Description | Test URL description |
- | id_externalurl | http://www.moodle.org |
- And I hide section "3"
+ Given I hide section "3"
And I hide section "7"
When I backup "Course 1" course using this options:
| Initial | Include enrolled users | 0 |
@@ -216,11 +201,7 @@ Feature: Restore Moodle 2 course backups
@javascript
Scenario: Restore a backup in an existing course deleting contents decreasing the number of sections
- Given I add a "URL" to section "3" and I fill the form with:
- | Name | Test URL name |
- | Description | Test URL description |
- | id_externalurl | http://www.moodle.org |
- And I hide section "3"
+ Given I hide section "3"
And I hide section "7"
When I backup "Course 1" course using this options:
| Initial | Include enrolled users | 0 |
diff --git a/badges/classes/reportbuilder/local/entities/badge.php b/badges/classes/reportbuilder/local/entities/badge.php
index 44bdb2f2d39..5fceb3980bc 100644
--- a/badges/classes/reportbuilder/local/entities/badge.php
+++ b/badges/classes/reportbuilder/local/entities/badge.php
@@ -19,6 +19,7 @@ declare(strict_types=1);
namespace core_badges\reportbuilder\local\entities;
use context_course;
+use context_helper;
use context_system;
use html_writer;
use lang_string;
@@ -48,7 +49,10 @@ class badge extends base {
* @return array
*/
protected function get_default_table_aliases(): array {
- return ['badge' => 'b'];
+ return [
+ 'badge' => 'b',
+ 'context' => 'bctx',
+ ];
}
/**
@@ -89,6 +93,7 @@ class badge extends base {
*/
protected function get_all_columns(): array {
$badgealias = $this->get_table_alias('badge');
+ $contextalias = $this->get_table_alias('context');
// Name.
$columns[] = (new column(
@@ -101,7 +106,7 @@ class badge extends base {
->add_field("{$badgealias}.name")
->set_is_sortable(true);
- // Description.
+ // Description (note, this column contains plaintext so requires no post-processing).
$columns[] = (new column(
'description',
new lang_string('description', 'core_badges'),
@@ -137,13 +142,20 @@ class badge extends base {
$this->get_entity_name()
))
->add_joins($this->get_joins())
+ ->add_join("LEFT JOIN {context} {$contextalias}
+ ON {$contextalias}.contextlevel = " . CONTEXT_COURSE . "
+ AND {$contextalias}.instanceid = {$badgealias}.courseid")
->set_type(column::TYPE_INTEGER)
->add_fields("{$badgealias}.id, {$badgealias}.type, {$badgealias}.courseid, {$badgealias}.imagecaption")
+ ->add_fields(context_helper::get_preload_record_columns_sql($contextalias))
->set_disabled_aggregation_all()
->add_callback(static function(int $badgeid, stdClass $badge): string {
- $context = $badge->type == BADGE_TYPE_SITE
- ? context_system::instance()
- : context_course::instance($badge->courseid);
+ if ($badge->type == BADGE_TYPE_SITE) {
+ $context = context_system::instance();
+ } else {
+ context_helper::preload_from_record($badge);
+ $context = context_course::instance($badge->courseid);
+ }
$badgeimage = moodle_url::make_pluginfile_url($context->id, 'badges', 'badgeimage', $badgeid, '/', 'f2');
return html_writer::img($badgeimage, $badge->imagecaption);
diff --git a/badges/tests/generator/lib.php b/badges/tests/generator/lib.php
new file mode 100644
index 00000000000..6f990c189b2
--- /dev/null
+++ b/badges/tests/generator/lib.php
@@ -0,0 +1,76 @@
+.
+
+declare(strict_types=1);
+
+use core_badges\badge;
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once("{$CFG->libdir}/badgeslib.php");
+
+/**
+ * Badges test generator
+ *
+ * @package core_badges
+ * @copyright 2022 Paul Holden
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_badges_generator extends component_generator_base {
+
+ /**
+ * Create badge
+ *
+ * TODO: MDL-73648 Use from Behat too
+ *
+ * @param array|stdClass $record
+ */
+ public function create_badge($record): badge {
+ global $DB, $USER;
+
+ $record = (object) array_merge([
+ 'name' => 'Test badge',
+ 'description' => 'Testing badges',
+ 'timecreated' => time(),
+ 'timemodified' => time(),
+ 'usercreated' => $USER->id,
+ 'usermodified' => $USER->id,
+ 'issuername' => 'Test issuer',
+ 'issuerurl' => 'http://issuer-url.domain.co.nz',
+ 'issuercontact' => 'issuer@example.com',
+ 'expiredate' => null,
+ 'expireperiod' => null,
+ 'type' => BADGE_TYPE_SITE,
+ 'courseid' => null,
+ 'messagesubject' => 'Test message subject',
+ 'message' => 'Test message body',
+ 'attachment' => 1,
+ 'notification' => 0,
+ 'status' => BADGE_STATUS_ACTIVE,
+ 'version' => OPEN_BADGES_V2,
+ 'language' => 'en',
+ 'imageauthorname' => 'Image author',
+ 'imageauthoremail' => 'author@example.com',
+ 'imageauthorurl' => 'http://image.example.com/',
+ 'imagecaption' => 'Image caption'
+ ], (array) $record);
+
+ $record->id = $DB->insert_record('badge', $record);
+
+ return new badge($record->id);
+ }
+}
diff --git a/badges/tests/reportbuilder/datasource/badges_test.php b/badges/tests/reportbuilder/datasource/badges_test.php
index 7a420a0c1b1..fc7d60ceb74 100644
--- a/badges/tests/reportbuilder/datasource/badges_test.php
+++ b/badges/tests/reportbuilder/datasource/badges_test.php
@@ -18,7 +18,7 @@ declare(strict_types=1);
namespace core_badges\reportbuilder\datasource;
-use core_badges\badge;
+use core_badges_generator;
use core_reportbuilder_generator;
use core_reportbuilder_testcase;
@@ -56,13 +56,16 @@ class badges_test extends core_reportbuilder_testcase {
$user1 = $this->getDataGenerator()->create_user(['firstname' => 'Alan', 'lastname' => 'Apple']);
$user2 = $this->getDataGenerator()->create_user(['firstname' => 'Barry', 'lastname' => 'Banana']);
- $sitebadge = $this->create_badge(['name' => 'Badge 1']);
+ /** @var core_badges_generator $generator */
+ $generator = $this->getDataGenerator()->get_plugin_generator('core_badges');
+
+ $sitebadge = $generator->create_badge(['name' => 'Badge 1']);
$sitebadge->issue($user1->id, true);
$sitebadge->issue($user2->id, true);
// Another badge, in a course, no issues.
$course = $this->getDataGenerator()->create_course();
- $coursebadge = $this->create_badge(['name' => 'Badge 2', 'type' => BADGE_TYPE_COURSE, 'courseid' => $course->id]);
+ $coursebadge = $generator->create_badge(['name' => 'Badge 2', 'type' => BADGE_TYPE_COURSE, 'courseid' => $course->id]);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
@@ -102,8 +105,11 @@ class badges_test extends core_reportbuilder_testcase {
$course = $this->getDataGenerator()->create_course(['tags' => ['horse']]);
$user = $this->getDataGenerator()->create_user(['interests' => ['pie']]);
+ /** @var core_badges_generator $generator */
+ $generator = $this->getDataGenerator()->get_plugin_generator('core_badges');
+
// Create course badge, issue to user.
- $badge = $this->create_badge(['name' => 'Course badge', 'type' => BADGE_TYPE_COURSE, 'courseid' => $course->id]);
+ $badge = $generator->create_badge(['name' => 'Course badge', 'type' => BADGE_TYPE_COURSE, 'courseid' => $course->id]);
$badge->issue($user->id, true);
/** @var core_reportbuilder_generator $generator */
@@ -128,45 +134,4 @@ class badges_test extends core_reportbuilder_testcase {
'pie',
], array_values($content[0]));
}
-
- /**
- * Helper method to create a badge
- *
- * @param array $params
- * @return badge
- */
- protected function create_badge(array $params = []): badge {
- global $DB, $USER;
-
- $record = (object) array_merge([
- 'name' => 'Test badge',
- 'description' => 'Testing badges',
- 'timecreated' => time(),
- 'timemodified' => time(),
- 'usercreated' => $USER->id,
- 'usermodified' => $USER->id,
- 'issuername' => 'Test issuer',
- 'issuerurl' => 'http://issuer-url.domain.co.nz',
- 'issuercontact' => 'issuer@example.com',
- 'expiredate' => null,
- 'expireperiod' => null,
- 'type' => BADGE_TYPE_SITE,
- 'courseid' => null,
- 'messagesubject' => 'Test message subject',
- 'message' => 'Test message body',
- 'attachment' => 1,
- 'notification' => 0,
- 'status' => BADGE_STATUS_ACTIVE,
- 'version' => OPEN_BADGES_V2,
- 'language' => 'en',
- 'imageauthorname' => 'Image author',
- 'imageauthoremail' => 'author@example.com',
- 'imageauthorurl' => 'http://image.example.com/',
- 'imagecaption' => 'Image caption'
- ], $params);
-
- $record->id = $DB->insert_record('badge', $record);
-
- return new badge($record->id);
- }
}
diff --git a/blocks/site_main_menu/tests/behat/add_url.feature b/blocks/site_main_menu/tests/behat/add_url.feature
index 779e862fe42..cd4483cf748 100644
--- a/blocks/site_main_menu/tests/behat/add_url.feature
+++ b/blocks/site_main_menu/tests/behat/add_url.feature
@@ -10,11 +10,18 @@ Feature: Add URL to main menu block
And I am on site homepage
And I turn editing mode on
And I add the "Main menu" block
- When I add a "URL" to section "0" and I fill the form with:
- | Name | reference link |
- | Description | mooooooooodle |
- | External URL | http://www.moodle.com |
- | id_display | In pop-up |
+ And the following "activity" exists:
+ | activity | url |
+ | course | Acceptance test site |
+ | name | reference link |
+ | intro | mooooooooodle |
+ | externalurl | http://www.moodle.com |
+ | section | 0 |
+ When I am on the "reference link" "url activity editing" page
+ And I expand all fieldsets
+ And I set the following fields to these values:
+ | id_display | In pop-up |
+ And I press "Save and return to course"
Then "reference link" "link" should exist in the "Main menu" "block"
And "Add an activity or resource" "button" should exist in the "Main menu" "block"
@@ -33,11 +40,18 @@ Feature: Add URL to main menu block
And I set the following fields to these values:
| Page contexts | Display throughout the entire site |
And I press "Save changes"
- And I add a "URL" to section "0" and I fill the form with:
- | Name | reference link |
- | Description | mooooooooodle |
- | External URL | http://www.moodle.com |
- | id_display | Embed |
+ And the following "activity" exists:
+ | activity | url |
+ | course | Acceptance test site |
+ | name | reference link |
+ | intro | mooooooooodle |
+ | externalurl | http://www.moodle.com |
+ | section | 0 |
+ And I am on the "reference link" "url activity editing" page
+ And I expand all fieldsets
+ And I set the following fields to these values:
+ | id_display | Embed |
+ And I press "Save and return to course"
Then I click on "reference link" "link" in the "Main menu" "block"
And "reference link" "link" should exist in the "Main menu" "block"
And I am on the "C1" "Course" page
@@ -60,11 +74,18 @@ Feature: Add URL to main menu block
And I set the following fields to these values:
| Page contexts | Display on the site home and any pages added to the site home. |
And I press "Save changes"
- And I add a "URL" to section "0" and I fill the form with:
- | Name | reference link |
- | Description | mooooooooodle |
- | External URL | http://www.moodle.com |
- | id_display | Embed |
+ And the following "activity" exists:
+ | activity | url |
+ | course | Acceptance test site |
+ | name | reference link |
+ | intro | mooooooooodle |
+ | externalurl | http://www.moodle.com |
+ | section | 0 |
+ And I am on the "reference link" "url activity editing" page
+ And I expand all fieldsets
+ And I set the following fields to these values:
+ | id_display | Embed |
+ And I press "Save and return to course"
Then I click on "reference link" "link" in the "Main menu" "block"
And "reference link" "link" should exist in the "Main menu" "block"
And I am on the "C1" "Course" page
@@ -87,12 +108,18 @@ Feature: Add URL to main menu block
And I set the following fields to these values:
| Page contexts | Display throughout the entire site |
And I press "Save changes"
- When I am on the "C1" Course page
- And I add a "URL" to section "0" and I fill the form with:
- | Name | reference link |
- | Description | mooooooooodle |
- | External URL | http://www.moodle.com |
- | Display description on course page | 1 |
- | id_display | In pop-up |
+ When the following "activity" exists:
+ | activity | url |
+ | course | C1 |
+ | name | reference link |
+ | intro | mooooooooodle |
+ | externalurl | http://www.moodle.com |
+ | section | 0 |
+ | showdescription | 1 |
+ And I am on the "reference link" "url activity editing" page
+ And I expand all fieldsets
+ And I set the following fields to these values:
+ | id_display | In pop-up |
+ And I press "Save and return to course"
Then "reference link" "link" should not exist in the "Main menu" "block"
And I should see "mooooooooodle" in the "region-main" "region"
diff --git a/cache/stores/mongodb/thirdpartylibs.xml b/cache/stores/mongodb/thirdpartylibs.xml
index 48540c05e75..3a4f8a0115c 100644
--- a/cache/stores/mongodb/thirdpartylibs.xml
+++ b/cache/stores/mongodb/thirdpartylibs.xml
@@ -3,8 +3,10 @@
MongoDB
MongoDB PHP Library
- Apache
+ This library provides a high-level abstraction around the lower-level PHP driver, also known as the mongodb extension.
1.8.0
+ Apache
2.0
+ https://github.com/mongodb/mongo-php-driver
-
\ No newline at end of file
+
diff --git a/cohort/classes/local/entities/cohort.php b/cohort/classes/local/entities/cohort.php
index f70c644dd21..df8f11a8cb5 100644
--- a/cohort/classes/local/entities/cohort.php
+++ b/cohort/classes/local/entities/cohort.php
@@ -45,7 +45,10 @@ class cohort extends base {
* @return array
*/
protected function get_default_table_aliases(): array {
- return ['cohort' => 'c'];
+ return [
+ 'cohort' => 'c',
+ 'context' => 'chctx',
+ ];
}
/**
@@ -86,6 +89,7 @@ class cohort extends base {
*/
protected function get_all_columns(): array {
$tablealias = $this->get_table_alias('cohort');
+ $contextalias = $this->get_table_alias('context');
// Category/context column.
$columns[] = (new column(
@@ -94,11 +98,13 @@ class cohort extends base {
$this->get_entity_name()
))
->add_joins($this->get_joins())
+ ->add_join("JOIN {context} {$contextalias} ON {$contextalias}.id = {$tablealias}.contextid")
->set_type(column::TYPE_TEXT)
- ->add_fields("{$tablealias}.contextid")
+ ->add_fields("{$tablealias}.contextid, " . context_helper::get_preload_record_columns_sql($contextalias))
->set_is_sortable(true)
- ->add_callback(static function($contextid): string {
- return context::instance_by_id((int) $contextid)->get_context_name(false);
+ ->add_callback(static function($contextid, stdClass $cohort): string {
+ context_helper::preload_from_record($cohort);
+ return context::instance_by_id($cohort->contextid)->get_context_name(false);
});
// Name column.
@@ -130,8 +136,10 @@ class cohort extends base {
$this->get_entity_name()
))
->add_joins($this->get_joins())
- ->set_type(column::TYPE_TEXT)
+ ->add_join("JOIN {context} {$contextalias} ON {$contextalias}.id = {$tablealias}.contextid")
+ ->set_type(column::TYPE_LONGTEXT)
->add_fields("{$tablealias}.description, {$tablealias}.descriptionformat, {$tablealias}.id, {$tablealias}.contextid")
+ ->add_fields(context_helper::get_preload_record_columns_sql($contextalias))
->add_callback(static function(?string $description, stdClass $cohort): string {
global $CFG;
require_once("{$CFG->libdir}/filelib.php");
@@ -140,12 +148,14 @@ class cohort extends base {
return '';
}
- $description = file_rewrite_pluginfile_urls($description, 'pluginfile.php', $cohort->contextid, 'cohort',
+ context_helper::preload_from_record($cohort);
+ $context = context::instance_by_id($cohort->contextid);
+
+ $description = file_rewrite_pluginfile_urls($description, 'pluginfile.php', $context->id, 'cohort',
'description', $cohort->id);
- return format_text($description, $cohort->descriptionformat, ['context' => $cohort->contextid]);
- })
- ->set_is_sortable(false);
+ return format_text($description, $cohort->descriptionformat, ['context' => $context->id]);
+ });
// Visible column.
$columns[] = (new column(
diff --git a/course/classes/local/entities/course_category.php b/course/classes/local/entities/course_category.php
index 6108a0a6228..7fb995c4293 100644
--- a/course/classes/local/entities/course_category.php
+++ b/course/classes/local/entities/course_category.php
@@ -18,7 +18,11 @@ declare(strict_types=1);
namespace core_course\local\entities;
+use context_coursecat;
+use context_helper;
+use html_writer;
use lang_string;
+use moodle_url;
use stdClass;
use core_course_category;
use core_reportbuilder\local\entities\base;
@@ -101,6 +105,26 @@ class course_category extends base {
})
->set_is_sortable(true);
+ // Category name with link column.
+ $columns[] = (new column(
+ 'namewithlink',
+ new lang_string('namewithlink', 'core_course'),
+ $this->get_entity_name()
+ ))
+ ->add_joins($this->get_joins())
+ ->add_join($this->get_context_join())
+ ->set_type(column::TYPE_TEXT)
+ ->add_fields("{$tablealias}.name, {$tablealias}.id")
+ ->add_fields(context_helper::get_preload_record_columns_sql($tablealiascontext))
+ ->add_callback(static function(string $name, stdClass $category): string {
+ context_helper::preload_from_record($category);
+ $context = context_coursecat::instance($category->id);
+ $url = new moodle_url('/course/management.php', ['categoryid' => $category->id]);
+ return html_writer::link($url,
+ format_string($category->name, true, ['context' => $context]));
+ })
+ ->set_is_sortable(true);
+
// Path column.
$columns[] = (new column(
'path',
@@ -134,12 +158,10 @@ class course_category extends base {
$this->get_entity_name()
))
->add_joins($this->get_joins())
- ->add_join("
- JOIN {context} {$tablealiascontext}
- ON {$tablealiascontext}.instanceid = {$tablealias}.id
- AND {$tablealiascontext}.contextlevel = " . CONTEXT_COURSECAT)
- ->set_type(column::TYPE_TEXT)
- ->add_fields("{$tablealias}.description, {$tablealias}.descriptionformat, {$tablealiascontext}.id AS contextid")
+ ->add_join($this->get_context_join())
+ ->set_type(column::TYPE_LONGTEXT)
+ ->add_fields("{$tablealias}.description, {$tablealias}.descriptionformat, {$tablealias}.id")
+ ->add_fields(context_helper::get_preload_record_columns_sql($tablealiascontext))
->add_callback(static function(?string $description, stdClass $category): string {
global $CFG;
require_once("{$CFG->libdir}/filelib.php");
@@ -148,12 +170,14 @@ class course_category extends base {
return '';
}
- $description = file_rewrite_pluginfile_urls($description, 'pluginfile.php', $category->contextid, 'coursecat',
+ context_helper::preload_from_record($category);
+ $context = context_coursecat::instance($category->id);
+
+ $description = file_rewrite_pluginfile_urls($description, 'pluginfile.php', $context->id, 'coursecat',
'description', null);
- return format_text($description, $category->descriptionformat, ['context' => $category->contextid]);
- })
- ->set_is_sortable(false);
+ return format_text($description, $category->descriptionformat, ['context' => $context->id]);
+ });
return $columns;
}
@@ -201,4 +225,16 @@ class course_category extends base {
return $filters;
}
+
+ /**
+ * Return context join used by columns
+ *
+ * @return string
+ */
+ private function get_context_join(): string {
+ $coursecategories = $this->get_table_alias('course_categories');
+ $context = $this->get_table_alias('context');
+ return "LEFT JOIN {context} {$context} ON {$context}.instanceid = {$coursecategories}.id
+ AND {$context}.contextlevel = " . CONTEXT_COURSECAT;
+ }
}
diff --git a/course/classes/local/repository/content_item_readonly_repository.php b/course/classes/local/repository/content_item_readonly_repository.php
index 54e58156d54..32bd29d2880 100644
--- a/course/classes/local/repository/content_item_readonly_repository.php
+++ b/course/classes/local/repository/content_item_readonly_repository.php
@@ -61,63 +61,6 @@ class content_item_readonly_repository implements content_item_readonly_reposito
return $help;
}
- /**
- * Create a content_item object based on legacy data returned from the get_shortcuts hook implementations.
- *
- * @param \stdClass $item the stdClass of legacy data.
- * @return content_item a content item object.
- */
- private function content_item_from_legacy_data(\stdClass $item): content_item {
- global $OUTPUT;
-
- // Make sure the legacy data results in a content_item with id = 0.
- // Even with an id, we can't uniquely identify the item, because we can't guarantee what component it came from.
- // An id of -1, signifies this.
- $item->id = -1;
-
- // If the module provides the helplink property, append it to the help text to match the look and feel
- // of the default course modules.
- if (isset($item->help) && isset($item->helplink)) {
- $linktext = get_string('morehelp');
- $item->help .= \html_writer::tag('div',
- $OUTPUT->doc_link($item->helplink, $linktext, true), ['class' => 'helpdoclink']);
- }
-
- if (is_string($item->title)) {
- $item->title = new string_title($item->title);
- } else if ($item->title instanceof \lang_string) {
- $item->title = new lang_string_title($item->title->get_identifier(), $item->title->get_component());
- }
-
- // Legacy items had names which are in one of 2 forms:
- // modname, i.e. 'assign' or
- // modname:link, i.e. lti:http://etc...
- // We need to grab the module name out to create the componentname.
- $modname = (strpos($item->name, ':') !== false) ? explode(':', $item->name)[0] : $item->name;
- $purpose = plugin_supports('mod', $modname, FEATURE_MOD_PURPOSE, MOD_PURPOSE_OTHER);
- return new content_item($item->id, $item->name, $item->title, $item->link, $item->icon, $item->help ?? '',
- $item->archetype, 'mod_' . $modname, $purpose);
- }
-
- /**
- * Create a stdClass type object based on a content_item instance.
- *
- * @param content_item $contentitem
- * @return \stdClass the legacy data.
- */
- private function content_item_to_legacy_data(content_item $contentitem): \stdClass {
- $item = new \stdClass();
- $item->id = $contentitem->get_id();
- $item->name = $contentitem->get_name();
- $item->title = $contentitem->get_title();
- $item->link = $contentitem->get_link();
- $item->icon = $contentitem->get_icon();
- $item->help = $contentitem->get_help();
- $item->archetype = $contentitem->get_archetype();
- $item->componentname = $contentitem->get_component_name();
- return $item;
- }
-
/**
* Helper to get the contentitems from all subplugin hooks for a given module plugin.
*
@@ -165,20 +108,6 @@ class content_item_readonly_repository implements content_item_readonly_reposito
return $contentitems;
}
- /**
- * Helper to make sure any legacy items have certain properties, which, if missing are inherited from the parent module item.
- *
- * @param \stdClass $legacyitem the legacy information, a stdClass coming from get_shortcuts() hook.
- * @param content_item $modulecontentitem The module's content item information, to inherit if needed.
- * @return \stdClass the updated legacy item stdClass
- */
- private function legacy_item_inherit_missing(\stdClass $legacyitem, content_item $modulecontentitem): \stdClass {
- // Fall back to the plugin parent value if the subtype didn't provide anything.
- $legacyitem->archetype = $legacyitem->archetype ?? $modulecontentitem->get_archetype();
- $legacyitem->icon = $legacyitem->icon ?? $modulecontentitem->get_icon();
- return $legacyitem;
- }
-
/**
* Find all the available content items, not restricted to course or user.
*
@@ -254,9 +183,6 @@ class content_item_readonly_repository implements content_item_readonly_reposito
$modules = $DB->get_records('modules', ['visible' => 1]);
$return = [];
- // A moodle_url is expected and required by modules in their implementation of the hook 'get_shortcuts'.
- $urlbase = new \moodle_url('/course/mod.php', ['id' => $course->id]);
-
// Now, generate the content_items.
foreach ($modules as $modid => $mod) {
// Exclude modules if the code doesn't exist.
@@ -273,7 +199,7 @@ class content_item_readonly_repository implements content_item_readonly_reposito
$mod->id,
$mod->name,
new lang_string_title("modulename", $mod->name),
- new \moodle_url($urlbase, ['add' => $mod->name]),
+ new \moodle_url('/course/mod.php', ['id' => $course->id, 'add' => $mod->name]),
$OUTPUT->pix_icon('monologo', '', $mod->name, ['class' => 'icon activityicon']),
$help,
$archetype,
@@ -281,10 +207,6 @@ class content_item_readonly_repository implements content_item_readonly_reposito
$purpose,
);
- // Legacy vs new hooks.
- // If the new hook is found for a module plugin, use that path (calling mod plugins and their subplugins directly)
- // If not, check the legacy hook. This won't provide us with enough information to identify items uniquely within their
- // component (lti + lti source being an example), but we can still list these items.
$modcontentitemreference = clone($contentitem);
if (component_callback_exists('mod_' . $mod->name, 'get_course_content_items')) {
@@ -301,37 +223,8 @@ class content_item_readonly_repository implements content_item_readonly_reposito
array_push($return, ...$subpluginitems);
}
- } else if (component_callback_exists('mod_' . $mod->name, 'get_shortcuts')) {
- // TODO: MDL-68011 this block needs to be removed in 4.1.
- debugging('The callback get_shortcuts has been deprecated. Please use get_course_content_items and
- get_all_content_items instead. Some features of the activity chooser, such as favourites and recommendations
- are not supported when providing content items via the deprecated callback.');
-
- // If get_shortcuts() callback is defined, the default module action is not added.
- // It is a responsibility of the callback to add it to the return value unless it is not needed.
- // The legacy hook, get_shortcuts, expects a stdClass representation of the core module content_item entry.
- $modcontentitemreference = $this->content_item_to_legacy_data($contentitem);
-
- $legacyitems = component_callback($mod->name, 'get_shortcuts', [$modcontentitemreference], null);
- if (!is_null($legacyitems)) {
- foreach ($legacyitems as $legacyitem) {
-
- $legacyitem = $this->legacy_item_inherit_missing($legacyitem, $contentitem);
-
- // All items must have different links, use them as a key in the return array.
- // If plugin returned the only one item with the same link as default item - keep $modname,
- // otherwise append the link url to the module name.
- $legacyitem->name = (count($legacyitems) == 1 &&
- $legacyitem->link->out() === $contentitem->get_link()->out()) ? $mod->name : $mod->name . ':' .
- $legacyitem->link;
-
- $plugincontentitem = $this->content_item_from_legacy_data($legacyitem);
-
- $return[] = $plugincontentitem;
- }
- }
} else {
- // Neither callback was found, so just use the default module content item.
+ // Callback was not found, so just use the default module content item.
$return[] = $contentitem;
}
}
diff --git a/course/format/classes/stateactions.php b/course/format/classes/stateactions.php
index 935c6e6690b..31c3b0968ff 100644
--- a/course/format/classes/stateactions.php
+++ b/course/format/classes/stateactions.php
@@ -247,11 +247,11 @@ class stateactions {
if (!empty($modinfo->sections[$section->section])) {
foreach ($modinfo->sections[$section->section] as $modnumber) {
$cm = $modinfo->cms[$modnumber];
- $updates->add_cm_delete($cm->id);
+ $updates->add_cm_remove($cm->id);
}
}
course_delete_section($course, $section, true, true);
- $updates->add_section_delete($sectionid);
+ $updates->add_section_remove($sectionid);
}
// Removing a section affects the full course structure.
diff --git a/course/format/classes/stateupdates.php b/course/format/classes/stateupdates.php
index a3921da0082..a1b095bf74c 100644
--- a/course/format/classes/stateupdates.php
+++ b/course/format/classes/stateupdates.php
@@ -128,9 +128,21 @@ class stateupdates implements JsonSerializable {
/**
* Add track about a section deleted.
*
+ * @deprecated since Moodle 4.1 MDL-74925 - please call add_section_remove() instead.
* @param int $sectionid The affected section id.
*/
public function add_section_delete(int $sectionid): void {
+ debugging('add_section_delete() is deprecated. Please use add_section_remove() instead.', DEBUG_DEVELOPER);
+
+ $this->add_update('section', 'remove', (object)['id' => $sectionid]);
+ }
+
+ /**
+ * Add track about a section removed.
+ *
+ * @param int $sectionid The affected section id.
+ */
+ public function add_section_remove(int $sectionid): void {
$this->add_update('section', 'remove', (object)['id' => $sectionid]);
}
@@ -178,9 +190,21 @@ class stateupdates implements JsonSerializable {
/**
* Add track about a course module deleted.
*
+ * @deprecated since Moodle 4.1 MDL-74925 - please call add_cm_remove() instead.
* @param int $cmid the affected course module id
*/
public function add_cm_delete(int $cmid): void {
+ debugging('add_cm_delete() is deprecated. Please use add_cm_remove() instead.', DEBUG_DEVELOPER);
+
+ $this->add_update('cm', 'remove', (object)['id' => $cmid]);
+ }
+
+ /**
+ * Add track about a course module removed.
+ *
+ * @param int $cmid the affected course module id
+ */
+ public function add_cm_remove(int $cmid): void {
$this->add_update('cm', 'remove', (object)['id' => $cmid]);
}
@@ -188,7 +212,7 @@ class stateupdates implements JsonSerializable {
* Add a valid update message to the update list.
*
* @param string $name the update name
- * @param string $action the update action (usually update, create, delete)
+ * @param string $action the update action (usually update, create, remove)
* @param stdClass $fields the object fields
*/
protected function add_update(string $name, string $action, stdClass $fields): void {
diff --git a/course/format/singleactivity/lib.php b/course/format/singleactivity/lib.php
index 7f6161ce47a..6a2dd522006 100644
--- a/course/format/singleactivity/lib.php
+++ b/course/format/singleactivity/lib.php
@@ -378,7 +378,6 @@ class format_singleactivity extends core_courseformat\base {
/**
* Checks if the activity type has multiple items in the activity chooser.
- * This may happen as a result of defining callback modulename_get_shortcuts().
*
* @return bool|null (null if the check is not possible)
*/
diff --git a/course/format/tests/external/update_course_test.php b/course/format/tests/external/update_course_test.php
index 355e14923ba..56d64f889bd 100644
--- a/course/format/tests/external/update_course_test.php
+++ b/course/format/tests/external/update_course_test.php
@@ -133,7 +133,7 @@ class update_course_test extends \externallib_advanced_testcase {
'action' => 'format_do_something',
'expected' => [
'count' => 1,
- 'action' => 'delete',
+ 'action' => 'remove',
'visible' => null,
],
'expectexception' => false,
diff --git a/course/format/tests/fixtures/format_theunittest_stateactions.php b/course/format/tests/fixtures/format_theunittest_stateactions.php
index 207f83acfb0..379fd929f64 100644
--- a/course/format/tests/fixtures/format_theunittest_stateactions.php
+++ b/course/format/tests/fixtures/format_theunittest_stateactions.php
@@ -65,7 +65,7 @@ class stateactions extends core_actions {
?int $targetcmid = null
): void {
- $updates->add_cm_delete(array_pop($ids));
+ $updates->add_cm_remove(array_pop($ids));
}
/**
diff --git a/course/format/tests/stateactions_test.php b/course/format/tests/stateactions_test.php
index 56c1f14c057..b0733c9885b 100644
--- a/course/format/tests/stateactions_test.php
+++ b/course/format/tests/stateactions_test.php
@@ -143,7 +143,7 @@ class stateactions_test extends \advanced_testcase {
'cm' => [],
'count' => 0,
],
- 'delete' => [
+ 'remove' => [
'course' => [],
'section' => [],
'cm' => [],
@@ -236,10 +236,10 @@ class stateactions_test extends \advanced_testcase {
// Format results in a way we can compare easily.
$results = $this->summarize_updates($updates);
- // The state actions does not use create or delete actions because they are designed
+ // The state actions does not use create or remove actions because they are designed
// to refresh parts of the state.
$this->assertEquals(0, $results['create']['count']);
- $this->assertEquals(0, $results['delete']['count']);
+ $this->assertEquals(0, $results['remove']['count']);
// Validate we have all the expected entries.
$expectedtotal = count($expectedresults['course']) + count($expectedresults['section']) + count($expectedresults['cm']);
diff --git a/course/format/tests/stateupdates_test.php b/course/format/tests/stateupdates_test.php
index d3eca691e60..e15f6415363 100644
--- a/course/format/tests/stateupdates_test.php
+++ b/course/format/tests/stateupdates_test.php
@@ -186,7 +186,7 @@ class stateupdates_test extends \advanced_testcase {
$this->assertEquals($action, $update->action);
$this->assertEquals('section', $update->name);
// Delete does not provide all fields.
- if ($action == 'delete') {
+ if ($action == 'remove') {
$this->assertEquals($section->id, $update->fields->id);
} else {
$this->assertEquals($expected, $update->fields);
@@ -203,7 +203,7 @@ class stateupdates_test extends \advanced_testcase {
return array_merge(
$this->add_section_provider_helper('put'),
$this->add_section_provider_helper('create'),
- $this->add_section_provider_helper('delete'),
+ $this->add_section_provider_helper('remove'),
);
}
@@ -215,7 +215,7 @@ class stateupdates_test extends \advanced_testcase {
*/
private function add_section_provider_helper(string $action): array {
// Delete does not depends on user permissions.
- if ($action == 'delete') {
+ if ($action == 'remove') {
$studentsections = [0, 1, 2];
} else {
$studentsections = [0, 1];
@@ -247,7 +247,7 @@ class stateupdates_test extends \advanced_testcase {
* @dataProvider add_cm_provider
* @covers ::add_cm_update
* @covers ::add_cm_create
- * @covers ::add_cm_delete
+ * @covers ::add_cm_remove
*
* @param string $action the action name
* @param string $role the user role name
@@ -330,7 +330,7 @@ class stateupdates_test extends \advanced_testcase {
$this->assertEquals($action, $update->action);
$this->assertEquals('cm', $update->name);
// Delete does not provide all fields.
- if ($action == 'delete') {
+ if ($action == 'remove') {
$this->assertEquals($cm->id, $update->fields->id);
} else {
$this->assertEquals($expected, $update->fields);
@@ -347,7 +347,7 @@ class stateupdates_test extends \advanced_testcase {
return array_merge(
$this->add_cm_provider_helper('put'),
$this->add_cm_provider_helper('create'),
- $this->add_cm_provider_helper('delete'),
+ $this->add_cm_provider_helper('remove'),
);
}
@@ -359,7 +359,7 @@ class stateupdates_test extends \advanced_testcase {
*/
private function add_cm_provider_helper(string $action): array {
// Delete does not depends on user permissions.
- if ($action == 'delete') {
+ if ($action == 'remove') {
$studentcms = [0, 1, 2, 3];
} else {
$studentcms = [0];
diff --git a/course/format/upgrade.txt b/course/format/upgrade.txt
index 8707452361c..e284e13ba24 100644
--- a/course/format/upgrade.txt
+++ b/course/format/upgrade.txt
@@ -2,6 +2,10 @@ This files describes API changes for course formats
Overview of this plugin type at http://docs.moodle.org/dev/Course_formats
+=== 4.1 ===
+* New \core_courseformat\stateupdates methods add_section_remove() and add_cm_remove() have been added to replace
+ the deprecated methods add_section_delete() and add_cm_delete().
+
=== 4.0 ===
* New core_courseformat\base::uses_course_index() to define whether the course format uses course index or not.
* New core_courseformat\base::supports_components() to specify if the format is compatible with reactive components.
diff --git a/course/lib.php b/course/lib.php
index 8fb28d6aa7d..eab7327fc5e 100644
--- a/course/lib.php
+++ b/course/lib.php
@@ -2470,9 +2470,7 @@ function update_course($data, $editoroptions = NULL) {
function average_number_of_participants(bool $onlyactive = false, int $lastloginsince = null): float {
global $DB;
- $params = [
- 'siteid' => SITEID,
- ];
+ $params = [];
$sql = "SELECT DISTINCT ue.userid, e.courseid
FROM {user_enrolments} ue
@@ -2483,8 +2481,7 @@ function average_number_of_participants(bool $onlyactive = false, int $lastlogin
$sql .= "JOIN {user} u ON u.id = ue.userid ";
}
- $sql .= "WHERE e.courseid <> :siteid
- AND c.visible = 1 ";
+ $sql .= "WHERE e.courseid <> " . SITEID . " AND c.visible = 1 ";
if ($onlyactive) {
$sql .= "AND ue.status = :active
@@ -4109,16 +4106,24 @@ function course_classify_for_timeline($course, $user = null, $completioninfo = n
$user = $USER;
}
+ if ($completioninfo == null) {
+ $completioninfo = new completion_info($course);
+ }
+
+ // Let plugins override data for timeline classification.
+ $pluginsfunction = get_plugins_with_function('extend_course_classify_for_timeline', 'lib.php');
+ foreach ($pluginsfunction as $plugintype => $plugins) {
+ foreach ($plugins as $pluginfunction) {
+ $pluginfunction($course, $user, $completioninfo);
+ }
+ }
+
$today = time();
// End date past.
if (!empty($course->enddate) && (course_classify_end_date($course) < $today)) {
return COURSE_TIMELINE_PAST;
}
- if ($completioninfo == null) {
- $completioninfo = new completion_info($course);
- }
-
// Course was completed.
if ($completioninfo->is_enabled() && $completioninfo->is_course_complete($user->id)) {
return COURSE_TIMELINE_PAST;
diff --git a/course/renderer.php b/course/renderer.php
index 27c53644075..bbd2f69be65 100644
--- a/course/renderer.php
+++ b/course/renderer.php
@@ -134,17 +134,10 @@ class core_course_renderer extends plugin_renderer_base {
}
/**
- * Build the HTML for the module chooser javascript popup
- *
- * @param array $modules A set of modules as returned form @see
- * get_module_metadata
- * @param object $course The course that will be displayed
- * @return string The composed HTML for the module
+ * @deprecated since 3.9
*/
- public function course_modchooser($modules, $course) {
- debugging('course_modchooser() is deprecated. Please use course_activitychooser() instead.', DEBUG_DEVELOPER);
-
- return $this->course_activitychooser($course->id);
+ public function course_modchooser() {
+ throw new coding_exception('course_modchooser() can not be used anymore, please use course_activitychooser() instead.');
}
/**
diff --git a/course/reset.php b/course/reset.php
index 412fe2b2f88..dba445de3b1 100644
--- a/course/reset.php
+++ b/course/reset.php
@@ -79,7 +79,7 @@ if ($mform->is_cancelled()) {
$line = array();
$line[] = $item['component'];
$line[] = $item['item'];
- $line[] = ($item['error']===false) ? get_string('ok') : ''.$item['error'].'
';
+ $line[] = ($item['error'] === false) ? get_string('statusok') : ''.$item['error'].'
';
$data[] = $line;
}
diff --git a/course/upgrade.txt b/course/upgrade.txt
index 776509a3cdb..d5e7973311b 100644
--- a/course/upgrade.txt
+++ b/course/upgrade.txt
@@ -1,6 +1,10 @@
This files describes API changes in /course/*,
information provided here is intended especially for developers.
+=== 4.1 ===
+* The function course_modchooser() has been finally deprecated and can not be used anymore. Please use
+ course_activitychooser() instead.
+
=== 4.0 ===
* All activity icons have been replaced with black monochrome icons. The background
colour for these icons is defined using a new 'FEATURE_MOD_PURPOSE' support variable in the module lib.php file
diff --git a/enrol/index.php b/enrol/index.php
index 75ccef7cd1c..ae767d3ff7e 100644
--- a/enrol/index.php
+++ b/enrol/index.php
@@ -55,6 +55,8 @@ if (!$course->visible && !has_capability('moodle/course:viewhiddencourses', cont
$PAGE->set_course($course);
$PAGE->set_pagelayout('incourse');
$PAGE->set_url('/enrol/index.php', array('id'=>$course->id));
+$PAGE->set_secondary_navigation(false);
+$PAGE->add_body_class('limitedwidth');
// do not allow enrols when in login-as session
if (\core\session\manager::is_loggedinas() and $USER->loginascontext->contextlevel == CONTEXT_COURSE) {
diff --git a/enrol/lti/classes/local/ltiadvantage/table/published_resources_table.php b/enrol/lti/classes/local/ltiadvantage/table/published_resources_table.php
index d375d9163c5..8367b634da2 100644
--- a/enrol/lti/classes/local/ltiadvantage/table/published_resources_table.php
+++ b/enrol/lti/classes/local/ltiadvantage/table/published_resources_table.php
@@ -92,7 +92,8 @@ class published_resources_table extends \table_sql {
* @return string
*/
public function col_name($tool) {
- $name = helper::get_name($tool);
+ $toolcontext = \context::instance_by_id($tool->contextid, IGNORE_MISSING);
+ $name = $toolcontext ? helper::get_name($tool) : $this->get_deleted_activity_name_html($tool);
return $this->get_display_text($tool, $name);
}
@@ -215,9 +216,39 @@ class published_resources_table extends \table_sql {
*/
protected function get_display_text($tool, $text) {
if ($tool->status != ENROL_INSTANCE_ENABLED) {
- return \html_writer::tag('span', $text, array('class' => 'dimmed_text'));
+ return \html_writer::tag('div', $text, array('class' => 'dimmed_text'));
}
return $text;
}
+
+ /**
+ * Get a warning icon, with tooltip, describing enrolment instances sharing activities which have been deleted.
+ *
+ * @param \stdClass $tool the tool instance record.
+ * @return string the HTML for the name column.
+ */
+ protected function get_deleted_activity_name_html(\stdClass $tool): string {
+ global $OUTPUT;
+ $icon = \html_writer::tag(
+ 'a',
+ $OUTPUT->pix_icon('enrolinstancewarning', get_string('deletedactivityalt' , 'enrol_lti'), 'enrol_lti'), [
+ "class" => "btn btn-link p-0",
+ "role" => "button",
+ "data-container" => "body",
+ "data-toggle" => "popover",
+ "data-placement" => right_to_left() ? "left" : "right",
+ "data-content" => get_string('deletedactivitydescription', 'enrol_lti'),
+ "data-html" => "true",
+ "tabindex" => "0",
+ "data-trigger" => "focus"
+ ]
+ );
+ $name = \html_writer::span($icon . get_string('deletedactivity', 'enrol_lti'));
+ if ($tool->name) {
+ $name .= \html_writer::empty_tag('br') . \html_writer::empty_tag('br') . $tool->name;
+ }
+
+ return $name;
+ }
}
diff --git a/enrol/lti/classes/manage_table.php b/enrol/lti/classes/manage_table.php
index bd9ccc208da..262010103ba 100644
--- a/enrol/lti/classes/manage_table.php
+++ b/enrol/lti/classes/manage_table.php
@@ -101,7 +101,8 @@ class manage_table extends \table_sql {
* @return string
*/
public function col_name($tool) {
- $name = helper::get_name($tool);
+ $toolcontext = \context::instance_by_id($tool->contextid, IGNORE_MISSING);
+ $name = $toolcontext ? helper::get_name($tool) : $this->get_deleted_activity_name_html($tool);
return $this->get_display_text($tool, $name);
}
@@ -247,9 +248,39 @@ class manage_table extends \table_sql {
*/
protected function get_display_text($tool, $text) {
if ($tool->status != ENROL_INSTANCE_ENABLED) {
- return \html_writer::tag('span', $text, array('class' => 'dimmed_text'));
+ return \html_writer::tag('div', $text, array('class' => 'dimmed_text'));
}
return $text;
}
+
+ /**
+ * Get a warning icon, with tooltip, describing enrolment instances sharing activities which have been deleted.
+ *
+ * @param \stdClass $tool the tool instance record.
+ * @return string the HTML for the name column.
+ */
+ protected function get_deleted_activity_name_html(\stdClass $tool): string {
+ global $OUTPUT;
+ $icon = \html_writer::tag(
+ 'a',
+ $OUTPUT->pix_icon('enrolinstancewarning', get_string('deletedactivityalt' , 'enrol_lti'), 'enrol_lti'), [
+ "class" => "btn btn-link p-0",
+ "role" => "button",
+ "data-container" => "body",
+ "data-toggle" => "popover",
+ "data-placement" => right_to_left() ? "left" : "right",
+ "data-content" => get_string('deletedactivitydescription', 'enrol_lti'),
+ "data-html" => "true",
+ "tabindex" => "0",
+ "data-trigger" => "focus"
+ ]
+ );
+ $name = \html_writer::span($icon . get_string('deletedactivity', 'enrol_lti'));
+ if ($tool->name) {
+ $name .= \html_writer::empty_tag('br') . \html_writer::empty_tag('br') . $tool->name;
+ }
+
+ return $name;
+ }
}
diff --git a/enrol/lti/db/upgrade.php b/enrol/lti/db/upgrade.php
index 92da6cab9b9..5cf89474132 100644
--- a/enrol/lti/db/upgrade.php
+++ b/enrol/lti/db/upgrade.php
@@ -450,5 +450,17 @@ function xmldb_enrol_lti_upgrade($oldversion) {
// Automatically generated Moodle v4.0.0 release upgrade line.
// Put any upgrade step following this.
+ if ($oldversion < 2022061500) {
+ // Disable all orphaned enrolment method instances.
+ $sql = "id IN (SELECT t.enrolid
+ FROM {enrol_lti_tools} t
+ LEFT JOIN {context} c ON (t.contextid = c.id)
+ WHERE c.id IS NULL)";
+ $DB->set_field_select('enrol', 'status', 1, $sql);
+
+ // Lti savepoint reached.
+ upgrade_plugin_savepoint(true, 2022061500, 'enrol', 'lti');
+ }
+
return true;
}
diff --git a/enrol/lti/lang/en/enrol_lti.php b/enrol/lti/lang/en/enrol_lti.php
index 80f89612bcf..5808a0334d5 100644
--- a/enrol/lti/lang/en/enrol_lti.php
+++ b/enrol/lti/lang/en/enrol_lti.php
@@ -36,6 +36,9 @@ $string['copytoclipboard'] = 'Copy to clipboard';
$string['couldnotestablishproxy'] = 'Could not establish proxy with consumer.';
$string['customproperties'] = 'Custom properties';
$string['deeplinkingurl'] = 'Deep linking URL';
+$string['deletedactivity'] = 'Deleted activity';
+$string['deletedactivityalt'] = 'The activity shared by this instance has been deleted';
+$string['deletedactivitydescription'] = 'The activity shared by this instance has been deleted. You can select another activity to share by editing the instance or, if no longer required, you can simply delete the instance. Deleting the instance will remove any associated user enrolments.';
$string['deploymentadd'] = 'Add a deployment';
$string['deploymentaddnotice'] = 'Deployment added';
$string['deploymentdelete'] = 'Delete deployment';
diff --git a/enrol/lti/lib.php b/enrol/lti/lib.php
index 7c8f78e3251..f01522cb9e1 100644
--- a/enrol/lti/lib.php
+++ b/enrol/lti/lib.php
@@ -475,6 +475,22 @@ function enrol_lti_extend_navigation_course($navigation, $course, $context) {
function enrol_lti_get_fontawesome_icon_map() {
return [
'enrol_lti:managedeployments' => 'fa-sitemap',
- 'enrol_lti:platformdetails' => 'fa-pencil-square-o'
+ 'enrol_lti:platformdetails' => 'fa-pencil-square-o',
+ 'enrol_lti:enrolinstancewarning' => 'fa-exclamation-circle text-danger',
];
}
+
+/**
+ * Pre-delete course module hook which disables any methods referring to the deleted module, preventing launches and allowing remap.
+ *
+ * @param stdClass $cm The deleted course module record.
+ */
+function enrol_lti_pre_course_module_delete(stdClass $cm) {
+ global $DB;
+ $sql = "id IN (SELECT t.enrolid
+ FROM {enrol_lti_tools} t
+ JOIN {context} c ON (t.contextid = c.id)
+ WHERE c.contextlevel = :contextlevel
+ AND c.instanceid = :cmid)";
+ $DB->set_field_select('enrol', 'status', ENROL_INSTANCE_DISABLED, $sql, ['contextlevel' => CONTEXT_MODULE, 'cmid' => $cm->id]);
+}
diff --git a/enrol/lti/pix/enrolinstancewarning.png b/enrol/lti/pix/enrolinstancewarning.png
new file mode 100644
index 00000000000..edb89e473ea
Binary files /dev/null and b/enrol/lti/pix/enrolinstancewarning.png differ
diff --git a/enrol/lti/pix/enrolinstancewarning.svg b/enrol/lti/pix/enrolinstancewarning.svg
new file mode 100644
index 00000000000..85671a11e18
--- /dev/null
+++ b/enrol/lti/pix/enrolinstancewarning.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/enrol/lti/tests/lib_test.php b/enrol/lti/tests/lib_test.php
index d11e0f82699..9698d5e818b 100644
--- a/enrol/lti/tests/lib_test.php
+++ b/enrol/lti/tests/lib_test.php
@@ -216,4 +216,36 @@ class lib_test extends \lti_advantage_testcase {
// LTI enrolment has 1 enrol actions for active users -- unenrol.
$this->assertCount(1, $actions);
}
+
+ /**
+ * Test the behaviour of an enrolment method when the activity to which it provides access is deleted.
+ *
+ * @covers \enrol_lti_pre_course_module_delete
+ */
+ public function test_course_module_deletion() {
+ // Create two modules and publish them.
+ $course = $this->getDataGenerator()->create_course();
+ $mod = $this->getDataGenerator()->create_module('assign', ['course' => $course->id]);
+ $mod2 = $this->getDataGenerator()->create_module('assign', ['course' => $course->id]);
+ $tooldata = [
+ 'cmid' => $mod->cmid,
+ 'courseid' => $course->id,
+ ];
+ $tool = $this->getDataGenerator()->create_lti_tool((object)$tooldata);
+ $tooldata['cmid'] = $mod2->cmid;
+ $tool2 = $this->getDataGenerator()->create_lti_tool((object)$tooldata);
+
+ // Verify the instances are both enabled.
+ $modinstance = helper::get_lti_tool($tool->id);
+ $mod2instance = helper::get_lti_tool($tool2->id);
+ $this->assertEquals(ENROL_INSTANCE_ENABLED, $modinstance->status);
+ $this->assertEquals(ENROL_INSTANCE_ENABLED, $mod2instance->status);
+
+ // Delete a module and verify the associated instance is disabled.
+ course_delete_module($mod->cmid);
+ $modinstance = helper::get_lti_tool($tool->id);
+ $mod2instance = helper::get_lti_tool($tool2->id);
+ $this->assertEquals(ENROL_INSTANCE_DISABLED, $modinstance->status);
+ $this->assertEquals(ENROL_INSTANCE_ENABLED, $mod2instance->status);
+ }
}
diff --git a/enrol/lti/tests/local/ltiadvantage/entity/resource_link_test.php b/enrol/lti/tests/local/ltiadvantage/entity/resource_link_test.php
index 33963c63345..04f35d0b01c 100644
--- a/enrol/lti/tests/local/ltiadvantage/entity/resource_link_test.php
+++ b/enrol/lti/tests/local/ltiadvantage/entity/resource_link_test.php
@@ -229,15 +229,16 @@ class resource_link_test extends \advanced_testcase {
* @covers ::add_user
*/
public function test_add_user() {
+ global $CFG;
$reslinkwithid = resource_link::create('res-link-id-123', 24, 44, 66, 33);
- $user = $reslinkwithid->add_user(2, 'platform-user-id-123', 'en', 'Sydney', 'AU', 'Test university', '99');
+ $user = $reslinkwithid->add_user(2, 'platform-user-id-123', $CFG->lang, 'Sydney', 'AU', 'Test university', '99');
$this->assertInstanceOf(user::class, $user);
$this->assertEquals(33, $user->get_resourcelinkid());
$reslinkwithoutid = resource_link::create('res-link-id-123', 24, 44);
$this->expectException(\coding_exception::class);
$this->expectExceptionMessage("Can't add user to a resource_link that hasn't first been saved");
- $reslinkwithoutid->add_user(2, 'platform-user-id-123', 'en', 'Sydney', 'Australia', 'Test university', '99');
+ $reslinkwithoutid->add_user(2, 'platform-user-id-123', $CFG->lang, 'Sydney', 'Australia', 'Test university', '99');
}
/**
diff --git a/enrol/lti/tests/local/ltiadvantage/entity/user_test.php b/enrol/lti/tests/local/ltiadvantage/entity/user_test.php
index e456961fda2..ae594f9c8ce 100644
--- a/enrol/lti/tests/local/ltiadvantage/entity/user_test.php
+++ b/enrol/lti/tests/local/ltiadvantage/entity/user_test.php
@@ -65,6 +65,7 @@ class user_test extends \advanced_testcase {
* @return array the data for testing.
*/
public function create_data_provider(): array {
+ global $CFG;
return [
'Valid create, only required args provided' => [
'args' => [
@@ -72,7 +73,7 @@ class user_test extends \advanced_testcase {
'userid' => 2,
'deploymentid' => 33,
'sourceid' => 'user-id-123',
- 'lang' => 'en',
+ 'lang' => $CFG->lang,
'timezone' => '99'
],
'expectations' => [
@@ -80,7 +81,7 @@ class user_test extends \advanced_testcase {
'resourceid' => 22,
'deploymentid' => 33,
'sourceid' => 'user-id-123',
- 'lang' => 'en',
+ 'lang' => $CFG->lang,
'timezone' => '99',
'city' => '',
'country' => '',
@@ -99,7 +100,7 @@ class user_test extends \advanced_testcase {
'userid' => 2,
'deploymentid' => 33,
'sourceid' => 'user-id-123',
- 'lang' => 'en',
+ 'lang' => $CFG->lang,
'timezone' => '99',
'city' => 'Melbourne',
'country' => 'AU',
@@ -115,7 +116,7 @@ class user_test extends \advanced_testcase {
'resourceid' => 22,
'deploymentid' => 33,
'sourceid' => 'user-id-123',
- 'lang' => 'en',
+ 'lang' => $CFG->lang,
'timezone' => '99',
'city' => 'Melbourne',
'country' => 'AU',
@@ -134,7 +135,7 @@ class user_test extends \advanced_testcase {
'userid' => 2,
'deploymentid' => 33,
'sourceid' => 'user-id-123',
- 'lang' => 'en',
+ 'lang' => $CFG->lang,
'timezone' => '99',
'city' => 'Melbourne',
'country' => 'AU',
@@ -151,7 +152,7 @@ class user_test extends \advanced_testcase {
'resourceid' => 22,
'deploymentid' => 33,
'sourceid' => 'user-id-123',
- 'lang' => 'en',
+ 'lang' => $CFG->lang,
'timezone' => '99',
'city' => 'Melbourne',
'country' => 'AU',
@@ -164,19 +165,19 @@ class user_test extends \advanced_testcase {
'id' => null
]
],
- 'Invalid create, lang with bad value (fr not installed)' => [
+ 'Invalid create, lang with bad value (vvvv not installed)' => [
'args' => [
'resourceid' => 22,
'userid' => 2,
'deploymentid' => 33,
'sourceid' => 'user-id-123',
- 'lang' => 'fr',
+ 'lang' => 'vvvv',
'timezone' => '99',
],
'expectations' => [
'valid' => false,
'exception' => \coding_exception::class,
- 'exceptionmessage' => "Invalid lang 'fr' provided."
+ 'exceptionmessage' => "Invalid lang 'vvvv' provided."
]
],
'Invalid create, timezone with bad value' => [
@@ -185,7 +186,7 @@ class user_test extends \advanced_testcase {
'userid' => 2,
'deploymentid' => 33,
'sourceid' => 'user-id-123',
- 'lang' => 'en',
+ 'lang' => $CFG->lang,
'timezone' => 'NOT/FOUND',
],
'expectations' => [
@@ -200,7 +201,7 @@ class user_test extends \advanced_testcase {
'userid' => 2,
'deploymentid' => 33,
'sourceid' => 'user-id-123',
- 'lang' => 'en',
+ 'lang' => $CFG->lang,
'timezone' => '99',
'city' => '',
'country' => 'FFF',
@@ -217,7 +218,7 @@ class user_test extends \advanced_testcase {
'userid' => 2,
'deploymentid' => 33,
'sourceid' => 'user-id-123',
- 'lang' => 'en',
+ 'lang' => $CFG->lang,
'timezone' => '99',
'city' => '',
'country' => '',
@@ -272,6 +273,7 @@ class user_test extends \advanced_testcase {
* @return array the data for testing.
*/
public function create_from_resource_link_data_provider(): array {
+ global $CFG;
return [
'Valid creation, all args provided explicitly' => [
'args' => [
@@ -280,7 +282,7 @@ class user_test extends \advanced_testcase {
'userid' => 2,
'deploymentid' => 33,
'sourceid' => 'user-id-123',
- 'lang' => 'en',
+ 'lang' => $CFG->lang,
'timezone' => '99',
'city' => 'Melbourne',
'country' => 'AU',
@@ -295,7 +297,7 @@ class user_test extends \advanced_testcase {
'resourceid' => 22,
'deploymentid' => 33,
'sourceid' => 'user-id-123',
- 'lang' => 'en',
+ 'lang' => $CFG->lang,
'timezone' => '99',
'city' => 'Melbourne',
'country' => 'AU',
@@ -312,7 +314,7 @@ class user_test extends \advanced_testcase {
'userid' => 2,
'deploymentid' => 33,
'sourceid' => 'user-id-123',
- 'lang' => 'en',
+ 'lang' => $CFG->lang,
'timezone' => 'UTC'
],
'expectations' => [
@@ -323,7 +325,7 @@ class user_test extends \advanced_testcase {
'resourceid' => 22,
'deploymentid' => 33,
'sourceid' => 'user-id-123',
- 'lang' => 'en',
+ 'lang' => $CFG->lang,
'timezone' => 'UTC',
'city' => '',
'country' => '',
@@ -340,7 +342,7 @@ class user_test extends \advanced_testcase {
'user' => 2,
'deploymentid' => 33,
'sourceid' => '',
- 'lang' => 'en',
+ 'lang' => $CFG->lang,
'timezone' => 'UTC'
],
'expectations' => [
@@ -372,7 +374,7 @@ class user_test extends \advanced_testcase {
'userid' => 2,
'deploymentid' => 33,
'sourceid' => 'user-id-123',
- 'lang' => 'en',
+ 'lang' => $CFG->lang,
'timezone' => ''
],
'expectations' => [
@@ -381,20 +383,20 @@ class user_test extends \advanced_testcase {
'exceptionmessage' => 'Invalid timezone value. Cannot be an empty string.'
]
],
- 'Invalid creation, only required args provided, invalid lang (fr not installed)' => [
+ 'Invalid creation, only required args provided, invalid lang (vvvv not installed)' => [
'args' => [
'resourcelinkid' => 11,
'resourceid' => 22,
'userid' => 2,
'deploymentid' => 33,
'sourceid' => 'user-id-123',
- 'lang' => 'fr',
+ 'lang' => 'vvvv',
'timezone' => 'UTC'
],
'expectations' => [
'valid' => false,
'exception' => \coding_exception::class,
- 'exceptionmessage' => "Invalid lang 'fr' provided."
+ 'exceptionmessage' => "Invalid lang 'vvvv' provided."
]
],
'Invalid creation, only required args provided, invalid timezone' => [
@@ -404,7 +406,7 @@ class user_test extends \advanced_testcase {
'userid' => 2,
'deploymentid' => 33,
'sourceid' => 'user-id-123',
- 'lang' => 'en',
+ 'lang' => $CFG->lang,
'timezone' => 'NOT/FOUND'
],
'expectations' => [
@@ -420,7 +422,7 @@ class user_test extends \advanced_testcase {
'userid' => 2,
'deploymentid' => 33,
'sourceid' => 'user-id-123',
- 'lang' => 'en',
+ 'lang' => $CFG->lang,
'timezone' => '99',
'city' => 'Melbourne',
'country' => 'FFF',
@@ -440,7 +442,7 @@ class user_test extends \advanced_testcase {
'userid' => 2,
'deploymentid' => 33,
'sourceid' => 'user-id-123',
- 'lang' => 'en',
+ 'lang' => $CFG->lang,
'timezone' => '99',
'city' => 'Melbourne',
'country' => 'AU',
@@ -462,13 +464,14 @@ class user_test extends \advanced_testcase {
* @return user a user instance.
*/
protected function create_test_user(): user {
+ global $CFG;
$args = [
'resourcelinkid' => 11,
'resourceid' => 22,
'userid' => 2,
'deploymentid' => 33,
'sourceid' => 'user-id-123',
- 'lang' => 'en',
+ 'lang' => $CFG->lang,
'timezone' => 'UTC'
];
return user::create_from_resource_link(...array_values($args));
@@ -509,6 +512,7 @@ class user_test extends \advanced_testcase {
* @return array the array of test data.
*/
public function setters_getters_data_provider(): array {
+ global $CFG;
return [
'Testing set_resourcelinkid with valid id' => [
'methodname' => 'resourcelinkid',
@@ -641,7 +645,7 @@ class user_test extends \advanced_testcase {
],
'Testing set_lang with valid lang code' => [
'methodname' => 'lang',
- 'arg' => 'en',
+ 'arg' => $CFG->lang,
'expectations' => [
'valid' => true,
]
diff --git a/enrol/lti/tests/local/ltiadvantage/repository/resource_link_repository_test.php b/enrol/lti/tests/local/ltiadvantage/repository/resource_link_repository_test.php
index 858a617d6d5..a9ff411f17c 100644
--- a/enrol/lti/tests/local/ltiadvantage/repository/resource_link_repository_test.php
+++ b/enrol/lti/tests/local/ltiadvantage/repository/resource_link_repository_test.php
@@ -197,6 +197,7 @@ class resource_link_repository_test extends \advanced_testcase {
* @covers ::find_by_resource_and_user
*/
public function test_find_by_resource_and_user() {
+ global $CFG;
$this->resetAfterTest();
$resourcelink = $this->generate_resource_link();
$repository = new resource_link_repository();
@@ -214,7 +215,7 @@ class resource_link_repository_test extends \advanced_testcase {
$user = $newreslink->add_user(
$user1->id,
'platform-user-id-123',
- 'en',
+ $CFG->lang,
'Sydney',
'AU',
'Test university',
@@ -227,7 +228,7 @@ class resource_link_repository_test extends \advanced_testcase {
$user2 = $newreslink3->add_user(
$user2->id,
'platform-user-id-777',
- 'en',
+ $CFG->lang,
'Melbourne',
'AU',
'Test university',
@@ -255,6 +256,7 @@ class resource_link_repository_test extends \advanced_testcase {
* @covers ::delete
*/
public function test_delete() {
+ global $CFG;
$this->resetAfterTest();
$resourcelink = $this->generate_resource_link();
$repository = new resource_link_repository();
@@ -265,7 +267,7 @@ class resource_link_repository_test extends \advanced_testcase {
$user = $newreslink->add_user(
2,
'source-id-123',
- 'en',
+ $CFG->lang,
'Perth',
'AU',
'An Example Institution',
@@ -293,6 +295,7 @@ class resource_link_repository_test extends \advanced_testcase {
* @covers ::delete_by_resource
*/
public function test_delete_by_resource() {
+ global $CFG;
$this->resetAfterTest();
$resourcelink = $this->generate_resource_link();
$repository = new resource_link_repository();
@@ -302,7 +305,7 @@ class resource_link_repository_test extends \advanced_testcase {
$user = $newreslink->add_user(
2,
'source-id-123',
- 'en',
+ $CFG->lang,
'Perth',
'AU',
'An Example Institution',
@@ -337,6 +340,7 @@ class resource_link_repository_test extends \advanced_testcase {
* @covers ::delete_by_deployment
*/
public function test_delete_by_deployment() {
+ global $CFG;
$this->resetAfterTest();
$resourcelink = $this->generate_resource_link();
$repository = new resource_link_repository();
@@ -347,7 +351,7 @@ class resource_link_repository_test extends \advanced_testcase {
$user = $newreslink->add_user(
2,
'source-id-123',
- 'en',
+ $CFG->lang,
'Perth',
'AU',
'An Example Institution',
diff --git a/enrol/lti/tests/local/ltiadvantage/repository/user_repository_test.php b/enrol/lti/tests/local/ltiadvantage/repository/user_repository_test.php
index c745a8a14f3..cd690379330 100644
--- a/enrol/lti/tests/local/ltiadvantage/repository/user_repository_test.php
+++ b/enrol/lti/tests/local/ltiadvantage/repository/user_repository_test.php
@@ -34,6 +34,7 @@ class user_repository_test extends \advanced_testcase {
* @return user a user instance
*/
protected function generate_user(int $mockresourceid = 1): user {
+ global $CFG;
$registration = application_registration::create(
'Test',
'a2c94a2c94',
@@ -66,7 +67,7 @@ class user_repository_test extends \advanced_testcase {
$ltiuser = $savedresourcelink->add_user(
$user->id,
'source-id-123',
- 'en',
+ $CFG->lang,
'Perth',
'AU',
'An Example Institution',
@@ -215,6 +216,7 @@ class user_repository_test extends \advanced_testcase {
* @covers ::save
*/
public function test_save_stale_id() {
+ global $CFG;
$this->resetAfterTest();
$instructoruser = $this->getDataGenerator()->create_user();
$userrepo = new user_repository();
@@ -223,7 +225,7 @@ class user_repository_test extends \advanced_testcase {
$instructoruser->id,
5,
'source-id-123',
- 'en',
+ $CFG->lang,
'99',
'',
'',
@@ -280,6 +282,7 @@ class user_repository_test extends \advanced_testcase {
* @covers ::find_by_resource
*/
public function test_find_by_resource() {
+ global $CFG;
$this->resetAfterTest();
$user = $this->generate_user();
$userrepo = new user_repository();
@@ -291,7 +294,7 @@ class user_repository_test extends \advanced_testcase {
$instructoruser->id,
$saveduser->get_deploymentid(),
'another-user-123',
- 'en',
+ $CFG->lang,
'99',
'Perth',
'AU',
@@ -314,6 +317,7 @@ class user_repository_test extends \advanced_testcase {
* @covers ::find_by_resource_link
*/
public function test_find_by_resource_link() {
+ global $CFG;
$this->resetAfterTest();
$user = $this->generate_user();
$user->set_resourcelinkid(33);
@@ -326,7 +330,7 @@ class user_repository_test extends \advanced_testcase {
$instructoruser->id,
$saveduser->get_deploymentid(),
'another-user-123',
- 'en',
+ $CFG->lang,
'99',
'Perth',
'AU',
@@ -390,6 +394,7 @@ class user_repository_test extends \advanced_testcase {
* @covers ::delete_by_deployment
*/
public function test_delete_by_deployment() {
+ global $CFG;
$this->resetAfterTest();
$user = $this->generate_user();
$userrepo = new user_repository();
@@ -402,7 +407,7 @@ class user_repository_test extends \advanced_testcase {
$instructoruser->id,
$saveduser->get_deploymentid(),
'another-user-123',
- 'en',
+ $CFG->lang,
'99',
'Perth',
'AU',
@@ -415,7 +420,7 @@ class user_repository_test extends \advanced_testcase {
$instructor2user->id,
$saveduser->get_deploymentid() + 1,
'another-user-678',
- 'en',
+ $CFG->lang,
'99',
'Melbourne',
'AU',
diff --git a/enrol/lti/tests/local/ltiadvantage/task/sync_grades_test.php b/enrol/lti/tests/local/ltiadvantage/task/sync_grades_test.php
index e8680a5f238..88e01c9dfd3 100644
--- a/enrol/lti/tests/local/ltiadvantage/task/sync_grades_test.php
+++ b/enrol/lti/tests/local/ltiadvantage/task/sync_grades_test.php
@@ -412,6 +412,35 @@ class sync_grades_test extends \lti_advantage_testcase {
$task->execute();
}
+ /**
+ * Test syncing grades when the enrolment instance is disabled.
+ *
+ * @covers ::execute
+ */
+ public function test_sync_grades_disabled_instance() {
+ $this->resetAfterTest();
+ global $DB;
+
+ [$course, $resource, $resource2, $resource3] = $this->create_test_environment();
+
+ // Disable resource 1.
+ $enrol = (object) ['id' => $resource->enrolid, 'status' => ENROL_INSTANCE_DISABLED];
+ $DB->update_record('enrol', $enrol);
+
+ // Delete the activity being shared by resource 2, leaving resource 2 disabled as a result.
+ $modcontext = \context::instance_by_id($resource2->contextid);
+ course_delete_module($modcontext->instanceid);
+
+ // Only the enabled resource 3 should sync grades.
+ $task = $this->get_task_with_mocked_grade_service();
+ $this->expectOutputRegex(
+ "/^Starting - LTI Advantage grade sync for shared resource '$resource3->id' in course '$course->id'.\n".
+ "Completed - Synced grades for tool '$resource3->id' in the course '$course->id'. Processed 0 users; ".
+ "sent 0 grades.\n$/"
+ );
+ $task->execute();
+ }
+
/**
* Test the grade sync when the context has been deleted in between launch and when the grade sync task is run.
*
@@ -419,6 +448,8 @@ class sync_grades_test extends \lti_advantage_testcase {
*/
public function test_sync_grades_deleted_context() {
$this->resetAfterTest();
+ global $DB;
+
[$course, $resource] = $this->create_test_environment();
$launchservice = $this->get_tool_launch_service();
@@ -427,10 +458,11 @@ class sync_grades_test extends \lti_advantage_testcase {
$instructoruser = $this->getDataGenerator()->create_user();
[$userid] = $launchservice->user_launches_tool($instructoruser, $teachermocklaunch);
- global $CFG;
- require_once($CFG->dirroot . '/course/lib.php');
+ // Delete the activity, then enable the enrolment method (it is disabled during activity deletion).
$modcontext = \context::instance_by_id($resource->contextid);
course_delete_module($modcontext->instanceid);
+ $enrol = (object) ['id' => $resource->enrolid, 'status' => ENROL_INSTANCE_ENABLED];
+ $DB->update_record('enrol', $enrol);
$task = $this->get_task_with_mocked_grade_service();
$this->expectOutputRegex(
diff --git a/enrol/lti/tests/local/ltiadvantage/task/sync_members_test.php b/enrol/lti/tests/local/ltiadvantage/task/sync_members_test.php
index 7a2c3d3044c..ec265de1b91 100644
--- a/enrol/lti/tests/local/ltiadvantage/task/sync_members_test.php
+++ b/enrol/lti/tests/local/ltiadvantage/task/sync_members_test.php
@@ -609,6 +609,38 @@ class sync_members_test extends \lti_advantage_testcase {
$this->assertCount(1, $userrepo->find_by_resource($resource->id));
}
+ /**
+ * Test syncing members when the enrolment instance is disabled.
+ *
+ * @covers ::execute
+ */
+ public function test_sync_members_disabled_instance() {
+ $this->resetAfterTest();
+ global $DB;
+
+ [$course, $resource, $resource2, $resource3] = $this->create_test_environment();
+ $userrepo = new user_repository();
+
+ // Disable resource 1.
+ $enrol = (object) ['id' => $resource->enrolid, 'status' => ENROL_INSTANCE_DISABLED];
+ $DB->update_record('enrol', $enrol);
+
+ // Delete the activity being shared by resource2, leaving resource 2 disabled as a result.
+ $modcontext = \context::instance_by_id($resource2->contextid);
+ course_delete_module($modcontext->instanceid);
+
+ // Only the enabled resource 3 should sync members.
+ $task = $this->get_mock_task_with_users($this->get_mock_members_with_ids(range(1, 1)));
+ $task->execute();
+
+ $this->expectOutputRegex(
+ "/^Starting - Member sync for published resource '$resource3->id' for course '$course->id'.\n".
+ "Completed - Synced members for tool '$resource3->id' in the course '$course->id'. Processed 0 users; ".
+ "enrolled 0 members; unenrolled 0 members.\n$/"
+ );
+ $this->assertCount(0, $userrepo->find_by_resource($resource->id));
+ }
+
/**
* Test syncing members for a membersync-enabled resource when the launch omits the NRPS service endpoints.
*
diff --git a/enrol/lti/thirdpartylibs.xml b/enrol/lti/thirdpartylibs.xml
index b736c2bfd70..6572a35391d 100644
--- a/enrol/lti/thirdpartylibs.xml
+++ b/enrol/lti/thirdpartylibs.xml
@@ -3,8 +3,10 @@
ims-blti
IMS-BLTI
+ Set of IMS-LTI tools originaly provided by IMS.
+ 1.0
MIT
-
-
+ https://github.com/jfederico/ims-dev
+
diff --git a/enrol/lti/version.php b/enrol/lti/version.php
index 87b900db4a1..5aa032eedb6 100644
--- a/enrol/lti/version.php
+++ b/enrol/lti/version.php
@@ -24,7 +24,7 @@
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2022041900; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->version = 2022061500; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2022041200; // Requires this Moodle version.
$plugin->component = 'enrol_lti'; // Full name of the plugin (used for diagnostics).
$plugin->dependencies = [
diff --git a/filter/algebra/thirdpartylibs.xml b/filter/algebra/thirdpartylibs.xml
index 93970af442c..b498c351439 100644
--- a/filter/algebra/thirdpartylibs.xml
+++ b/filter/algebra/thirdpartylibs.xml
@@ -3,8 +3,9 @@
AlgParser.pm
WeBWorK
- GPL
2.4.9+
+ GPL
2.0+
+ false
diff --git a/filter/tex/thirdpartylibs.xml b/filter/tex/thirdpartylibs.xml
index 7a426fcddc5..79b12f865c8 100644
--- a/filter/tex/thirdpartylibs.xml
+++ b/filter/tex/thirdpartylibs.xml
@@ -3,8 +3,14 @@
mimetex.*
mimeTeX
- GPL
+ Compiled C program to convert TeX into GIFs
1.77
+ GPL
3.0+
+ https://github.com/icaoberg/mimetex
+
+ 2002-2004 John Forkosh Associates, Inc
+
+ false
diff --git a/h5p/h5plib/v124/thirdpartylibs.xml b/h5p/h5plib/v124/thirdpartylibs.xml
index 00f4906dee9..f65a10bdb96 100644
--- a/h5p/h5plib/v124/thirdpartylibs.xml
+++ b/h5p/h5plib/v124/thirdpartylibs.xml
@@ -3,13 +3,25 @@
joubel/core
h5p-php-library
- GPL-3.0
+ The general H5P library.
1.24.3
+ GPL
+ 3.0+
+ https://github.com/h5p/h5p-php-library/
+
+ Joubel
+
joubel/editor
h5p-editor-php-library
- GPL-3.0
+ A general library that is supposed to be used in most PHP implementations of H5P.
moodle-1.20.2
+ GPL
+ 3.0+
+ https://github.com/h5p/h5p-editor-php-library/
+
+ Joubel
+
diff --git a/install/lang/de_du/langconfig.php b/install/lang/de_du/langconfig.php
index d598e3e274e..6c02b128fb3 100644
--- a/install/lang/de_du/langconfig.php
+++ b/install/lang/de_du/langconfig.php
@@ -31,4 +31,4 @@ defined('MOODLE_INTERNAL') || die();
$string['parentlanguage'] = 'de';
$string['thisdirection'] = 'ltr';
-$string['thislanguage'] = 'Deutsch (Du)';
+$string['thislanguage'] = 'Deutsch (du)';
diff --git a/lang/en/course.php b/lang/en/course.php
index 28221c04ab8..72a05b8f2d3 100644
--- a/lang/en/course.php
+++ b/lang/en/course.php
@@ -91,6 +91,7 @@ $string['favourite'] = 'Starred course';
$string['gradetopassnotset'] = 'This course does not have a grade to pass set. It may be set in the grade item of the course (Gradebook setup).';
$string['informationformodule'] = 'Information about the {$a} activity';
$string['module'] = 'Activity';
+$string['namewithlink'] = 'Category name with link';
$string['nocourseactivity'] = 'Not enough course activity between the start and the end of the course';
$string['nocourseendtime'] = 'The course does not have an end time';
$string['nocoursesections'] = 'No course sections';
diff --git a/lang/en/reportbuilder.php b/lang/en/reportbuilder.php
index 54ac706d2ae..dc15edcb3f9 100644
--- a/lang/en/reportbuilder.php
+++ b/lang/en/reportbuilder.php
@@ -255,6 +255,7 @@ $string['sorting'] = 'Sorting';
$string['sorting_help'] = 'Sorting defines the initial sort order of columns in the report. The order can be reversed by toggling the Up/down icon. Users can then define their own sort order by clicking on a column name.';
$string['switchedit'] = 'Switch to edit mode';
$string['switchpreview'] = 'Switch to preview mode';
+$string['tasksendschedule'] = 'Send report schedule';
$string['tasksendschedules'] = 'Send report schedules';
$string['timeadded'] = 'Time added';
$string['timecreated'] = 'Time created';
diff --git a/lib/amd/build/chart_output_chartjs.min.js b/lib/amd/build/chart_output_chartjs.min.js
index b716b77873a..37237e0b650 100644
--- a/lib/amd/build/chart_output_chartjs.min.js
+++ b/lib/amd/build/chart_output_chartjs.min.js
@@ -5,6 +5,6 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @module core/chart_output_chartjs
*/
-define("core/chart_output_chartjs",["jquery","core/chartjs","core/chart_axis","core/chart_bar","core/chart_output_base","core/chart_line","core/chart_pie","core/chart_series"],(function($,Chartjs,Axis,Bar,Base,Line,Pie,Series){var makeAxisId=function(xy,index){return"axis-"+xy+"-"+index};function Output(){Base.prototype.constructor.apply(this,arguments),this._canvas=this._node,"CANVAS"!=this._canvas.prop("tagName")&&(this._canvas=$("