MDL-74608 activities: a new option to force the activity language

For a long time, Moodle has had the feature to force the language
for a whole course. This change adds the same feature at activity
level.

The course-level feature was controlled by a capability
moodle/course:setforcedlanguage, and I decided to use the same
capability to control this feature. I think a new capability would be
overkill.
This commit is contained in:
Tim Hunt
2022-08-15 22:15:52 +01:00
parent 896e126a66
commit 8babdbd396
44 changed files with 528 additions and 85 deletions
+1 -1
View File
@@ -341,7 +341,7 @@ class backup_module_structure_step extends backup_structure_step {
'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'));
+4
View File
@@ -4491,6 +4491,10 @@ class restore_module_structure_step extends restore_structure_step {
$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
@@ -57,6 +57,7 @@ abstract class helper_for_get_mods_by_courses {
$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 @@ abstract class helper_for_get_mods_by_courses {
'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),
];
}
}
+4 -3
View File
@@ -251,11 +251,12 @@ class course_edit_form extends moodleform {
$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);
}
+13 -2
View File
@@ -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;
+10 -7
View File
@@ -625,11 +625,8 @@ abstract class moodleform_mod extends moodleform {
$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 @@ abstract class moodleform_mod extends moodleform {
$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 @@ abstract class moodleform_mod extends moodleform {
$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');
@@ -0,0 +1,137 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
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;
}
}
+12 -18
View File
@@ -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
+1
View File
@@ -120,6 +120,7 @@ class modlib_test extends \advanced_testcase {
$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',
+1 -1
View File
@@ -178,7 +178,7 @@ $string['course:configuredownloadcontent'] = 'Configure download course content'
$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';
+1 -1
View File
@@ -526,7 +526,7 @@ class core_string_manager_standard implements core_string_manager {
$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;
}
+2 -2
View File
@@ -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
+1
View File
@@ -1094,6 +1094,7 @@ $capabilities = array(
'clonepermissionsfrom' => 'moodle/course:update'
),
// Ability to set a forced language for a course or activity.
'moodle/course:setforcedlanguage' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_COURSE,
+2 -1
View File
@@ -93,7 +93,7 @@
<FIELD NAME="groupmode" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="groupmodeforce" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="defaultgroupingid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="default grouping used in course modules, does not have key intentionally"/>
<FIELD NAME="lang" TYPE="char" LENGTH="30" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="lang" TYPE="char" LENGTH="30" NOTNULL="true" SEQUENCE="false" COMMENT="Forced language for this course. Null or '' means 'Do not force'. Otherwise a Moodle lang pack name like 'fr' or 'en_us'."/>
<FIELD NAME="calendartype" TYPE="char" LENGTH="30" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="theme" TYPE="char" LENGTH="50" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
@@ -309,6 +309,7 @@
<FIELD NAME="availability" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Availability restrictions for viewing this activity, in JSON format. Null if no restrictions."/>
<FIELD NAME="deletioninprogress" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="downloadcontent" TYPE="int" LENGTH="1" NOTNULL="false" DEFAULT="1" SEQUENCE="false" COMMENT="Whether the ability to download course module content is enabled for this activity"/>
<FIELD NAME="lang" TYPE="char" LENGTH="30" NOTNULL="false" SEQUENCE="false" COMMENT="Forced language for this activity. Null or '' means 'Do not force'. Otherwise a Moodle lang pack name like 'fr' or 'en_us'."/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+15
View File
@@ -2879,5 +2879,20 @@ privatefiles,moodle|/user/files.php';
upgrade_main_savepoint(true, 2022061500.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;
}
+13 -4
View File
@@ -865,6 +865,7 @@ class course_modinfo {
$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 @@ class course_modinfo {
* @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 @@ class cm_info implements IteratorAggregate {
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 @@ class cm_info implements IteratorAggregate {
$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
+8 -4
View File
@@ -7087,7 +7087,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
@@ -7096,9 +7096,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.
@@ -7151,7 +7155,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();
+8 -4
View File
@@ -949,12 +949,13 @@ EOD;
/**
* 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 @@ EOD;
$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);
}
/**
+6 -1
View File
@@ -268,8 +268,13 @@ abstract class testing_module_generator extends component_generator_base {
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);
@@ -0,0 +1,196 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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;
}
}
+2 -2
View File
@@ -160,7 +160,7 @@ class externallib_test extends externallib_advanced_testcase {
$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 @@ class externallib_test extends externallib_advanced_testcase {
$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']);
+3 -3
View File
@@ -218,7 +218,7 @@ class externallib_test extends externallib_advanced_testcase {
* 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 @@ class externallib_test extends externallib_advanced_testcase {
$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 @@ class externallib_test extends externallib_advanced_testcase {
$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']);
@@ -58,6 +58,11 @@ class database_summary_exporter extends exporter {
'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',
+6 -2
View File
@@ -178,15 +178,19 @@ class externallib_test extends externallib_advanced_testcase {
// 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();
@@ -62,6 +62,11 @@ class feedback_summary_exporter extends exporter {
'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.',
+3 -2
View File
@@ -107,7 +107,6 @@ class external_test extends externallib_advanced_testcase {
* 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 @@ class external_test extends externallib_advanced_testcase {
// 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 @@ class external_test extends externallib_advanced_testcase {
$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) {
+3 -1
View File
@@ -149,7 +149,7 @@ class externallib_test extends externallib_advanced_testcase {
$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 @@ class externallib_test extends externallib_advanced_testcase {
$folder1->groupmode = 0;
$folder1->groupingid = 0;
$folder1->introfiles = [];
$folder1->lang = '';
$folder2->coursemodule = $folder2->cmid;
$folder2->introformat = 1;
@@ -169,6 +170,7 @@ class externallib_test extends externallib_advanced_testcase {
$folder2->groupmode = 0;
$folder2->groupingid = 0;
$folder2->introfiles = [];
$folder2->lang = '';
foreach ($expectedfields as $field) {
$expected1[$field] = $folder1->{$field};
+3
View File
@@ -97,6 +97,8 @@ class mod_forum_external extends external_api {
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 @@ class mod_forum_external extends external_api {
'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'),
+2
View File
@@ -145,6 +145,7 @@ class externallib_test extends externallib_advanced_testcase {
$forum1->istracked = true;
$forum1->unreadpostscount = 0;
$forum1->introfiles = [];
$forum1->lang = '';
$record = new \stdClass();
$record->course = $course2->id;
@@ -157,6 +158,7 @@ class externallib_test extends externallib_advanced_testcase {
// 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',
+3 -1
View File
@@ -78,7 +78,7 @@ class externallib_test extends externallib_advanced_testcase {
// 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 @@ class externallib_test extends externallib_advanced_testcase {
$label1->groupmode = 0;
$label1->groupingid = 0;
$label1->introfiles = [];
$label1->lang = '';
$label2->coursemodule = $label2->cmid;
$label2->introformat = 1;
@@ -96,6 +97,7 @@ class externallib_test extends externallib_advanced_testcase {
$label2->groupmode = 0;
$label2->groupingid = 0;
$label2->introfiles = [];
$label2->lang = '';
foreach ($expectedfields as $field) {
$expected1[$field] = $label1->{$field};
+1
View File
@@ -54,6 +54,7 @@ class mod_lesson_external extends external_api {
$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;
@@ -66,6 +66,11 @@ class lesson_summary_exporter extends exporter {
'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?',
+8 -6
View File
@@ -142,8 +142,8 @@ class external_test extends externallib_advanced_testcase {
// 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 @@ class external_test extends externallib_advanced_testcase {
$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 @@ class external_test extends externallib_advanced_testcase {
// 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 @@ class external_test extends externallib_advanced_testcase {
// 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 @@ class external_test extends externallib_advanced_testcase {
// 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 @@ class external_test extends externallib_advanced_testcase {
// 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);
}
}
+4 -2
View File
@@ -192,7 +192,7 @@ class externallib_test extends mod_lti_testcase {
// 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 @@ class externallib_test extends mod_lti_testcase {
$lti1->groupingid = 0;
$lti1->section = 0;
$lti1->introfiles = [];
$lti1->lang = '';
$lti2->coursemodule = $lti2->cmid;
$lti2->introformat = 1;
@@ -214,6 +215,7 @@ class externallib_test extends mod_lti_testcase {
$lti2->groupingid = 0;
$lti2->section = 0;
$lti2->introfiles = [];
$lti2->lang = '';
foreach ($expectedfields as $field) {
$expected1[$field] = $lti1->{$field};
@@ -257,7 +259,7 @@ class externallib_test extends mod_lti_testcase {
$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};
+3 -1
View File
@@ -147,7 +147,7 @@ class externallib_test extends externallib_advanced_testcase {
$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 @@ class externallib_test extends externallib_advanced_testcase {
$page1->groupingid = 0;
$page1->introfiles = [];
$page1->contentfiles = [];
$page1->lang = '';
$page2->coursemodule = $page2->cmid;
$page2->introformat = 1;
@@ -171,6 +172,7 @@ class externallib_test extends externallib_advanced_testcase {
$page2->groupingid = 0;
$page2->introfiles = [];
$page2->contentfiles = [];
$page2->lang = '';
foreach ($expectedfields as $field) {
$expected1[$field] = $page1->{$field};
+5 -3
View File
@@ -214,8 +214,8 @@ class external_test extends externallib_advanced_testcase {
// 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 @@ class external_test extends externallib_advanced_testcase {
$quiz1->completionpass = 0;
$quiz1->autosaveperiod = get_config('quiz', 'autosaveperiod');
$quiz1->introfiles = [];
$quiz1->lang = '';
$quiz2->coursemodule = $quiz2->cmid;
$quiz2->introformat = 1;
@@ -251,6 +252,7 @@ class external_test extends externallib_advanced_testcase {
$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 @@ class external_test extends externallib_advanced_testcase {
$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']);
+3 -1
View File
@@ -148,7 +148,7 @@ class externallib_test extends externallib_advanced_testcase {
$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 @@ class externallib_test extends externallib_advanced_testcase {
$resource1->groupingid = 0;
$resource1->introfiles = [];
$resource1->contentfiles = [];
$resource1->lang = '';
$resource2->coursemodule = $resource2->cmid;
$resource2->introformat = 1;
@@ -172,6 +173,7 @@ class externallib_test extends externallib_advanced_testcase {
$resource2->groupingid = 0;
$resource2->introfiles = [];
$resource2->contentfiles = [];
$resource2->lang = '';
foreach ($expectedfields as $field) {
$expected1[$field] = $resource1->{$field};
+5 -3
View File
@@ -647,7 +647,7 @@ class externallib_test extends externallib_advanced_testcase {
$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 @@ class externallib_test extends externallib_advanced_testcase {
$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 @@ class externallib_test extends externallib_advanced_testcase {
// 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 @@ class externallib_test extends externallib_advanced_testcase {
$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);
+4 -2
View File
@@ -96,8 +96,8 @@ class externallib_test extends externallib_advanced_testcase {
// 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 @@ class externallib_test extends externallib_advanced_testcase {
$survey1->groupmode = 0;
$survey1->groupingid = 0;
$survey1->introfiles = [];
$survey1->lang = '';
$survey2->coursemodule = $survey2->cmid;
$survey2->introformat = 1;
@@ -120,6 +121,7 @@ class externallib_test extends externallib_advanced_testcase {
$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};
+5 -3
View File
@@ -147,9 +147,9 @@ class externallib_test extends externallib_advanced_testcase {
$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 @@ class externallib_test extends externallib_advanced_testcase {
$url1->groupmode = 0;
$url1->groupingid = 0;
$url1->introfiles = [];
$url1->lang = '';
$url2->coursemodule = $url2->cmid;
$url2->introformat = 1;
@@ -167,6 +168,7 @@ class externallib_test extends externallib_advanced_testcase {
$url2->groupmode = 0;
$url2->groupingid = 0;
$url2->introfiles = [];
$url2->lang = '';
foreach ($expectedfields as $field) {
$expected1[$field] = $url1->{$field};
+5 -3
View File
@@ -168,9 +168,9 @@ class externallib_test extends externallib_advanced_testcase {
// 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 @@ class externallib_test extends externallib_advanced_testcase {
$wiki1->groupmode = 0;
$wiki1->groupingid = 0;
$wiki1->introfiles = [];
$wiki1->lang = '';
$wiki2->coursemodule = $wiki2->cmid;
$wiki2->introformat = 1;
@@ -189,6 +190,7 @@ class externallib_test extends externallib_advanced_testcase {
$wiki2->groupmode = 0;
$wiki2->groupingid = 0;
$wiki2->introfiles = [];
$wiki2->lang = '';
foreach ($expectedfields as $field) {
$expected1[$field] = $wiki1->{$field};
@@ -64,6 +64,11 @@ class workshop_summary_exporter extends exporter {
'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.',
+2
View File
@@ -161,6 +161,7 @@ class external_test extends externallib_advanced_testcase {
$workshop1->coursemodule = $workshop1->cmid;
$workshop1->introformat = 1;
$workshop1->introfiles = [];
$workshop1->lang = '';
$workshop1->instructauthorsfiles = [];
$workshop1->instructauthorsformat = 1;
$workshop1->instructreviewersfiles = [];
@@ -173,6 +174,7 @@ class external_test extends externallib_advanced_testcase {
$workshop2->coursemodule = $workshop2->cmid;
$workshop2->introformat = 1;
$workshop2->introfiles = [];
$workshop2->lang = '';
$workshop2->instructauthorsfiles = [];
$workshop2->instructauthorsformat = 1;
$workshop2->instructreviewersfiles = [];
+1 -1
View File
@@ -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