Compare commits

..

44 Commits

Author SHA1 Message Date
Sara Arjona d3f01844da Moodle release 3.5.11 2020-03-06 12:34:16 +01:00
AMOS bot 7f2a97e632 Automatically generated installer lang files 2020-03-06 00:11:15 +00:00
Sara Arjona 2a512be9f6 weekly release 3.5.10+ 2020-03-05 17:31:40 +01:00
Sara Arjona 3b643e9cad Merge branch 'install_35_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_35_STABLE 2020-03-05 17:31:37 +01:00
Michael Hawkins cc446ad1ec MDL-67861 core: Fix getremoteaddr unit test to match new proxy setup 2020-03-05 14:41:46 +08:00
Brendan Heywood 7251f154ef MDL-67861 core: Added $CFG->reverseproxyignore IP subnet list
If your server is behind multiple reverse proxies that append to the
X-Forwarded-For header then you will need to specify a comma separated
list of ip addresses or subnets of the reverse proxies to be ignored
in order to find the users correct IP address.
2020-03-05 01:13:12 +01:00
Brendan Heywood e6e07e0960 MDL-67861 libraries: Refactor is_ip_in_subnet_list in ip_utils 2020-03-05 01:13:04 +01:00
Brendan Heywood 51d52d273c MDL-67861 admin: Default $CFG->getremoteaddrconf to only use REMOTE_ADDR 2020-03-05 01:11:10 +01:00
Brendan Heywood 67c44ebd2e MDL-67861 core: Use last ip in X-Forwarded-For list 2020-03-05 01:11:09 +01:00
Marina Glancy eeaf8e0e62 MDL-56287 gradereport_history: Separate groups queries improved 2020-03-04 11:48:01 +01:00
Jun Pataleta 651a6bf5fa MDL-56287 gradereport_history: Show users from groups that can be viewed 2020-03-04 11:48:01 +01:00
AMOS bot 7135dc455a Automatically generated installer lang files 2020-03-04 00:11:33 +00:00
AMOS bot c6b033233f Automatically generated installer lang files 2020-02-16 00:11:32 +00:00
Sara Arjona cd7201ad49 weekly release 3.5.10+ 2020-02-14 12:12:18 +01:00
Sara Arjona 15a4027603 Merge branch 'install_35_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_35_STABLE 2020-02-14 12:12:15 +01:00
Jun Pataleta d6eb0134af Merge branch 'MDL-67965-35' of git://github.com/andrewnicols/moodle into MOODLE_35_STABLE 2020-02-14 13:24:44 +08:00
Andrew Nicols 7505c8ce76 MDL-67965 behat: Get theme-specific context classes correctly 2020-02-14 11:36:08 +08:00
AMOS bot 90e5c2b889 Automatically generated installer lang files 2020-02-08 00:12:18 +00:00
Sara Arjona 84a9a24b96 weekly release 3.5.10+ 2020-02-07 09:45:01 +01:00
Sara Arjona 78255acbf9 Merge branch 'install_35_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_35_STABLE 2020-02-07 09:44:59 +01:00
Brendan Heywood 34af3ee683 MDL-67726 tool_phpunit: add missing input escaping 2020-02-06 19:46:18 +01:00
Andrew Nicols ae422c191c Merge branch 'MDL-67175-35' of git://github.com/ferranrecio/moodle into MOODLE_35_STABLE 2020-02-06 10:22:49 +08:00
Ferran Recio 8e77498176 MDL-67175 mod_scorm: fixing js eslint errors 2020-02-04 12:37:31 +01:00
Sam Hemelryk 89fb92a551 MDL-67175 mod_scorm: sync XHR requests use sendBeacon when available
Totara reference TL-22621 (original code by Sam Hemelryk)
https://github.com/moodle/moodle/commit/2add402f8183c50bc1238ef2da5ea9cd06cb1dd9
2020-02-04 12:37:31 +01:00
Brendan Cox a624a36856 MDL-67175 session: set SameSite=None for Chrome 78 and above
Totara reference TL-22311 (original code by Brendan Cox and Sam Hemelryk)
https://github.com/moodle/moodle/commit/a3f4de2b7efe66de6617a67ce7c87f2862e76ac6
2020-02-04 12:37:15 +01:00
AMOS bot 56cfcc65cb Automatically generated installer lang files 2020-02-04 00:11:37 +00:00
Eloy Lafuente (stronk7) efb7b7d668 weekly release 3.5.10+ 2020-01-30 16:11:06 +01:00
Eloy Lafuente (stronk7) 2f5e4bc739 Merge branch 'install_35_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_35_STABLE 2020-01-30 16:11:03 +01:00
Eloy Lafuente (stronk7) f21f69f0b8 Merge branch 'MDL-67722-35' of git://github.com/mickhawkins/moodle into MOODLE_35_STABLE 2020-01-28 20:14:20 +01:00
AMOS bot fda8d5d13d Automatically generated installer lang files 2020-01-28 00:12:03 +00:00
Adrian Greeve ea12d48932 Merge branch 'MDL-67691_35' of https://github.com/stronk7/moodle into MOODLE_35_STABLE 2020-01-28 07:56:33 +08:00
AMOS bot 383af5dab9 Automatically generated installer lang files 2020-01-25 00:12:05 +00:00
Adrian Greeve f1cf68fedc weekly release 3.5.10+ 2020-01-24 10:17:02 +08:00
Adrian Greeve 7fdc181bfe Merge branch 'install_35_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_35_STABLE 2020-01-24 10:16:53 +08:00
Adrian Greeve 0422d333a8 Merge branch 'MDL-67656-35' of git://github.com/andrewnicols/moodle into MOODLE_35_STABLE 2020-01-22 09:49:16 +08:00
Michael Hawkins f857f28d7a MDL-67722 mod_forum: Reduce wait time on time limit expires behat test 2020-01-21 11:40:38 +08:00
Eloy Lafuente (stronk7) c777c77434 Merge branch 'MDL-67724-35' of git://github.com/junpataleta/moodle into MOODLE_35_STABLE 2020-01-20 10:54:00 +01:00
Eloy Lafuente (stronk7) e2d5d5457f MDL-67691 behat: inform about the_following_exist() removal
behat_data_generators::the_following_exist() has been removed, please use
behat_data_generators::the_following_entities_exist() instead.

See MDL-67691 for more info.
2020-01-20 00:15:39 +01:00
AMOS bot 4802739d19 Automatically generated installer lang files 2020-01-18 00:11:10 +00:00
Jun Pataleta 01087d1e45 MDL-67724 mod_glossary: Improve Behat test
Improve the "Glossary entries are not always editable" scenario by:
* No need to use the @javascript tag
* The maxediting time can be set to a lower value to
reduce the overall wait time.
2020-01-17 17:51:34 +08:00
AMOS bot f30469004a Automatically generated installer lang files 2020-01-17 00:12:42 +00:00
AMOS bot e34df5a1c4 Automatically generated installer lang files 2020-01-14 00:10:45 +00:00
Andrew Nicols dd3e9a0c25 MDL-67656 behat: Removed nested spin 2020-01-10 14:36:12 +08:00
Andrew Nicols f246b1c05c MDL-67656 behat: Fix usleep call in spin 2020-01-10 14:36:12 +08:00
36 changed files with 720 additions and 112 deletions
+4 -1
View File
@@ -98,7 +98,10 @@ $options = array(
GETREMOTEADDR_SKIP_HTTP_CLIENT_IP => 'HTTP_X_FORWARDED_FOR, REMOTE_ADDR',
GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR => 'HTTP_CLIENT, REMOTE_ADDR',
GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR|GETREMOTEADDR_SKIP_HTTP_CLIENT_IP => 'REMOTE_ADDR');
$temp->add(new admin_setting_configselect('getremoteaddrconf', new lang_string('getremoteaddrconf', 'admin'), new lang_string('configgetremoteaddrconf', 'admin'), 0, $options));
$temp->add(new admin_setting_configselect('getremoteaddrconf', new lang_string('getremoteaddrconf', 'admin'),
new lang_string('configgetremoteaddrconf', 'admin'),
GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR|GETREMOTEADDR_SKIP_HTTP_CLIENT_IP, $options));
$temp->add(new admin_setting_configtext('reverseproxyignore', new lang_string('reverseproxyignore', 'admin'), new lang_string('configreverseproxyignore', 'admin'), ''));
$temp->add(new admin_setting_heading('webproxy', new lang_string('webproxy', 'admin'), new lang_string('webproxyinfo', 'admin')));
$temp->add(new admin_setting_configtext('proxyhost', new lang_string('proxyhost', 'admin'), new lang_string('configproxyhost', 'admin'), '', PARAM_HOST));
+6 -5
View File
@@ -131,11 +131,12 @@ if ($execute) {
tool_phpunit_problem('Can not create configuration file');
}
}
$configdir = escapeshellarg($configdir);
// no cleanup of path - this is tricky because we can not use escapeshellarg and friends for escaping,
// this is from admin user so PARAM_PATH must be enough
chdir($CFG->dirroot);
passthru("php $CFG->admin/tool/phpunit/cli/util.php --run -c $configdir $testclass $testpath", $code);
$configdir = escapeshellarg($configdir);
$cleanclass = escapeshellarg($testclass);
$cleanpath = escapeshellarg($testpath);
passthru("php $CFG->admin/tool/phpunit/cli/util.php --run -c $configdir $cleanclass $cleanpath", $code);
chdir($oldcwd);
echo '</pre>';
@@ -146,7 +147,7 @@ if ($execute) {
}
echo $OUTPUT->box_start('generalbox boxwidthwide boxaligncenter');
echo '<form method="get" action="webrunner.php">';
echo '<form method="post" action="webrunner.php">';
echo '<fieldset class="invisiblefieldset">';
echo '<label for="testpath">Test one file</label> ';
echo '<input type="text" id="testpath" name="testpath" value="'.s($testpath).'" size="50" /> (all test cases from webrunner.xml if empty)';
+39 -5
View File
@@ -128,6 +128,7 @@ class helper {
* @return array sql and params list
*/
protected static function get_users_sql_and_params($context, $search = '', $count = false) {
global $DB, $USER;
// Fields we need from the user table.
$extrafields = get_extra_user_fields($context);
@@ -147,13 +148,33 @@ class helper {
$select = "SELECT DISTINCT $ufields ";
$orderby = " ORDER BY u.lastname ASC, u.firstname ASC";
}
$groupjoinsql = '';
$groupwheresql = '';
$courseid = $context->instanceid;
$groupmode = groups_get_course_groupmode(get_course($courseid));
// We're only interested in separate groups mode because it's the only group mode that requires the user to be a member of
// specific group(s), except when they have the 'moodle/site:accessallgroups' capability.
if ($groupmode == SEPARATEGROUPS && !has_capability('moodle/site:accessallgroups', $context)) {
// Fetch the groups that the user can see.
$groups = groups_get_all_groups($courseid, $USER->id, 0, 'g.id');
// Add join condition to include users that only belong to the same group as the user.
list($insql, $inparams) = $DB->get_in_or_equal(array_keys($groups), SQL_PARAMS_NAMED, 'gid', true, 0);
$groupjoinsql = " JOIN {groups_members} gm ON gm.userid = u.id ";
$groupwheresql = " AND gm.groupid $insql ";
$params = array_merge($params, $inparams);
}
$sql = "$select
FROM {user} u
JOIN {grade_grades_history} ggh ON u.id = ggh.userid
JOIN {grade_items} gi ON gi.id = ggh.itemid
WHERE $filtersql gi.courseid = :courseid";
$groupjoinsql
WHERE $filtersql gi.courseid = :courseid $groupwheresql";
$sql .= $orderby;
$params['courseid'] = $context->instanceid;
$params['courseid'] = $courseid;
return array($sql, $params);
}
@@ -166,18 +187,31 @@ class helper {
* @return array list of graders.
*/
public static function get_graders($courseid) {
global $DB;
global $DB, $USER;
$groupjoinsql = $groupwheresql = '';
$inparams = [];
$groupmode = groups_get_course_groupmode(get_course($courseid));
if ($groupmode == SEPARATEGROUPS && !has_capability('moodle/site:accessallgroups', \context_course::instance($courseid))) {
// Fetch the groups that the user can see.
$groups = groups_get_all_groups($courseid, $USER->id, 0, 'g.id');
// Add join condition to include users that only belong to the same group as the user.
list($insql, $inparams) = $DB->get_in_or_equal(array_keys($groups), SQL_PARAMS_NAMED, 'gid', true, 0);
$groupjoinsql = " JOIN {groups_members} gm ON gm.userid = u.id ";
$groupwheresql = " AND gm.groupid $insql ";
}
$ufields = get_all_user_name_fields(true, 'u');
$sql = "SELECT u.id, $ufields
FROM {user} u
JOIN {grade_grades_history} ggh ON ggh.usermodified = u.id
JOIN {grade_items} gi ON gi.id = ggh.itemid
WHERE gi.courseid = :courseid
$groupjoinsql
WHERE gi.courseid = :courseid $groupwheresql
GROUP BY u.id, $ufields
ORDER BY u.lastname ASC, u.firstname ASC";
$graders = $DB->get_records_sql($sql, array('courseid' => $courseid));
$graders = $DB->get_records_sql($sql, array('courseid' => $courseid) + $inparams);
$return = array(0 => get_string('allgraders', 'gradereport_history'));
foreach ($graders as $grader) {
$return[$grader->id] = fullname($grader);
@@ -324,7 +324,7 @@ class tablelog extends \table_sql implements \renderable {
* @return array containing sql to use and an array of params.
*/
protected function get_filters_sql_and_params() {
global $DB;
global $DB, $USER;
$coursecontext = $this->context;
$filter = 'gi.courseid = :courseid';
@@ -355,6 +355,16 @@ class tablelog extends \table_sql implements \renderable {
$params += array('grader' => $this->filters->grader);
}
// If the course is separate group mode and the current user is not allowed to see all groups make sure
// that we display only users from the same groups as current user.
$groupmode = get_course($coursecontext->instanceid)->groupmode;
if ($groupmode == SEPARATEGROUPS && !has_capability('moodle/site:accessallgroups', $coursecontext)) {
$groupids = array_column(groups_get_all_groups($coursecontext->instanceid, $USER->id, 0, 'g.id'), 'id');
list($gsql, $gparams) = $DB->get_in_or_equal($groupids, SQL_PARAMS_NAMED, 'gmuparam', true, 0);
$filter .= " AND EXISTS (SELECT 1 FROM {groups_members} gmu WHERE gmu.userid=ggh.userid AND gmu.groupid $gsql)";
$params += $gparams;
}
return array($filter, $params);
}
+157
View File
@@ -53,6 +53,12 @@ class gradereport_history_report_testcase extends advanced_testcase {
$u5 = $this->getDataGenerator()->create_user();
$grader1 = $this->getDataGenerator()->create_user();
$grader2 = $this->getDataGenerator()->create_user();
self::getDataGenerator()->enrol_user($grader1->id, $c1->id, 'teacher');
self::getDataGenerator()->enrol_user($grader2->id, $c1->id, 'teacher');
self::getDataGenerator()->enrol_user($u2->id, $c1->id, 'student');
self::getDataGenerator()->enrol_user($u3->id, $c1->id, 'student');
self::getDataGenerator()->enrol_user($u4->id, $c1->id, 'student');
self::getDataGenerator()->enrol_user($u5->id, $c1->id, 'student');
// Modules.
$c1m1 = $this->getDataGenerator()->create_module('assign', array('course' => $c1));
@@ -65,6 +71,8 @@ class gradereport_history_report_testcase extends advanced_testcase {
$giparams = array('itemtype' => 'mod', 'itemmodule' => 'assign');
$grades = array();
$this->setUser($grader1);
$gi = grade_item::fetch($giparams + array('iteminstance' => $c1m1->id));
$grades['c1m1u1'] = $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u1->id,
'timemodified' => time() - 3600));
@@ -163,6 +171,20 @@ class gradereport_history_report_testcase extends advanced_testcase {
$this->assertEquals(null, $results[$grades['c2m2u1a']->id]->prevgrade);
$this->assertEquals($grades['c2m2u1a']->finalgrade, $results[$grades['c2m2u1c']->id]->prevgrade);
$this->assertEquals($grades['c2m2u1c']->finalgrade, $results[$grades['c2m2u1e']->id]->prevgrade);
// Put course in separate groups mode, add grader1 and two students to the same group.
$c1->groupmode = SEPARATEGROUPS;
update_course($c1);
$this->assertFalse(has_capability('moodle/site:accessallgroups', \context_course::instance($c1->id)));
$g1 = self::getDataGenerator()->create_group(['courseid' => $c1->id, 'name' => 'g1']);
self::getDataGenerator()->create_group_member(['groupid' => $g1->id, 'userid' => $grader1->id]);
self::getDataGenerator()->create_group_member(['groupid' => $g1->id, 'userid' => $u1->id]);
self::getDataGenerator()->create_group_member(['groupid' => $g1->id, 'userid' => $u2->id]);
$this->assertEquals(2, $this->get_tablelog_results($c1ctx, array(), true));
// Grader2 is not in any groups.
$this->setUser($grader2);
$this->assertEquals(0, $this->get_tablelog_results($c1ctx, array(), true));
}
/**
@@ -219,6 +241,118 @@ class gradereport_history_report_testcase extends advanced_testcase {
$this->assertEquals(1, \gradereport_history\helper::get_users_count($c1ctx, 'c'));
}
/**
* Data provider method for \gradereport_history_report_testcase::test_get_users_with_groups()
*/
public function get_users_provider() {
return [
'Visible groups, non-editing teacher, not in any group' => [
VISIBLEGROUPS, 'teacher', ['g1', 'g2'], ['s1', 's2', 's3', 's4', 's5']
],
'Visible groups, non-editing teacher' => [
VISIBLEGROUPS, 'teacher', [], ['s1', 's2', 's3', 's4', 's5']
],
'Visible groups, editing teacher' => [
VISIBLEGROUPS, 'editingteacher', ['g1', 'g2'], ['s1', 's2', 's3', 's4', 's5']
],
'Separate groups, non-editing teacher' => [
SEPARATEGROUPS, 'teacher', ['g1', 'g2'], ['s1', 's2']
],
'Separate groups, non-editing teacher, not in any group' => [
SEPARATEGROUPS, 'teacher', [], []
],
'Separate groups, non-editing teacher and student share two groups' => [
SEPARATEGROUPS, 'teacher', ['g4', 'g5'], ['s5']
],
'Separate groups, editing teacher' => [
SEPARATEGROUPS, 'editingteacher', ['g1', 'g2'], ['s1', 's2', 's3', 's4', 's5']
],
];
}
/**
* Test for helper::get_users() with course group mode set.
*
* @dataProvider get_users_provider
* @param $groupmode
* @param $teacherrole
* @param $teachergroups
* @param $expectedusers
*/
public function test_get_users_with_groups($groupmode, $teacherrole, $teachergroups, $expectedusers) {
global $DB;
$this->resetAfterTest();
$generator = $this->getDataGenerator();
// Create a test course.
$course = $generator->create_course(['groupmode' => $groupmode]);
// Create an assignment module.
$assign = $generator->create_module('assign', ['course' => $course]);
// Fetch roles.
$role = $DB->get_record('role', ['shortname' => $teacherrole], '*', MUST_EXIST);
$studentrole = $DB->get_record('role', ['shortname' => 'student'], '*', MUST_EXIST);
// Create users.
$t1 = $generator->create_user(['username' => 't1', 'email' => 't1@example.com']);
$s1 = $generator->create_user(['username' => 's1', 'email' => 's1@example.com']);
$s2 = $generator->create_user(['username' => 's2', 'email' => 's2@example.com']);
$s3 = $generator->create_user(['username' => 's3', 'email' => 's3@example.com']);
$s4 = $generator->create_user(['username' => 's4', 'email' => 's4@example.com']);
$s5 = $generator->create_user(['username' => 's5', 'email' => 's5@example.com']);
// Enrol users.
$generator->enrol_user($t1->id, $course->id, $role->id);
$generator->enrol_user($s1->id, $course->id, $studentrole->id);
$generator->enrol_user($s2->id, $course->id, $studentrole->id);
$generator->enrol_user($s3->id, $course->id, $studentrole->id);
$generator->enrol_user($s4->id, $course->id, $studentrole->id);
$generator->enrol_user($s5->id, $course->id, $studentrole->id);
// Create groups.
$groups = [];
$groups['g1'] = $generator->create_group(['courseid' => $course->id, 'name' => 'g1']);
$groups['g2'] = $generator->create_group(['courseid' => $course->id, 'name' => 'g2']);
$groups['g3'] = $generator->create_group(['courseid' => $course->id, 'name' => 'g3']);
$groups['g4'] = $generator->create_group(['courseid' => $course->id, 'name' => 'g4']);
$groups['g5'] = $generator->create_group(['courseid' => $course->id, 'name' => 'g5']);
// Add teacher to the assigned groups.
foreach ($teachergroups as $groupname) {
$group = $groups[$groupname];
$generator->create_group_member(['groupid' => $group->id, 'userid' => $t1->id]);
}
// Add students to groups.
$generator->create_group_member(['groupid' => $groups['g1']->id, 'userid' => $s1->id]);
$generator->create_group_member(['groupid' => $groups['g2']->id, 'userid' => $s2->id]);
$generator->create_group_member(['groupid' => $groups['g3']->id, 'userid' => $s3->id]);
$generator->create_group_member(['groupid' => $groups['g4']->id, 'userid' => $s5->id]);
$generator->create_group_member(['groupid' => $groups['g5']->id, 'userid' => $s5->id]);
// Creating grade history for the students.
$gi = grade_item::fetch(['iteminstance' => $assign->id, 'itemtype' => 'mod', 'itemmodule' => 'assign']);
$this->create_grade_history(['itemid' => $gi->id, 'userid' => $s1->id]);
$this->create_grade_history(['itemid' => $gi->id, 'userid' => $s2->id]);
$this->create_grade_history(['itemid' => $gi->id, 'userid' => $s3->id]);
$this->create_grade_history(['itemid' => $gi->id, 'userid' => $s4->id]);
$this->create_grade_history(['itemid' => $gi->id, 'userid' => $s5->id]);
// Log in as the teacher.
$this->setUser($t1);
// Fetch the users.
$users = \gradereport_history\helper::get_users(context_course::instance($course->id));
// Confirm that the number of users fetched is the same as the count of expected users.
$this->assertCount(count($expectedusers), $users);
foreach ($users as $user) {
// Confirm that each user returned is in the list of expected users.
$this->assertTrue(in_array($user->username, $expectedusers));
}
}
/**
* Test the get graders helper method.
*/
@@ -228,9 +362,11 @@ class gradereport_history_report_testcase extends advanced_testcase {
// Making the setup.
$c1 = $this->getDataGenerator()->create_course();
$c2 = $this->getDataGenerator()->create_course();
$c3 = $this->getDataGenerator()->create_course(['groupmode' => SEPARATEGROUPS]);
$c1m1 = $this->getDataGenerator()->create_module('assign', array('course' => $c1));
$c2m1 = $this->getDataGenerator()->create_module('assign', array('course' => $c2));
$c3m1 = $this->getDataGenerator()->create_module('assign', array('course' => $c3));
// Users.
$u1 = $this->getDataGenerator()->create_user(array('firstname' => 'Eric', 'lastname' => 'Cartman'));
@@ -238,6 +374,12 @@ class gradereport_history_report_testcase extends advanced_testcase {
$u3 = $this->getDataGenerator()->create_user(array('firstname' => 'Kyle', 'lastname' => 'Broflovski'));
$u4 = $this->getDataGenerator()->create_user(array('firstname' => 'Kenny', 'lastname' => 'McCormick'));
foreach ([$c1, $c2, $c3] as $course) {
foreach ([$u1, $u2, $u3, $u4] as $user) {
self::getDataGenerator()->enrol_user($user->id, $course->id, 'student');
}
}
// Creating grade history for some users.
$gi = grade_item::fetch(array('iteminstance' => $c1m1->id, 'itemtype' => 'mod', 'itemmodule' => 'assign'));
$this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u1->id, 'usermodified' => $u1->id));
@@ -247,6 +389,10 @@ class gradereport_history_report_testcase extends advanced_testcase {
$gi = grade_item::fetch(array('iteminstance' => $c2m1->id, 'itemtype' => 'mod', 'itemmodule' => 'assign'));
$this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u1->id, 'usermodified' => $u4->id));
$gi = grade_item::fetch(array('iteminstance' => $c3m1->id, 'itemtype' => 'mod', 'itemmodule' => 'assign'));
$this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u1->id, 'usermodified' => $u1->id));
$this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u2->id, 'usermodified' => $u2->id));
// Checking fetching some users.
$graders = \gradereport_history\helper::get_graders($c1->id);
$this->assertCount(4, $graders); // Including "all graders" .
@@ -256,6 +402,17 @@ class gradereport_history_report_testcase extends advanced_testcase {
$graders = \gradereport_history\helper::get_graders($c2->id);
$this->assertCount(2, $graders); // Including "all graders" .
$this->assertArrayHasKey($u4->id, $graders);
// Third course is in separate groups mode. Only graders from the same group will be returned.
$g = self::getDataGenerator()->create_group(['courseid' => $course->id, 'name' => 'g1']);
self::getDataGenerator()->create_group_member(['groupid' => $g->id, 'userid' => $u1->id]);
self::getDataGenerator()->create_group_member(['groupid' => $g->id, 'userid' => $u2->id]);
$this->setUser($u1);
$graders = \gradereport_history\helper::get_graders($c3->id);
$this->assertCount(3, $graders); // Including "all graders" .
$this->setUser($u3);
$graders = \gradereport_history\helper::get_graders($c3->id);
$this->assertCount(1, $graders); // Including "all graders" .
}
/**
+34
View File
@@ -0,0 +1,34 @@
<?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/>.
/**
* Automatically generated strings for Moodle installer
*
* Do not edit this file manually! It contains just a subset of strings
* needed during the very first steps of installation. This file was
* generated automatically by export-installer.php (which is part of AMOS
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
* list of strings defined in /install/stringnames.txt.
*
* @package installer
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$string['parentlanguage'] = 'ar';
$string['thislanguage'] = 'بالعربي workplace';
+1 -1
View File
@@ -46,7 +46,7 @@ $string['downloadedfilecheckfailed'] = 'Ha fallat la comprovació del fitxer bai
$string['invalidmd5'] = 'L\'md5 no és vàlid. Torneu a provar-ho';
$string['missingrequiredfield'] = 'Falta algun camp necessari';
$string['remotedownloaderror'] = '<p>No s\'ha pogut baixar el component al vostre servidor. Verifiqueu els paràmetres del servidor intermediari. Es recomana vivament l\'extensió cURL de PHP.</p>
<p>Haureu de baixar manualment el fitxer <a href="{$a->url}">{$a->url}</a>, copiar-lo a la ubicació «{$a->dest}» del vostre servidor i descomprimir-lo allí.</p>';
<p>Haureu de baixar manualment el fitxer <a href="{$a->url}">{$a->url}</a>, copiar-lo a la ubicació «{$a->dest}» del vostre servidor i descomprimir-lo allà.</p>';
$string['wrongdestpath'] = 'El camí de destinació és erroni';
$string['wrongsourcebase'] = 'L\'adreça (URL) base de la font és errònia';
$string['wrongzipfilename'] = 'El nom del fitxer ZIP és erroni';
+2 -2
View File
@@ -45,7 +45,7 @@ $string['datarootpermission'] = 'Zugriffsrechte zum Datenverzeichnis';
$string['dbprefix'] = 'Tabellen-Prefix';
$string['dirroot'] = 'Moodle-Verzeichnis';
$string['environmenthead'] = 'Installationsvoraussetzungen werden geprüft ...';
$string['environmentsub2'] = 'Jede Version hat Mindestvoraussetzungen für der PHP-Version und für verbindliche PHP-Extensions. Vor einer Installation oder einer Aktualisierung wird eine vollständige Prüfung durchgeführt. Bitte fragen Sie den Administrator des Servers, wenn Sie mit der Installation einer neuen Version oder mit der Aktivierung von PHP-Extensions nicht weiterkommen.';
$string['environmentsub2'] = 'Jede Version hat Mindestvoraussetzungen für der PHP-Version und für verbindliche PHP-Extensions. Vor einer Installation oder einer Aktualisierung wird eine vollständige Prüfung durchgeführt. Bitte fragen Sie die Administrator/innen des Servers, wenn Sie mit der Installation einer neuen Version oder mit der Aktivierung von PHP-Extensions nicht weiterkommen.';
$string['errorsinenvironment'] = 'Fehler bei der Prüfung der Systemvoraussetzungen!';
$string['installation'] = 'Installation';
$string['langdownloaderror'] = 'Das Sprachpaket \'{$a}\' konnte nicht heruntergeladen werden. Die Installation wird in englischer Sprache fortgesetzt.';
@@ -63,7 +63,7 @@ $string['pathserrcreatedataroot'] = 'Das Datenverzeichnis ({$a->dataroot}) kann
$string['pathshead'] = 'Pfade bestätigen';
$string['pathsrodataroot'] = 'Das Verzeichnis dataroot ist schreibgeschützt.';
$string['pathsroparentdataroot'] = 'Das Verzeichnis ({$a->parent}) ist schreibgeschützt. Deswegen kann das Datenverzeichnis ({$a->dataroot}) vom Installer nicht angelegt werden.';
$string['pathssubadmindir'] = 'Einige Webserver benutzen /admin als speziellen Link, um auf Einstellungsseiten oder Ähnliches zu verweisen. Unglücklicherweise kollidiert dies mit dem standardmäßigen Verzeichnis für die Moodle-Administration. Sie können dieses Problem beheben, indem Sie das Verzeichnis admin in Ihrer Moodle-Installation umbenennen und den neuen Namen hier eingeben (z.B. <em>moodleadmin</em>). Mit dieser Änderung werden alle Admin-Links korrigiert.';
$string['pathssubadmindir'] = 'Einige Webserver benutzen /admin als speziellen Link, um auf Einstellungsseiten oder Ähnliches zu verweisen. Unglücklicherweise kollidiert dies mit dem Verzeichnis für die Moodle-Administration. Sie können dieses Problem beheben, indem Sie das Verzeichnis admin in Ihrer Moodle-Installation umbenennen und den neuen Namen hier eingeben (z.B. <em>moodleadmin</em>). Mit dieser Änderung werden alle Admin-Links korrigiert.';
$string['pathssubdataroot'] = '<p>Sie benötigen einen Platz, wo Moodle hochgeladene Dateien abspeichern kann. </p><p>Dieses Verzeichnis muss Lese- und Schreibrechte für das Nutzerkonto besitzen, mit dem Ihr Webservers läuft (üblicherweise \'nobody\', \'apache\' oder \'www-data).</p><p> Außerdem sollte das Verzeichnis nicht direkt aus dem Internet erreichbar sein. </p><p>Das Intallationsskript wird versuchen, ein solches Verzeichnis zu erstellen, falls es nicht existiert.</p>';
$string['pathssubdirroot'] = '<p>Vollständiger Pfad der Moodle-Installation.</p>';
$string['pathssubwwwroot'] = '<p>Webadresse, die ein Nutzer im Browser eingibt, um auf Moodle zuzugreifen.</p><p> Es ist nicht möglich, über unterschiedliche Adressen auf Moodle zuzugreifen. Sollte Ihre Website mehrere öffentliche Adressen verwenden, so müssen Sie eine Adresse festlegen und für die übrigen Adressen dauerhafte Weiterleitungen dorthin einrichten.</p>
+1 -1
View File
@@ -45,6 +45,6 @@ $string['downloadedfilecheckfailed'] = 'Αποτυχία ελέγχου αρχε
$string['invalidmd5'] = 'Η μεταβλητή ελέγχου ήταν λανθασμένη - δοκιμάστε ξανά';
$string['missingrequiredfield'] = 'Κάποιο απαιτούμενο πεδίο λείπει';
$string['remotedownloaderror'] = '<p>Η λήψη του στοιχείου λογισμικού στον εξυπηρετητή σας απέτυχε. Παρακαλούμε επαληθεύστε τις ρυθμίσεις του διακομιστή μεσολάβησης (proxy)· η επέκταση PHP cURL συνιστάται θερμά.</p><br /><p>Πρέπει να κατεβάσετε το αρχείο <a href="{$a->url}">{$a->url}</a> χειροκίνητα, να το αντιγράψετε στο «{$a->dest}» στον εξυπηρετητή σας και να το αποσυμπιέσετε εκεί.</p>';
$string['wrongdestpath'] = 'Λανθασμένη διαδρομή προορισμού (πλήρες όνομα).';
$string['wrongdestpath'] = 'Λανθασμένο μονοπάτι προορισμού.';
$string['wrongsourcebase'] = 'Λανθασμένη βάση πηγής URL.';
$string['wrongzipfilename'] = 'Λανθασμένo όνομα αρχείου ZIP.';
+2 -2
View File
@@ -70,12 +70,12 @@ $string['pathsroparentdataroot'] = 'Ο γονικός φάκελος ({$a->paren
$string['pathssubadmindir'] = 'Κάποιοι λίγοι κεντρικοί υπολογιστές ιστού χρησιμοποιούν το /admin ως ειδική διεύθυνση URL για την πρόσβαση σε κάποιο πίνακα ελέγχου ή κάτι τέτοιο. Δυστυχώς αυτό έρχεται σε αντίθεση με την τυπική τοποθεσία των σελίδων διαχείρισης (admin) του Moodle. Αυτό μπορεί να διορθωθεί με την μετονομασία του admin φακέλου στην εγκατάστασή σας, και βάζοντας αυτό το καινούργιο όνομα εδώ. Για παράδειγμα: <em>moodleadmin</em>. Αυτό θα διορθώσει όλους τους συνδέσμους με το admin στην διεύθυνσή τους σε όλη την εγκατάσταση του Moodle σας.';
$string['pathssubdataroot'] = '<p>Ένας φάκελος όπου το Moodle θα αποθηκεύει όλα τα ανεβασμένα από τους χρήστες αρχεία.</p> <p>Αυτος ο φάκελος θα πρέπει να είναι αναγνώσιμος ΚΑΙ ΕΓΓΡΑΨΙΜΟΣ από τον χρήστη του εξυπηρετητή ιστού (συνήθως «nobody» ή «apache»).</p> <p>Δεν πρέπει να είναι προσβάσιμος κατευθείαν από τον ιστό.</p> <p>Αν ο φάκελος δεν υπάρχει, η διαδικασία εγκατάστασης θα προσπαθήσει να τον δημιουργήσει.</p>';
$string['pathssubdirroot'] = '<p>Η πλήρης διαδρομή του φακέλου που περιέχει τα αρχεία κώδικα του Moodle.</p>';
$string['pathssubwwwroot'] = '<p>Η πλήρης διεύθυνση (ιστού) από την οποία θα γίνεται η πρόσβαση στο Moodle, δηλαδή η διεύθυνση που οι χρήστες θα εισάγουν στην γραμμή διεύθυνσης του περιηγητή, για να έχουν πρόσβαση στου Moodle.</p>
$string['pathssubwwwroot'] = '<p>Η πλήρης διεύθυνση από την οποία θα γίνεται η πρόσβαση στο Moodle, δηλαδή η διεύθυνση που οι χρήστες θα εισάγουν στην γραμμή διεύθυνσης του περιηγητή, για να έχουν πρόσβαση στου Moodle.</p>
<p>Δεν είναι δυνατόν να έχετε πρόβαση στο Moodle χρησιμοποιώντας πολλαπλές διευθύνσεις. Εάν ο ιστότοπος θα είναι προσβάσιμος μέσω πολλαπλών διευθύνσεων τότε επιλέξτε την ευκολότερη και εγκαταστήστε μια μόνιμη ανακατεύθυνση για καθεμία από τις άλλες διευθύνσεις.</p>
<p>Εάν ο ιστότοπός σας είναι προσβάσιμος τόσο από το Διαδίκτυο όσο και από ένα εσωτερικό δίκτυο (που συχνά λέγεται intranet) τότε χρησιμοποιήστε εδώ την δημόσια διεύθυνση.</p>
<p>Αν η τρέχουσα διεύθυνση δεν είναι σωστή, παρακαλούμε αλλάξτε την URL διεύθυνση στην γραμμή διευθύνσεων του περιηγητή σας και επανεκκινήστε την εγκατάσταση.</p>';
$string['pathsunsecuredataroot'] = 'Η τοποθεσία του Φάκελου Δεδομένων δεν είναι ασφαλής';
$string['pathswrongadmindir'] = 'Ο Φάκελος Admin δεν υπάρχει';
$string['pathswrongadmindir'] = 'Ο φάκελος Admin δεν υπάρχει';
$string['phpextension'] = 'Επέκταση {$a} της PHP';
$string['phpversion'] = 'Έκδοση της PHP';
$string['phpversionhelp'] = 'p>Το Moodle απαιτεί η έκδοση της PHP να είναι τουλάχιστον 5.6.5 ή 7.1 (η 7.0.x έχει κάποιους περιορισμούς στη μηχανή).</p>
+1
View File
@@ -31,4 +31,5 @@
defined('MOODLE_INTERNAL') || die();
$string['parentlanguage'] = 'es';
$string['thisdirection'] = 'ltr';
$string['thislanguage'] = 'Español para la Empresa';
+1
View File
@@ -31,4 +31,5 @@
defined('MOODLE_INTERNAL') || die();
$string['parentlanguage'] = 'he';
$string['thisdirection'] = 'rtl';
$string['thislanguage'] = 'עברית בתי־ספר';
+35
View File
@@ -0,0 +1,35 @@
<?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/>.
/**
* Automatically generated strings for Moodle installer
*
* Do not edit this file manually! It contains just a subset of strings
* needed during the very first steps of installation. This file was
* generated automatically by export-installer.php (which is part of AMOS
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
* list of strings defined in /install/stringnames.txt.
*
* @package installer
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$string['clianswerno'] = 'ל';
$string['cliansweryes'] = 'כ';
$string['cliincorrectvalueretry'] = 'ערך לא תקין, נסו שוב בבקשה';
+34
View File
@@ -0,0 +1,34 @@
<?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/>.
/**
* Automatically generated strings for Moodle installer
*
* Do not edit this file manually! It contains just a subset of strings
* needed during the very first steps of installation. This file was
* generated automatically by export-installer.php (which is part of AMOS
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
* list of strings defined in /install/stringnames.txt.
*
* @package installer
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$string['parentlanguage'] = 'ja';
$string['thislanguage'] = 'Workplace準拠の日本語';
+1 -1
View File
@@ -30,4 +30,4 @@
defined('MOODLE_INTERNAL') || die();
$string['thislanguage'] = 'myanma bhasa';
$string['thislanguage'] = 'ဗမာစာ';
+33
View File
@@ -0,0 +1,33 @@
<?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/>.
/**
* Automatically generated strings for Moodle installer
*
* Do not edit this file manually! It contains just a subset of strings
* needed during the very first steps of installation. This file was
* generated automatically by export-installer.php (which is part of AMOS
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
* list of strings defined in /install/stringnames.txt.
*
* @package installer
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$string['thislanguage'] = 'Afaan Oromoo';
+2
View File
@@ -315,6 +315,7 @@ $string['configrequestedstudentname'] = 'Word for student used in requested cour
$string['configrequestedstudentsname'] = 'Word for students used in requested courses';
$string['configrequestedteachername'] = 'Word for teacher used in requested courses';
$string['configrequestedteachersname'] = 'Word for teachers used in requested courses';
$string['configreverseproxyignore'] = 'If your server is behind multiple reverse proxies that append to the X-Forwarded-For header then you will need to specify a comma separated list of ip addresses or subnets of the reverse proxies to be ignored in order to find the users correct IP address.';
$string['configuserquota'] = 'The maximum number of bytes that a user can store in their own private file area. {$a->bytes} bytes == {$a->displaysize}';
$string['configsectioninterface'] = 'Interface';
$string['configsectionmail'] = 'Mail';
@@ -988,6 +989,7 @@ $string['restorecourse'] = 'Restore course';
$string['restorernewroleid'] = 'Restorers\' role in courses';
$string['restorernewroleid_help'] = 'If the user does not already have the permission to manage the newly restored course, the user is automatically assigned this role and enrolled if necessary. Select "None" if you do not want restorers to be able to manage every restored course.';
$string['reverseproxy'] = 'Reverse proxy';
$string['reverseproxyignore'] = 'Ignore reverse proxies';
$string['riskconfig'] = 'Users could change site configuration and behaviour';
$string['riskconfigshort'] = 'Configuration risk';
$string['riskdataloss'] = 'Users could destroy large amounts of content or information';
+9 -19
View File
@@ -328,20 +328,11 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
if (!$timeout) {
$timeout = self::get_timeout();
}
if ($microsleep) {
// Will sleep 1/10th of a second by default for self::get_timeout() seconds.
$loops = $timeout * 10;
} else {
// Will sleep for self::get_timeout() seconds.
$loops = $timeout;
}
// DOM will never change on non-javascript case; do not wait or try again.
if (!$this->running_javascript()) {
$loops = 1;
}
$start = microtime(true);
$end = $start + $timeout;
for ($i = 0; $i < $loops; $i++) {
do {
// We catch the exception thrown by the step definition to execute it again.
try {
// We don't check with !== because most of the time closures will return
@@ -357,14 +348,13 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
}
}
if ($this->running_javascript()) {
if ($microsleep) {
usleep(100000);
} else {
sleep(1);
}
if (!$this->running_javascript()) {
break;
}
}
usleep(100000);
} while (microtime(true) < $end);
// Using coding_exception as is a development issue if no exception has been provided.
if (!$exception) {
+38 -15
View File
@@ -90,31 +90,54 @@ class behat_context_helper {
* @return behat_base
*/
public static function get($classname) {
if (self::$environment->hasContextClass($classname)) {
return self::$environment->getContext($classname);
$definedclassname = self::get_theme_override($classname);
if ($definedclassname) {
return self::$environment->getContext($definedclassname);
}
// Just fall back on getContext to ensure that we throw the correct exception.
return self::$environment->getContext($classname);
}
/**
* Get the context for the specified component or subsystem.
*
* @param string $component The component or subsystem to find the context for
* @return behat_base|null
*/
public static function get_component_context(string $component) {
$component = str_replace('core_', '', $component);
if ($classname = self::get_theme_override("behat_{$component}")) {
return self::get($classname);
}
return null;
}
/**
* Check for any theme override of the specified class name.
*
* @param string $classname
* @return string|null
*/
protected static function get_theme_override(string $classname) {
$suitename = self::$environment->getSuite()->getName();
// If default suite, then get the default theme name.
if ($suitename == 'default') {
$suitename = theme_config::DEFAULT_THEME;
}
$overridencontextname = 'behat_theme_'.$suitename.'_'.$classname;
// If contexts has not been checked before and doesn't exist then just use core one.
if (!isset(self::$nonexistingcontexts[$overridencontextname])) {
try {
$subcontext = self::$environment->getContext($overridencontextname);
return $subcontext;
} catch (Behat\Behat\Context\Exception\ContextNotFoundException $e) {
// If context not found then it's not overridden.
self::$nonexistingcontexts[$overridencontextname] = 1;
}
$overrideclassname = "behat_theme_{$suitename}_{$classname}";
if (self::$environment->hasContextClass($overrideclassname)) {
return $overrideclassname;
}
// Get the actual context.
return self::$environment->getContext($classname);
if (self::$environment->hasContextClass($classname)) {
return $classname;
}
return null;
}
/**
+21
View File
@@ -224,4 +224,25 @@ final class ip_utils {
}
return false;
}
/**
* Is an ip in a given list of subnets?
*
* @param string $ip - the IP to test against the list
* @param string $list - the list of IP subnets
* @param string $delim a delimiter of the list
* @return bool
*/
public static function is_ip_in_subnet_list($ip, $list, $delim = "\n") {
$list = explode($delim, $list);
foreach ($list as $line) {
$tokens = explode('#', $line);
$subnet = trim($tokens[0]);
if (address_in_subnet($ip, $subnet)) {
return true;
}
}
return false;
}
}
+80 -1
View File
@@ -260,7 +260,27 @@ class manager {
// Set configuration.
session_name($sessionname);
session_set_cookie_params(0, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $cookiesecure, $CFG->cookiehttponly);
if (version_compare(PHP_VERSION, '7.3.0', '>=')) {
$sessionoptions = [
'lifetime' => 0,
'path' => $CFG->sessioncookiepath,
'domain' => $CFG->sessioncookiedomain,
'secure' => $cookiesecure,
'httponly' => $CFG->cookiehttponly,
];
if (self::should_use_samesite_none()) {
// If $samesite is empty, we don't want there to be any SameSite attribute.
$sessionoptions['samesite'] = 'None';
}
session_set_cookie_params($sessionoptions);
} else {
// Once PHP 7.3 becomes our minimum, drop this in favour of the alternative call to session_set_cookie_params above,
// as that does not require a hack to work with same site settings on cookies.
session_set_cookie_params(0, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $cookiesecure, $CFG->cookiehttponly);
}
ini_set('session.use_trans_sid', '0');
ini_set('session.use_only_cookies', '1');
ini_set('session.hash_function', '0'); // For now MD5 - we do not have room for sha-1 in sessions table.
@@ -420,6 +440,8 @@ class manager {
if ($timedout) {
$_SESSION['SESSION']->has_timed_out = true;
}
self::append_samesite_cookie_attribute();
}
/**
@@ -487,6 +509,61 @@ class manager {
// Setup $USER object.
self::set_user($user);
self::append_samesite_cookie_attribute();
}
/**
* Returns a valid setting for the SameSite cookie attribute.
*
* @return string The desired setting for the SameSite attribute on the cookie. Empty string indicates the SameSite attribute
* should not be set at all.
*/
private static function should_use_samesite_none() {
// We only want None or no attribute at this point. When we have cookie handling compatible with Lax,
// we can look at checking a setting.
// Browser support for none is not consistent yet. There are known issues with Safari, and IE11.
// Things are stablising, however as they're not stable yet we will deal specifically with the version of chrome
// that introduces a default of lax, setting it to none for the current version of chrome (2 releases before the change).
// We also check you are using secure cookies and HTTPS because if you are not running over HTTPS
// then setting SameSite=None will cause your session cookie to be rejected.
if (\core_useragent::is_chrome() && \core_useragent::check_chrome_version('78') && is_moodle_cookie_secure()) {
return true;
}
return false;
}
/**
* Conditionally append the SameSite attribute to the session cookie if necessary.
*
* Contains a hack for versions of PHP lower than 7.3 as there is no API built into PHP cookie API
* for adding the SameSite setting.
*
* This won't change the Set-Cookie headers if:
* - PHP 7.3 or higher is being used. That already adds the SameSite attribute without any hacks.
* - If the samesite setting is empty.
* - If the samesite setting is None but the browser is not compatible with that setting.
*/
private static function append_samesite_cookie_attribute() {
if (version_compare(PHP_VERSION, '7.3.0', '>=')) {
// This hack is only necessary if we weren't able to set the samesite flag via the session_set_cookie_params API.
return;
}
if (!self::should_use_samesite_none()) {
return;
}
$cookies = headers_list();
header_remove('Set-Cookie');
$setcookiesession = 'Set-Cookie: ' . session_name() . '=';
foreach ($cookies as $cookie) {
if (strpos($cookie, $setcookiesession) === 0) {
$cookie .= '; SameSite=None';
}
header($cookie, false);
}
}
/**
@@ -523,6 +600,8 @@ class manager {
self::add_session_record($_SESSION['USER']->id); // Do not use $USER here because it may not be set up yet.
session_write_close();
self::$sessionactive = false;
self::append_samesite_cookie_attribute();
}
/**
+10 -12
View File
@@ -8993,23 +8993,13 @@ function cleardoubleslashes ($path) {
* @return bool
*/
function remoteip_in_list($list) {
$inlist = false;
$clientip = getremoteaddr(null);
if (!$clientip) {
// Ensure access on cli.
return true;
}
$list = explode("\n", $list);
foreach ($list as $subnet) {
$subnet = trim($subnet);
if (address_in_subnet($clientip, $subnet)) {
$inlist = true;
break;
}
}
return $inlist;
return \core\ip_utils::is_ip_in_subnet_list($clientip, $list);
}
/**
@@ -9037,7 +9027,15 @@ function getremoteaddr($default='0.0.0.0') {
if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR)) {
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$forwardedaddresses = explode(",", $_SERVER['HTTP_X_FORWARDED_FOR']);
$address = $forwardedaddresses[0];
$forwardedaddresses = array_filter($forwardedaddresses, function($ip) {
global $CFG;
return !\core\ip_utils::is_ip_in_subnet_list($ip, $CFG->reverseproxyignore, ',');
});
// Multiple proxies can append values to this header including an
// untrusted original request header so we must only trust the last ip.
$address = end($forwardedaddresses);
if (substr_count($address, ":") > 1) {
// Remove port and brackets from IPv6.
+1 -5
View File
@@ -990,11 +990,7 @@ EOF;
// Using the spin method as we want a reduced timeout but there is no need for a 0.1 seconds interval
// because in the optimistic case we will timeout.
// If all goes good it will throw an ElementNotFoundExceptionn that we will catch.
$this->spin(
function($context, $args) use ($selectortype, $element) {
return $this->find($selectortype, $element);
}, [], behat_base::get_reduced_timeout(), $exception, false
);
return $this->find($selectortype, $element, $exception, false, behat_base::get_reduced_timeout());
} catch (ElementNotFoundException $e) {
// We expect the element to not be found.
return;
+3 -11
View File
@@ -688,20 +688,12 @@ class behat_hooks extends behat_base {
* @param string $component
*/
public function register_component_selectors_for_component(string $component) {
$componentclassname = "behat_{$component}";
$context = behat_context_helper::get_component_context($component);
if (!behat_context_helper::has_context($componentclassname)) {
if ("core_" === substr($component, 0, 5)) {
$componentclassname = "behat_" . substr($component, 5);
if (!behat_context_helper::has_context($componentclassname)) {
return;
}
} else {
return;
}
if ($context === null) {
return;
}
$context = behat_context_helper::get($componentclassname);
$namedpartial = $this->getSession()->getSelectorsHandler()->getSelector('named_partial');
$namedexact = $this->getSession()->getSelectorsHandler()->getSelector('named_exact');
+28
View File
@@ -376,4 +376,32 @@ class core_ip_utils_testcase extends basic_testcase {
[false, 'trouble.com.au'] // The allowed domain (above) has a space at the front and so will return false.
];
}
/**
* Data provider for test_is_ip_in_subnet_list.
*
* @return array
*/
public function data_is_ip_in_subnet_list() {
return [
[true, '1.1.1.1', '1.1.1.1', "\n"],
[false, '1.1.1.1', '2.2.2.2', "\n"],
[true, '1.1.1.1', "1.1.1.5\n1.1.1.1", "\n"],
[true, '1.1.1.1', "1.1.1.5,1.1.1.1", ","],
];
}
/**
* Test checking ips against a list of allowed domains.
*
* @param bool $expected Expected result
* @param string $ip IP address
* @param string $list list of IP subnets
* @param string $delim delimiter of list
* @dataProvider data_is_ip_in_subnet_list
*/
public function test_is_ip_in_subnet_list($expected, $ip, $list, $delim) {
$this->assertEquals($expected, \core\ip_utils::is_ip_in_subnet_list($ip, $list, $delim));
}
}
+15 -11
View File
@@ -3569,6 +3569,10 @@ class core_moodlelib_testcase extends advanced_testcase {
* Tests the getremoteaddr() function.
*/
public function test_getremoteaddr() {
global $CFG;
$this->resetAfterTest();
$CFG->getremoteaddrconf = GETREMOTEADDR_SKIP_HTTP_CLIENT_IP;
$xforwardedfor = isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : null;
$_SERVER['HTTP_X_FORWARDED_FOR'] = '';
@@ -3585,27 +3589,27 @@ class core_moodlelib_testcase extends advanced_testcase {
$_SERVER['HTTP_X_FORWARDED_FOR'] = '127.0.0.1,127.0.0.2';
$twoip = getremoteaddr();
$this->assertEquals('127.0.0.1', $twoip);
$this->assertEquals('127.0.0.2', $twoip);
$_SERVER['HTTP_X_FORWARDED_FOR'] = '127.0.0.1,127.0.0.2, 127.0.0.3';
$_SERVER['HTTP_X_FORWARDED_FOR'] = '127.0.0.1,127.0.0.2,127.0.0.3';
$threeip = getremoteaddr();
$this->assertEquals('127.0.0.1', $threeip);
$this->assertEquals('127.0.0.3', $threeip);
$_SERVER['HTTP_X_FORWARDED_FOR'] = '127.0.0.1:65535,127.0.0.2';
$_SERVER['HTTP_X_FORWARDED_FOR'] = '127.0.0.1,127.0.0.2:65535';
$portip = getremoteaddr();
$this->assertEquals('127.0.0.1', $portip);
$this->assertEquals('127.0.0.2', $portip);
$_SERVER['HTTP_X_FORWARDED_FOR'] = '0:0:0:0:0:0:0:1,127.0.0.2';
$_SERVER['HTTP_X_FORWARDED_FOR'] = '127.0.0.1,0:0:0:0:0:0:0:2';
$portip = getremoteaddr();
$this->assertEquals('0:0:0:0:0:0:0:1', $portip);
$this->assertEquals('0:0:0:0:0:0:0:2', $portip);
$_SERVER['HTTP_X_FORWARDED_FOR'] = '0::1,127.0.0.2';
$_SERVER['HTTP_X_FORWARDED_FOR'] = '127.0.0.1,0::2';
$portip = getremoteaddr();
$this->assertEquals('0:0:0:0:0:0:0:1', $portip);
$this->assertEquals('0:0:0:0:0:0:0:2', $portip);
$_SERVER['HTTP_X_FORWARDED_FOR'] = '[0:0:0:0:0:0:0:1]:65535,127.0.0.2';
$_SERVER['HTTP_X_FORWARDED_FOR'] = '127.0.0.1,[0:0:0:0:0:0:0:2]:65535';
$portip = getremoteaddr();
$this->assertEquals('0:0:0:0:0:0:0:1', $portip);
$this->assertEquals('0:0:0:0:0:0:0:2', $portip);
$_SERVER['HTTP_X_FORWARDED_FOR'] = $xforwardedfor;
+5
View File
@@ -1,6 +1,11 @@
This files describes API changes in core libraries and APIs,
information provided here is intended especially for developers.
=== 3.5.10 ===
* behat_data_generators::the_following_exist() has been removed, please use
behat_data_generators::the_following_entities_exist() instead. See MDL-67691 for more info.
=== 3.5.6 ===
* Behat timeout constants behat_base::TIMEOUT, EXTENDED_TIMEOUT, and REDUCED_TIMEOUT will be
@@ -44,9 +44,8 @@ Feature: Students can edit or delete their forum posts within a set time limit
Scenario: Time limit expires
Given I log out
And I log in as "admin"
And I navigate to "Security > Site security settings" in site administration
And I set the field "Maximum time to edit posts" to "1 minutes"
And I press "Save changes"
And the following config values are set as admin:
| maxeditingtime | 1 |
And I am on "Course 1" course homepage with editing mode on
And I add the "Recent activity" block
And I log out
@@ -54,7 +53,7 @@ Feature: Students can edit or delete their forum posts within a set time limit
And I am on "Course 1" course homepage
And I should see "New forum posts:" in the "Recent activity" "block"
And I should see "Forum post subject" in the "Recent activity" "block"
When I wait "61" seconds
When I wait "2" seconds
And I follow "Forum post subject"
Then I should not see "Edit" in the "region-main" "region"
And I should not see "Delete" in the "region-main" "region"
@@ -4,7 +4,6 @@ Feature: A teacher can set whether glossary entries are always editable or not
As a teacher
I need to prevent entries to be always editable
@javascript
Scenario: Glossary entries are not always editable
Given the following "users" exist:
| username | firstname | lastname | email |
@@ -18,7 +17,7 @@ Feature: A teacher can set whether glossary entries are always editable or not
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following config values are set as admin:
| maxeditingtime | 60 |
| maxeditingtime | 5 |
And I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
And I add a "Glossary" to section "1" and I fill the form with:
@@ -34,7 +33,7 @@ Feature: A teacher can set whether glossary entries are always editable or not
| Definition | Test concept description |
Then "Delete entry: Test concept name" "link" should exist
And "Edit entry: Test concept name" "link" should exist
And I wait "65" seconds
And I wait "6" seconds
And I reload the page
Then "Delete entry: Test concept name" "link" should not exist
And "Delete entry: Test concept name" "link" should not exist
And "Edit entry: Test concept name" "link" should not exist
+7 -2
View File
@@ -508,6 +508,7 @@ function AICCapi(def, cmiobj, scormauto, cfgwwwroot, scormid, scoid, attempt, vi
}
function StoreData(data,storetotaltime) {
var datastring = '';
if (storetotaltime) {
if (cmi.core.lesson_mode == 'normal') {
if (cmi.core.credit == 'credit') {
@@ -531,9 +532,13 @@ function AICCapi(def, cmiobj, scormauto, cfgwwwroot, scormid, scoid, attempt, vi
datastring = CollectData(data,'cmi');
}
//popupwin(datastring);
var myRequest = NewHttpReq();
result = DoRequest(myRequest,datamodelurl,datamodelurlparams + datastring);
var result = DoRequest(myRequest, datamodelurl, datamodelurlparams + datastring);
if (result === false) {
return false;
}
results = String(result).split('\n');
errorCode = results[1];
return results[0];
+7 -1
View File
@@ -619,6 +619,7 @@ function SCORMapi1_2(def, cmiobj, cmiint, cmistring256, cmistring4096, scormdebu
}
function StoreData(data,storetotaltime) {
var datastring = '';
if (storetotaltime) {
if (cmi.core.lesson_status == 'not attempted') {
cmi.core.lesson_status = 'completed';
@@ -647,7 +648,12 @@ function SCORMapi1_2(def, cmiobj, cmiint, cmistring256, cmistring4096, scormdebu
var myRequest = NewHttpReq();
//alert('going to:' + "<?php p($CFG->wwwroot) ?>/mod/scorm/datamodel.php" + "id=<?php p($id) ?>&a=<?php p($a) ?>&sesskey=<?php echo sesskey() ?>"+datastring);
result = DoRequest(myRequest,datamodelurl,datamodelurlparams + datastring);
var result = DoRequest(myRequest, datamodelurl, datamodelurlparams + datastring);
if (result === false) {
return false;
}
results = String(result).split('\n');
errorCode = results[1];
return results[0];
+5 -1
View File
@@ -1227,7 +1227,11 @@ function SCORMapi1_3(def, cmiobj, cmiint, cmicommentsuser, cmicommentslms, scorm
datastring += navrequest;
var myRequest = NewHttpReq();
result = DoRequest(myRequest, datamodelurl, datamodelurlparams + datastring);
var result = DoRequest(myRequest, datamodelurl, datamodelurlparams + datastring);
if (result === false) {
return false;
}
var results = String(result).split('\n');
if ((results.length > 2) && (navrequest != '')) {
+19 -5
View File
@@ -460,9 +460,16 @@ M.mod_scorm.init = function(Y, nav_display, navposition_left, navposition_top, h
if (scoes_nav[launch_sco].flow === 1) {
var datastring = scoes_nav[launch_sco].url + '&function=scorm_seq_flow&request=backward';
result = scorm_ajax_request(M.cfg.wwwroot + '/mod/scorm/datamodels/sequencinghandler.php?', datastring);
mod_scorm_seq = encodeURIComponent(result);
result = Y.JSON.parse (result);
if (typeof result.nextactivity.id != undefined) {
if (result === false) {
// Either the outcome was a failure, or we are unloading and simply just don't know
// what the outcome actually was.
result = {};
} else {
result = Y.JSON.parse(result);
}
if (typeof result.nextactivity !== 'undefined' && typeof result.nextactivity.id !== 'undefined') {
var node = scorm_prev(scorm_tree_node.getSelectedNodes()[0]);
if (node == null) {
// Avoid use of TreeView for Navigation.
@@ -492,8 +499,15 @@ M.mod_scorm.init = function(Y, nav_display, navposition_left, navposition_top, h
if (scoes_nav[launch_sco].flow === 1) {
var datastring = scoes_nav[launch_sco].url + '&function=scorm_seq_flow&request=forward';
result = scorm_ajax_request(M.cfg.wwwroot + '/mod/scorm/datamodels/sequencinghandler.php?', datastring);
mod_scorm_seq = encodeURIComponent(result);
result = Y.JSON.parse (result);
if (result === false) {
// Either the outcome was a failure, or we are unloading and simply just don't know
// what the outcome actually was.
result = {};
} else {
result = Y.JSON.parse(result);
}
if (typeof result.nextactivity !== 'undefined' && typeof result.nextactivity.id !== 'undefined') {
var node = scorm_next(scorm_tree_node.getSelectedNodes()[0]);
if (node === null) {
+49
View File
@@ -37,6 +37,34 @@ function NewHttpReq() {
function DoRequest(httpReq,url,param) {
// If we are unloading, and we can use sendBeacon then do that, Chrome does not permit synchronous XHR requests on unload.
if (window.mod_scorm_is_window_closing && navigator && navigator.sendBeacon && FormData) {
// Ok, old API alert, the param is a URI encoded string. We need to split it and convert it to a supported format.
// I've chosen FormData and FormData.append as they are compatible with our supported browsers:
// - https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData
// - https://developer.mozilla.org/en-US/docs/Web/API/FormData/append
var vars = param.split('&'),
i = 0,
pair,
key,
value,
formData = new FormData();
for (i = 0; i < vars.length; i++) {
pair = vars[i].split('=');
key = decodeURIComponent(pair[0]);
value = decodeURIComponent(pair[1]);
formData.append(key, value);
}
// We'll also inform it that we are unloading, potentially useful in the future.
formData.append('unloading', '1');
// The results is true or false, we don't get the response from the server. Make it look like it was a success.
navigator.sendBeacon(url, formData);
// This is what a success looks like when it comes back from the server.
return "true\n0";
}
// httpReq.open (Method("get","post"), URL(string), Asyncronous(true,false))
//popupwin(url+"\n"+param);
httpReq.open("POST", url,false);
@@ -60,3 +88,24 @@ function popupwin(content) {
op.document.write(content);
op.document.close();
}
/**
* We wire up a small marker for the unload events triggered when the user is navigating away or closing the tab.
* This is done because Chrome does not allow synchronous XHR requests on the following unload events:
* - beforeunload
* - unload
* - pagehide
* - visibilitychange
*/
(function() {
// Set up a global var. Sorry about this, old code ... old ways.
window.mod_scorm_is_window_closing = false;
var toggle = function() {
window.mod_scorm_is_window_closing = true;
};
// Listen to the four events known to represent an unload operation.
window.addEventListener('beforeunload', toggle);
window.addEventListener('unload', toggle);
window.addEventListener('pagehide', toggle);
window.addEventListener('visibilitychange', toggle);
})();
@@ -0,0 +1,51 @@
@mod @mod_scorm @_file_upload @_switch_iframe @_alert
Feature: Confirm progress gets saved on unload events
In order to let students access a scorm package
As a teacher
I need to add scorm activity to a course
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
@javascript
Scenario: Test progress gets saved correctly when the user navigates away from the scorm activity
Given I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
And I add a "SCORM package" to section "1"
And I set the following fields to these values:
| Name | Runtime Basic Calls SCORM 2004 3rd Edition package |
| Description | Description |
And I upload "mod/scorm/tests/packages/RuntimeBasicCalls_SCORM20043rdEdition.zip" file to "Package file" filemanager
And I click on "Save and display" "button"
And I should see "Runtime Basic Calls SCORM 2004 3rd Edition package"
And I log out
When I log in as "student1"
And I am on "Course 1" course homepage
And I follow "Runtime Basic Calls SCORM 2004 3rd Edition package"
Then I should see "Normal"
And I press "Enter"
And I switch to "scorm_object" iframe
And I press "Next"
And I press "Next"
And I switch to "contentFrame" iframe
And I should see "Scoring"
And I switch to the main frame
And I follow "C1"
And I follow "Runtime Basic Calls SCORM 2004 3rd Edition package"
And I should see "Normal"
And I click on "Enter" "button" confirming the dialogue
And I switch to "scorm_object" iframe
And I switch to "contentFrame" iframe
And I should see "Scoring"
And I switch to the main frame
# Go away from the scorm to stop background requests
And I am on homepage
+2 -2
View File
@@ -29,11 +29,11 @@
defined('MOODLE_INTERNAL') || die();
$version = 2018051710.00; // 20180517 = branching date YYYYMMDD - do not modify!
$version = 2018051711.00; // 20180517 = branching date YYYYMMDD - do not modify!
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
$release = '3.5.10 (Build: 20200113)'; // Human-friendly version name
$release = '3.5.11 (Build: 20200309)'; // Human-friendly version name
$branch = '35'; // This version's branch.
$maturity = MATURITY_STABLE; // This version's maturity level.