Compare commits

..

38 Commits

Author SHA1 Message Date
Jake Dallimore 84d3ae1b08 Moodle release 3.9.15 2022-07-08 11:28:28 +08:00
Jun Pataleta 71f83af13c weekly release 3.9.14+ 2022-07-07 21:33:14 +08:00
Víctor Déniz d587d8b242 Merge branch 'MDL-74835-M39_update-nodejs-version-in-travis-integration-to-lts-gallium' of https://github.com/ziegenberg/moodle into MOODLE_39_STABLE 2022-07-06 12:13:40 +01:00
stevando f1b1570b02 MDL-72171 tool_mobile: Ensure auto login URL is always local 2022-07-06 14:29:10 +08:00
Mihail Geshoski f9ca67a78d MDL-71921 scorm: Sanitize element names before outputting track details 2022-07-01 12:47:18 +02:00
Alex Morris c8f9140e70 MDL-75044 assignfeedback_editpdf: Add SAFER option to GS command
Adds the SAFER option to the Ghostscript command to limit interaction
with IO and OS commands
2022-07-01 12:47:18 +02:00
Huong Nguyen 47c211ba0e MDL-72029 Lesson: Prevent arbitrary file read when importing lesson questions in blackboard format 2022-07-01 12:47:18 +02:00
Huong Nguyen 9f270d956e MDL-72029 lib: Prevent path traversal for clean_param with PARAM_SAFEPATH 2022-07-01 12:47:18 +02:00
Laurent David 32ee8e3d34 MDL-74473 core_analytics: Upgrade ML Backend to 2.6.6 2022-07-01 12:47:18 +02:00
Jake Dallimore 77a1413fa0 MDL-72299 mod_lti: fix html escaping when reposting cross site posts 2022-07-01 12:47:18 +02:00
Víctor Déniz 14760d9787 weekly release 3.9.14+ 2022-07-01 11:28:53 +01:00
Jun Pataleta 03b6b93589 Merge branch 'MDL-74613-39' of https://github.com/NoelDeMartin/moodle into MOODLE_39_STABLE 2022-06-30 20:23:48 +08:00
Noel De Martin 47402416fd MDL-74613 grade: Fix undefined behat step 2022-06-30 11:32:35 +02:00
Andrew Nicols 0087cede97 Merge branch 'MDL-75084-39' of https://github.com/NoelDeMartin/moodle into MOODLE_39_STABLE 2022-06-30 11:14:09 +08:00
Andrew Nicols 264c405192 Merge branch 'MDL-74613-39' of https://github.com/NoelDeMartin/moodle into MOODLE_39_STABLE 2022-06-30 11:05:38 +08:00
Andrew Nicols 1742e0c27c Merge branch 'MDL-74220-39' of https://github.com/NoelDeMartin/moodle into MOODLE_39_STABLE 2022-06-30 11:00:59 +08:00
Andrew Nicols 7371cb4838 Merge branch 'MDL-74744-39' of https://github.com/andelacruz/moodle into MOODLE_39_STABLE 2022-06-30 10:56:19 +08:00
Andrew Nicols c1512e8d89 Merge branch 'MDL-74841-39' of https://github.com/andelacruz/moodle into MOODLE_39_STABLE 2022-06-30 10:45:12 +08:00
Jake Dallimore e5aaeed609 Merge branch 'MDL-74674-39' of https://github.com/alfonso-salces/moodle into MOODLE_39_STABLE 2022-06-29 16:45:23 +08:00
Alfonso Salces a950ed04a7 MDL-74674 core: create notification generators 2022-06-29 10:29:33 +02:00
Noel De Martin 964c98c8d9 MDL-75084 blog: Implement behat generators 2022-06-29 09:38:55 +02:00
Jake Dallimore 635389b235 Merge branch 'MDL-74864-39' of https://github.com/dpalou/moodle into MOODLE_39_STABLE 2022-06-29 13:41:31 +08:00
Dani Palou 1385e3bc09 MDL-74864 forum: Allow specifying group in discussions generator 2022-06-28 07:00:18 +02:00
Jun Pataleta 2dc581f717 weekly release 3.9.14+ 2022-06-17 15:30:38 +08:00
Víctor Déniz 4f8369c3f1 Merge branch 'MDL-74242-39-randomize_update_check' of https://github.com/mudrd8mz/moodle into MOODLE_39_STABLE 2022-06-14 09:48:51 +01:00
Jun Pataleta 730b405d68 Merge branch 'MDL-74816-39' of https://github.com/dpalou/moodle into MOODLE_39_STABLE 2022-06-14 13:58:29 +08:00
Angelia Dela Cruz 87c04f1e0b MDL-74841 behat: Label behat generators use UI
Replace steps that manually add Label instances via the UI and use
Behat generators. This improves the speed of Behat test runs.
2022-06-10 13:58:46 +08:00
Andrew Nicols f4df13fb6c weekly release 3.9.14+ 2022-06-03 11:06:38 +08:00
Paul Holden 4a8abf77ac MDL-71276 message_email: include alternate email in privacy export. 2022-06-01 18:48:11 +02:00
Noel De Martin 7e48ec1820 MDL-74613 grade: Add missing generators 2022-05-31 18:15:23 +02:00
Jake Dallimore 05087afd40 weekly release 3.9.14+ 2022-05-27 10:02:16 +08:00
Noel De Martin 56afed9678 MDL-74220 data: Implement behat generators 2022-05-26 10:39:38 +02:00
Paul Holden af8c168f78 MDL-73801 backup: set predictably ordered backup next start time.
Prior to this change, the testcase would assert ordering based on
identical fields which could lead to random failures. Set distinct
next start time of course backup, and also remove random ordering
by sorting on defined course order (all other fields being equal).
2022-05-26 12:10:49 +08:00
Dani Palou 4c52746b41 MDL-74816 mod_resource: Allow specify default file name in generator 2022-05-25 10:41:43 +02:00
Angelia Dela Cruz 7768a35366 MDL-74744 behat: Quiz behat generators use UI
Replace steps that manually add Quiz instances via the UI and use
Behat generators. This improves the speed of Behat test runs.
2022-05-24 11:23:21 +08:00
Daniel Ziegenberg 05847affc1 MDL-74835 ci: Update nodejs version in travis integration to lts/gallium
In MDL-73915 the nodejs version was switched to lts/gallium but the
travis integration was not updated.

Signed-off-by: Daniel Ziegenberg <daniel@ziegenberg.at>
2022-05-22 23:55:08 +02:00
David Mudrák f652a2b613 MDL-74242 task: Fix calculation of random minute in the task schedule
The random minute was incorrectly set to a random value from the range
0 - 23 instead of 0 - 59 due to this copy & paste mistake.

This is a backport of MDL-73765 already present in 3.11.
2022-03-20 17:06:51 +01:00
David Mudrák 04d4cbde78 MDL-74242 admin: Check for available updates daily on random time
It's been a mistake to make every Moodle site out there to check for
available update every two hours, on the hour. We are experiencing
server load peaks that are harder and harder to deal with.

The patch addresses this by (1) checking for available updates just once
a day and (2) distributing the checking times randomly around the clock.

This is a backport of MDL-73748 already present in 3.11.
2022-03-20 17:06:51 +01:00
42 changed files with 767 additions and 265 deletions
+2 -2
View File
@@ -36,7 +36,7 @@ jobs:
- php: 7.2
env: DB=none TASK=CITEST
- php: 7.2
env: DB=none TASK=GRUNT NVM_VERSION='lts/carbon'
env: DB=none TASK=GRUNT NVM_VERSION='lts/gallium'
- if: env(MOODLE_DATABASE) = "pgsql" OR env(MOODLE_DATABASE) = "all" OR env(MOODLE_DATABASE) IS NOT present
php: 7.2
@@ -52,7 +52,7 @@ jobs:
env: DB=none TASK=CITEST
- if: env(MOODLE_PHP) = "all"
php: 7.4
env: DB=none TASK=GRUNT NVM_VERSION='lts/carbon'
env: DB=none TASK=GRUNT NVM_VERSION='lts/gallium'
- if: env(MOODLE_PHP) = "all" AND (env(MOODLE_DATABASE) = "pgsql" OR env(MOODLE_DATABASE) = "all" OR env(MOODLE_DATABASE) IS NOT present)
php: 7.4
@@ -20,9 +20,10 @@ Feature: Forms manipulation
Given the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And I log in as "admin"
And I am on "Course 1" course homepage with editing mode on
And I add a "Quiz" to section "1"
And the following "activities" exist:
| activity | course | section | name |
| quiz | C1 | 1 | Quiz 1 |
And I am on the "Quiz 1" "quiz activity editing" page logged in as admin
When I expand all fieldsets
Then I should see "Close the quiz"
And I should see "Group mode"
+2 -1
View File
@@ -27,7 +27,8 @@ require_once($CFG->libdir . '/externallib.php');
$userid = required_param('userid', PARAM_INT); // The user id the key belongs to (for double-checking).
$key = required_param('key', PARAM_ALPHANUMEXT); // The key generated by the tool_mobile_external::get_autologin_key() external function.
$urltogo = optional_param('urltogo', $CFG->wwwroot, PARAM_URL); // URL to redirect.
$urltogo = optional_param('urltogo', $CFG->wwwroot, PARAM_LOCALURL); // URL to redirect.
$urltogo = $urltogo ?: $CFG->wwwroot;
$context = context_system::instance();
$PAGE->set_context($context);
@@ -19,14 +19,13 @@ Feature: Backup user data
And the following config values are set as admin:
| coursebinenable | 1 | tool_recyclebin |
| autohide | 0 | tool_recyclebin |
And the following "activities" exist:
| activity | course | section | name | intro |
| quiz | C1 | 1 | Quiz 1 | Test quiz description |
@javascript
Scenario: Delete and restore a quiz with user data
Given I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
And I add a "Quiz" to section "1" and I fill the form with:
| Name | Quiz 1 |
| Description | Test quiz description |
Given I am on the "Quiz 1" "quiz activity" page logged in as teacher1
And I add a "True/False" question to the "Quiz 1" quiz with:
| Question name | TF1 |
| Question text | First question |
@@ -41,6 +40,8 @@ Feature: Backup user data
| Correct answer | False |
| Feedback for the response 'True'. | So you think it is true |
| Feedback for the response 'False'. | So you think it is false |
And I set the field "maxgrade" to "10.0"
And I press "savechanges"
And I log out
When I am on the "Quiz 1" "quiz activity" page logged in as student1
And I press "Attempt quiz now"
@@ -77,6 +77,7 @@ abstract class backup_cron_automated_helper {
* First backup courses that do not have an entry in backup_courses first,
* as they are likely new and never been backed up. Do the oldest modified courses first.
* Then backup courses that have previously been backed up starting with the oldest next start time.
* Finally, all else being equal, defer to the sortorder of the courses.
*
* @param null|int $now timestamp to use in course selection.
* @return moodle_recordset The recordset of matching courses.
@@ -93,7 +94,8 @@ abstract class backup_cron_automated_helper {
LEFT JOIN {backup_courses} bc ON bc.courseid = c.id
WHERE bc.nextstarttime IS NULL OR bc.nextstarttime < ?
ORDER BY nextstarttime ASC,
c.timemodified DESC';
c.timemodified DESC,
c.sortorder';
$params = array(
$now, // Only get courses where the backup start time is in the past.
+1 -1
View File
@@ -383,7 +383,7 @@ class backup_cron_helper_testcase extends advanced_testcase {
$backupcourse3 = new stdClass;
$backupcourse3->courseid = $course3->id;
$backupcourse3->laststatus = testable_backup_cron_automated_helper::BACKUP_STATUS_OK;
$backupcourse3->nextstarttime = 1554858160;
$backupcourse3->nextstarttime = 1554822160;
$DB->insert_record('backup_courses', $backupcourse3);
$backupcourse4 = new stdClass;
+3 -9
View File
@@ -9,6 +9,9 @@ Feature: Comment on a blog entry
| username | firstname | lastname | email |
| testuser | Test | User | moodle@example.com |
| testuser2 | Test2 | User2 | moodle2@example.com |
And the following "core_blog > entries" exist:
| subject | body | user |
| Blog post from user 1 | User 1 blog post content | testuser |
And I log in as "admin"
And I am on site homepage
And I turn editing mode on
@@ -19,15 +22,6 @@ Feature: Comment on a blog entry
| Page contexts | Display throughout the entire site |
And I press "Save changes"
And I log out
And I log in as "testuser"
And I click on "Site pages" "list_item" in the "Navigation" "block"
And I click on "Site blogs" "link" in the "Navigation" "block"
And I follow "Add a new entry"
And I set the following fields to these values:
| Entry title | Blog post from user 1 |
| Blog entry body | User 1 blog post content |
And I press "Save changes"
And I log out
@javascript
Scenario: Commenting on my own blog entry
+4 -11
View File
@@ -8,6 +8,10 @@ Feature: Delete a blog entry
Given the following "users" exist:
| username | firstname | lastname | email |
| testuser | Test | User | moodle@example.com |
And the following "core_blog > entries" exist:
| subject | body | user |
| Blog post one | User 1 blog post content | testuser |
| Blog post two | User 1 blog post content | testuser |
And I log in as "admin"
And I am on site homepage
And I turn editing mode on
@@ -19,17 +23,6 @@ Feature: Delete a blog entry
And I press "Save changes"
And I log out
And I log in as "testuser"
And I click on "Site blogs" "link" in the "Navigation" "block"
And I follow "Add a new entry"
And I set the following fields to these values:
| Entry title | Blog post one |
| Blog entry body | User 1 blog post content |
And I press "Save changes"
And I follow "Add a new entry"
And I set the following fields to these values:
| Entry title | Blog post two |
| Blog entry body | User 1 blog post content |
And I press "Save changes"
And I am on site homepage
And I click on "Site blogs" "link" in the "Navigation" "block"
@@ -0,0 +1,51 @@
<?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/>.
/**
* Behat data generator for core_blog.
*
* @package core_blog
* @category test
* @copyright 2022 Noel De Martin
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Behat data generator for core_blog.
*
* @package core_blog
* @category test
* @copyright 2022 Noel De Martin
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_core_blog_generator extends behat_generator_base {
/**
* Get a list of the entities that can be created.
*
* @return array entity name => information about how to generate.
*/
protected function get_creatable_entities(): array {
return [
'entries' => [
'singular' => 'entry',
'datagenerator' => 'entry',
'required' => ['subject', 'body'],
'switchids' => ['user' => 'userid'],
],
];
}
}
+55
View File
@@ -0,0 +1,55 @@
<?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/>.
/**
* Generator for blog area.
*
* @package core_blog
* @category test
* @copyright 2022 Noel De Martin
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/blog/locallib.php');
/**
* Blog module test data generator class
*
* @package core_blog
* @category test
* @copyright 2022 Noel De Martin
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_blog_generator extends component_generator_base {
/**
* Create a blog entry
*
* @param array $data Entry data.
* @return blog_entry Entry instance.
*/
public function create_entry(array $data = []): blog_entry {
$data['publishstate'] = $data['publishstate'] ?? 'site';
$data['summary'] = $data['summary'] ?? $data['body'];
$entry = new blog_entry(null, $data);
$entry->add();
return $entry;
}
}
@@ -34,9 +34,9 @@ Feature: Restrict sections availability through completion or grade conditions
And I set the following fields to these values:
| Enable completion tracking | Yes |
And I press "Save and display"
And I add a "Label" to section "1" and I fill the form with:
| Label text | Test label |
| Completion tracking | Students can manually mark the activity as completed |
And the following "activities" exist:
| activity | course | section | intro | completion | idnumber |
| label | C1 | 1 | Test label | 1 | 1 |
And I add a "Page" to section "2" and I fill the form with:
| Name | Test page name |
| Description | Test page description |
@@ -12,7 +12,7 @@ Feature: We can change what we are viewing on the grader report
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
| student2 | Student | 1 | student2@example.com |
| student2 | Student | 2 | student2@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
@@ -26,19 +26,15 @@ Feature: We can change what we are viewing on the grader report
| assign | user | onlinetext |
| Test assignment name 1 | student1 | This is a submission for assignment 1 |
| Test assignment name 2 | student1 | This is a submission for assignment 2 |
And I am on the "Test assignment name 1" "assign activity" page logged in as student1
Then I should see "Submitted for grading"
And I am on the "Test assignment name 2" "assign activity" page
Then I should see "Submitted for grading"
And I log out
And the following "grade items" exist:
| itemname | grademin | grademax | course |
| Manual grade | 20 | 40 | C1 |
And the following "grade grades" exist:
| gradeitem | user | grade |
| Test assignment name 1 | student1 | 80 |
| Test assignment name 2 | student1 | 90 |
| Manual grade | student1 | 30 |
And I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to "View > Grader report" in the course gradebook
And I turn editing mode on
And I give the grade "80.00" to the user "Student 1" for the grade item "Test assignment name 1"
And I give the grade "90.00" to the user "Student 1" for the grade item "Test assignment name 2"
And I press "Save changes"
And I turn editing mode off
@javascript
Scenario: View and minimise the grader report containing hidden activities
@@ -49,13 +45,15 @@ Feature: We can change what we are viewing on the grader report
And I navigate to "View > Grader report" in the course gradebook
And I should see "Test assignment name 1"
And I should see "Test assignment name 2"
And I should see "Manual grade"
And I should see "Course total"
And the following should exist in the "user-grades" table:
| -1- | -4- | -5- | -6- |
| Student 1 | 80 | 90 | 170 |
| -1- | -4- | -5- | -6- | -7- |
| Student 1 | 80 | 90 | 30 | 170 |
And I click on "Change to aggregates only" "link"
And I should not see "Test assignment name 1"
And I should not see "Test assignment name 2"
And I should not see "Manual grade"
And I should see "Course total"
And the following should exist in the "user-grades" table:
| -1- | -4- |
@@ -63,10 +61,11 @@ Feature: We can change what we are viewing on the grader report
And I click on "Change to grades only" "link"
And I should see "Test assignment name 1"
And I should see "Test assignment name 2"
And I should see "Manual grade"
And I should not see "Course total"
And the following should exist in the "user-grades" table:
| -1- | -4- | -5- |
| Student 1 | 80 | 90 |
| -1- | -4- | -5- | -6- |
| Student 1 | 80 | 90 | 30 |
@javascript @skip_chrome_zerosize
Scenario: View and minimise the grader report containing hidden activities without the 'moodle/grade:viewhidden' capability
@@ -84,21 +83,24 @@ Feature: We can change what we are viewing on the grader report
And I navigate to "View > Grader report" in the course gradebook
And I should see "Test assignment name 1"
And I should see "Test assignment name 2"
And I should see "Manual grade"
And I should see "Course total"
And the following should exist in the "user-grades" table:
| -1- | -4- | -5- | -6- |
| Student 1 | 80 | - | 80 |
| -1- | -4- | -5- | -6- | -7- |
| Student 1 | 80 | - | 30 | 105.71 |
And I click on "Change to aggregates only" "link"
And I should not see "Test assignment name 1"
And I should not see "Test assignment name 2"
And I should not see "Manual grade"
And I should see "Course total"
And the following should exist in the "user-grades" table:
| -1- | -4- |
| Student 1 | 80 |
| Student 1 | 105.71 |
And I click on "Change to grades only" "link"
And I should see "Test assignment name 1"
And I should see "Test assignment name 2"
And I should see "Manual grade"
And I should not see "Course total"
And the following should exist in the "user-grades" table:
| -1- | -4- | -5- |
| Student 1 | 80 | - |
| -1- | -4- | -5- | -6- |
| Student 1 | 80 | - | 30 |
@@ -150,6 +150,12 @@ class behat_core_generator extends behat_generator_base {
'required' => ['fullname', 'course'],
'switchids' => ['course' => 'courseid', 'gradecategory' => 'parent'],
],
'grade grades' => [
'singular' => 'grade grade',
'datagenerator' => 'grade_grade',
'required' => ['gradeitem'],
'switchids' => ['user' => 'userid', 'gradeitem' => 'itemid'],
],
'grade items' => [
'singular' => 'grade item',
'datagenerator' => 'grade_item',
@@ -275,11 +281,33 @@ class behat_core_generator extends behat_generator_base {
'required' => ['user', 'course', 'lastaccess'],
'switchids' => ['user' => 'userid', 'course' => 'courseid'],
],
'notifications' => [
'singular' => 'notification',
'datagenerator' => 'notification',
'required' => ['subject', 'userfrom', 'userto'],
'switchids' => ['userfrom' => 'userfromid', 'userto' => 'usertoid'],
],
];
return $entities;
}
/**
* Get the grade item id using a name.
*
* @param string $name
* @return int The grade item id
*/
protected function get_gradeitem_id(string $name): int {
global $DB;
if (!$id = $DB->get_field('grade_items', 'id', ['itemname' => $name])) {
throw new Exception('The specified grade item with name "' . $name . '" could not be found.');
}
return $id;
}
/**
* Remove any empty custom fields, to avoid errors when creating the course.
*
@@ -1009,6 +1037,39 @@ class behat_core_generator extends behat_generator_base {
$DB->insert_record('badge_backpack', $backpack);
}
/**
* Creates notifications to specific user.
*
* @param array $data
* @return void
*/
protected function process_notification(array $data) {
global $DB;
$notification = new stdClass();
$notification->useridfrom = $data['userfromid'];
$notification->useridto = $data['usertoid'];
$notification->subject = $data['subject'];
$notification->fullmessage = $data['subject'] . ' description';
$notification->smallmessage = $data['subject'] . ' description';
$notification->fullmessagehtml = $data['subject'] . ' description';
if ($data['timecreated'] !== 'null') {
$notification->timecreated = $data['timecreated'];
}
if ($data['timeread'] !== 'null') {
$notification->timeread = $data['timeread'];
}
if (!empty($data)) {
$popupnotification = new stdClass();
$popupnotification->notificationid = $DB->insert_record('notifications', $notification);
$DB->insert_record('message_popup_notifications', $popupnotification);
}
}
/**
* Creates user last access data within given courses.
*
@@ -316,6 +316,36 @@ abstract class behat_generator_base {
return $id;
}
/**
* Gets the user id from it's username.
* @throws Exception
* @param string $username
* @return int
*/
protected function get_userfrom_id(string $username) {
global $DB;
if (!$id = $DB->get_field('user', 'id', ['username' => $username])) {
throw new Exception('The specified user with username "' . $username . '" does not exist');
}
return $id;
}
/**
* Gets the user id from it's username.
* @throws Exception
* @param string $username
* @return int
*/
protected function get_userto_id(string $username) {
global $DB;
if (!$id = $DB->get_field('user', 'id', ['username' => $username])) {
throw new Exception('The specified user with username "' . $username . '" does not exist');
}
return $id;
}
/**
* Gets the role id from it's shortname.
* @throws Exception
+1 -1
View File
@@ -111,7 +111,7 @@ abstract class scheduled_task extends task_base {
*/
public function set_minute($minute, $expandr = true) {
if ($minute === 'R' && $expandr) {
$minute = mt_rand(self::HOURMIN, self::HOURMAX);
$minute = mt_rand(self::MINUTEMIN, self::MINUTEMAX);
}
$this->minute = $minute;
}
+2 -2
View File
@@ -242,8 +242,8 @@ $tasks = array(
array(
'classname' => 'core\task\check_for_updates_task',
'blocking' => 0,
'minute' => '0',
'hour' => '*/2',
'minute' => 'R',
'hour' => 'R',
'day' => '*',
'dayofweek' => '*',
'month' => '*'
+1 -1
View File
@@ -38,7 +38,7 @@ class processor implements \core_analytics\classifier, \core_analytics\regresso
/**
* The required version of the python package that performs all calculations.
*/
const REQUIRED_PIP_PACKAGE_VERSION = '2.6.5';
const REQUIRED_PIP_PACKAGE_VERSION = '2.6.6';
/**
* The python package is installed in a server.
+12 -2
View File
@@ -978,8 +978,18 @@ function clean_param($param, $type) {
return preg_replace('/[^a-zA-Z0-9_-]/i', '', $param);
case PARAM_SAFEPATH:
// Remove everything not a-zA-Z0-9/_- .
return preg_replace('/[^a-zA-Z0-9\/_-]/i', '', $param);
// Replace MS \ separators.
$param = str_replace('\\', '/', $param);
// Remove any number of ../ to prevent path traversal.
$param = preg_replace('/\.\.+\//', '', $param);
// Remove everything not a-zA-Z0-9/:_- .
$param = preg_replace('/[^a-zA-Z0-9\/:_-]/i', '', $param);
// Remove leading slash.
$param = ltrim($param, '/');
if ($param === '.') {
$param = '';
}
return $param;
case PARAM_FILE:
// Strip all suspicious characters from filename.
+51
View File
@@ -1002,6 +1002,57 @@ EOD;
return $gradecategory->get_record_data();
}
/**
* Create a grade_grade.
*
* @param array $record
* @return grade_grade the grade record
*/
public function create_grade_grade(?array $record = null): grade_grade {
global $DB, $USER;
$item = $DB->get_record('grade_items', ['id' => $record['itemid']]);
$userid = $record['userid'] ?? $USER->id;
unset($record['itemid']);
unset($record['userid']);
if ($item->itemtype === 'mod') {
$cm = get_coursemodule_from_instance($item->itemmodule, $item->iteminstance);
$module = new $item->itemmodule(context_module::instance($cm->id), $cm, false);
$record['attemptnumber'] = $record['attemptnumber'] ?? 0;
$module->save_grade($userid, (object) $record);
$grade = grade_grade::fetch(['userid' => $userid, 'itemid' => $item->id]);
} else {
$grade = grade_grade::fetch(['userid' => $userid, 'itemid' => $item->id]);
$record['rawgrade'] = $record['rawgrade'] ?? $record['grade'] ?? null;
$record['finalgrade'] = $record['finalgrade'] ?? $record['grade'] ?? null;
unset($record['grade']);
if ($grade) {
$fields = $grade->required_fields + array_keys($grade->optional_fields);
foreach ($fields as $field) {
$grade->{$field} = $record[$field] ?? $grade->{$field};
}
$grade->update();
} else {
$record['userid'] = $userid;
$record['itemid'] = $item->id;
$grade = new grade_grade($record, false);
$grade->insert();
}
}
return $grade;
}
/**
* Create a grade_item.
*
+34
View File
@@ -839,6 +839,40 @@ class core_moodlelib_testcase extends advanced_testcase {
}
}
/**
* Provide some tested base url and expected results.
*
* @return array Array of tested base url and expected results.
*/
public function clean_param_safepath_provider(): array {
return [
// MS separator test.
['c:\temp', 'c:/temp'],
// Leading slash test.
['/tmp/', 'tmp/'],
// Path traversal test.
['../../../../../etc/', 'etc/'],
['../', ''],
['.../...//', ''],
['.', '']
];
}
/**
* Test clean_param() method with PARAM_SAFEPATH type.
*
* @dataProvider clean_param_safepath_provider
* @covers ::clean_param
* @param string $path
* @param string $expected
*/
public function test_clean_param_safepath(string $path, string $expected) {
$result = clean_param($path, PARAM_SAFEPATH);
$this->assertSame($expected, $result);
}
public function test_validate_param() {
try {
$param = validate_param('11a', PARAM_INT);
@@ -30,6 +30,7 @@ use \core_privacy\local\metadata\collection;
use \core_privacy\local\request\contextlist;
use \core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\userlist;
use core_privacy\local\request\writer;
use \core_privacy\local\request\approved_userlist;
/**
@@ -42,6 +43,7 @@ use \core_privacy\local\request\approved_userlist;
class provider implements
\core_privacy\local\metadata\provider,
\core_privacy\local\request\core_userlist_provider,
\core_privacy\local\request\user_preference_provider,
\core_privacy\local\request\plugin\provider {
/**
@@ -125,4 +127,21 @@ class provider implements
*/
public static function delete_data_for_user(approved_contextlist $contextlist) {
}
/**
* Export all user preferences for the plugin
*
* @param int $userid
*/
public static function export_user_preferences(int $userid) {
$preference = get_user_preferences('message_processor_email_email', null, $userid);
if (!empty($preference)) {
writer::export_user_preference(
'message_email',
'email',
$preference,
get_string('privacy:preference:email', 'message_email')
);
}
}
}
@@ -43,6 +43,7 @@ $string['privacy:metadata:replyto'] = 'The email address to reply to.';
$string['privacy:metadata:replytoname'] = 'Name of reply to recipient.';
$string['privacy:metadata:subject'] = 'The subject line of the message.';
$string['privacy:metadata:userfrom'] = 'The user sending the message.';
$string['privacy:preference:email'] = 'Preferred email notification address';
$string['tasksendemail'] = 'Messages digest mailings';
// Deprecated since Moodle 3.9.
+44 -12
View File
@@ -13,28 +13,27 @@
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Base class for unit tests for message_email.
*
* @package message_email
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
namespace message_email\privacy;
use context_system;
use core_message_external;
use core_privacy\local\request\writer;
use core_privacy\tests\provider_testcase;
use \core_privacy\tests\provider_testcase;
/**
* Unit tests for message\output\email\classes\privacy\provider.php
*
* @package message_email
* @covers \message_email\privacy\provider
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class message_email_testcase extends provider_testcase {
class provider_test extends provider_testcase {
/**
* Basic setup for these tests.
*/
public function setUp() {
public function setUp(): void {
$this->resetAfterTest(true);
}
@@ -56,5 +55,38 @@ class message_email_testcase extends provider_testcase {
$contextlist = \message_email\privacy\provider::get_contexts_for_userid($user->id);
$this->assertEmpty($contextlist);
}
}
/**
* Test exporting user preferences
*/
public function test_export_user_preferences(): void {
global $CFG;
require_once("{$CFG->dirroot}/message/externallib.php");
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
// Submit configuration form, which adds the preferences..
core_message_external::message_processor_config_form($user->id, 'email', [
[
'name' => 'email_email',
'value' => 'alternate@example.com',
],
]);
// Switch to admin user (so we can validate preferences of the correct user are being exported).
$this->setAdminUser();
provider::export_user_preferences($user->id);
$writer = writer::with_context(context_system::instance());
$this->assertTrue($writer->has_any_data());
$preferences = $writer->get_user_preferences('message_email');
$this->assertNotEmpty($preferences->email);
$this->assertEquals('alternate@example.com', $preferences->email->value);
$this->assertEquals(get_string('privacy:preference:email', 'message_email'), $preferences->email->description);
}
}
@@ -5,61 +5,41 @@ Feature: Notification popover unread notifications
I am notified about relevant events in Moodle
Background:
# This will make sure popup notifications are enabled and create
# two assignment notifications. One for the student submitting their
# assignment and another for the teacher grading it.
Given the following "courses" exist:
| fullname | shortname | category | groupmode |
| Course 1 | C1 | 0 | 1 |
# Make sure the popup notifications are enabled for assignments.
And the following config values are set as admin:
| popup_provider_mod_assign_assign_notification_permitted | permitted | message |
| message_provider_mod_assign_assign_notification_loggedin | popup | message |
| message_provider_mod_assign_assign_notification_loggedoff | popup | message |
And the following "users" exist:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activity" exists:
| activity | assign |
| course | C1 |
| name | Test assignment name |
| assignsubmission_onlinetext_enabled | 1 |
| assignsubmission_file_enabled | 0 |
| submissiondrafts | 0 |
# This should generate a notification.
And the following "mod_assign > submissions" exist:
| assign | user | onlinetext |
| Test assignment name | student1 | I'm the student1 submission |
| student2 | Student | 2 | student2@example.com |
# This should generate some notifications
And the following "notifications" exist:
| subject | userfrom | userto | timecreated | timeread |
| Test 01 | student2 | student1 | 1654587996 | null |
| Test 02 | student2 | student1 | 1654587997 | null |
Scenario: Notification popover shows correct unread count
When I log in as "student1"
Given I log in as "student1"
# Confirm the popover is saying 1 unread notifications.
Then I should see "1" in the "#nav-notification-popover-container [data-region='count-container']" "css_element"
And I should see "2" in the "#nav-notification-popover-container [data-region='count-container']" "css_element"
# Open the popover.
And I open the notification popover
# Confirm the submission notification is visible.
And I should see "You have submitted your assignment submission for Test assignment name" in the "#nav-notification-popover-container" "css_element"
When I open the notification popover
# Confirm the notifications are visible.
Then I should see "Test 01" in the "#nav-notification-popover-container" "css_element"
And I should see "Test 02" in the "#nav-notification-popover-container" "css_element"
@_bug_phantomjs
Scenario: Clicking a notification marks it as read
When I log in as "student1"
# Open the popover.
Given I log in as "student1"
# Open the notifications.
When I open the notification popover
And I follow "Test 01"
And I open the notification popover
# Click on the submission notification.
And I follow "You have submitted your assignment submission for Test assignment name"
And I follow "Test 02"
# Confirm the count element is hidden (i.e. there are no unread notifications).
Then "[data-region='count-container']" "css_element" in the "#nav-notification-popover-container" "css_element" should not be visible
Scenario: Mark all notifications as read
When I log in as "student1"
# Open the popover.
And I open the notification popover
# Click the mark all as read button.
Given I log in as "student1"
When I open the notification popover
And I click on "Mark all as read" "link" in the "#nav-notification-popover-container" "css_element"
# Refresh the page to make sure we send a new request for the unread count.
And I reload the page
+1 -1
View File
@@ -632,7 +632,7 @@ class pdf extends TcpdfFpdi {
$gsexec = \escapeshellarg($CFG->pathtogs);
$tempdstarg = \escapeshellarg($tempdst);
$tempsrcarg = \escapeshellarg($tempsrc);
$command = "$gsexec -q -sDEVICE=pdfwrite -dBATCH -dNOPAUSE -sOutputFile=$tempdstarg $tempsrcarg";
$command = "$gsexec -q -sDEVICE=pdfwrite -dSAFER -dBATCH -dNOPAUSE -sOutputFile=$tempdstarg $tempsrcarg";
exec($command);
if (!file_exists($tempdst)) {
// Something has gone wrong in the conversion.
+17 -22
View File
@@ -24,32 +24,27 @@ Feature: Users can view and search database entries
And the following "activities" exist:
| activity | name | intro | course | idnumber |
| data | Test database name | Database intro | C1 | data1 |
And I log in as "teacher1"
And I am on "Course 1" course homepage
And I add a "Text input" field to "Test database name" database and I fill the form with:
| Field name | Test field name |
| Field description | Test field description |
And I add a "Text input" field to "Test database name" database and I fill the form with:
| Field name | Test field 2 name |
| Field description | Test field 2 description |
# To generate the default templates.
And I follow "Templates"
And I log out
And the following "mod_data > fields" exist:
| database | type | name | description |
| data1 | text | Test field name | Test field description |
| data1 | text | Test field 2 name | Test field 2 description |
And the following "mod_data > templates" exist:
| database | name |
| data1 | singletemplate |
| data1 | listtemplate |
| data1 | addtemplate |
| data1 | asearchtemplate |
| data1 | rsstemplate |
Scenario: Students can add view, list and search entries
Given I log in as "student1"
Given the following "mod_data > entries" exist:
| database | Test field name | Test field 2 name |
| data1 | Student entry 1 | |
| data1 | Student entry 2 | |
| data1 | Student entry 3 | |
When I log in as "student1"
And I am on "Course 1" course homepage
And I follow "Test database name"
And I add an entry to "Test database name" database with:
| Test field name | Student entry 1 |
And I press "Save and add another"
And I add an entry to "Test database name" database with:
| Test field name | Student entry 2 |
And I press "Save and add another"
And I add an entry to "Test database name" database with:
| Test field name | Student entry 3 |
And I press "Save and view"
And I follow "Test database name"
Then I should see "Student entry 1"
And I should see "Student entry 2"
And I should see "Student entry 3"
@@ -0,0 +1,136 @@
<?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/>.
/**
* Behat data generator for mod_data.
*
* @package mod_data
* @category test
* @copyright 2022 Noel De Martin
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_mod_data_generator extends behat_generator_base {
/**
* Get a list of the entities that Behat can create using the generator step.
*
* @return array
*/
protected function get_creatable_entities(): array {
return [
'entries' => [
'singular' => 'entry',
'datagenerator' => 'entry',
'required' => ['database'],
'switchids' => ['database' => 'databaseid'],
],
'fields' => [
'singular' => 'field',
'datagenerator' => 'field',
'required' => ['database', 'type', 'name'],
'switchids' => ['database' => 'databaseid'],
],
'templates' => [
'singular' => 'template',
'datagenerator' => 'template',
'required' => ['database', 'name'],
'switchids' => ['database' => 'databaseid'],
],
];
}
/**
* Get the database id using an activity idnumber.
*
* @param string $idnumber
* @return int The database id
*/
protected function get_database_id(string $idnumber): int {
$cm = $this->get_cm_by_activity_name('data', $idnumber);
return $cm->instance;
}
/**
* Add an entry.
*
* @param array $data Entry data.
*/
public function process_entry(array $data): void {
global $DB;
$database = $DB->get_record('data', ['id' => $data['databaseid']], '*', MUST_EXIST);
unset($data['databaseid']);
$data = array_reduce(array_keys($data), function ($fields, $fieldname) use ($data, $database) {
global $DB;
$field = $DB->get_record('data_fields', ['name' => $fieldname, 'dataid' => $database->id], 'id', MUST_EXIST);
$fields[$field->id] = $data[$fieldname];
return $fields;
}, []);
$this->get_data_generator()->create_entry($database, $data);
}
/**
* Add a field.
*
* @param array $data Field data.
*/
public function process_field(array $data): void {
global $DB;
$database = $DB->get_record('data', ['id' => $data['databaseid']], '*', MUST_EXIST);
unset($data['databaseid']);
$this->get_data_generator()->create_field((object) $data, $database);
}
/**
* Add a template.
*
* @param array $data Template data.
*/
public function process_template(array $data): void {
global $DB;
$database = $DB->get_record('data', ['id' => $data['databaseid']], '*', MUST_EXIST);
if (empty($data['content'])) {
data_generate_default_template($database, $data['name']);
} else {
$newdata = new stdClass();
$newdata->id = $database->id;
$newdata->{$data['name']} = $data['content'];
$DB->update_record('data', $newdata);
}
}
/**
* Get the module data generator.
*
* @return mod_data_generator Database data generator.
*/
protected function get_data_generator(): mod_data_generator {
return $this->componentdatagenerator;
}
}
@@ -42,15 +42,36 @@ Feature: Posting to all groups in a separate group discussion is restricted to u
And the following "activities" exist:
| activity | name | intro | course | idnumber | groupmode |
| forum | Standard forum name | Standard forum description | C1 | sepgroups | 1 |
And the following "mod_forum > discussions" exist:
| forum | name | subject | message | group |
| sepgroups | Initial Disc ALL | Initial Disc ALL | Disc ALL content | All participants |
| sepgroups | Initial Disc G1 | Initial Disc G1 | Disc G1 content | G1 |
| sepgroups | Initial Disc G2 | Initial Disc G2 | Disc G2 content | G2 |
| sepgroups | Initial Disc G3 | Initial Disc G3 | Disc G3 content | G3 |
Scenario: Teacher with accessallgroups can view all groups
Given I log in as "teacher1"
And I am on "Course 1" course homepage
When I follow "Standard forum name"
Then the "Separate groups" select box should contain "All participants"
Then the "Separate groups" select box should contain "Group A"
Then the "Separate groups" select box should contain "Group B"
Then the "Separate groups" select box should contain "Group C"
And the "Separate groups" select box should contain "Group A"
And the "Separate groups" select box should contain "Group B"
And the "Separate groups" select box should contain "Group C"
And I select "All participants" from the "Separate groups" singleselect
And I should see "Initial Disc ALL"
And I should see "Initial Disc G1"
And I should see "Initial Disc G2"
And I should see "Initial Disc G2"
And I select "Group A" from the "Separate groups" singleselect
And I should see "Initial Disc ALL"
And I should see "Initial Disc G1"
But I should not see "Initial Disc G2"
And I should not see "Initial Disc G3"
And I select "Group B" from the "Separate groups" singleselect
And I should see "Initial Disc ALL"
And I should see "Initial Disc G2"
But I should not see "Initial Disc G1"
And I should not see "Initial Disc G3"
Scenario: Teacher with accessallgroups can select any group when posting
Given I log in as "teacher1"
@@ -249,14 +270,7 @@ Feature: Posting to all groups in a separate group discussion is restricted to u
And I should see "Post a copy to all groups"
Scenario: Students can view all participants discussions in separate groups mode
Given I log in as "teacher1"
Given I log in as "student1"
And I am on "Course 1" course homepage
When I add a new discussion to "Standard forum name" forum with:
| Subject | Forum post to all participants |
| Message | This is the body |
| Group | All participants |
And I log out
And I log in as "student1"
And I am on "Course 1" course homepage
And I follow "Standard forum name"
Then I should see "Forum post to all participants"
When I follow "Standard forum name"
Then I should see "Initial Disc ALL"
@@ -33,15 +33,36 @@ Feature: Posting to all groups in a visible group discussion is restricted to us
And the following "activities" exist:
| activity | name | intro | course | idnumber | groupmode |
| forum | Standard forum name | Standard forum description | C1 | groups | 2 |
And the following "mod_forum > discussions" exist:
| forum | name | subject | message | group |
| groups | Initial Disc ALL | Initial Disc ALL | Disc ALL content | All participants |
| groups | Initial Disc G1 | Initial Disc G1 | Disc G1 content | G1 |
| groups | Initial Disc G2 | Initial Disc G2 | Disc G2 content | G2 |
| groups | Initial Disc G3 | Initial Disc G3 | Disc G3 content | G3 |
Scenario: Teacher with accessallgroups can view all groups
Given I log in as "teacher1"
And I am on "Course 1" course homepage
When I follow "Standard forum name"
Then the "Visible groups" select box should contain "All participants"
Then the "Visible groups" select box should contain "Group A"
Then the "Visible groups" select box should contain "Group B"
Then the "Visible groups" select box should contain "Group C"
And the "Visible groups" select box should contain "Group A"
And the "Visible groups" select box should contain "Group B"
And the "Visible groups" select box should contain "Group C"
And I select "All participants" from the "Visible groups" singleselect
And I should see "Initial Disc ALL"
And I should see "Initial Disc G1"
And I should see "Initial Disc G2"
And I should see "Initial Disc G2"
And I select "Group A" from the "Visible groups" singleselect
And I should see "Initial Disc ALL"
And I should see "Initial Disc G1"
But I should not see "Initial Disc G2"
And I should not see "Initial Disc G3"
And I select "Group B" from the "Visible groups" singleselect
And I should see "Initial Disc ALL"
And I should see "Initial Disc G2"
But I should not see "Initial Disc G1"
And I should not see "Initial Disc G3"
Scenario: Teacher with accessallgroups can select any group when posting
Given I log in as "teacher1"
@@ -150,9 +171,24 @@ Feature: Posting to all groups in a visible group discussion is restricted to us
And I am on "Course 1" course homepage
When I follow "Standard forum name"
Then the "Visible groups" select box should contain "All participants"
Then the "Visible groups" select box should contain "Group A"
Then the "Visible groups" select box should contain "Group B"
Then the "Visible groups" select box should contain "Group C"
And the "Visible groups" select box should contain "Group A"
And the "Visible groups" select box should contain "Group B"
And the "Visible groups" select box should contain "Group C"
And I select "All participants" from the "Visible groups" singleselect
And I should see "Initial Disc ALL"
And I should see "Initial Disc G1"
And I should see "Initial Disc G2"
And I should see "Initial Disc G2"
And I select "Group A" from the "Visible groups" singleselect
And I should see "Initial Disc ALL"
And I should see "Initial Disc G1"
But I should not see "Initial Disc G2"
And I should not see "Initial Disc G3"
And I select "Group B" from the "Visible groups" singleselect
And I should see "Initial Disc ALL"
And I should see "Initial Disc G2"
But I should not see "Initial Disc G1"
And I should not see "Initial Disc G3"
Scenario: Students in one group can only post in their group
Given I log in as "student1"
@@ -35,7 +35,7 @@ class behat_mod_forum_generator extends behat_generator_base {
'singular' => 'discussion',
'datagenerator' => 'discussion',
'required' => ['forum'],
'switchids' => ['forum' => 'forumid', 'user' => 'userid'],
'switchids' => ['forum' => 'forumid', 'user' => 'userid', 'group' => 'groupid'],
],
'posts' => [
'singular' => 'post',
@@ -62,6 +62,21 @@ class behat_mod_forum_generator extends behat_generator_base {
return $id;
}
/**
* Gets the group id from it's idnumber. It allows using 'All participants' as idnumber.
*
* @throws Exception
* @param string $idnumber
* @return int
*/
protected function get_group_id($idnumber): int {
if ($idnumber === 'All participants') {
return -1;
}
return parent::get_group_id($idnumber);
}
/**
* Preprocess discussion data.
*
+5 -11
View File
@@ -5,7 +5,6 @@ Feature: set label idnumber
As a teacher
I should create label activity and set an ID number
@javascript
Scenario: label ID number input box should be shown.
Given the following "courses" exist:
| fullname | shortname | category |
@@ -18,12 +17,11 @@ Feature: set label idnumber
| user | course | role |
| teacher | C1 | editingteacher |
| student | C1 | student |
Given I log in as "teacher"
And the following "activities" exist:
| activity | course | section | intro | idnumber |
| label | C1 | 1 | Label with ID number set | C1LABEL1 |
When I log in as "teacher"
And I am on "Test" course homepage with editing mode on
When I add a "label" to section "1" and I fill the form with:
| Label text | Label with ID number set |
| Availability | Show on course page |
| ID number | C1LABEL1 |
Then "Label with ID number set" activity should be visible
And I turn editing mode off
And "Label with ID number set" activity should be visible
@@ -32,11 +30,7 @@ Feature: set label idnumber
And I am on "Test" course homepage
And I should see "Label with ID number set"
And I log out
And I log in as "teacher"
And I am on "Test" course homepage
And I turn editing mode on
And I open "Label with ID number set" actions menu
And I click on "Edit settings" "link" in the "Label with ID number set" activity
And I am on the "Label with ID number set" "label activity editing" page logged in as teacher
And I expand all fieldsets
And I should see "ID number" in the "Common module settings" "fieldset"
And the field "ID number" matches value "C1LABEL1"
+13 -40
View File
@@ -5,8 +5,7 @@ Feature: Check label visibility works
As a teacher
I should create label activity
@javascript
Scenario: Hidden label activity should be show as hidden.
Background:
Given the following "courses" exist:
| fullname | shortname | category |
| Test | C1 | 0 |
@@ -18,38 +17,26 @@ Feature: Check label visibility works
| user | course | role |
| teacher | C1 | editingteacher |
| student | C1 | student |
And the following "activities" exist:
| activity | course | section | intro | idnumber | visible |
| label | C1 | 1 | Swanky label | 1 | 1 |
| label | C1 | 1 | Swanky label 2 | 2 | 0 |
Scenario: Hidden label activity should be show as hidden.
Given I log in as "teacher"
And I am on "Test" course homepage with editing mode on
When I add a "label" to section "1" and I fill the form with:
| Label text | Swanky label |
| Availability | Hide from students |
Then "Swanky label" activity should be hidden
When I am on "Test" course homepage with editing mode on
Then "Swanky label 2" activity should be hidden
And I turn editing mode off
And "Swanky label" activity should be hidden
Then "Swanky label 2" activity should be hidden
And I log out
And I log in as "student"
And I am on "Test" course homepage
And I should not see "Swanky label"
And I should not see "Swanky label 2"
And I log out
@javascript
Scenario: Visible label activity should be shown as visible.
Given the following "courses" exist:
| fullname | shortname | category |
| Test | C1 | 0 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher | Teacher | Frist | teacher1@example.com |
| student | Student | First | student1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher | C1 | editingteacher |
| student | C1 | student |
Given I log in as "teacher"
And I am on "Test" course homepage with editing mode on
When I add a "label" to section "1" and I fill the form with:
| Label text | Swanky label |
| Availability | Show on course page |
When I am on "Test" course homepage with editing mode on
Then "Swanky label" activity should be visible
And I log out
And I log in as "student"
@@ -59,23 +46,9 @@ Feature: Check label visibility works
@javascript
Scenario: Teacher can not show label inside the hidden section
Given the following "courses" exist:
| fullname | shortname | category |
| Test | C1 | 0 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher | Teacher | Frist | teacher1@example.com |
| student | Student | First | student1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher | C1 | editingteacher |
| student | C1 | student |
Given I log in as "teacher"
And I am on "Test" course homepage with editing mode on
When I add a "label" to section "1" and I fill the form with:
| Label text | Swanky label |
| Availability | Show on course page |
And I hide section "1"
When I hide section "1"
Then "Swanky label" activity should be dimmed
And I open "Swanky label" actions menu
And "Swanky label" actions menu should not have "Show" item
@@ -56,7 +56,7 @@ class repost_crosssite_page implements renderable, templatable {
*/
public function __construct(string $url, array $post) {
$this->params = array_map(function($k) use ($post) {
return ["key" => $k, "value" => str_replace("\"", "&quot;", $post[$k])];
return ["key" => $k, "value" => $post[$k]];
}, array_keys($post));
$this->url = $url;
}
+2 -2
View File
@@ -37,9 +37,9 @@
}
}}
<form action="{{{url}}}" method="POST" id="autopostme">
<form action="{{url}}" method="POST" id="autopostme">
{{#params}}
<input type="hidden" name="{{{key}}}" value="{{{value}}}">
<input type="hidden" name="{{key}}" value="{{value}}">
{{/params}}
<input type="hidden" name="repost" value="true">
</form>
@@ -5,12 +5,12 @@ Feature: Safe Exam Browser settings in quiz edit form
Given the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
And I log in as "admin"
And I am on "Course 1" course homepage
And I turn editing mode on
And the following "activities" exist:
| activity | course | section | name |
| quiz | C1 | 1 | Quiz 1 |
Scenario: Quiz setting "Require the use of Safe Exam Browser" has all types, except "Use an existing template".
When I add a "Quiz" to section "1"
When I am on the "Quiz 1" "quiz activity editing" page logged in as admin
And I expand all fieldsets
And the "Require the use of Safe Exam Browser" select box should contain "Yes Configure manually"
And the "Require the use of Safe Exam Browser" select box should not contain "Yes Use an existing template"
@@ -22,7 +22,7 @@ Feature: Safe Exam Browser settings in quiz edit form
Given the following "quizaccess_seb > seb templates" exist:
| name |
| Template 1 |
When I add a "Quiz" to section "1"
When I am on the "Quiz 1" "quiz activity editing" page logged in as admin
And I expand all fieldsets
And the "Require the use of Safe Exam Browser" select box should contain "Yes Configure manually"
And the "Require the use of Safe Exam Browser" select box should contain "Yes Use an existing template"
@@ -34,7 +34,7 @@ Feature: Safe Exam Browser settings in quiz edit form
Given the following "quizaccess_seb > seb templates" exist:
| name |
| Template 1 |
When I add a "Quiz" to section "1"
When I am on the "Quiz 1" "quiz activity editing" page logged in as admin
And I expand all fieldsets
And I set the field "Require the use of Safe Exam Browser" to "No"
Then I should not see "Upload Safe Exam Browser config file"
@@ -66,7 +66,7 @@ Feature: Safe Exam Browser settings in quiz edit form
Given the following "quizaccess_seb > seb templates" exist:
| name |
| Template 1 |
When I add a "Quiz" to section "1"
And I am on the "Quiz 1" "quiz activity editing" page logged in as admin
And I expand all fieldsets
And I set the field "Require the use of Safe Exam Browser" to "Yes Use SEB client config"
Then I should see "Show Safe Exam Browser download button"
@@ -98,7 +98,7 @@ Feature: Safe Exam Browser settings in quiz edit form
Given the following "quizaccess_seb > seb templates" exist:
| name |
| Template 1 |
When I add a "Quiz" to section "1"
And I am on the "Quiz 1" "quiz activity editing" page logged in as admin
And I expand all fieldsets
And I set the field "Require the use of Safe Exam Browser" to "Yes Upload my own config"
Then I should see "Upload Safe Exam Browser config file"
@@ -130,7 +130,7 @@ Feature: Safe Exam Browser settings in quiz edit form
Given the following "quizaccess_seb > seb templates" exist:
| name |
| Template 1 |
When I add a "Quiz" to section "1"
And I am on the "Quiz 1" "quiz activity editing" page logged in as admin
And I expand all fieldsets
And I set the field "Require the use of Safe Exam Browser" to "Yes Use an existing template"
Then I should see "Safe Exam Browser config template"
@@ -164,7 +164,7 @@ Feature: Safe Exam Browser settings in quiz edit form
Given the following "quizaccess_seb > seb templates" exist:
| name |
| Template 1 |
When I add a "Quiz" to section "1"
And I am on the "Quiz 1" "quiz activity editing" page logged in as admin
And I expand all fieldsets
And I set the field "Require the use of Safe Exam Browser" to "Yes Configure manually"
Then I should see "Show Safe Exam Browser download button"
@@ -18,39 +18,39 @@ Feature: Settings form fields disabled if not required
| teacher | C1 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
And I log in as "teacher"
And I am on "Course 1" course homepage with editing mode on
And the following "activities" exist:
| activity | course | section | name |
| quiz | C1 | 1 | Test quiz 1 |
@javascript
Scenario: Depending on the number of attempts, different form fields are disabled.
When I add a "Quiz" to section "1"
When I am on the "Test quiz 1" "quiz activity editing" page logged in as teacher
And I expand all fieldsets
And I set the field "Name" to "Test quiz"
And I set the field "Attempts allowed" to "1"
Then the "Grading method" "field" should be disabled
And the "Each attempt builds on the last" "field" should be disabled
And the "id_delay1_enabled" "field" should be disabled
And the "id_delay2_enabled" "field" should be disabled
When I set the field "Attempts allowed" to "2"
Then the "Grading method" "field" should be enabled
And I set the field "Attempts allowed" to "2"
And the "Grading method" "field" should be enabled
And the "Each attempt builds on the last" "field" should be enabled
And the "id_delay1_enabled" "field" should be enabled
And the "id_delay2_enabled" "field" should be disabled
When I set the field "Attempts allowed" to "3"
Then the "Grading method" "field" should be enabled
And I set the field "Attempts allowed" to "3"
And the "Grading method" "field" should be enabled
And the "Each attempt builds on the last" "field" should be enabled
And the "id_delay1_enabled" "field" should be enabled
And the "id_delay2_enabled" "field" should be enabled
When I set the field "Attempts allowed" to "Unlimited"
Then the "Grading method" "field" should be enabled
And I set the field "Attempts allowed" to "Unlimited"
And the "Grading method" "field" should be enabled
And the "Each attempt builds on the last" "field" should be enabled
# And the "id_delay1_enabled" "field" should be enabled
# And the "id_delay2_enabled" "field" should be enabled
When I press "Save and display"
And I press "Save and display"
And I navigate to "User overrides" in current page administration
And I press "Add user override"
And I set the following fields to these values:
@@ -61,12 +61,12 @@ Feature: Settings form fields disabled if not required
And I navigate to "Edit settings" in current page administration
And I expand all fieldsets
And I set the field "Attempts allowed" to "1"
Then the "Grading method" "field" should be enabled
And the "Grading method" "field" should be enabled
And the "Each attempt builds on the last" "field" should be enabled
And the "id_delay1_enabled" "field" should be enabled
And the "id_delay2_enabled" "field" should be enabled
When I press "Save and display"
And I press "Save and display"
And I navigate to "User overrides" in current page administration
And I click on "Edit" "link" in the "region-main" "region"
And I set the field "Attempts allowed" to "2"
@@ -75,12 +75,12 @@ Feature: Settings form fields disabled if not required
And I navigate to "Edit settings" in current page administration
And I expand all fieldsets
And I set the field "Attempts allowed" to "1"
Then the "Grading method" "field" should be enabled
And the "Grading method" "field" should be enabled
And the "Each attempt builds on the last" "field" should be enabled
And the "id_delay1_enabled" "field" should be enabled
And the "id_delay2_enabled" "field" should be disabled
When I press "Save and display"
And I press "Save and display"
And I navigate to "User overrides" in current page administration
And I press "Add user override"
And I set the following fields to these values:
@@ -91,28 +91,31 @@ Feature: Settings form fields disabled if not required
And I navigate to "Edit settings" in current page administration
And I expand all fieldsets
And I set the field "Attempts allowed" to "1"
Then the "Grading method" "field" should be enabled
And the "Grading method" "field" should be enabled
And the "Each attempt builds on the last" "field" should be enabled
And the "id_delay1_enabled" "field" should be enabled
And the "id_delay2_enabled" "field" should be enabled
@javascript
Scenario: Depending on whether there is a close date, some review options are disabled.
When I add a "Quiz" to section "1"
When I log in as "teacher"
And I am on "Course 1" course homepage with editing mode on
And I add a "Quiz" to section "1"
And I expand all fieldsets
And I set the field "Name" to "Test quiz"
Then the "id_attemptclosed" "checkbox" should be disabled
Then the "id_correctnessclosed" "checkbox" should be disabled
Then the "id_marksclosed" "checkbox" should be disabled
Then the "id_specificfeedbackclosed" "checkbox" should be disabled
Then the "id_generalfeedbackclosed" "checkbox" should be disabled
Then the "id_rightanswerclosed" "checkbox" should be disabled
Then the "id_overallfeedbackclosed" "checkbox" should be disabled
And the "id_correctnessclosed" "checkbox" should be disabled
And the "id_marksclosed" "checkbox" should be disabled
And the "id_specificfeedbackclosed" "checkbox" should be disabled
And the "id_generalfeedbackclosed" "checkbox" should be disabled
And the "id_rightanswerclosed" "checkbox" should be disabled
And the "id_overallfeedbackclosed" "checkbox" should be disabled
And I set the field "id_timeclose_enabled" to "1"
Then the "id_attemptclosed" "checkbox" should be enabled
Then the "id_correctnessclosed" "checkbox" should be enabled
Then the "id_marksclosed" "checkbox" should be enabled
Then the "id_specificfeedbackclosed" "checkbox" should be enabled
Then the "id_generalfeedbackclosed" "checkbox" should be enabled
Then the "id_rightanswerclosed" "checkbox" should be enabled
Then the "id_overallfeedbackclosed" "checkbox" should be enabled
And the "id_attemptclosed" "checkbox" should be enabled
And the "id_correctnessclosed" "checkbox" should be enabled
And the "id_marksclosed" "checkbox" should be enabled
And the "id_specificfeedbackclosed" "checkbox" should be enabled
And the "id_generalfeedbackclosed" "checkbox" should be enabled
And the "id_rightanswerclosed" "checkbox" should be enabled
And the "id_overallfeedbackclosed" "checkbox" should be enabled
And I should not see "Repaginate now"
+6 -4
View File
@@ -39,7 +39,8 @@ class mod_resource_generator extends testing_module_generator {
* text file.
*
* @param array|stdClass $record data for module being generated. Requires 'course' key
* (an id or the full object). Also can have any fields from add module form.
* (an id or the full object). Also can have any fields from add module form, and a
* 'defaultfilename' to set the name of the file created if no draft ID is supplied.
* @param null|array $options general options for course module. Since 2.6 it is
* possible to omit this argument by merging options into $record
* @return stdClass record from module-defined table with additional field
@@ -72,16 +73,17 @@ class mod_resource_generator extends testing_module_generator {
throw new coding_exception('resource generator requires a current user');
}
$usercontext = context_user::instance($USER->id);
$filename = $record->defaultfilename ?? 'resource' . ($this->instancecount + 1) . '.txt';
// Pick a random context id for specified user.
$record->files = file_get_unused_draft_itemid();
// Add actual file there.
$filerecord = array('component' => 'user', 'filearea' => 'draft',
$filerecord = ['component' => 'user', 'filearea' => 'draft',
'contextid' => $usercontext->id, 'itemid' => $record->files,
'filename' => 'resource' . ($this->instancecount+1) . '.txt', 'filepath' => '/');
'filename' => $filename, 'filepath' => '/'];
$fs = get_file_storage();
$fs->create_file_from_string($filerecord, 'Test resource ' . ($this->instancecount+1) . ' file');
$fs->create_file_from_string($filerecord, 'Test resource ' . $filename . ' file');
}
// Do work to actually add the instance.
+16 -1
View File
@@ -70,6 +70,21 @@ class mod_resource_generator_testcase extends advanced_testcase {
// Check that generated resource module contains a file.
$fs = get_file_storage();
$files = $fs->get_area_files($context->id, 'mod_resource', 'content', false, '', false);
$this->assertEquals(1, count($files));
$file = array_values($files)[0];
$this->assertCount(1, $files);
$this->assertEquals('resource3.txt', $file->get_filename());
$this->assertEquals('Test resource resource3.txt file', $file->get_content());
// Create a new resource specifying the file name.
$resource = $generator->create_instance(['course' => $SITE->id, 'defaultfilename' => 'myfile.pdf']);
// Check that generated resource module contains a file with the specified name.
$cm = get_coursemodule_from_instance('resource', $resource->id);
$context = \context_module::instance($cm->id);
$files = $fs->get_area_files($context->id, 'mod_resource', 'content', false, '', false);
$file = array_values($files)[0];
$this->assertCount(1, $files);
$this->assertEquals('myfile.pdf', $file->get_filename());
$this->assertEquals('Test resource myfile.pdf file', $file->get_content());
}
}
+1 -1
View File
@@ -496,7 +496,7 @@ function scorm_user_complete($course, $user, $mod, $scorm) {
$report .= html_writer::start_tag('li').html_writer::start_tag('ul', array('class' => $liststyle));
foreach ($usertrack as $element => $value) {
if (substr($element, 0, 3) == 'cmi') {
$report .= html_writer::tag('li', $element.' => '.s($value));
$report .= html_writer::tag('li', s($element) . ' => ' . s($value));
}
}
$report .= html_writer::end_tag('ul').html_writer::end_tag('li');
+2 -2
View File
@@ -144,9 +144,9 @@ foreach ($trackdata as $element => $value) {
}
if (empty($string) || $table->is_downloading()) {
$row[] = $element;
$row[] = s($element);
} else {
$row[] = $element.$OUTPUT->help_icon($string, 'scorm');
$row[] = s($element) . $OUTPUT->help_icon($string, 'scorm');
}
if (strpos($element, '_time') === false) {
$row[] = s($value);
+2 -1
View File
@@ -152,7 +152,8 @@ class qformat_blackboard_six extends qformat_blackboard_six_base {
}
if ($examfile->getAttribute('type') == 'assessment/x-bb-pool') {
if ($examfile->getAttribute('baseurl')) {
$fileobj->filebase = $this->tempdir. '/' . $examfile->getAttribute('baseurl');
$fileobj->filebase = clean_param($this->tempdir . '/'
. $examfile->getAttribute('baseurl'), PARAM_SAFEPATH);
}
if ($content = $this->get_filecontent($examfile->getAttribute('file'))) {
$fileobj->filetype = self::FILETYPE_POOL;
+2 -2
View File
@@ -29,9 +29,9 @@
defined('MOODLE_INTERNAL') || die();
$version = 2020061514.00; // 20200615 = branching date YYYYMMDD - do not modify!
$version = 2020061515.00; // 20200615 = branching date YYYYMMDD - do not modify!
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
$release = '3.9.14 (Build: 20220509)'; // Human-friendly version name
$release = '3.9.15 (Build: 20220711)'; // Human-friendly version name
$branch = '39'; // This version's branch.
$maturity = MATURITY_STABLE; // This version's maturity level.