Compare commits

...

41 Commits

Author SHA1 Message Date
Huong Nguyen 1a1d59ef61 Moodle release 3.9.25 2023-12-08 09:21:37 +07:00
Sara Arjona a2c785e795 weekly release 3.9.24+ 2023-12-07 16:22:24 +01:00
Paul Holden 9badb9eab5 MDL-79980 mod_survey: remove responses link if user cannot access. 2023-12-07 12:10:16 +01:00
Brendan Heywood c142ad2370 MDL-80309 tasks: Clean up mtrace logs 2023-12-07 12:07:12 +08:00
Paul Holden 6ebae3663e MDL-79980 mod_survey: respect activity group mode getting report. 2023-12-07 09:12:26 +08:00
Stephan Robotta 39c6bfbfdb MDL-41465 reports: In separate group mode, limit to same group users 2023-12-06 13:47:54 +07:00
Huong Nguyen bcd17420a8 MDL-79759 repository_url: Remove types properties 2023-12-05 08:48:00 +07:00
David Woloszyn 8fcbfe5218 MDL-79759 repository_url: Limit css imports and remove fragments
There are checks to urls that attempt to limit recurrsion when
parse_file is called. This is problematic for css import urls that
can call an indefinite amount of nested import urls. An import limit
has been introduced to address this. Fragments have also been removed.
2023-12-04 15:07:36 +07:00
Stevani Andolo e1c1ff4be3 MDL-80174 tool_log: ensure "other" data is safely unserialized. 2023-12-04 13:42:39 +07:00
Paul Holden 031220a009 MDL-80268 badges: require capability to view awarded badges. 2023-12-04 10:16:24 +07:00
Paul Holden acb2ce417f MDL-79797 backup: ensure block config is safely unserialized. 2023-12-01 06:17:20 +01:00
Ilya Tregubov 89ec99bfa3 weekly release 3.9.24+ 2023-12-01 13:12:52 +08:00
Ilya Tregubov a30aece8ad Merge branch 'MDL-79603-39' of https://github.com/andelacruz/moodle into MOODLE_39_STABLE 2023-11-28 13:55:32 +08:00
Huong Nguyen 2d08bba8ab weekly release 3.9.24+ 2023-11-24 12:24:05 +07:00
Huong Nguyen 8c7fcbf590 Merge branch 'MDL-78496-39' of https://github.com/andrewnicols/moodle into MOODLE_39_STABLE 2023-11-23 09:24:45 +07:00
Andrew Nicols ea2f0af064 MDL-78496 environment: Moodle 4.4 requirements
The only changes to requirements since Moodle 4.3 are increases to:

* the minimum PHP version required; and
* the minimum required Moodle version.
2023-11-17 21:50:16 +08:00
Sara Arjona 1eae36faf6 weekly release 3.9.24+ 2023-11-16 16:23:03 +01:00
Sara Arjona fa60800431 Merge branch 'MDL-79981-39' of https://github.com/andrewnicols/moodle into MOODLE_39_STABLE 2023-11-15 17:13:32 +01:00
Angelia Dela Cruz e564206e33 MDL-79603 mod_scorm: Behat coverage for Scorm package display options 2023-11-15 13:16:29 +08:00
Ilya Tregubov c046a86fa5 weekly release 3.9.24+ 2023-11-10 09:17:37 +08:00
Ilya Tregubov c02f5c7391 Merge branch 'MDL-80029-39' of https://github.com/junpataleta/moodle into MOODLE_39_STABLE 2023-11-09 09:37:27 +08:00
Andrew Nicols aa335a7aba MDL-79981 core: Add ability to manually run and filter phpunit on GHA 2023-11-08 12:57:00 +08:00
Jun Pataleta 857507ce09 MDL-80029 grade: Behat test fixes
* Feature: We can use a minimum grade different than zero
  - Use correct minimum input values.
  - Use data generators for faster test execution.
* Scenario: Disable category overriding
  - Use the field name to ensure that the correct field is being tested.

Cherry-picked from MDL-79062.
2023-11-07 20:46:28 +08:00
Jun Pataleta f14576473d weekly release 3.9.24+ 2023-10-27 09:43:26 +08:00
Jun Pataleta 475702b9ef Merge branch 'MDL-79768-39-fix' of https://github.com/andelacruz/moodle into MOODLE_39_STABLE 2023-10-26 23:04:39 +08:00
Angelia Dela Cruz 9be29c8ded MDL-79768 fix: Fixed random failing Behat due to time difference 2023-10-26 17:23:08 +08:00
Jun Pataleta f574806a9a Merge branch 'MDL-79768-39' of https://github.com/andelacruz/moodle into MOODLE_39_STABLE 2023-10-26 10:40:46 +08:00
Angelia Dela Cruz 8622c37ecc MDL-79768 mod_data: Test for student can see upcoming data activity 2023-10-23 08:40:51 +08:00
Sara Arjona 0a4c70d7c2 weekly release 3.9.24+ 2023-10-20 12:16:49 +02:00
Huong Nguyen decc44fb26 Merge branch 'MDL-79689-39' of https://github.com/andelacruz/moodle into MOODLE_39_STABLE 2023-10-19 10:06:49 +07:00
Angelia Dela Cruz 151fd4caf9 MDL-79689 mod_assign: Behat for enabling anonymous submissions 2023-10-18 12:36:07 +08:00
Ilya Tregubov ca1da020ee Merge branch 'MDL-79691-39' of https://github.com/andelacruz/moodle into MOODLE_39_STABLE 2023-10-17 10:30:48 +08:00
Angelia Dela Cruz 4756608dbe MDL-79691 mod_h5pactivity: Behat coverage for H5P activity grades reset 2023-10-16 18:13:38 +08:00
Huong Nguyen 8176483e0a Merge branch 'MDL-79698-39' of https://github.com/andrewnicols/moodle into MOODLE_39_STABLE 2023-10-16 11:41:48 +07:00
Huong Nguyen 4fe25d925f Merge branch 'MDL-79653-39' of https://github.com/andelacruz/moodle into MOODLE_39_STABLE 2023-10-16 10:57:01 +07:00
Andrew Nicols f1a34c2ea0 MDL-79698 core: Correct duplicate array key in test provider 2023-10-13 15:44:56 +08:00
Ilya Tregubov b611b74e33 weekly release 3.9.24+ 2023-10-13 13:40:05 +08:00
Angelia Dela Cruz 6f6d548cfa MDL-79653 gradingform_guide: Behat coverage for deleting marking guide 2023-10-13 12:31:05 +08:00
Huong Nguyen ed0f8f1de4 Merge branch 'MDL-79638-39' of https://github.com/andrewnicols/moodle into MOODLE_39_STABLE 2023-10-12 11:09:37 +07:00
Andrew Nicols 09b8347175 MDL-79638 phpunit: Add unit test for increment resets 2023-10-11 00:10:33 +08:00
Andrew Nicols ab65333718 MDL-79638 phpunit: Remove mysql/mariadb hack
This hack was introduced to work around a bug in MySQL 5.6.14 and
MariaDB at the time.

https://bugs.mysql.com/bug.php?id=69882

It was addressed a few months later in 5.6.16, and 5.7.4.
MariaDB merged version 5.6.16 of MySQL's InnoDB engine in MariaDB
10.0.11 and got hte patch from there.

Moodle has required MariaDB 10.2.29 since Moodle 3.9 and it is therefore
safe to remove these hacks for MariaDB.
MySQL is also affected for version 5.7.0 >= MySQL < 5.7.4.
2023-10-11 00:05:55 +08:00
31 changed files with 936 additions and 104 deletions
+7 -1
View File
@@ -7,6 +7,12 @@ on:
- MOODLE_[0-9]+_STABLE
tags-ignore:
- v[0-9]+.[0-9]+.[0-9]+*
workflow_dispatch:
inputs:
phpunit_extra_options:
description: Additional options to apply to PHPUnit
required: false
default: ''
env:
php: 7.4
@@ -109,4 +115,4 @@ jobs:
- name: Running PHPUnit tests
env:
dbtype: ${{ matrix.db }}
run: vendor/bin/phpunit -v
run: vendor/bin/phpunit -v ${{ inputs.phpunit_extra_options }}
+191
View File
@@ -4305,4 +4305,195 @@
</CUSTOM_CHECK>
</CUSTOM_CHECKS>
</MOODLE>
<MOODLE version="4.4" requires="4.1.2">
<UNICODE level="required">
<FEEDBACK>
<ON_ERROR message="unicoderequired" />
</FEEDBACK>
</UNICODE>
<DATABASE level="required">
<VENDOR name="mariadb" version="10.6.7" />
<VENDOR name="mysql" version="8.0" />
<VENDOR name="postgres" version="13" />
<VENDOR name="mssql" version="14.0" />
<VENDOR name="oracle" version="19" />
</DATABASE>
<PHP version="8.1.0" level="required">
</PHP>
<PCREUNICODE level="optional">
<FEEDBACK>
<ON_CHECK message="pcreunicodewarning" />
</FEEDBACK>
</PCREUNICODE>
<PHP_EXTENSIONS>
<PHP_EXTENSION name="iconv" level="required">
<FEEDBACK>
<ON_ERROR message="iconvrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="mbstring" level="required">
<FEEDBACK>
<ON_ERROR message="mbstringrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="curl" level="required">
<FEEDBACK>
<ON_ERROR message="curlrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="openssl" level="required">
<FEEDBACK>
<ON_ERROR message="opensslrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="tokenizer" level="optional">
<FEEDBACK>
<ON_CHECK message="tokenizerrecommended" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="soap" level="optional">
<FEEDBACK>
<ON_CHECK message="soaprecommended" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="ctype" level="required">
<FEEDBACK>
<ON_ERROR message="ctyperequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="zip" level="required">
<FEEDBACK>
<ON_ERROR message="ziprequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="zlib" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="gd" level="required">
<FEEDBACK>
<ON_ERROR message="gdrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="simplexml" level="required">
<FEEDBACK>
<ON_ERROR message="simplexmlrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="spl" level="required">
<FEEDBACK>
<ON_ERROR message="splrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="pcre" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="dom" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="xml" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="xmlreader" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="intl" level="required">
<FEEDBACK>
<ON_ERROR message="intlrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="json" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="hash" level="required"/>
<PHP_EXTENSION name="fileinfo" level="required"/>
<PHP_EXTENSION name="sodium" level="required"/>
<PHP_EXTENSION name="exif" level="optional"/>
</PHP_EXTENSIONS>
<PHP_SETTINGS>
<PHP_SETTING name="memory_limit" value="96M" level="required">
<FEEDBACK>
<ON_ERROR message="settingmemorylimit" />
</FEEDBACK>
</PHP_SETTING>
<PHP_SETTING name="file_uploads" value="1" level="optional">
<FEEDBACK>
<ON_CHECK message="settingfileuploads" />
</FEEDBACK>
</PHP_SETTING>
<PHP_SETTING name="opcache.enable" value="1" level="optional">
<FEEDBACK>
<ON_CHECK message="opcacherecommended" />
</FEEDBACK>
</PHP_SETTING>
</PHP_SETTINGS>
<CUSTOM_CHECKS>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_storage_engine" level="required">
<FEEDBACK>
<ON_ERROR message="unsupporteddbstorageengine" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="question/engine/upgrade/upgradelib.php" function="quiz_attempts_upgraded" level="required">
<FEEDBACK>
<ON_ERROR message="quizattemptsupgradedmessage" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_slasharguments" level="optional">
<FEEDBACK>
<ON_CHECK message="slashargumentswarning" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_tables_row_format" level="optional">
<FEEDBACK>
<ON_CHECK message="unsupporteddbtablerowformat" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_unoconv_version" level="optional">
<FEEDBACK>
<ON_CHECK message="unoconvwarning" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_libcurl_version" level="optional">
<FEEDBACK>
<ON_CHECK message="libcurlwarning" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_format" level="required">
<FEEDBACK>
<ON_ERROR message="unsupporteddbfileformat" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_per_table" level="required">
<FEEDBACK>
<ON_ERROR message="unsupporteddbfilepertable" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_large_prefix" level="required">
<FEEDBACK>
<ON_ERROR message="unsupporteddblargeprefix" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_is_https" level="optional">
<FEEDBACK>
<ON_CHECK message="ishttpswarning" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_incomplete_unicode_support" level="optional">
<FEEDBACK>
<ON_CHECK message="incompleteunicodesupport" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_sixtyfour_bits" level="required">
<FEEDBACK>
<ON_ERROR message="sixtyfourbitsrequired" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_max_input_vars" level="optional">
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_admin_dir_usage" level="optional">
<FEEDBACK>
<ON_CHECK message="iscustomadminwarnings" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_xmlrpc_usage" level="optional">
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mod_assignment" level="required">
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_db_prefix_length" level="required">
</CUSTOM_CHECK>
</CUSTOM_CHECKS>
</MOODLE>
</COMPATIBILITY_MATRIX>
+1 -1
View File
@@ -75,7 +75,7 @@ trait reader {
*/
public static function decode_other(?string $other) {
if ($other === 'N;' || preg_match('~^.:~', $other)) {
return unserialize($other);
return unserialize($other, ['allowed_classes' => [stdClass::class]]);
} else {
return json_decode($other, true);
}
+10
View File
@@ -37,3 +37,13 @@ function tool_task_status_checks() : array {
];
}
/**
* Function used to handle mtrace by outputting the text to normal browser window.
*
* @param string $message Message to output
* @param string $eol End of line character
*/
function tool_task_mtrace_wrapper(string $message, string $eol): void {
$message = s($message);
echo $message . $eol;
}
+1 -10
View File
@@ -30,16 +30,6 @@ require('../../../config.php');
require_once($CFG->libdir.'/cronlib.php');
/**
* Function used to handle mtrace by outputting the text to normal browser window.
*
* @param string $message Message to output
* @param string $eol End of line character
*/
function tool_task_mtrace_wrapper($message, $eol) {
echo s($message . $eol);
}
// Allow execution of single task. This requires login and has different rules.
$taskname = required_param('task', PARAM_RAW_TRIMMED);
@@ -85,6 +75,7 @@ require_sesskey();
// Prepare to handle output via mtrace.
echo html_writer::start_tag('pre');
require('lib.php');
$CFG->mtrace_wrapper = 'tool_task_mtrace_wrapper';
// Run the specified task (this will output an error if it doesn't exist).
+7 -1
View File
@@ -1518,7 +1518,13 @@ class backup_block_instance_structure_step extends backup_structure_step {
// Transform configdata information if needed (process links and friends)
$blockrec = $DB->get_record('block_instances', array('id' => $this->task->get_blockid()));
if ($attrstotransform = $this->task->get_configdata_encoded_attributes()) {
$configdata = (array)unserialize(base64_decode($blockrec->configdata));
$configdata = array_filter(
(array) unserialize_object(base64_decode($blockrec->configdata)),
static function($value): bool {
return !($value instanceof __PHP_Incomplete_Class);
}
);
foreach ($configdata as $attribute => $value) {
if (in_array($attribute, $attrstotransform)) {
$configdata[$attribute] = $this->contenttransformer->process($value);
+7 -1
View File
@@ -4196,7 +4196,13 @@ class restore_block_instance_structure_step extends restore_structure_step {
// Let's look for anything within configdata neededing processing
// (nulls and uses of legacy file.php)
if ($attrstotransform = $this->task->get_configdata_encoded_attributes()) {
$configdata = (array) unserialize_object(base64_decode($data->configdata));
$configdata = array_filter(
(array) unserialize_object(base64_decode($data->configdata)),
static function($value): bool {
return !($value instanceof __PHP_Incomplete_Class);
}
);
foreach ($configdata as $attribute => $value) {
if (in_array($attribute, $attrstotransform)) {
$configdata[$attribute] = $this->contentprocessor->process_cdata($value);
+2
View File
@@ -54,6 +54,8 @@ $badge = new badge($badgeid);
$context = $badge->get_context();
$navurl = new moodle_url('/badges/index.php', array('type' => $badge->type));
require_capability('moodle/badges:viewawarded', $context);
if ($badge->type == BADGE_TYPE_COURSE) {
if (empty($CFG->badges_allowcoursebadges)) {
print_error('coursebadgesdisabled', 'badges');
+37
View File
@@ -1122,6 +1122,43 @@ class core_enrollib_testcase extends advanced_testcase {
$this->assertCount(1, enrol_get_course_users($course1->id, true));
}
/**
* test_course_users in groups
*
* @covers \enrol_get_course_users()
* @return void
*/
public function test_course_users_in_groups() {
$this->resetAfterTest();
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$user3 = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course();
$group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
$group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
$this->getDataGenerator()->enrol_user($user1->id, $course->id);
$this->getDataGenerator()->enrol_user($user2->id, $course->id);
$this->getDataGenerator()->enrol_user($user3->id, $course->id);
$this->getDataGenerator()->create_group_member(['groupid' => $group1->id, 'userid' => $user1->id]);
$this->getDataGenerator()->create_group_member(['groupid' => $group2->id, 'userid' => $user1->id]);
$this->getDataGenerator()->create_group_member(['groupid' => $group2->id, 'userid' => $user2->id]);
$this->assertCount(3, enrol_get_course_users($course->id));
$this->assertCount(1, enrol_get_course_users($course->id, false, [], [], [$group1->id]));
$this->assertCount(2, enrol_get_course_users($course->id, false, [], [], [$group2->id]));
$instances = enrol_get_instances($course->id, true);
$manualinstance = reset($instances);
$manualplugin = enrol_get_plugin('manual');
$manualplugin->update_user_enrol($manualinstance, $user1->id, ENROL_USER_SUSPENDED);
$this->assertCount(2, enrol_get_course_users($course->id, false, [], [], [$group2->id]));
$this->assertCount(1, enrol_get_course_users($course->id, true, [], [], [$group2->id]));
}
/**
* Test count of enrolled users
*
@@ -0,0 +1,41 @@
@gradingform @gradingform_guide
Feature: Teacher can delete marking guide
As a teacher,
I should be able to delete a marking guide
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | One | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "activities" exist:
| activity | course | name | advancedgradingmethod_submissions |
| assign | C1 | Assign 1 | guide |
And I am on the "Course 1" course page logged in as teacher1
And I go to "Assign 1" advanced grading definition page
And I set the following fields to these values:
| Name | Marking guide 1 |
And I define the following marking guide:
| Criterion name | Description for students | Description for markers | Maximum score |
| Criterion 1 | Criterion 1 description for student | Criterion 1 description for markers | 100 |
And I press "Save marking guide and make it ready"
Scenario: Delete a marking guide
Given I am on the "Course 1" course page
And I go to "Assign 1" advanced grading page
When I click on "Delete the currently defined form" "link"
And I press "Cancel"
# Confirm that marking guide was not deleted if Cancel is pressed
Then I should see "Marking guide 1 Ready for use"
And I should see "Criterion 1"
And I click on "Delete the currently defined form" "link"
And I press "Continue"
# Confirm that marking guide was deleted successfully if Continue is pressed
And I should see "Please note: the advanced grading form is not ready at the moment. Simple grading method will be used until the form has a valid status."
And I should not see "Marking guide 1 Ready for use"
And I should not see "Criterion 1"
+4 -4
View File
@@ -43,9 +43,9 @@ Feature: Site settings can be used to hide parts of the gradebook UI
@javascript
Scenario: Disable category overriding
And "tr .course input[type='text']" "css_element" should exist
Then I navigate to "Grades > Grade category settings" in site administration
Given "Student 1 Course total" "field" should exist
And I navigate to "Grades > Grade category settings" in site administration
And I set the field "Allow category grades to be manually overridden" to "0"
And I press "Save changes"
And I am on the "Course 1" "grades > Grader report > View" page
And "tr .course input[type='text']" "css_element" should not exist
When I am on the "Course 1" "grades > Grader report > View" page
Then "Student 1 Course total" "field" should not exist
+16 -49
View File
@@ -19,50 +19,23 @@ Feature: We can use a minimum grade different than zero
| student1 | C1 | student |
| student2 | C1 | student |
And the following "grade categories" exist:
| fullname | course |
| Sub category 1 | C1 |
| Sub category 2 | C1 |
| fullname | course | aggregateonlygraded |
| Sub category 1 | C1 | 0 |
| Sub category 2 | C1 | 0 |
And the following "grade items" exist:
| itemname | grademin | course |
| Manual item 1 | -100 | C1 |
| Manual item 2 | 50 | C1 |
And the following "grade items" exist:
| itemname | grademin | grademax | course | gradecategory |
| Manual item 3 | -100 | 50 | C1 | Sub category 1 |
And the following "grade items" exist:
| itemname | grademin | course | gradecategory |
| Manual item 4 | -100 | C1 | Sub category 1 |
| Manual item 5 | 50 | C1 | Sub category 2 |
| Manual item 6 | 50 | C1 | Sub category 2 |
And I log in as "admin"
And I set the following administration settings values:
| grade_aggregations_visible | Mean of grades,Weighted mean of grades,Simple weighted mean of grades,Mean of grades (with extra credits),Median of grades,Lowest grade,Highest grade,Mode of grades,Natural |
And I am on the "Course 1" "grades > gradebook setup" page
And I press "Add grade item"
And I set the following fields to these values:
| Item name | Manual item 1 |
| Minimum grade | -100 |
| Grade category | Course 1 |
And I press "Save changes"
And I press "Add grade item"
And I set the following fields to these values:
| Item name | Manual item 2 |
| Minimum grade | 50 |
| Grade category | Course 1 |
And I press "Save changes"
And I press "Add grade item"
And I set the following fields to these values:
| Item name | Manual item 3 |
| Maximum grade | 50 |
| Minimum grade | -100 |
| Grade category | Sub category 1 |
And I press "Save changes"
And I press "Add grade item"
And I set the following fields to these values:
| Item name | Manual item 4 |
| Minimum grade | -100 |
| Grade category | Sub category 1 |
And I press "Save changes"
And I press "Add grade item"
And I set the following fields to these values:
| Item name | Manual item 5 |
| Minimum grade | 50 |
| Grade category | Sub category 2 |
And I press "Save changes"
And I press "Add grade item"
And I set the following fields to these values:
| Item name | Manual item 6 |
| Minimum grade | 50 |
| Grade category | Sub category 2 |
And I press "Save changes"
And I navigate to "Setup > Course grade settings" in the course gradebook
And I set the field "Show weightings" to "Show"
And I set the field "Show contribution to course total" to "Show"
@@ -70,13 +43,7 @@ Feature: We can use a minimum grade different than zero
@javascript
Scenario: Natural aggregation with negative and positive grade
And I navigate to "Setup > Gradebook setup" in the course gradebook
And I set the following settings for grade item "Sub category 1":
| Aggregation | Natural |
| Exclude empty grades | 0 |
And I set the following settings for grade item "Sub category 2":
| Aggregation | Natural |
| Exclude empty grades | 0 |
Given I navigate to "Setup > Gradebook setup" in the course gradebook
And I set the following settings for grade item "Course 1":
| Aggregation | Natural |
| Exclude empty grades | 0 |
+30 -1
View File
@@ -965,9 +965,38 @@ class manager {
$command = "{$phpbinary} {$scriptpath} {$taskarg}";
// Execute it.
passthru($command);
self::passthru_via_mtrace($command);
}
return true;
}
/**
* This behaves similar to passthru but filters every line via
* the mtrace function so it can be post processed.
*
* @param string $command to run
* @return void
*/
public static function passthru_via_mtrace(string $command) {
$descriptorspec = [
0 => ['pipe', 'r'], // STDIN.
1 => ['pipe', 'w'], // STDOUT.
2 => ['pipe', 'w'], // STDERR.
];
flush();
$process = proc_open($command, $descriptorspec, $pipes, realpath('./'), []);
if (is_resource($process)) {
while ($s = fgets($pipes[1])) {
mtrace($s, '');
flush();
}
}
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
}
}
+13 -1
View File
@@ -1740,9 +1740,11 @@ function enrol_get_course_by_user_enrolment_id($ueid) {
* @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
* @param array $usersfilter Limit the results obtained to this list of user ids. $uefilter compatibility not guaranteed.
* @param array $uefilter Limit the results obtained to this list of user enrolment ids. $usersfilter compatibility not guaranteed.
* @param array $usergroups Limit the results of users to the ones that belong to one of the submitted group ids.
* @return stdClass[]
*/
function enrol_get_course_users($courseid = false, $onlyactive = false, $usersfilter = array(), $uefilter = array()) {
function enrol_get_course_users($courseid = false, $onlyactive = false, $usersfilter = [], $uefilter = [],
$usergroups = []) {
global $DB;
if (!$courseid && !$usersfilter && !$uefilter) {
@@ -1785,6 +1787,16 @@ function enrol_get_course_users($courseid = false, $onlyactive = false, $usersfi
$params = $params + $ueparams;
}
// Only select enrolled users that belong to a specific group(s).
if (!empty($usergroups)) {
$usergroups = array_map(function ($item) { // Sanitize groupid to int to be save for sql.
return (int)$item;
}, $usergroups);
list($ugsql, $ugparams) = $DB->get_in_or_equal($usergroups, SQL_PARAMS_NAMED);
$conditions[] = 'ue.userid IN (SELECT userid FROM {groups_members} WHERE groupid ' . $ugsql . ')';
$params = $params + $ugparams;
}
return $DB->get_records_sql($sql . ' ' . implode(' AND ', $conditions), $params);
}
+11 -12
View File
@@ -643,20 +643,19 @@ abstract class testing_util {
$borkedmysql = false;
if ($DB->get_dbfamily() === 'mysql') {
$version = $DB->get_server_info();
if (version_compare($version['version'], '5.6.0') == 1 and version_compare($version['version'], '5.6.16') == -1) {
// Everything that comes from Oracle is evil!
//
// See http://dev.mysql.com/doc/refman/5.6/en/alter-table.html
// You cannot reset the counter to a value less than or equal to to the value that is currently in use.
//
// From 5.6.16 release notes:
// InnoDB: The ALTER TABLE INPLACE algorithm would fail to decrease the auto-increment value.
// (Bug #17250787, Bug #69882)
// Everything that comes from Oracle is evil!
//
// See http://dev.mysql.com/doc/refman/5.6/en/alter-table.html
// You cannot reset the counter to a value less than or equal to to the value that is currently in use.
//
// From 5.6.16 release notes:
// InnoDB: The ALTER TABLE INPLACE algorithm would fail to decrease the auto-increment value.
// (Bug #17250787, Bug #69882)
if (version_compare($version['version'], '5.6.0', '>=') && version_compare($version['version'], '5.6.16', '<')) {
$borkedmysql = true;
}
} else if (version_compare($version['version'], '10.0.0') == 1) {
// And MariaDB is no better!
// Let's hope they pick the patch sometime later...
if (version_compare($version['version'], '5.7.0', '>=') && version_compare($version['version'], '5.7.4', '<')) {
$borkedmysql = true;
}
}
+85
View File
@@ -0,0 +1,85 @@
<?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\testing;
/**
* Testing util tests.
*
* @package core
* @category phpunit
* @copyright 2023 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class util_test extends \advanced_testcase {
/**
* Note: This test is required for the other two parts because the first time
* a table is written to it may not have had the initial value reset.
*
* @coversNothing
*/
public function test_increment_reset_part_one(): void {
global $DB;
switch ($DB->get_dbfamily()) {
case 'mssql':
$this->markTestSkipped('MSSQL does not support sequences');
return;
case 'mysql':
$version = $DB->get_server_info();
if (version_compare($version['version'], '5.7.4', '<')) {
return;
}
}
$this->resetAfterTest();
$DB->insert_record('config_plugins', [
'plugin' => 'example',
'name' => 'test_increment',
'value' => 0,
]);
}
/**
* @coversNothing
* @depends test_increment_reset_part_one
*/
public function test_increment_reset_part_two(): int {
global $DB;
$this->resetAfterTest();
return $DB->insert_record('config_plugins', [
'plugin' => 'example',
'name' => 'test_increment',
'value' => 0,
]);
}
/**
* @depends test_increment_reset_part_two
*/
public function test_increment_reset_part_three(int $previousid): void {
global $DB;
$this->resetAfterTest();
$id = $DB->insert_record('config_plugins', [
'plugin' => 'example',
'name' => 'test_increment',
'value' => 0,
]);
$this->assertEquals($previousid, $id);
}
}
+1 -1
View File
@@ -762,7 +762,7 @@ class core_component_testcase extends advanced_testcase {
'separators' => ['\\'],
'result' => $CFG->dirroot . "/test/src/Multiple/Namespaces.php",
],
'Getting a file with multiple namespaces' => [
'Getting a file with multiple namespaces (non-existent)' => [
'classname' => 'Nonexistant\\Namespace\\Test',
'prefix' => "Test",
'path' => 'test/src',
+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.9.25 ===
* In enrollib.php, the method enrol_get_course_users() got an optional 5th parameter $usergroups that
defaults to an empty array. Here, user group ids can be provided, to select enrolled users in a course
that are also members of these groups.
=== 3.9.24 ===
* New Behat step \behat_general::the_page_title_should_contain() has been added to allow checking of page titles. You can use this
when writing feature files to check that the page title contains the expected string.
@@ -0,0 +1,47 @@
@mod @mod_assign
Feature: Teacher can enable anonymous submissions for an assignment
In order to make an anonymous submission to an assignment
As a teacher
I should be able to enable anonymous submissions
Background:
Given the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | One | teacher1@example.com |
| student1 | Student | One | student1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activities" exist:
| activity | course | name | assignsubmission_onlinetext_enabled | blindmarking |
| assign | C1 | Assign 1 | 1 | 1 |
@javascript
Scenario: Teacher can enable anonymous submissions
# Submit an assignment as student1
Given the following "mod_assign > submissions" exist:
| assign | user | onlinetext |
| Assign 1 | student1 | I'm the student's first submission |
When I am on the "Assign 1" "assign activity editing" page logged in as teacher1
# Confirm that anonymous submissions can't be changed to no anymore
Then "blindmarking" "select" should not exist
And I press "Cancel"
And I am on the "Assign 1" "assign activity" page
And I navigate to "View all submissions" in current page administration
# Confirm that Participant [n] is displayed instead of Student One - student name is hidden
And I should not see "Student One" in the "Participant" "table_row"
And I click on "Grade" "link" in the "Participant" "table_row"
And I set the field "Grade out of 100" to "70"
And I press "Save changes"
And I am on the "Assign 1" "assign activity" page
And I navigate to "View all submissions" in current page administration
And I select "Reveal student identities" from the "Grading action" singleselect
And I should see "Are you sure you want to reveal student identities for this assignment? This operation cannot be undone. Once the student identities have been revealed, the marks will be released to the gradebook."
And I press "Continue"
# Confirm that student identity is no longer hidden and grade is retained
And I should not see "Participant" in the "Student One" "table_row"
And I should see "70.00" in the "Student One" "table_row"
@@ -0,0 +1,40 @@
@mod @mod_data
Feature: Students can view upcoming data activities in the timeline block
In order for student to see upcoming data activities in timeline block
As a teacher
I should be able to set the availability dates of data activities
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | One | student1@example.com |
And the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
@javascript
Scenario Outline: Student can view upcoming data activities in the timeline block
Given the following "activities" exist:
| activity | course | name | id_timeavailablefrom_enabled | timeavailablefrom | id_timeavailableto_enabled | timeavailableto |
| data | C1 | DB Past | 1 | <pastfrom> | 1 | <pastto> |
| data | C1 | DB Future | 1 | <futurefrom> | 1 | <futureto> |
| data | C1 | DB No date | 0 | | 0 | |
# Confirm that student can see future but not past db activity in the timeline block
When I log in as "student 1"
Then I should not see "DB Past" in the "Timeline" "block"
# Also confirm that student can't see db activity where availability is disabled
And I should not see "DB No Date" in the "Timeline" "block"
And I should see "DB Future" in the "Timeline" "block"
# Confirm link works and redirects to db activity
And I click on "DB Future" "link" in the "Timeline" "block"
And I should see "Sorry, this activity is not available until"
And I should see "<futurefrom>%A, %d %B %Y, %I:%M##"
Examples:
| pastfrom | pastto | futurefrom | futureto |
| ##1 month ago## | ##yesterday## | ##tomorrow## | ##tomorrow +1day## |
| ##yesterday## | ##yesterday +3hours## | ##tomorrow noon## | ##tomorrow noon +3hours## |
| ##6 months ago## | ##1 week ago## | ##tomorrow +5days## | ##tomorrow +6days## |
@@ -0,0 +1,51 @@
@mod @mod_h5pactivity @core_h5p
Feature: Teacher can reset H5P activity grades
As a teacher,
I should be able to reset H5P activity grades
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | One | teacher1@example.com |
| student1 | First | Student | student1@example.com |
| student2 | Second | Student | student2@example.com |
| student3 | Third | Student | student3@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 |
| student2 | C1 | student |
| student3 | C1 | student |
And the following "activities" exist:
| activity | course | name | grade[modgrade_type] | grade[modgrade_point] |
| h5pactivity | C1 | H5P Activity | point | 10 |
@javascript
Scenario:Teacher can reset H5P activity grades
Given I am on the "Course 1" course page logged in as teacher1
And I navigate to "View > Grader report" in the course gradebook
And I turn editing mode on
And I give the grade "7" to the user "First Student" for the grade item "H5P Activity"
And I give the grade "5" to the user "Second Student" for the grade item "H5P Activity"
And I give the grade "0" to the user "Third Student" for the grade item "H5P Activity"
And I press "Save changes"
# Confirm that grade was sucessfully saved
And I turn editing mode off
And I should see "7.00" in the "First Student" "table_row"
And I should see "5.00" in the "Second Student" "table_row"
And I should see "0.00" in the "Third Student" "table_row"
And I am on "Course 1" course homepage
# Check `Delete all grades` in course reset page to reset grades
When I navigate to "Reset" in current page administration
And I set the following fields to these values:
| Delete all grades | 1 |
And I press "Reset course"
And I press "Continue"
# Confirm that previously saved grades are gone
And I navigate to "View > Grader report" in the course gradebook
Then I should not see "7.00" in the "First Student" "table_row"
And I should not see "5.00" in the "Second Student" "table_row"
And I should not see "0.00" in the "Third Student" "table_row"
@@ -0,0 +1,60 @@
@mod @mod_scorm
Feature: Scorm display options
In order to set how Scorm is displayed
As a teacher
I need to be able to choose from Scorm package display options
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | One | teacher1@example.com |
| student1 | Student | One | student1@example.com |
And the following "courses" exist:
| fullname | shortname | format | activitytype |
| Course 1 | C1 | topics | |
| Course 2 | C2 | singleactivity | scorm |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
| teacher1 | C2 | editingteacher |
| student1 | C2 | student |
@javascript
Scenario Outline: Teacher can change to various Scorm package display options
Given the following "activities" exist:
| activity | course | name | packagefilepath | hidetoc | nav |
| scorm | C1 | C1 Scorm 1 | mod/scorm/tests/packages/RuntimeMinimumCalls_SCORM12.zip | <toc> | <shownavigation> |
And I am on the "C1 Scorm 1" "scorm activity" page logged in as teacher1
When I click on "Preview" "radio"
And I press "Enter"
# Confirm TOC display
# Used css_element to check > and < button display in TOC since similar buttons also exist in navigation
Then I <tocdisplay> see "Golf Explained - Minimum Run-time Calls"
And "[title='Show']" "css_element" <showdisplay> exist
And "[title='Hide']" "css_element" <hidedisplay> exist
# Confirm TOC dropdown display
And "scoid" "select" <tocdropdown> exist
# Confirm the navigation display
And "[id='scorm_nav']" "css_element" <navbar> exist
Examples:
| toc | shownavigation | tocdisplay | showdisplay | hidedisplay | tocdropdown | navbar |
| 1 | | should not | should | should not | should not | should not |
| 2 | 1 | should | should | should not | should | should not |
| 0 | 0 | should | should not | should | should not | should not |
| 0 | 1 | should | should not | should | should not | should |
Scenario: Student can exit Scorm activity in single activity course format
Given the following "activities" exist:
| activity | course | name | packagefilepath | popup |
| scorm | C2 | C2 Scorm 1 | mod/scorm/tests/packages/RuntimeMinimumCalls_SCORM12.zip | 0 |
And I am on the "C2 Scorm 1" "scorm activity" page logged in as student1
And I press "Enter"
When I click on "Exit activity" "link"
# Confirm that student can exit activity
Then "Preview" "radio" should exist
And "Normal" "radio" should exist
And "Enter" "button" should exist
And "Exit activity" "link" should not exist
And I should not see "Golf Explained - Minimum Run-time Calls"
+10 -1
View File
@@ -808,7 +808,16 @@ function survey_supports($feature) {
function survey_extend_settings_navigation($settings, $surveynode) {
global $PAGE;
if (has_capability('mod/survey:readresponses', $PAGE->cm->context)) {
$cm = get_coursemodule_from_id('survey', $PAGE->cm->id);
$context = context_module::instance($cm->id);
// Check to see if groups are being used in this survey, confirm user can access.
$groupmode = groups_get_activity_groupmode($cm);
$currentgroup = groups_get_activity_group($cm, true);
if (has_capability('mod/survey:readresponses', $context) &&
!($currentgroup === 0 && $groupmode == SEPARATEGROUPS && !has_capability('moodle/site:accessallgroups', $context))) {
$responsesnode = $surveynode->add(get_string("responsereports", "survey"));
$url = new moodle_url('/mod/survey/report.php', array('id' => $PAGE->cm->id, 'action'=>'summary'));
+11 -3
View File
@@ -121,9 +121,16 @@
echo $OUTPUT->heading(format_string($survey->name));
/// Check to see if groups are being used in this survey
if ($groupmode = groups_get_activity_groupmode($cm)) { // Groups are being used
$groupmode = groups_get_activity_groupmode($cm);
if ($groupmode != NOGROUPS) {
$menuaction = $action == "student" ? "students" : $action;
// Get the current activity group, confirm user can access.
$currentgroup = groups_get_activity_group($cm, true);
if ($currentgroup === 0 && $groupmode == SEPARATEGROUPS && !has_capability('moodle/site:accessallgroups', $context)) {
throw new moodle_exception('notingroup');
}
groups_print_activity_menu($cm, $CFG->wwwroot . "/mod/survey/report.php?id=$cm->id&amp;action=$menuaction&amp;qid=$qid");
} else {
$currentgroup = 0;
@@ -378,8 +385,9 @@
break;
case "student":
if (!$user = $DB->get_record("user", array("id"=>$student))) {
print_error('invaliduserid');
$user = core_user::get_user($student, '*', MUST_EXIST);
if ($currentgroup && !array_key_exists($user->id, $users)) {
throw new moodle_exception('usernotavailable', 'error');
}
echo $OUTPUT->heading(get_string("analysisof", "survey", fullname($user)), 3);
+4 -1
View File
@@ -83,7 +83,10 @@ if (has_capability('mod/survey:readresponses', $context) or ($groupmode == VISIB
$currentgroup = 0;
}
if (has_capability('mod/survey:readresponses', $context)) {
$currentactivitygroup = groups_get_activity_group($cm, true);
if (has_capability('mod/survey:readresponses', $context) &&
!($currentactivitygroup === 0 && $groupmode == SEPARATEGROUPS && !has_capability('moodle/site:accessallgroups', $context))) {
$numusers = survey_count_responses($survey->id, $currentgroup, $groupingid);
echo "<div class=\"reportlink\"><a href=\"report.php?id=$cm->id\">".
get_string("viewsurveyresponses", "survey", $numusers)."</a></div>";
+46 -12
View File
@@ -93,6 +93,9 @@ class report_log_renderable implements renderable {
/** @var table_log table log which will be used for rendering logs */
public $tablelog;
/** @var array group ids */
public $grouplist;
/**
* Constructor.
*
@@ -343,30 +346,41 @@ class report_log_renderable implements renderable {
}
/**
* Return list of groups.
* Return list of groups that are used in this course. This is done when groups are used in the course
* and the user is allowed to see all groups or groups are visible anyway. If groups are used but the
* mode is separate groups and the user is not allowed to see all groups, the list contains the groups
* only, where the user is member.
* If the course uses no groups, the list is empty.
*
* @return array list of groups.
*/
public function get_group_list() {
global $USER;
if ($this->grouplist !== null) {
return $this->grouplist;
}
// No groups for system.
if (empty($this->course)) {
return array();
$this->grouplist = [];
return $this->grouplist;
}
$context = context_course::instance($this->course->id);
$groups = array();
$this->grouplist = [];
$groupmode = groups_get_course_groupmode($this->course);
$cgroups = [];
if (($groupmode == VISIBLEGROUPS) ||
($groupmode == SEPARATEGROUPS and has_capability('moodle/site:accessallgroups', $context))) {
// Get all groups.
if ($cgroups = groups_get_all_groups($this->course->id)) {
foreach ($cgroups as $cgroup) {
$groups[$cgroup->id] = $cgroup->name;
}
}
($groupmode == SEPARATEGROUPS && has_capability('moodle/site:accessallgroups', $context))) {
$cgroups = groups_get_all_groups($this->course->id);
} else if ($groupmode == SEPARATEGROUPS && !has_capability('moodle/site:accessallgroups', $context)) {
$cgroups = groups_get_all_groups($this->course->id, $USER->id);
}
return $groups;
foreach ($cgroups as $cgroup) {
$this->grouplist[$cgroup->id] = $cgroup->name;
}
return $this->grouplist;
}
/**
@@ -384,8 +398,28 @@ class report_log_renderable implements renderable {
$context = context_course::instance($courseid);
$limitfrom = empty($this->showusers) ? 0 : '';
$limitnum = empty($this->showusers) ? COURSE_MAX_USERS_PER_DROPDOWN + 1 : '';
$courseusers = get_enrolled_users($context, '', $this->groupid, 'u.id, ' . get_all_user_name_fields(true, 'u'),
// Get the groups of that course.
$groups = $this->get_group_list();
// Check here if we are not in group mode, or in group mode but narrow the group selection
// to the group of the user.
if (empty($groups) || !empty($this->groupid) && isset($groups[(int)$this->groupid])) {
// No groups are used in that course, therefore get all users (maybe limited to one group).
$courseusers = get_enrolled_users($context, '', $this->groupid, 'u.id, ' . get_all_user_name_fields(true, 'u'),
null, $limitfrom, $limitnum);
} else {
// The course uses groups, get the users from these groups.
$groupids = array_keys($groups);
try {
$enrolments = enrol_get_course_users($courseid, false, [], [], $groupids);
$courseusers = [];
foreach ($enrolments as $enrolment) {
$courseusers[$enrolment->id] = $enrolment;
}
} catch (Exception $e) {
$courseusers = [];
}
}
if (count($courseusers) < COURSE_MAX_USERS_PER_DROPDOWN && !$this->showusers) {
$this->showusers = 1;
+158
View File
@@ -0,0 +1,158 @@
<?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 report_log;
/**
* Class report_log\renderable_test to cover functions in \report_log_renderable.
*
* @package report_log
* @copyright 2023 Stephan Robotta <stephan.robotta@bfh.ch>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
*/
class renderable_test extends \advanced_testcase {
/**
* @var [stdClass] The students.
*/
private $student = [];
/**
* @var [stdClass] The teachers.
*/
private $teacher = [];
/**
* @var [stdClass] The groups.
*/
private $group = [];
/**
* @var stdClass The course.
*/
private $course;
/**
* Set up a course with two groups, three students being each in one of the groups,
* two teachers each in either group while the second teacher is also member of the other group.
* @return void
* @throws \coding_exception
*/
public function setUp(): void {
global $PAGE;
$this->course = $this->getDataGenerator()->create_course(['groupmode' => 1]);
$this->group[] = $this->getDataGenerator()->create_group(['courseid' => $this->course->id]);
$this->group[] = $this->getDataGenerator()->create_group(['courseid' => $this->course->id]);
for ($i = 0; $i < 3; $i++) {
$this->student[$i] = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($this->student[$i]->id, $this->course->id, 'student');
$this->getDataGenerator()->create_group_member([
'groupid' => $this->group[$i % 2]->id,
'userid' => $this->student[$i]->id,
]);
}
for ($i = 0; $i < 2; $i++) {
$this->teacher[$i] = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($this->teacher[$i]->id, $this->course->id, 'editingteacher');
$this->getDataGenerator()->create_group_member([
'groupid' => $this->group[$i]->id,
'userid' => $this->teacher[$i]->id,
]);
}
// Make teacher2 also member of group1.
$this->getDataGenerator()->create_group_member([
'groupid' => $this->group[0]->id,
'userid' => $this->teacher[1]->id,
]);
$PAGE->set_url('/report/log/index.php?id=' . $this->course->id);
$this->resetAfterTest();
}
/**
* Test report_log_renderable::get_user_list().
* @covers \report_log_renderable::get_user_list
* @return void
*/
public function test_get_user_list() {
// Fetch all users of group 1 and the guest user.
$userids = $this->fetch_users_from_renderable((int)$this->student[0]->id);
$this->assertCount(5, $userids);
$this->assertContains((int)$this->student[0]->id, $userids); // His own group (group 1).
$this->assertNotContains((int)$this->student[1]->id, $userids); // He is in group 2.
$this->assertContains((int)$this->teacher[0]->id, $userids); // He is in group 1.
$this->assertContains((int)$this->teacher[1]->id, $userids); // He is in both groups.
// Fetch users of all groups and the guest user. The teacher has the capability moodle/site:accessallgroups.
$this->setUser($this->teacher[1]->id);
$renderable = new \report_log_renderable("", (int)$this->course->id, $this->teacher[1]->id);
$users = $renderable->get_user_list();
$this->assertCount(6, $users);
// Fetch users of group 2 and the guest user.
$userids = $this->fetch_users_from_renderable((int)$this->student[1]->id);
$this->assertCount( 3, $userids);
$this->assertNotContains((int)$this->student[0]->id, $userids);
$this->assertContains((int)$this->student[1]->id, $userids);
$this->assertNotContains((int)$this->teacher[0]->id, $userids);
$this->assertContains((int)$this->teacher[1]->id, $userids);
// Fetch users of group 2 and test user as teacher2 but limited to his group.
$userids = $this->fetch_users_from_renderable((int)$this->teacher[1]->id, (int)$this->group[1]->id);
$this->assertCount( 3, $userids);
$this->assertNotContains((int)$this->student[0]->id, $userids);
$this->assertContains((int)$this->student[1]->id, $userids);
$this->assertNotContains((int)$this->teacher[0]->id, $userids);
$this->assertContains((int)$this->teacher[1]->id, $userids);
}
/**
* Helper function to return a list of user ids from the renderable object.
* @param int $userid
* @param ?int $groupid
* @return array
*/
protected function fetch_users_from_renderable(int $userid, ?int $groupid = 0): array {
$this->setUser($userid);
$renderable = new \report_log_renderable(
"", (int)$this->course->id, $userid, 0, '', $groupid);
$users = $renderable->get_user_list();
return \array_keys($users);
}
/**
* Test report_log_renderable::get_group_list().
* @covers \report_log_renderable::get_group_list
* @return void
*/
public function test_get_group_list() {
// The student sees his own group only.
$this->setUser($this->student[0]->id);
$renderable = new \report_log_renderable("", (int)$this->course->id, $this->student[0]->id);
$groups = $renderable->get_group_list();
$this->assertCount(1, $groups);
// While the teacher is allowed to see all groups.
$this->setUser($this->teacher[0]->id);
$renderable = new \report_log_renderable("", (int)$this->course->id, $this->teacher[0]->id);
$groups = $renderable->get_group_list();
$this->assertCount(2, $groups);
}
}
+11
View File
@@ -168,6 +168,8 @@ class report_loglive_renderable implements renderable {
* @return stdClass filters
*/
protected function setup_filters() {
global $USER;
$readers = $this->get_readers();
// Set up filters.
@@ -178,6 +180,15 @@ class report_loglive_renderable implements renderable {
if (!has_capability('moodle/site:viewanonymousevents', $context)) {
$filter->anonymous = 0;
}
if (groups_get_course_groupmode($this->course) == SEPARATEGROUPS &&
!has_capability('moodle/site:accessallgroups', $context)) {
if ($cgroups = groups_get_all_groups($this->course->id, $USER->id)) {
$filter->groups = [];
foreach ($cgroups as $cgroup) {
$filter->groups[] = (int)$cgroup->id;
}
}
}
} else {
$filter->courseid = 0;
}
+16 -1
View File
@@ -56,6 +56,7 @@ class report_loglive_table_log extends table_sql {
* - int userid: user id
* - int|string modid: Module id or "site_errors" to view site errors
* - int groupid: Group id
* - array groups: List of group ids
* - \core\log\sql_reader logreader: reader from which data will be fetched.
* - int edulevel: educational level.
* - string action: view action
@@ -304,14 +305,29 @@ class report_loglive_table_log extends table_sql {
* @param bool $useinitialsbar do you want to use the initials bar.
*/
public function query_db($pagesize, $useinitialsbar = true) {
global $DB;
$joins = array();
$params = array();
// Set up filtering.
if (!empty($this->filterparams->courseid)) {
// For a normal course, set the course filter.
$joins[] = "courseid = :courseid";
$params['courseid'] = $this->filterparams->courseid;
// If we have a course, then check if the groups filter is set.
if ($this->filterparams->courseid != SITEID && !empty($this->filterparams->groups)) {
// If that's the case, limit the users to be in the groups only, defined by the filter.
$useringroups = [];
foreach ($this->filterparams->groups as $groupid) {
$gusers = groups_get_members($groupid, 'u.id');
$useringroups = array_merge($useringroups, array_keys($gusers));
}
$useringroups = array_unique($useringroups);
list($ugsql, $ugparams) = $DB->get_in_or_equal($useringroups, SQL_PARAMS_NAMED);
$joins[] = 'userid ' . $ugsql;
$params = array_merge($params, $ugparams);
}
}
if (!empty($this->filterparams->date)) {
@@ -323,7 +339,6 @@ class report_loglive_table_log extends table_sql {
$joins[] = "anonymous = :anon";
$params['anon'] = $this->filterparams->anonymous;
}
$selector = implode(' AND ', $joins);
$total = $this->filterparams->logreader->get_events_select_count($selector, $params);
+11 -2
View File
@@ -38,6 +38,10 @@ require_once(__DIR__.'/locallib.php');
class repository_url extends repository {
/** @var int Maximum time of recursion. */
const MAX_RECURSION_TIME = 5;
/** @var int Maximum number of CSS imports. */
const MAX_CSS_IMPORTS = 10;
/** @var int CSS import counter. */
var $cssimportcounter = 0;
var $processedfiles = array();
/** @var int Recursion counter. */
var $recursioncounter = 0;
@@ -134,8 +138,8 @@ EOD;
// Avoid endless recursion for the same URL with same parameters.
return;
}
// Remove the query string before check.
$recursioncheckurl = preg_replace('/\?.*/', '', $url);
// Remove the query string and anchors before check.
$recursioncheckurl = (new moodle_url($url))->out_omit_querystring();
if (in_array($recursioncheckurl, $this->processedfiles)) {
$this->recursioncounter++;
}
@@ -200,6 +204,11 @@ EOD;
}
if (!empty($urls['import'])) {
foreach ($urls['import'] as $cssurl) {
// Limit the number of CSS imports to avoid infinite imports.
if ($this->cssimportcounter >= self::MAX_CSS_IMPORTS) {
return;
}
$this->cssimportcounter++;
$this->parse_file($info['url'], $cssurl, $list);
}
}
+2 -2
View File
@@ -29,9 +29,9 @@
defined('MOODLE_INTERNAL') || die();
$version = 2020061524.00; // 20200615 = branching date YYYYMMDD - do not modify!
$version = 2020061525.00; // 20200615 = branching date YYYYMMDD - do not modify!
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
$release = '3.9.24 (Build: 20231009)'; // Human-friendly version name
$release = '3.9.25 (Build: 20231211)'; // Human-friendly version name
$branch = '39'; // This version's branch.
$maturity = MATURITY_STABLE; // This version's maturity level.