diff --git a/backup/moodle2/backup_stepslib.php b/backup/moodle2/backup_stepslib.php
index a31bfda80e207..2d2552dbe4b3c 100644
--- a/backup/moodle2/backup_stepslib.php
+++ b/backup/moodle2/backup_stepslib.php
@@ -341,7 +341,7 @@ protected function define_structure() {
'visibleold', 'groupmode', 'groupingid',
'completion', 'completiongradeitemnumber', 'completionpassgrade',
'completionview', 'completionexpected',
- 'availability', 'showdescription', 'downloadcontent'));
+ 'availability', 'showdescription', 'downloadcontent', 'lang'));
$tags = new backup_nested_element('tags');
$tag = new backup_nested_element('tag', array('id'), array('name', 'rawname'));
diff --git a/backup/moodle2/restore_stepslib.php b/backup/moodle2/restore_stepslib.php
index 3bcf1291f95b0..34a860b523f25 100644
--- a/backup/moodle2/restore_stepslib.php
+++ b/backup/moodle2/restore_stepslib.php
@@ -4491,6 +4491,10 @@ protected function process_module($data) {
$data->availability = upgrade_group_members_only($data->groupingid, $data->availability);
}
+ if (!has_capability('moodle/course:setforcedlanguage', context_course::instance($data->course))) {
+ unset($data->lang);
+ }
+
// course_module record ready, insert it
$newitemid = $DB->insert_record('course_modules', $data);
// save mapping
diff --git a/course/classes/external/helper_for_get_mods_by_courses.php b/course/classes/external/helper_for_get_mods_by_courses.php
index 44d497d64f250..8a1a33fbaea15 100644
--- a/course/classes/external/helper_for_get_mods_by_courses.php
+++ b/course/classes/external/helper_for_get_mods_by_courses.php
@@ -57,6 +57,7 @@ public static function standard_coursemodule_element_values(\stdClass $modinstan
$moddetails['coursemodule'] = $modinstance->coursemodule;
$moddetails['course'] = $modinstance->course;
$moddetails['name'] = $modinstance->name;
+ $moddetails['lang'] = clean_param($modinstance->lang, PARAM_LANG);
if (!$capabilityforintro || has_capability($capabilityforintro, $context)) {
$moddetails['intro'] = $modinstance->intro;
$moddetails['introformat'] = $modinstance->introformat;
@@ -130,6 +131,7 @@ public static function standard_coursemodule_elements_returns(bool $introoptiona
'visible' => new external_value(PARAM_BOOL, 'Visible', VALUE_OPTIONAL),
'groupmode' => new external_value(PARAM_INT, 'Group mode', VALUE_OPTIONAL),
'groupingid' => new external_value(PARAM_INT, 'Group id', VALUE_OPTIONAL),
+ 'lang' => new external_value(PARAM_SAFEDIR, 'Forced activity language', VALUE_OPTIONAL),
];
}
}
diff --git a/course/edit_form.php b/course/edit_form.php
index 50677e56a530d..0c6d9028ff1c8 100644
--- a/course/edit_form.php
+++ b/course/edit_form.php
@@ -251,11 +251,12 @@ function definition() {
$mform->addElement('select', 'theme', get_string('forcetheme'), $themes);
}
- $languages=array();
- $languages[''] = get_string('forceno');
- $languages += get_string_manager()->get_list_of_translations();
if ((empty($course->id) && guess_if_creator_will_have_course_capability('moodle/course:setforcedlanguage', $categorycontext))
|| (!empty($course->id) && has_capability('moodle/course:setforcedlanguage', $coursecontext))) {
+
+ $languages = ['' => get_string('forceno')];
+ $languages += get_string_manager()->get_list_of_translations();
+
$mform->addElement('select', 'lang', get_string('forcelanguage'), $languages);
$mform->setDefault('lang', $courseconfig->lang);
}
diff --git a/course/modlib.php b/course/modlib.php
index 5ec9c375641d1..ee5490cba97e1 100644
--- a/course/modlib.php
+++ b/course/modlib.php
@@ -70,6 +70,11 @@ function add_moduleinfo($moduleinfo, $course, $mform = null) {
if (isset($moduleinfo->downloadcontent)) {
$newcm->downloadcontent = $moduleinfo->downloadcontent;
}
+ if (has_capability('moodle/course:setforcedlanguage', context_course::instance($course->id))) {
+ $newcm->lang = $moduleinfo->lang ?? null;
+ } else {
+ $newcm->lang = null;
+ }
$newcm->groupmode = $moduleinfo->groupmode;
$newcm->groupingid = $moduleinfo->groupingid;
$completion = new completion_info($course);
@@ -552,6 +557,13 @@ function update_moduleinfo($cm, $moduleinfo, $course, $mform = null) {
$moduleinfo->course = $course->id;
$moduleinfo = set_moduleinfo_defaults($moduleinfo);
+ $modcontext = context_module::instance($moduleinfo->coursemodule);
+ if (has_capability('moodle/course:setforcedlanguage', $modcontext)) {
+ $cm->lang = $moduleinfo->lang ?? null;
+ } else {
+ unset($cm->lang);
+ }
+
if (!empty($course->groupmodeforce) or !isset($moduleinfo->groupmode)) {
$moduleinfo->groupmode = $cm->groupmode; // Keep original.
}
@@ -611,8 +623,6 @@ function update_moduleinfo($cm, $moduleinfo, $course, $mform = null) {
$DB->update_record('course_modules', $cm);
- $modcontext = context_module::instance($moduleinfo->coursemodule);
-
// Update embedded links and save files.
if (plugin_supports('mod', $moduleinfo->modulename, FEATURE_MOD_INTRO, true)) {
$moduleinfo->intro = file_save_draft_area_files($moduleinfo->introeditor['itemid'], $modcontext->id,
@@ -745,6 +755,7 @@ function get_moduleinfo_data($cm, $course) {
$data->completiongradeitemnumber = $cm->completiongradeitemnumber;
$data->showdescription = $cm->showdescription;
$data->downloadcontent = $cm->downloadcontent;
+ $data->lang = $cm->lang;
$data->tags = core_tag_tag::get_item_tags_array('core', 'course_modules', $cm->id);
if (!empty($CFG->enableavailability)) {
$data->availabilityconditionsjson = $cm->availability;
diff --git a/course/moodleform_mod.php b/course/moodleform_mod.php
index ada5c5e81beec..2dae3216d9c2e 100644
--- a/course/moodleform_mod.php
+++ b/course/moodleform_mod.php
@@ -625,11 +625,8 @@ protected function standard_coursemodule_elements() {
$mform->addElement('modvisible', 'visible', get_string($modvisiblelabel), null,
array('allowstealth' => $allowstealth, 'sectionvisible' => $section->visible, 'cm' => $this->_cm));
$mform->addHelpButton('visible', $modvisiblelabel);
- if (!empty($this->_cm)) {
- $context = context_module::instance($this->_cm->id);
- if (!has_capability('moodle/course:activityvisibility', $context)) {
- $mform->hardFreeze('visible');
- }
+ if (!empty($this->_cm) && !has_capability('moodle/course:activityvisibility', $this->get_context())) {
+ $mform->hardFreeze('visible');
}
if ($this->_features->idnumber) {
@@ -638,6 +635,13 @@ protected function standard_coursemodule_elements() {
$mform->addHelpButton('cmidnumber', 'idnumbermod');
}
+ if (has_capability('moodle/course:setforcedlanguage', $this->get_context())) {
+ $languages = ['' => get_string('forceno')];
+ $languages += get_string_manager()->get_list_of_translations();
+
+ $mform->addElement('select', 'lang', get_string('forcelanguage'), $languages);
+ }
+
if ($CFG->downloadcoursecontentallowed) {
$choices = [
DOWNLOAD_COURSE_CONTENT_DISABLED => get_string('no'),
@@ -900,9 +904,8 @@ protected function add_rating_settings($mform, int $itemnumber) {
$rolenamestring = null;
if ($isupdate) {
- $context = context_module::instance($this->_cm->id);
$capabilities = ['moodle/rating:rate', "mod/{$this->_cm->modname}:rate"];
- $rolenames = get_role_names_with_caps_in_context($context, $capabilities);
+ $rolenames = get_role_names_with_caps_in_context($this->get_context(), $capabilities);
$rolenamestring = implode(', ', $rolenames);
} else {
$rolenamestring = get_string('capabilitychecknotavailable', 'rating');
diff --git a/course/tests/backup_restore_activity_test.php b/course/tests/backup_restore_activity_test.php
new file mode 100644
index 0000000000000..0fc1d9ebbb255
--- /dev/null
+++ b/course/tests/backup_restore_activity_test.php
@@ -0,0 +1,137 @@
+.
+
+namespace core_course;
+use backup;
+
+/**
+ * Restore date tests.
+ *
+ * @package core_course
+ * @copyright 2022 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @covers \backup_module_structure_step
+ * @covers \restore_module_structure_step
+ */
+class backup_restore_activity_test extends \advanced_testcase {
+
+ /**
+ * Test that duplicating a page preserves the lang setting.
+ */
+ public function test_duplicating_page_preserves_lang() {
+ $this->resetAfterTest();
+ $this->setAdminUser();
+
+ // Make a test course.
+ $generator = $this->getDataGenerator();
+ $course = $generator->create_course();
+
+ // Create a page with forced language set.
+ $page = $generator->create_module('page', ['course' => $course->id, 'lang' => 'en']);
+
+ // Duplicate the page.
+ $newpagecm = duplicate_module($course, get_fast_modinfo($course)->get_cm($page->cmid));
+
+ // Verify the settings of the duplicated activity.
+ $this->assertEquals('en', $newpagecm->lang);
+ }
+
+ public function test_activity_forced_lang_not_restored_without_capability() {
+ global $DB;
+ $this->resetAfterTest();
+ $this->setAdminUser();
+
+ // Make a test course.
+ $generator = $this->getDataGenerator();
+ $course = $generator->create_course();
+
+ // Create a page with forced language set.
+ $generator->create_module('page', ['course' => $course->id, 'lang' => 'en']);
+
+ // Backup the course.
+ $backupid = $this->backup_course($course);
+
+ // Create a manger user without 'moodle/course:setforcedlanguage' to do the restore.
+ $manager = $generator->create_user();
+ $generator->role_assign('manager', $manager->id);
+ role_change_permission($DB->get_field('role', 'id', ['shortname' => 'manager'], MUST_EXIST),
+ \context_system::instance(), 'moodle/course:setforcedlanguage', CAP_INHERIT);
+ $this->setUser($manager);
+
+ // Restore the course.
+ $newcourseid = $this->restore_course($backupid);
+
+ // Verify the settings of the duplicated activity.
+ $newmodinfo = get_fast_modinfo($newcourseid);
+ $newcms = $newmodinfo->instances['page'];
+ $newpagecm = reset($newcms);
+ $this->assertNull($newpagecm->lang);
+ }
+
+ /**
+ * Makes a backup of the course.
+ *
+ * @param \stdClass $course The course object.
+ * @return string Unique identifier for this backup.
+ */
+ protected function backup_course(\stdClass $course): string {
+ global $CFG, $USER;
+
+ // Turn off file logging, otherwise it can't delete the file (Windows).
+ $CFG->backup_file_logger_level = backup::LOG_NONE;
+
+ // Do backup with default settings. MODE_IMPORT means it will just
+ // create the directory and not zip it.
+ $bc = new \backup_controller(backup::TYPE_1COURSE, $course->id,
+ backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_IMPORT,
+ $USER->id);
+ $backupid = $bc->get_backupid();
+ $bc->execute_plan();
+ $bc->destroy();
+
+ return $backupid;
+ }
+
+ /**
+ * Restores a backup that has been made earlier.
+ *
+ * @param string $backupid The unique identifier of the backup.
+ * @return int The new course id.
+ */
+ protected function restore_course(string $backupid): int {
+ global $CFG, $DB, $USER;
+
+ // Turn off file logging, otherwise it can't delete the file (Windows).
+ $CFG->backup_file_logger_level = backup::LOG_NONE;
+
+ $defaultcategoryid = $DB->get_field('course_categories', 'id',
+ ['parent' => 0], IGNORE_MULTIPLE);
+
+ // Do restore to new course with default settings.
+ $newcourseid = \restore_dbops::create_new_course('Restored course', 'R1', $defaultcategoryid);
+ $rc = new \restore_controller($backupid, $newcourseid,
+ backup::INTERACTIVE_NO, backup::MODE_GENERAL, $USER->id,
+ backup::TARGET_NEW_COURSE);
+
+ $precheck = $rc->execute_precheck();
+ $this->assertTrue($precheck);
+
+ $rc->execute_plan();
+ $rc->destroy();
+
+ return $newcourseid;
+ }
+}
diff --git a/course/tests/behat/add_activities.feature b/course/tests/behat/add_activities.feature
index 71e4db9212032..e979323a9735b 100644
--- a/course/tests/behat/add_activities.feature
+++ b/course/tests/behat/add_activities.feature
@@ -5,36 +5,30 @@ Feature: Add activities to courses
I need to add activites to a course
Background:
- Given the following "users" exist:
- | username | firstname | lastname | email |
- | student1 | Student | 1 | student1@example.com |
- | student2 | Student | 2 | student2@example.com |
- And the following "courses" exist:
+ Given the following "courses" exist:
| fullname | shortname | format |
- | Course 1 | Course 1 | topics |
- And the following "course enrolments" exist:
- | user | course | role |
- | student1 | Course 1 | student |
- | student2 | Course 1 | student |
+ | Course 1 | Course 1 | topics |
@javascript
Scenario: Add an activity to a course
Given I am on the "Course 1" Course page logged in as admin
And I am on "Course 1" course homepage with editing mode on
When I add a "Database" to section "3" and I fill the form with:
- | Name | Test name |
- | Description | Test database description |
- | ID number | TESTNAME |
- | Allow comments on entries | Yes |
+ | Name | Test name |
+ | Description | Test database description |
+ | ID number | TESTNAME |
+ | Allow comments on entries | Yes |
+ | Force language | English |
And I turn editing mode off
Then I should not see "Adding a new"
And I turn editing mode on
And I open "Test name" actions menu
And I click on "Edit settings" "link" in the "Test name" activity
- And I expand all fieldsets
- And the field "Name" matches value "Test name"
- And the field "ID number" matches value "TESTNAME"
- And the field "Allow comments on entries" matches value "Yes"
+ And the following fields match these values:
+ | Name | Test name |
+ | ID number | TESTNAME |
+ | Allow comments on entries | Yes |
+ | Force language | English (en) |
@javascript
Scenario: Add an activity supplying only the name
diff --git a/course/tests/modlib_test.php b/course/tests/modlib_test.php
index 6313bc2d6ba14..112bdd935082a 100644
--- a/course/tests/modlib_test.php
+++ b/course/tests/modlib_test.php
@@ -120,6 +120,7 @@ public function test_get_moduleinfo_data() {
$expecteddata->showdescription = $assigncm->showdescription;
$expecteddata->downloadcontent = $assigncm->downloadcontent;
$expecteddata->tags = \core_tag_tag::get_item_tags_array('core', 'course_modules', $assigncm->id);
+ $expecteddata->lang = null;
$expecteddata->availabilityconditionsjson = null;
$expecteddata->advancedgradingmethod_submissions = null;
if ($items = \grade_item::fetch_all(array('itemtype' => 'mod', 'itemmodule' => 'assign',
diff --git a/lang/en/role.php b/lang/en/role.php
index 486cd50352315..994768db8fcbc 100644
--- a/lang/en/role.php
+++ b/lang/en/role.php
@@ -178,7 +178,7 @@
$string['course:downloadcoursecontent'] = 'Download course content';
$string['course:enrolconfig'] = 'Configure enrol instances in courses';
$string['course:enrolreview'] = 'Review course enrolments';
-$string['course:setforcedlanguage'] = 'Force course language';
+$string['course:setforcedlanguage'] = 'Force course or activity language';
$string['course:ignoreavailabilityrestrictions'] = 'Ignore availability restrictions';
$string['course:ignorefilesizelimits'] = 'Use files larger than any file size restrictions';
$string['course:isincompletionreports'] = 'Be shown on completion reports';
diff --git a/lib/classes/string_manager_standard.php b/lib/classes/string_manager_standard.php
index fc1c3c44f824e..78f5c940f7b6e 100644
--- a/lib/classes/string_manager_standard.php
+++ b/lib/classes/string_manager_standard.php
@@ -526,7 +526,7 @@ public function get_list_of_translations($returnall = false) {
$cachekey = 'list_'.$this->get_key_suffix();
$cachedlist = $this->menucache->get($cachekey);
if ($cachedlist !== false) {
- // The cache content is invalid.
+ // The cache content is valid.
if ($returnall or empty($this->translist)) {
return $cachedlist;
}
diff --git a/lib/datalib.php b/lib/datalib.php
index aded6e4152a99..dba5309d5435b 100644
--- a/lib/datalib.php
+++ b/lib/datalib.php
@@ -1352,7 +1352,7 @@ function get_coursemodules_in_course($modulename, $courseid, $extrafields='') {
* in the course. Returns an empty array on any errors.
*
* The returned objects includle the columns cw.section, cm.visible,
- * cm.groupmode, and cm.groupingid, and are indexed by cm.id.
+ * cm.groupmode, cm.groupingid and cm.lang and are indexed by cm.id.
*
* @global object
* @global object
@@ -1380,7 +1380,7 @@ function get_all_instances_in_courses($modulename, $courses, $userid=NULL, $incl
$params['modulename'] = $modulename;
if (!$rawmods = $DB->get_records_sql("SELECT cm.id AS coursemodule, m.*, cw.section, cm.visible AS visible,
- cm.groupmode, cm.groupingid
+ cm.groupmode, cm.groupingid, cm.lang
FROM {course_modules} cm, {course_sections} cw, {modules} md,
{".$modulename."} m
WHERE cm.course $coursessql AND
diff --git a/lib/db/access.php b/lib/db/access.php
index 16d372aac3591..ef1e11b74f23c 100644
--- a/lib/db/access.php
+++ b/lib/db/access.php
@@ -1094,6 +1094,7 @@
'clonepermissionsfrom' => 'moodle/course:update'
),
+ // Ability to set a forced language for a course or activity.
'moodle/course:setforcedlanguage' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_COURSE,
diff --git a/lib/db/install.xml b/lib/db/install.xml
index a070c9d7ef66e..39c44669cf3c5 100644
--- a/lib/db/install.xml
+++ b/lib/db/install.xml
@@ -93,7 +93,7 @@
-
+
@@ -311,6 +311,7 @@
+
diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php
index 6d3f0da2c05eb..afa05ac6cc22d 100644
--- a/lib/db/upgrade.php
+++ b/lib/db/upgrade.php
@@ -2888,5 +2888,20 @@ function xmldb_main_upgrade($oldversion) {
upgrade_main_savepoint(true, 2022072900.00);
}
+ if ($oldversion < 2022081200.01) {
+
+ // Define field lang to be added to course_modules.
+ $table = new xmldb_table('course_modules');
+ $field = new xmldb_field('lang', XMLDB_TYPE_CHAR, '30', null, null, null, null, 'downloadcontent');
+
+ // Conditionally launch add field lang.
+ if (!$dbman->field_exists($table, $field)) {
+ $dbman->add_field($table, $field);
+ }
+
+ // Main savepoint reached.
+ upgrade_main_savepoint(true, 2022081200.01);
+ }
+
return true;
}
diff --git a/lib/modinfolib.php b/lib/modinfolib.php
index ae07d8a13e77e..0e2b5dd5d3542 100644
--- a/lib/modinfolib.php
+++ b/lib/modinfolib.php
@@ -865,6 +865,7 @@ public static function get_array_of_activities(stdClass $course, bool $usecache
$mods[$cmid]->availability = $rawmods[$cmid]->availability;
$mods[$cmid]->deletioninprogress = $rawmods[$cmid]->deletioninprogress;
$mods[$cmid]->downloadcontent = $rawmods[$cmid]->downloadcontent;
+ $mods[$cmid]->lang = $rawmods[$cmid]->lang;
$modname = $mods[$cmid]->mod;
$functionname = $modname . "_get_coursemodule_info";
@@ -1110,6 +1111,7 @@ public static function purge_course_cache(int $courseid): void {
* @property-read string $afterediticons Extra HTML code to display after editing icons (e.g. more icons) - calculated on request
* @property-read bool $deletioninprogress True if this course module is scheduled for deletion, false otherwise.
* @property-read bool $downloadcontent True if content download is enabled for this course module, false otherwise.
+ * @property-read bool $lang the forced language for this activity (language pack name). Null means not forced.
*/
class cm_info implements IteratorAggregate {
/**
@@ -1433,12 +1435,17 @@ class cm_info implements IteratorAggregate {
*/
private $downloadcontent;
+ /**
+ * @var string|null the forced language for this activity (language pack name). Null means not forced.
+ */
+ private $lang;
+
/**
* List of class read-only properties and their getter methods.
* Used by magic functions __get(), __isset(), __empty()
* @var array
*/
- private static $standardproperties = array(
+ private static $standardproperties = [
'url' => 'get_url',
'content' => 'get_content',
'extraclasses' => 'get_extra_classes',
@@ -1488,8 +1495,9 @@ class cm_info implements IteratorAggregate {
'visibleoncoursepage' => false,
'visibleold' => false,
'deletioninprogress' => false,
- 'downloadcontent' => false
- );
+ 'downloadcontent' => false,
+ 'lang' => false,
+ ];
/**
* List of methods with no arguments that were public prior to Moodle 2.6.
@@ -1948,7 +1956,7 @@ public function get_course_module_record($additionalfields = false) {
static $cmfields = array('id', 'course', 'module', 'instance', 'section', 'idnumber', 'added',
'score', 'indent', 'visible', 'visibleoncoursepage', 'visibleold', 'groupmode', 'groupingid',
'completion', 'completiongradeitemnumber', 'completionview', 'completionexpected', 'completionpassgrade',
- 'showdescription', 'availability', 'deletioninprogress', 'downloadcontent');
+ 'showdescription', 'availability', 'deletioninprogress', 'downloadcontent', 'lang');
foreach ($cmfields as $key) {
$cmrecord->$key = $this->$key;
@@ -2175,6 +2183,7 @@ public function __construct(course_modinfo $modinfo, $notused1, $mod, $notused2)
$this->visibleold = isset($mod->visibleold) ? $mod->visibleold : 0;
$this->deletioninprogress = isset($mod->deletioninprogress) ? $mod->deletioninprogress : 0;
$this->downloadcontent = $mod->downloadcontent ?? null;
+ $this->lang = $mod->lang ?? null;
// Note: it saves effort and database space to always include the
// availability and completion fields, even if availability or completion
diff --git a/lib/moodlelib.php b/lib/moodlelib.php
index 1ad3d5b881ab1..d555ca6f7a87f 100644
--- a/lib/moodlelib.php
+++ b/lib/moodlelib.php
@@ -7080,7 +7080,7 @@ function clean_filename($string) {
* @return string
*/
function current_language() {
- global $CFG, $USER, $SESSION, $COURSE;
+ global $CFG, $PAGE, $SESSION, $USER;
if (!empty($SESSION->forcelang)) {
// Allows overriding course-forced language (useful for admins to check
@@ -7089,9 +7089,13 @@ function current_language() {
// specific language (see force_current_language()).
$return = $SESSION->forcelang;
- } else if (!empty($COURSE->id) and $COURSE->id != SITEID and !empty($COURSE->lang)) {
+ } else if (!empty($PAGE->cm->lang)) {
+ // Activity language, if set.
+ $return = $PAGE->cm->lang;
+
+ } else if (!empty($PAGE->course->id) && $PAGE->course->id != SITEID && !empty($PAGE->course->lang)) {
// Course language can override all other settings for this page.
- $return = $COURSE->lang;
+ $return = $PAGE->course->lang;
} else if (!empty($SESSION->lang)) {
// Session language can override other settings.
@@ -7144,7 +7148,7 @@ function force_current_language($language) {
global $SESSION;
$sessionforcelang = isset($SESSION->forcelang) ? $SESSION->forcelang : '';
if ($language !== $sessionforcelang) {
- // Seting forcelang to null or an empty string disables it's effect.
+ // Setting forcelang to null or an empty string disables its effect.
if (empty($language) || get_string_manager()->translation_exists($language, false)) {
$SESSION->forcelang = $language;
moodle_setlocale();
diff --git a/lib/testing/generator/data_generator.php b/lib/testing/generator/data_generator.php
index 73ee0cb104756..1863c7f19dc35 100644
--- a/lib/testing/generator/data_generator.php
+++ b/lib/testing/generator/data_generator.php
@@ -949,12 +949,13 @@ public function enrol_user($userid, $courseid, $roleidorshortname = null, $enrol
/**
* Assigns the specified role to a user in the context.
*
- * @param int $roleid
+ * @param int|string $role either an int role id or a string role shortname.
* @param int $userid
* @param int $contextid Defaults to the system context
* @return int new/existing id of the assignment
*/
- public function role_assign($roleid, $userid, $contextid = false) {
+ public function role_assign($role, $userid, $contextid = false) {
+ global $DB;
// Default to the system context.
if (!$contextid) {
@@ -962,15 +963,18 @@ public function role_assign($roleid, $userid, $contextid = false) {
$contextid = $context->id;
}
- if (empty($roleid)) {
+ if (empty($role)) {
throw new coding_exception('roleid must be present in testing_data_generator::role_assign() arguments');
}
+ if (!is_number($role)) {
+ $role = $DB->get_field('role', 'id', ['shortname' => $role], MUST_EXIST);
+ }
if (empty($userid)) {
throw new coding_exception('userid must be present in testing_data_generator::role_assign() arguments');
}
- return role_assign($roleid, $userid, $contextid);
+ return role_assign($role, $userid, $contextid);
}
/**
diff --git a/lib/testing/generator/module_generator.php b/lib/testing/generator/module_generator.php
index bfb543e0af4e4..bb4ef49f11bb4 100644
--- a/lib/testing/generator/module_generator.php
+++ b/lib/testing/generator/module_generator.php
@@ -268,8 +268,13 @@ public function create_instance($record = null, array $options = null) {
debugging('Did you forget to enable completion tracking for the course before generating module with completion tracking?', DEBUG_DEVELOPER);
}
+ if (!empty($record->lang) && !has_capability('moodle/course:setforcedlanguage', context_course::instance($course->id))) {
+ throw new coding_exception('Attempt to generate an activity when the current user does not have ' .
+ 'permission moodle/course:setforcedlanguage. This does not work.');
+ }
+
// Add the module to the course.
- $moduleinfo = add_moduleinfo($record, $course, $mform = null);
+ $moduleinfo = add_moduleinfo($record, $course);
// Prepare object to return with additional field cmid.
$instance = $DB->get_record($this->get_modulename(), array('id' => $moduleinfo->instance), '*', MUST_EXIST);
diff --git a/lib/tests/moodlelib_current_language_test.php b/lib/tests/moodlelib_current_language_test.php
new file mode 100644
index 0000000000000..2b6c6cf273ede
--- /dev/null
+++ b/lib/tests/moodlelib_current_language_test.php
@@ -0,0 +1,196 @@
+.
+
+/**
+ * Unit tests for current_language() in moodlelib.php.
+ *
+ * @package core
+ * @category test
+ * @copyright 2022 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core;
+
+use moodle_page;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Unit tests for current_language() in moodlelib.php.
+ *
+ * @copyright 2022 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @covers ::current_language
+ */
+class moodlelib_current_language_test extends \advanced_testcase {
+
+ public function test_current_language_site_default(): void {
+ $this->resetAfterTest();
+ testable_string_manager_for_current_language_tests::set_fake_list_of_installed_languages(
+ ['en' => 'English', 'en_ar' => 'English (pirate)']);
+
+ set_config('lang', 'en_ar');
+
+ $this->assertEquals('en_ar', current_language());
+
+ testable_string_manager_for_current_language_tests::reset_installed_languages_override();
+ }
+
+ public function test_current_language_user_pref(): void {
+ $this->resetAfterTest();
+ testable_string_manager_for_current_language_tests::set_fake_list_of_installed_languages(
+ ['en' => 'English', 'en_ar' => 'English (pirate)', 'fr' => 'French']);
+
+ set_config('lang', 'en_ar');
+ $this->setUser($this->getDataGenerator()->create_user(['lang' => 'fr']));
+
+ $this->assertEquals('fr', current_language());
+
+ testable_string_manager_for_current_language_tests::reset_installed_languages_override();
+ }
+
+ public function test_current_language_forced(): void {
+ $this->resetAfterTest();
+ testable_string_manager_for_current_language_tests::set_fake_list_of_installed_languages(
+ ['en' => 'English', 'en_ar' => 'English (pirate)', 'fr' => 'French', 'de' => 'German']);
+
+ set_config('lang', 'en_ar');
+ $this->setUser($this->getDataGenerator()->create_user(['lang' => 'fr']));
+ force_current_language('en');
+
+ $this->assertEquals('en', current_language());
+ }
+
+ public function test_current_language_course_setting(): void {
+ global $PAGE;
+ $this->resetAfterTest();
+ testable_string_manager_for_current_language_tests::set_fake_list_of_installed_languages(
+ ['en' => 'English', 'en_ar' => 'English (pirate)', 'fr' => 'French']);
+
+ set_config('lang', 'en_ar');
+ $this->setUser($this->getDataGenerator()->create_user(['lang' => 'fr']));
+ $PAGE = new moodle_page();
+ $PAGE->set_course($this->getDataGenerator()->create_course(['lang' => 'de']));
+
+ $this->assertEquals('de', current_language());
+
+ testable_string_manager_for_current_language_tests::reset_installed_languages_override();
+ }
+
+ public function test_current_language_in_course_no_lang_set(): void {
+ global $PAGE;
+ $this->resetAfterTest();
+ testable_string_manager_for_current_language_tests::set_fake_list_of_installed_languages(
+ ['en' => 'English', 'en_ar' => 'English (pirate)', 'fr' => 'French']);
+
+ set_config('lang', 'en_ar');
+ $PAGE = new moodle_page();
+ $PAGE->set_course($this->getDataGenerator()->create_course());
+
+ $this->assertEquals('en_ar', current_language());
+
+ testable_string_manager_for_current_language_tests::reset_installed_languages_override();
+ }
+
+ public function test_current_language_activity_setting(): void {
+ global $PAGE;
+ $this->resetAfterTest();
+ testable_string_manager_for_current_language_tests::set_fake_list_of_installed_languages(
+ ['en' => 'English', 'en_ar' => 'English (pirate)', 'fr' => 'French']);
+
+ $this->setAdminUser();
+ $course = $this->getDataGenerator()->create_course(['lang' => 'de']);
+ $pageactivity = $this->getDataGenerator()->create_module('page', ['course' => $course->id, 'lang' => 'en']);
+ $cm = get_fast_modinfo($course)->get_cm($pageactivity->cmid);
+
+ set_config('lang', 'en_ar');
+ $this->setUser($this->getDataGenerator()->create_user(['lang' => 'fr']));
+ $PAGE = new moodle_page();
+ $PAGE->set_cm($cm, $course, $pageactivity);
+
+ $this->assertEquals('en', current_language());
+
+ testable_string_manager_for_current_language_tests::reset_installed_languages_override();
+ }
+
+ public function test_current_language_activity_setting_not_set(): void {
+ global $PAGE;
+ $this->resetAfterTest();
+ testable_string_manager_for_current_language_tests::set_fake_list_of_installed_languages(
+ ['en' => 'English', 'en_ar' => 'English (pirate)', 'fr' => 'French']);
+
+ $this->setAdminUser();
+ $course = $this->getDataGenerator()->create_course(['lang' => 'de']);
+ $pageactivity = $this->getDataGenerator()->create_module('page', ['course' => $course->id]);
+ $cm = get_fast_modinfo($course)->get_cm($pageactivity->cmid);
+
+ set_config('lang', 'en_ar');
+ $this->setUser($this->getDataGenerator()->create_user(['lang' => 'fr']));
+ $PAGE = new moodle_page();
+ $PAGE->set_cm($cm, $course, $pageactivity);
+
+ $this->assertEquals('de', current_language());
+
+ testable_string_manager_for_current_language_tests::reset_installed_languages_override();
+ }
+}
+
+
+/**
+ * Test helper class for test which need Moodle to think there are other languages installed.
+ *
+ * @copyright 2022 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class testable_string_manager_for_current_language_tests extends \core_string_manager_standard {
+
+ /** @var array $installedlanguages list of languages which we want to pretend are installed. */
+ protected $installedlanguages;
+
+ /**
+ * Start pretending that the list of installed languages is other than what it is.
+ *
+ * You need to pass in an array like ['en' => 'English', 'fr' => 'French'].
+ *
+ * @param array $installedlanguages the list of languages to assume are installed.
+ */
+ public static function set_fake_list_of_installed_languages(array $installedlanguages): void {
+ global $CFG;
+
+ // Re-create the custom string-manager instance using this class, and force the thing we are overriding.
+ $oldsetting = $CFG->config_php_settings['customstringmanager'] ?? null;
+ $CFG->config_php_settings['customstringmanager'] = self::class;
+ get_string_manager(true)->installedlanguages = $installedlanguages;
+
+ // Reset the setting we overrode.
+ unset($CFG->config_php_settings['customstringmanager']);
+ if ($oldsetting) {
+ $CFG->config_php_settings['customstringmanager'] = $oldsetting;
+ }
+ }
+
+ /**
+ * Must be called at the end of any test which called set_fake_list_of_installed_languages to reset things.
+ */
+ public static function reset_installed_languages_override(): void {
+ get_string_manager(true);
+ }
+
+ public function get_list_of_translations($returnall = false) {
+ return $this->installedlanguages;
+ }
+}
diff --git a/mod/book/tests/externallib_test.php b/mod/book/tests/externallib_test.php
index aec10eee9a604..9508f647e57c6 100644
--- a/mod/book/tests/externallib_test.php
+++ b/mod/book/tests/externallib_test.php
@@ -160,7 +160,7 @@ public function test_get_books_by_courses() {
$this->assertCount(1, $books['books']);
$this->assertEquals('First Book', $books['books'][0]['name']);
// We see 10 fields.
- $this->assertCount(10, $books['books'][0]);
+ $this->assertCount(11, $books['books'][0]);
// As Student you cannot see some book properties like 'section'.
$this->assertFalse(isset($books['books'][0]['section']));
@@ -182,7 +182,7 @@ public function test_get_books_by_courses() {
$this->assertCount(1, $books['books']);
$this->assertEquals('Second Book', $books['books'][0]['name']);
// We see 17 fields.
- $this->assertCount(17, $books['books'][0]);
+ $this->assertCount(18, $books['books'][0]);
// As an Admin you can see some book properties like 'section'.
$this->assertEquals(0, $books['books'][0]['section']);
diff --git a/mod/chat/tests/externallib_test.php b/mod/chat/tests/externallib_test.php
index 9d7db19807676..3219c8da2cd13 100644
--- a/mod/chat/tests/externallib_test.php
+++ b/mod/chat/tests/externallib_test.php
@@ -218,7 +218,7 @@ public function test_view_chat() {
* Test get_chats_by_courses
*/
public function test_get_chats_by_courses() {
- global $DB, $USER, $CFG;
+ global $DB, $CFG;
$this->resetAfterTest(true);
$this->setAdminUser();
@@ -250,7 +250,7 @@ public function test_get_chats_by_courses() {
$this->assertCount(1, $chats['chats']);
$this->assertEquals('First Chat', $chats['chats'][0]['name']);
// We see 12 fields.
- $this->assertCount(12, $chats['chats'][0]);
+ $this->assertCount(13, $chats['chats'][0]);
// As Student you cannot see some chat properties like 'section'.
$this->assertFalse(isset($chats['chats'][0]['section']));
@@ -273,7 +273,7 @@ public function test_get_chats_by_courses() {
$this->assertEquals('Second Chat', $chats['chats'][0]['name']);
$this->assertEquals('header_js', $chats['chats'][0]['chatmethod']);
// We see 17 fields.
- $this->assertCount(17, $chats['chats'][0]);
+ $this->assertCount(18, $chats['chats'][0]);
// As an Admin you can see some chat properties like 'section'.
$this->assertEquals(0, $chats['chats'][0]['section']);
diff --git a/mod/data/classes/external/database_summary_exporter.php b/mod/data/classes/external/database_summary_exporter.php
index 79e522d5b90f6..ec85060293419 100644
--- a/mod/data/classes/external/database_summary_exporter.php
+++ b/mod/data/classes/external/database_summary_exporter.php
@@ -58,6 +58,11 @@ protected static function define_properties() {
'type' => PARAM_INT,
'default' => FORMAT_MOODLE
),
+ 'lang' => array(
+ 'type' => PARAM_LANG,
+ 'description' => 'Forced activity language',
+ 'null' => NULL_ALLOWED,
+ ),
'comments' => array(
'type' => PARAM_BOOL,
'description' => 'comments enabled',
diff --git a/mod/data/tests/externallib_test.php b/mod/data/tests/externallib_test.php
index 98b43bdad261f..671c3b9bba239 100644
--- a/mod/data/tests/externallib_test.php
+++ b/mod/data/tests/externallib_test.php
@@ -193,15 +193,19 @@ public function test_mod_data_get_databases_by_courses() {
// First for the student user.
$expectedfields = array('id', 'coursemodule', 'course', 'name', 'comments', 'timeavailablefrom',
'timeavailableto', 'timeviewfrom', 'timeviewto', 'requiredentries', 'requiredentriestoview',
- 'intro', 'introformat', 'introfiles', 'maxentries', 'rssarticles', 'singletemplate', 'listtemplate',
+ 'intro', 'introformat', 'introfiles', 'lang',
+ 'maxentries', 'rssarticles', 'singletemplate', 'listtemplate',
'listtemplateheader', 'listtemplatefooter', 'addtemplate', 'rsstemplate', 'rsstitletemplate',
- 'csstemplate', 'jstemplate', 'asearchtemplate', 'approval', 'defaultsort', 'defaultsortdir', 'manageapproved');
+ 'csstemplate', 'jstemplate', 'asearchtemplate', 'approval',
+ 'defaultsort', 'defaultsortdir', 'manageapproved');
// Add expected coursemodule.
$database1->coursemodule = $database1->cmid;
$database1->introfiles = [];
+ $database1->lang = '';
$database2->coursemodule = $database2->cmid;
$database2->introfiles = [];
+ $database2->lang = '';
$expected1 = array();
$expected2 = array();
diff --git a/mod/feedback/classes/external/feedback_summary_exporter.php b/mod/feedback/classes/external/feedback_summary_exporter.php
index 856b67bc983be..08f07fde4e6b6 100644
--- a/mod/feedback/classes/external/feedback_summary_exporter.php
+++ b/mod/feedback/classes/external/feedback_summary_exporter.php
@@ -62,6 +62,11 @@ protected static function define_properties() {
'default' => FORMAT_MOODLE,
'description' => 'Feedback intro text format.',
),
+ 'lang' => array(
+ 'type' => PARAM_LANG,
+ 'description' => 'Forced activity language',
+ 'null' => NULL_ALLOWED,
+ ),
'anonymous' => array(
'type' => PARAM_INT,
'description' => 'Whether the feedback is anonymous.',
diff --git a/mod/feedback/tests/external/external_test.php b/mod/feedback/tests/external/external_test.php
index 19555d713274f..df8e12b5dbd2f 100644
--- a/mod/feedback/tests/external/external_test.php
+++ b/mod/feedback/tests/external/external_test.php
@@ -107,7 +107,6 @@ public function populate_feedback($feedback, $pagescount = 1) {
* Test test_mod_feedback_get_feedbacks_by_courses
*/
public function test_mod_feedback_get_feedbacks_by_courses() {
- global $DB;
// Create additional course.
$course2 = self::getDataGenerator()->create_course();
@@ -134,7 +133,7 @@ public function test_mod_feedback_get_feedbacks_by_courses() {
// Create what we expect to be returned when querying the two courses.
// First for the student user.
- $expectedfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles', 'anonymous',
+ $expectedfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles', 'lang', 'anonymous',
'multiple_submit', 'autonumbering', 'page_after_submitformat', 'publish_stats', 'completionsubmit');
$properties = feedback_summary_exporter::read_properties_definition();
@@ -144,10 +143,12 @@ public function test_mod_feedback_get_feedbacks_by_courses() {
$feedback1->coursemodule = $feedback1->cmid;
$feedback1->introformat = 1;
$feedback1->introfiles = [];
+ $feedback1->lang = '';
$feedback2->coursemodule = $feedback2->cmid;
$feedback2->introformat = 1;
$feedback2->introfiles = [];
+ $feedback2->lang = '';
foreach ($expectedfields as $field) {
if (!empty($properties[$field]) && $properties[$field]['type'] == PARAM_BOOL) {
diff --git a/mod/folder/tests/externallib_test.php b/mod/folder/tests/externallib_test.php
index 06f4df8717b40..1a0b0a63df518 100644
--- a/mod/folder/tests/externallib_test.php
+++ b/mod/folder/tests/externallib_test.php
@@ -149,7 +149,7 @@ public function test_mod_folder_get_folders_by_courses() {
$returndescription = mod_folder_external::get_folders_by_courses_returns();
// Create what we expect to be returned when querying the two courses.
- $expectedfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles', 'revision',
+ $expectedfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles', 'lang', 'revision',
'timemodified', 'display', 'showexpanded', 'showdownloadfolder', 'section', 'visible',
'forcedownload', 'groupmode', 'groupingid');
@@ -161,6 +161,7 @@ public function test_mod_folder_get_folders_by_courses() {
$folder1->groupmode = 0;
$folder1->groupingid = 0;
$folder1->introfiles = [];
+ $folder1->lang = '';
$folder2->coursemodule = $folder2->cmid;
$folder2->introformat = 1;
@@ -169,6 +170,7 @@ public function test_mod_folder_get_folders_by_courses() {
$folder2->groupmode = 0;
$folder2->groupingid = 0;
$folder2->introfiles = [];
+ $folder2->lang = '';
foreach ($expectedfields as $field) {
$expected1[$field] = $folder1->{$field};
diff --git a/mod/forum/externallib.php b/mod/forum/externallib.php
index 43be7e6506a0d..b019ab3e73547 100644
--- a/mod/forum/externallib.php
+++ b/mod/forum/externallib.php
@@ -97,6 +97,8 @@ public static function get_forums_by_courses($courseids = array()) {
list($forum->intro, $forum->introformat) =
external_format_text($forum->intro, $forum->introformat, $context->id, 'mod_forum', 'intro', null, $options);
$forum->introfiles = external_util::get_area_files($context->id, 'mod_forum', 'intro', false, false);
+ $forum->lang = clean_param($forum->lang, PARAM_LANG);
+
// Discussions count. This function does static request cache.
$forum->numdiscussions = forum_count_discussions($forum, $cm, $course);
$forum->cmid = $forum->coursemodule;
@@ -134,6 +136,7 @@ public static function get_forums_by_courses_returns() {
'intro' => new external_value(PARAM_RAW, 'The forum intro'),
'introformat' => new external_format_value('intro'),
'introfiles' => new external_files('Files in the introduction text', VALUE_OPTIONAL),
+ 'lang' => new external_value(PARAM_SAFEDIR, 'Forced activity language', VALUE_OPTIONAL),
'duedate' => new external_value(PARAM_INT, 'duedate for the user', VALUE_OPTIONAL),
'cutoffdate' => new external_value(PARAM_INT, 'cutoffdate for the user', VALUE_OPTIONAL),
'assessed' => new external_value(PARAM_INT, 'Aggregate type'),
diff --git a/mod/forum/tests/externallib_test.php b/mod/forum/tests/externallib_test.php
index 6d293f082b0f6..5de6f02ffeb4a 100644
--- a/mod/forum/tests/externallib_test.php
+++ b/mod/forum/tests/externallib_test.php
@@ -145,6 +145,7 @@ public function test_mod_forum_get_forums_by_courses() {
$forum1->istracked = true;
$forum1->unreadpostscount = 0;
$forum1->introfiles = [];
+ $forum1->lang = '';
$record = new \stdClass();
$record->course = $course2->id;
@@ -157,6 +158,7 @@ public function test_mod_forum_get_forums_by_courses() {
// Default limited role, no create discussion capability enabled.
$forum2->cancreatediscussions = false;
$forum2->istracked = false;
+ $forum2->lang = '';
// Check the forum was correctly created.
$this->assertEquals(2, $DB->count_records_select('forum', 'id = :forum1 OR id = :forum2',
diff --git a/mod/label/tests/externallib_test.php b/mod/label/tests/externallib_test.php
index 95e5544708c07..0a36608c0767a 100644
--- a/mod/label/tests/externallib_test.php
+++ b/mod/label/tests/externallib_test.php
@@ -78,7 +78,7 @@ public function test_mod_label_get_labels_by_courses() {
// Create what we expect to be returned when querying the two courses.
$expectedfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles', 'timemodified',
- 'section', 'visible', 'groupmode', 'groupingid');
+ 'section', 'visible', 'groupmode', 'groupingid', 'lang');
// Add expected coursemodule and data.
$label1->coursemodule = $label1->cmid;
@@ -88,6 +88,7 @@ public function test_mod_label_get_labels_by_courses() {
$label1->groupmode = 0;
$label1->groupingid = 0;
$label1->introfiles = [];
+ $label1->lang = '';
$label2->coursemodule = $label2->cmid;
$label2->introformat = 1;
@@ -96,6 +97,7 @@ public function test_mod_label_get_labels_by_courses() {
$label2->groupmode = 0;
$label2->groupingid = 0;
$label2->introfiles = [];
+ $label2->lang = '';
foreach ($expectedfields as $field) {
$expected1[$field] = $label1->{$field};
diff --git a/mod/lesson/classes/external.php b/mod/lesson/classes/external.php
index fc99b71d0f0fe..2d1e648717d33 100644
--- a/mod/lesson/classes/external.php
+++ b/mod/lesson/classes/external.php
@@ -54,6 +54,7 @@ protected static function get_lesson_summary_for_exporter($lessonrecord, $passwo
$lesson = new lesson($lessonrecord);
$lesson->update_effective_access($USER->id);
+ $lessonrecord->lang = $lesson->get_cm()->lang;
$lessonavailable = $lesson->get_time_restriction_status() === false;
$lessonavailable = $lessonavailable && $lesson->get_password_restriction_status($password) === false;
$lessonavailable = $lessonavailable && $lesson->get_dependencies_restriction_status() === false;
diff --git a/mod/lesson/classes/external/lesson_summary_exporter.php b/mod/lesson/classes/external/lesson_summary_exporter.php
index e0fa14a5d53f7..a5b30448ae632 100644
--- a/mod/lesson/classes/external/lesson_summary_exporter.php
+++ b/mod/lesson/classes/external/lesson_summary_exporter.php
@@ -66,6 +66,11 @@ protected static function define_properties() {
'type' => PARAM_INT,
'default' => FORMAT_MOODLE
),
+ 'lang' => array(
+ 'type' => PARAM_LANG,
+ 'description' => 'Forced activity language',
+ 'null' => NULL_ALLOWED,
+ ),
'practice' => array(
'type' => PARAM_BOOL,
'description' => 'Practice lesson?',
diff --git a/mod/lesson/tests/external/external_test.php b/mod/lesson/tests/external/external_test.php
index 4f21c11978a31..74a369650d67a 100644
--- a/mod/lesson/tests/external/external_test.php
+++ b/mod/lesson/tests/external/external_test.php
@@ -142,8 +142,8 @@ public function test_mod_lesson_get_lessons_by_courses() {
// Create what we expect to be returned when querying the two courses.
// First for the student user.
- $expectedfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles', 'practice',
- 'modattempts', 'usepassword', 'grade', 'custom', 'ongoing', 'usemaxgrade',
+ $expectedfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles', 'lang',
+ 'practice', 'modattempts', 'usepassword', 'grade', 'custom', 'ongoing', 'usemaxgrade',
'maxanswers', 'maxattempts', 'review', 'nextpagedefault', 'feedback', 'minquestions',
'maxpages', 'timelimit', 'retake', 'mediafile', 'mediafiles', 'mediaheight', 'mediawidth',
'mediaclose', 'slideshow', 'width', 'height', 'bgcolor', 'displayleft', 'displayleftif',
@@ -155,11 +155,13 @@ public function test_mod_lesson_get_lessons_by_courses() {
$lesson1->introformat = 1;
$lesson1->introfiles = [];
$lesson1->mediafiles = [];
+ $lesson1->lang = '';
$lesson2->coursemodule = $lesson2->cmid;
$lesson2->introformat = 1;
$lesson2->introfiles = [];
$lesson2->mediafiles = [];
+ $lesson2->lang = '';
$booltypes = array('practice', 'modattempts', 'usepassword', 'custom', 'ongoing', 'review', 'feedback', 'retake',
'slideshow', 'displayleft', 'progressbar', 'allowofflineattempts');
@@ -1322,7 +1324,7 @@ public function test_get_lesson_user_student() {
// Lesson not using password.
$result = mod_lesson_external::get_lesson($this->lesson->id);
$result = \external_api::clean_returnvalue(mod_lesson_external::get_lesson_returns(), $result);
- $this->assertCount(36, $result['lesson']); // Expect most of the fields.
+ $this->assertCount(37, $result['lesson']); // Expect most of the fields.
$this->assertFalse(isset($result['password']));
}
@@ -1340,7 +1342,7 @@ public function test_get_lesson_user_student_with_missing_password() {
// Lesson not using password.
$result = mod_lesson_external::get_lesson($this->lesson->id);
$result = \external_api::clean_returnvalue(mod_lesson_external::get_lesson_returns(), $result);
- $this->assertCount(6, $result['lesson']); // Expect just this few fields.
+ $this->assertCount(7, $result['lesson']); // Expect just this few fields.
$this->assertFalse(isset($result['intro']));
}
@@ -1358,7 +1360,7 @@ public function test_get_lesson_user_student_with_correct_password() {
// Lesson not using password.
$result = mod_lesson_external::get_lesson($this->lesson->id, $password);
$result = \external_api::clean_returnvalue(mod_lesson_external::get_lesson_returns(), $result);
- $this->assertCount(36, $result['lesson']);
+ $this->assertCount(37 , $result['lesson']);
$this->assertFalse(isset($result['intro']));
}
@@ -1376,7 +1378,7 @@ public function test_get_lesson_teacher() {
// Lesson not passing a valid password (but we are teachers, we should see all the info).
$result = mod_lesson_external::get_lesson($this->lesson->id);
$result = \external_api::clean_returnvalue(mod_lesson_external::get_lesson_returns(), $result);
- $this->assertCount(45, $result['lesson']); // Expect all the fields.
+ $this->assertCount(46, $result['lesson']); // Expect all the fields.
$this->assertEquals($result['lesson']['password'], $password);
}
}
diff --git a/mod/lti/tests/externallib_test.php b/mod/lti/tests/externallib_test.php
index b9bd638a05f60..62fa2f692d749 100644
--- a/mod/lti/tests/externallib_test.php
+++ b/mod/lti/tests/externallib_test.php
@@ -192,7 +192,7 @@ public function test_mod_lti_get_ltis_by_courses() {
// Create what we expect to be returned when querying the two courses.
// First for the student user.
- $expectedfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles',
+ $expectedfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles', 'lang',
'launchcontainer', 'showtitlelaunch', 'showdescriptionlaunch', 'icon', 'secureicon');
// Add expected coursemodule and data.
@@ -205,6 +205,7 @@ public function test_mod_lti_get_ltis_by_courses() {
$lti1->groupingid = 0;
$lti1->section = 0;
$lti1->introfiles = [];
+ $lti1->lang = '';
$lti2->coursemodule = $lti2->cmid;
$lti2->introformat = 1;
@@ -214,6 +215,7 @@ public function test_mod_lti_get_ltis_by_courses() {
$lti2->groupingid = 0;
$lti2->section = 0;
$lti2->introfiles = [];
+ $lti2->lang = '';
foreach ($expectedfields as $field) {
$expected1[$field] = $lti1->{$field};
@@ -257,7 +259,7 @@ public function test_mod_lti_get_ltis_by_courses() {
$additionalfields = array('timecreated', 'timemodified', 'typeid', 'toolurl', 'securetoolurl',
'instructorchoicesendname', 'instructorchoicesendemailaddr', 'instructorchoiceallowroster',
'instructorchoiceallowsetting', 'instructorcustomparameters', 'instructorchoiceacceptgrades', 'grade',
- 'resourcekey', 'password', 'debuglaunch', 'servicesalt', 'visible', 'groupmode', 'groupingid', 'section');
+ 'resourcekey', 'password', 'debuglaunch', 'servicesalt', 'visible', 'groupmode', 'groupingid', 'section', 'lang');
foreach ($additionalfields as $field) {
$expectedltis[0][$field] = $lti1->{$field};
diff --git a/mod/page/tests/externallib_test.php b/mod/page/tests/externallib_test.php
index 87ec9f555876f..92fdad8246837 100644
--- a/mod/page/tests/externallib_test.php
+++ b/mod/page/tests/externallib_test.php
@@ -147,7 +147,7 @@ public function test_mod_page_get_pages_by_courses() {
$returndescription = mod_page_external::get_pages_by_courses_returns();
// Create what we expect to be returned when querying the two courses.
- $expectedfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles',
+ $expectedfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles', 'lang',
'content', 'contentformat', 'contentfiles', 'legacyfiles', 'legacyfileslast', 'display',
'displayoptions', 'revision', 'timemodified', 'section', 'visible', 'groupmode', 'groupingid');
@@ -161,6 +161,7 @@ public function test_mod_page_get_pages_by_courses() {
$page1->groupingid = 0;
$page1->introfiles = [];
$page1->contentfiles = [];
+ $page1->lang = '';
$page2->coursemodule = $page2->cmid;
$page2->introformat = 1;
@@ -171,6 +172,7 @@ public function test_mod_page_get_pages_by_courses() {
$page2->groupingid = 0;
$page2->introfiles = [];
$page2->contentfiles = [];
+ $page2->lang = '';
foreach ($expectedfields as $field) {
$expected1[$field] = $page1->{$field};
diff --git a/mod/quiz/tests/external/external_test.php b/mod/quiz/tests/external/external_test.php
index c77b3ce6408d6..98bdb06062d56 100644
--- a/mod/quiz/tests/external/external_test.php
+++ b/mod/quiz/tests/external/external_test.php
@@ -214,8 +214,8 @@ public function test_mod_quiz_get_quizzes_by_courses() {
// Create what we expect to be returned when querying the two courses.
// First for the student user.
- $allusersfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles', 'timeopen',
- 'timeclose', 'grademethod', 'section', 'visible', 'groupmode', 'groupingid',
+ $allusersfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles', 'lang',
+ 'timeopen', 'timeclose', 'grademethod', 'section', 'visible', 'groupmode', 'groupingid',
'attempts', 'timelimit', 'grademethod', 'decimalpoints', 'questiondecimalpoints', 'sumgrades',
'grade', 'preferredbehaviour', 'hasfeedback');
$userswithaccessfields = array('attemptonlast', 'reviewattempt', 'reviewcorrectness', 'reviewmarks',
@@ -239,6 +239,7 @@ public function test_mod_quiz_get_quizzes_by_courses() {
$quiz1->completionpass = 0;
$quiz1->autosaveperiod = get_config('quiz', 'autosaveperiod');
$quiz1->introfiles = [];
+ $quiz1->lang = '';
$quiz2->coursemodule = $quiz2->cmid;
$quiz2->introformat = 1;
@@ -251,6 +252,7 @@ public function test_mod_quiz_get_quizzes_by_courses() {
$quiz2->completionpass = 0;
$quiz2->autosaveperiod = get_config('quiz', 'autosaveperiod');
$quiz2->introfiles = [];
+ $quiz2->lang = '';
foreach (array_merge($allusersfields, $userswithaccessfields) as $field) {
$expected1[$field] = $quiz1->{$field};
@@ -317,7 +319,7 @@ public function test_mod_quiz_get_quizzes_by_courses() {
$result = \external_api::clean_returnvalue($returndescription, $result);
$this->assertCount(2, $result['quizzes']);
// We only see a limited set of fields.
- $this->assertCount(4, $result['quizzes'][0]);
+ $this->assertCount(5, $result['quizzes'][0]);
$this->assertEquals($quiz2->id, $result['quizzes'][0]['id']);
$this->assertEquals($quiz2->cmid, $result['quizzes'][0]['coursemodule']);
$this->assertEquals($quiz2->course, $result['quizzes'][0]['course']);
diff --git a/mod/resource/tests/externallib_test.php b/mod/resource/tests/externallib_test.php
index 49824f971260e..946ffbeefb664 100644
--- a/mod/resource/tests/externallib_test.php
+++ b/mod/resource/tests/externallib_test.php
@@ -148,7 +148,7 @@ public function test_mod_resource_get_resources_by_courses() {
$returndescription = mod_resource_external::get_resources_by_courses_returns();
// Create what we expect to be returned when querying the two courses.
- $expectedfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles',
+ $expectedfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles', 'lang',
'contentfiles', 'tobemigrated', 'legacyfiles', 'legacyfileslast', 'display', 'displayoptions',
'filterfiles', 'revision', 'timemodified', 'section', 'visible', 'groupmode', 'groupingid');
@@ -162,6 +162,7 @@ public function test_mod_resource_get_resources_by_courses() {
$resource1->groupingid = 0;
$resource1->introfiles = [];
$resource1->contentfiles = [];
+ $resource1->lang = '';
$resource2->coursemodule = $resource2->cmid;
$resource2->introformat = 1;
@@ -172,6 +173,7 @@ public function test_mod_resource_get_resources_by_courses() {
$resource2->groupingid = 0;
$resource2->introfiles = [];
$resource2->contentfiles = [];
+ $resource2->lang = '';
foreach ($expectedfields as $field) {
$expected1[$field] = $resource1->{$field};
diff --git a/mod/scorm/tests/externallib_test.php b/mod/scorm/tests/externallib_test.php
index 4955e00097b58..f26ee06e80412 100644
--- a/mod/scorm/tests/externallib_test.php
+++ b/mod/scorm/tests/externallib_test.php
@@ -647,7 +647,7 @@ public function test_mod_scorm_get_scorms_by_courses() {
$result = \external_api::clean_returnvalue($returndescription, $result);
$this->assertCount(1, $result['warnings']);
// Only 'id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles'.
- $this->assertCount(7, $result['scorms'][0]);
+ $this->assertCount(8, $result['scorms'][0]);
$this->assertEquals('expired', $result['warnings'][0]['warningcode']);
$scorm1->timeopen = $timenow + DAYSECS;
@@ -658,7 +658,7 @@ public function test_mod_scorm_get_scorms_by_courses() {
$result = \external_api::clean_returnvalue($returndescription, $result);
$this->assertCount(1, $result['warnings']);
// Only 'id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles'.
- $this->assertCount(7, $result['scorms'][0]);
+ $this->assertCount(8, $result['scorms'][0]);
$this->assertEquals('notopenyet', $result['warnings'][0]['warningcode']);
// Reset times.
@@ -668,7 +668,7 @@ public function test_mod_scorm_get_scorms_by_courses() {
// Create what we expect to be returned when querying the two courses.
// First for the student user.
- $expectedfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'version', 'maxgrade',
+ $expectedfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'lang', 'version', 'maxgrade',
'grademethod', 'whatgrade', 'maxattempt', 'forcecompleted', 'forcenewattempt', 'lastattemptlock',
'displayattemptstatus', 'displaycoursestructure', 'sha1hash', 'md5hash', 'revision', 'launch',
'skipview', 'hidebrowse', 'hidetoc', 'nav', 'navpositionleft', 'navpositiontop', 'auto',
@@ -681,12 +681,14 @@ public function test_mod_scorm_get_scorms_by_courses() {
$scorm1->visible = true;
$scorm1->groupmode = 0;
$scorm1->groupingid = 0;
+ $scorm1->lang = '';
$scorm2->coursemodule = $scorm2->cmid;
$scorm2->section = 0;
$scorm2->visible = true;
$scorm2->groupmode = 0;
$scorm2->groupingid = 0;
+ $scorm2->lang = '';
// SCORM size. The same package is used in both SCORMs.
$scormcontext1 = \context_module::instance($scorm1->cmid);
diff --git a/mod/survey/tests/externallib_test.php b/mod/survey/tests/externallib_test.php
index d9bf2fb213d26..95fba65130386 100644
--- a/mod/survey/tests/externallib_test.php
+++ b/mod/survey/tests/externallib_test.php
@@ -96,8 +96,8 @@ public function test_mod_survey_get_surveys_by_courses() {
// Create what we expect to be returned when querying the two courses.
// First for the student user.
- $expectedfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles', 'template', 'days',
- 'questions', 'surveydone');
+ $expectedfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles', 'lang',
+ 'template', 'days', 'questions', 'surveydone');
// Add expected coursemodule and data.
$survey1 = $this->survey;
@@ -109,6 +109,7 @@ public function test_mod_survey_get_surveys_by_courses() {
$survey1->groupmode = 0;
$survey1->groupingid = 0;
$survey1->introfiles = [];
+ $survey1->lang = '';
$survey2->coursemodule = $survey2->cmid;
$survey2->introformat = 1;
@@ -120,6 +121,7 @@ public function test_mod_survey_get_surveys_by_courses() {
$tempo = $DB->get_field("survey", "intro", array("id" => $survey2->template));
$survey2->intro = nl2br(get_string($tempo, "survey"));
$survey2->introfiles = [];
+ $survey2->lang = '';
foreach ($expectedfields as $field) {
$expected1[$field] = $survey1->{$field};
diff --git a/mod/url/tests/externallib_test.php b/mod/url/tests/externallib_test.php
index 9b8efa93e4b34..d361c3d21ca1e 100644
--- a/mod/url/tests/externallib_test.php
+++ b/mod/url/tests/externallib_test.php
@@ -147,9 +147,9 @@ public function test_mod_url_get_urls_by_courses() {
$returndescription = mod_url_external::get_urls_by_courses_returns();
// Create what we expect to be returned when querying the two courses.
- $expectedfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles', 'externalurl',
- 'display', 'displayoptions', 'parameters', 'timemodified', 'section', 'visible', 'groupmode',
- 'groupingid');
+ $expectedfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles', 'lang',
+ 'externalurl', 'display', 'displayoptions', 'parameters', 'timemodified', 'section', 'visible', 'groupmode',
+ 'groupingid');
// Add expected coursemodule and data.
$url1->coursemodule = $url1->cmid;
@@ -159,6 +159,7 @@ public function test_mod_url_get_urls_by_courses() {
$url1->groupmode = 0;
$url1->groupingid = 0;
$url1->introfiles = [];
+ $url1->lang = '';
$url2->coursemodule = $url2->cmid;
$url2->introformat = 1;
@@ -167,6 +168,7 @@ public function test_mod_url_get_urls_by_courses() {
$url2->groupmode = 0;
$url2->groupingid = 0;
$url2->introfiles = [];
+ $url2->lang = '';
foreach ($expectedfields as $field) {
$expected1[$field] = $url1->{$field};
diff --git a/mod/wiki/tests/externallib_test.php b/mod/wiki/tests/externallib_test.php
index 7e762a3fc0f45..9f16ca4e02303 100644
--- a/mod/wiki/tests/externallib_test.php
+++ b/mod/wiki/tests/externallib_test.php
@@ -168,9 +168,9 @@ public function test_mod_wiki_get_wikis_by_courses() {
// Create what we expect to be returned when querying the two courses.
// First for the student user.
- $expectedfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles', 'firstpagetitle',
- 'wikimode', 'defaultformat', 'forceformat', 'editbegin', 'editend', 'section', 'visible',
- 'groupmode', 'groupingid');
+ $expectedfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles', 'lang',
+ 'firstpagetitle', 'wikimode', 'defaultformat', 'forceformat', 'editbegin', 'editend', 'section', 'visible',
+ 'groupmode', 'groupingid');
// Add expected coursemodule and data.
$wiki1 = $this->wiki;
@@ -181,6 +181,7 @@ public function test_mod_wiki_get_wikis_by_courses() {
$wiki1->groupmode = 0;
$wiki1->groupingid = 0;
$wiki1->introfiles = [];
+ $wiki1->lang = '';
$wiki2->coursemodule = $wiki2->cmid;
$wiki2->introformat = 1;
@@ -189,6 +190,7 @@ public function test_mod_wiki_get_wikis_by_courses() {
$wiki2->groupmode = 0;
$wiki2->groupingid = 0;
$wiki2->introfiles = [];
+ $wiki2->lang = '';
foreach ($expectedfields as $field) {
$expected1[$field] = $wiki1->{$field};
diff --git a/mod/workshop/classes/external/workshop_summary_exporter.php b/mod/workshop/classes/external/workshop_summary_exporter.php
index 45edd78d9625e..02c12d2134ac4 100644
--- a/mod/workshop/classes/external/workshop_summary_exporter.php
+++ b/mod/workshop/classes/external/workshop_summary_exporter.php
@@ -64,6 +64,11 @@ protected static function define_properties() {
'default' => FORMAT_MOODLE,
'description' => 'Workshop intro text format.',
),
+ 'lang' => array(
+ 'type' => PARAM_LANG,
+ 'description' => 'Forced activity language',
+ 'null' => NULL_ALLOWED,
+ ),
'instructauthors' => array(
'type' => PARAM_RAW,
'description' => 'Instructions for the submission phase.',
diff --git a/mod/workshop/tests/external/external_test.php b/mod/workshop/tests/external/external_test.php
index 02bc686b6c4d1..e8906a0ed721f 100644
--- a/mod/workshop/tests/external/external_test.php
+++ b/mod/workshop/tests/external/external_test.php
@@ -161,6 +161,7 @@ public function test_mod_workshop_get_workshops_by_courses() {
$workshop1->coursemodule = $workshop1->cmid;
$workshop1->introformat = 1;
$workshop1->introfiles = [];
+ $workshop1->lang = '';
$workshop1->instructauthorsfiles = [];
$workshop1->instructauthorsformat = 1;
$workshop1->instructreviewersfiles = [];
@@ -173,6 +174,7 @@ public function test_mod_workshop_get_workshops_by_courses() {
$workshop2->coursemodule = $workshop2->cmid;
$workshop2->introformat = 1;
$workshop2->introfiles = [];
+ $workshop2->lang = '';
$workshop2->instructauthorsfiles = [];
$workshop2->instructauthorsformat = 1;
$workshop2->instructreviewersfiles = [];
diff --git a/version.php b/version.php
index f287acb30b352..b0cf33b1f7072 100644
--- a/version.php
+++ b/version.php
@@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
-$version = 2022081200.00; // YYYYMMDD = weekly release date of this DEV branch.
+$version = 2022081200.01; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
$release = '4.1dev (Build: 20220812)'; // Human-friendly version name