Compare commits

..

35 Commits

Author SHA1 Message Date
Jun Pataleta f810374a0e Moodle release 4.3.2 2023-12-22 10:00:26 +08:00
Sara Arjona 6c73be6e8e weekly release 4.3.1+ 2023-12-21 13:34:17 +01:00
Huong Nguyen 5a7599385f Merge branch 'MDL-80151-403' of https://github.com/lameze/moodle into MOODLE_403_STABLE 2023-12-20 15:39:54 +07:00
Huong Nguyen eae457d10d MDL-80151 behat: Improve viewing site calendar step 2023-12-20 16:32:38 +08:00
Angelia Dela Cruz 56eb910aed MDL-80151 mod_chat: Behat for chat repeat times and past session view 2023-12-20 16:32:38 +08:00
Simey Lameze 11dcef291d MDL-80151 behat: add calendar step to navigate to specific view 2023-12-20 16:32:38 +08:00
Jun Pataleta 42cbafd610 Merge branch 'MDL-68124-403' of https://github.com/sarjona/moodle into MOODLE_403_STABLE 2023-12-20 15:01:05 +08:00
Jun Pataleta fb1752711e Merge branch 'MDL-80393-403' of https://github.com/paulholden/moodle into MOODLE_403_STABLE 2023-12-19 17:25:10 +08:00
Paul Holden 99eb359503 MDL-80393 lang: expose current page language to JS modules.
Since 4be39296 the root HTML element contains the iso6391 language
code of the current language pack. However various JS modules need
access to the original Moodle language pack code, so expose that
in the `M.cfg` structure.
2023-12-19 07:47:22 +00:00
Sara Arjona 6f344ff877 MDL-68124 lesson: Fix random behat error with Oracle
This random error was caused because when reviewing an attempt,
the first pageid to display is calculated based on the timeseen
field in the lesson_attempst table.
In Oracle, sometimes it returned the second pageid (because both
had the same timeseen), so which led to these random failures.

Apart from adding 1-second wait when the attempt is created (to
guarantee the first page is returned properly), a few more
improvements have been also made, like removing manual steps for
setting the lesson activity or adding some extra checks to
confirm the information displayed is expected.
2023-12-18 09:48:48 +01:00
Huong Nguyen 68c20d41fe Merge branch 'MDL-80410-403' of https://github.com/lucaboesch/moodle into MOODLE_403_STABLE 2023-12-18 14:09:47 +07:00
Luca Bösch 4272fc3f28 MDL-80410 resource: File icon without 'nofilter' 2023-12-15 07:01:47 +01:00
Huong Nguyen 1bd5dfe090 weekly release 4.3.1+ 2023-12-15 08:44:19 +07:00
Huong Nguyen f8d0e86350 Merge branch 'install_403_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_403_STABLE 2023-12-15 08:44:13 +07:00
Huong Nguyen e092c75d15 Merge branch 'MDL-79828-403' of https://github.com/andelacruz/moodle into MOODLE_403_STABLE 2023-12-14 10:14:43 +07:00
Huong Nguyen 90b3f5fc26 Merge branch 'MDL-79050-403' of https://github.com/andelacruz/moodle into MOODLE_403_STABLE 2023-12-14 09:56:47 +07:00
Huong Nguyen c77bef3ad7 Merge branch 'MDL-80394-403' of https://github.com/ilyatregubov/moodle into MOODLE_403_STABLE 2023-12-14 09:22:42 +07:00
Ilya Tregubov 260eef0a72 Merge branch 'MDL-79938-m403' of https://github.com/sammarshallou/moodle into MOODLE_403_STABLE 2023-12-14 08:50:54 +08:00
AMOS bot af0fe95d84 Automatically generated installer lang files 2023-12-14 00:11:12 +00:00
Sara Arjona ee0c928f10 Merge branch 'MDL-80233-403' of https://github.com/aanabit/moodle into MOODLE_403_STABLE 2023-12-13 10:22:10 +01:00
Angelia Dela Cruz d1796d1f0e MDL-79050 Behat: Add behat coverage to retake lesson activity 2023-12-13 16:25:13 +08:00
Huong Nguyen ac2acd0072 Merge branch 'MDL-74466-403' of https://github.com/paulholden/moodle into MOODLE_403_STABLE 2023-12-13 09:28:57 +07:00
Ilya Tregubov 155abc6945 MDL-80394 core_grades: Revert changes to graded_users_iterator
Third party plugins are using it and change was breaking it
2023-12-13 09:38:14 +08:00
Ilya Tregubov cf0b182dee Merge branch 'MDL-80003_403' of https://github.com/PhilippImhof/moodle into MOODLE_403_STABLE 2023-12-13 09:22:16 +08:00
Sara Arjona f863084cc9 Merge branch 'MDL-77572_403' of https://github.com/santoshndev/moodle into MOODLE_403_STABLE 2023-12-12 17:55:57 +01:00
sam marshall 2ddad8ce7a MDL-79938 Behat: Add steps to support multiple tabs 2023-12-12 16:43:08 +00:00
Santosh Nagargoje 8d45a9f2a2 MDL-77572 Course: Added fix for section,activity visibility capabilities
added fix
2023-12-12 21:38:45 +05:30
Amaia Anabitarte 4b0a34f08a MDL-80233 completion: Use default for empty values 2023-12-12 16:34:11 +01:00
Sara Arjona 3a7f291d38 Merge branch 'MDL-80315-403' of https://github.com/andelacruz/moodle into MOODLE_403_STABLE 2023-12-12 14:19:52 +01:00
Sara Arjona bbfc6312a8 Merge branch 'MDL-79835_403' of https://github.com/timhunt/moodle into MOODLE_403_STABLE 2023-12-12 14:11:41 +01:00
Philipp Imhof 983921ecff MDL-80003 mod_quiz: watch all instances of tinyMCE 2023-12-07 17:29:58 +01:00
Tim Hunt d7a1ef5dfa MDL-79835 behat_core_generator: remove misleading error obfuscation 2023-12-06 18:12:20 +00:00
Angelia Dela Cruz 0cb2fd1a7f MDL-80315 block_comments: Removed duplicate Behat test
Deleted the duplicated course comments block behat test. The file
blocks/comments/tests/behat/add_comment.feature is deleted as a
result.
2023-12-06 18:18:53 +08:00
Paul Holden 4ac4b93064 MDL-74466 repository: avoid passing around sesskey when not required. 2023-12-04 09:22:32 +00:00
Angelia Dela Cruz 26c5ffd356 MDL-79828 core_badges: Delete already awarded course badges 2023-11-21 16:30:12 +08:00
41 changed files with 502 additions and 170 deletions
+32 -43
View File
@@ -49,26 +49,11 @@ if ($action == 'newon') {
admin_externalpage_setup($pagename);
$sesskeyurl = $CFG->wwwroot.'/'.$CFG->admin.'/repository.php?sesskey=' . sesskey();
$baseurl = $CFG->wwwroot.'/'.$CFG->admin.'/repository.php';
$configstr = get_string('manage', 'repository');
// The URL used for redirection, and that all edit related URLs will be based off.
$baseurl = new moodle_url('/admin/repository.php');
$return = true;
if (!empty($action)) {
require_sesskey();
}
/**
* Helper function that generates a moodle_url object
* relevant to the repository
*/
function repository_action_url($repository) {
global $baseurl;
return new moodle_url($baseurl, array('sesskey'=>sesskey(), 'repos'=>$repository));
}
if (($action == 'edit') || ($action == 'new')) {
$pluginname = '';
if ($action == 'edit') {
@@ -182,16 +167,12 @@ if (($action == 'edit') || ($action == 'new')) {
}
}
} else if ($action == 'show') {
if (!confirm_sesskey()) {
throw new \moodle_exception('confirmsesskeybad', '', $baseurl);
}
require_sesskey();
$class = \core_plugin_manager::resolve_plugininfo_class('repository');
$class::enable_plugin($repository, 1);
$return = true;
} else if ($action == 'hide') {
if (!confirm_sesskey()) {
throw new \moodle_exception('confirmsesskeybad', '', $baseurl);
}
require_sesskey();
$class = \core_plugin_manager::resolve_plugininfo_class('repository');
$class::enable_plugin($repository, 0);
$return = true;
@@ -199,9 +180,7 @@ if (($action == 'edit') || ($action == 'new')) {
$repositorytype = repository::get_type_by_typename($repository);
if ($sure) {
$PAGE->set_pagetype('admin-repository-' . $repository);
if (!confirm_sesskey()) {
throw new \moodle_exception('confirmsesskeybad', '', $baseurl);
}
require_sesskey();
if ($repositorytype->delete($downloadcontents)) {
// Include this information into config changes table.
@@ -220,20 +199,15 @@ if (($action == 'edit') || ($action == 'new')) {
$output = $OUTPUT->box_start('generalbox', 'notice');
$output .= html_writer::tag('p', $message);
$removeurl = new moodle_url($sesskeyurl);
$removeurl->params(array(
$removeurl = new moodle_url($baseurl, [
'action' =>'delete',
'repos' => $repository,
'sure' => 'yes',
));
]);
$removeanddownloadurl = new moodle_url($sesskeyurl);
$removeanddownloadurl->params(array(
'action' =>'delete',
'repos'=> $repository,
'sure' => 'yes',
$removeanddownloadurl = new moodle_url($removeurl, [
'downloadcontents' => 1,
));
]);
$output .= $OUTPUT->single_button($removeurl, get_string('continueuninstall', 'repository'));
$output .= $OUTPUT->single_button($removeanddownloadurl, get_string('continueuninstallanddownload', 'repository'));
@@ -245,9 +219,11 @@ if (($action == 'edit') || ($action == 'new')) {
$return = false;
}
} else if ($action == 'moveup') {
require_sesskey();
$repositorytype = repository::get_type_by_typename($repository);
$repositorytype->move_order('up');
} else if ($action == 'movedown') {
require_sesskey();
$repositorytype = repository::get_type_by_typename($repository);
$repositorytype->move_order('down');
} else {
@@ -339,8 +315,7 @@ if (($action == 'edit') || ($action == 'new')) {
$userinstancenumbertext = "";
}
$settings .= '<a href="' . $sesskeyurl . '&amp;action=edit&amp;repos=' . $typename . '">' . $settingsstr .'</a>';
$settings = html_writer::link(new moodle_url($baseurl, ['action' => 'edit', 'repos' => $typename]), $settingsstr);
$settings .= $OUTPUT->container_start('mdl-left');
$settings .= '<br/>';
$settings .= $admininstancenumbertext;
@@ -357,22 +332,34 @@ if (($action == 'edit') || ($action == 'new')) {
$currentaction = 'hide';
}
$select = new single_select(repository_action_url($typename, 'repos'), 'action', $actionchoicesforexisting, $currentaction, null, 'applyto' . basename($typename));
// Active toggle.
$selectaction = new moodle_url($baseurl, ['sesskey' => sesskey(), 'repos' => $typename]);
$select = new single_select($selectaction, 'action', $actionchoicesforexisting, $currentaction, null,
'applyto' . basename($typename));
$select->set_label(get_string('action'), array('class' => 'accesshide'));
// Display up/down link
$updown = '';
$spacer = $OUTPUT->spacer(array('height'=>15, 'width'=>15)); // should be done with CSS instead
if ($updowncount > 1) {
$updown .= "<a href=\"$sesskeyurl&amp;action=moveup&amp;repos=".$typename."\">";
$updown .= $OUTPUT->pix_icon('t/up', get_string('moveup')) . "</a>&nbsp;";
$moveupaction = new moodle_url($baseurl, [
'sesskey' => sesskey(),
'action' => 'moveup',
'repos' => $typename,
]);
$updown .= html_writer::link($moveupaction, $OUTPUT->pix_icon('t/up', get_string('moveup'))) . '&nbsp;';
}
else {
$updown .= $spacer;
}
if ($updowncount < $totalrepositorytypes) {
$updown .= "<a href=\"$sesskeyurl&amp;action=movedown&amp;repos=".$typename."\">";
$updown .= $OUTPUT->pix_icon('t/down', get_string('movedown')) . "</a>&nbsp;";
$movedownaction = new moodle_url($baseurl, [
'sesskey' => sesskey(),
'action' => 'movedown',
'repos' => $typename,
]);
$updown .= html_writer::link($movedownaction, $OUTPUT->pix_icon('t/down', get_string('movedown'))) . '&nbsp;';
}
else {
$updown .= $spacer;
@@ -400,7 +387,9 @@ if (($action == 'edit') || ($action == 'new')) {
foreach ($plugins as $plugin => $dir) {
// Check that it has not already been listed
if (!in_array($plugin, $alreadyplugins)) {
$select = new single_select(repository_action_url($plugin, 'repos'), 'action', $actionchoicesfornew, 'delete', null, 'applyto' . basename($plugin));
$selectaction = new moodle_url($baseurl, ['sesskey' => sesskey(), 'repos' => $plugin]);
$select = new single_select($selectaction, 'action', $actionchoicesfornew, 'delete', null,
'applyto' . basename($plugin));
$select->set_label(get_string('action'), array('class' => 'accesshide'));
$uninstall = '';
if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('repository_' . $plugin, 'manage')) {
+9 -10
View File
@@ -18,8 +18,6 @@ require_once(__DIR__ . '/../config.php');
require_once($CFG->dirroot . '/repository/lib.php');
require_once($CFG->libdir . '/adminlib.php');
require_sesskey();
// id of repository
$edit = optional_param('edit', 0, PARAM_INT);
$new = optional_param('new', '', PARAM_PLUGIN);
@@ -43,12 +41,8 @@ if ($edit){
admin_externalpage_setup($pagename, '', null, new moodle_url('/admin/repositoryinstance.php'));
$baseurl = new moodle_url("/$CFG->admin/repositoryinstance.php", array('sesskey'=>sesskey()));
$parenturl = new moodle_url("/$CFG->admin/repository.php", array(
'sesskey'=>sesskey(),
'action'=>'edit',
));
// The URL used for redirection, and that all edit related URLs will be based off.
$parenturl = new moodle_url('/admin/repository.php', ['action' => 'edit']);
if ($new) {
$parenturl->param('repos', $new);
@@ -116,6 +110,7 @@ if (!empty($edit) || !empty($new)) {
$return = false;
}
} else if (!empty($hide)) {
require_sesskey();
$instance = repository::get_type_by_typename($hide);
$instance->hide();
core_plugin_manager::reset_caches();
@@ -129,6 +124,7 @@ if (!empty($edit) || !empty($new)) {
throw new repository_exception('nopermissiontoaccess', 'repository');
}
if ($sure) {
require_sesskey();
if ($instance->delete($downloadcontents)) {
$deletedstr = get_string('instancedeleted', 'repository');
core_plugin_manager::reset_caches();
@@ -141,14 +137,17 @@ if (!empty($edit) || !empty($new)) {
echo $OUTPUT->header();
echo $OUTPUT->box_start('generalbox', 'notice');
$continueurl = new moodle_url($baseurl, array(
$continueurl = new moodle_url($PAGE->url, [
'type' => $type,
'delete' => $delete,
'sure' => 'yes',
));
]);
$continueanddownloadurl = new moodle_url($continueurl, array(
'downloadcontents' => 1
));
$message = get_string('confirmdelete', 'repository', $instance->name);
echo html_writer::tag('p', $message);
+34
View File
@@ -0,0 +1,34 @@
@tool_behat
Feature: Confirm that we can open multiple browser tabs
In order to use multiple browser tabs
As a test writer
I need the relevant Behat steps to work
@javascript @_switch_window
Scenario: Open multiple browser tabs
Given the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
| Course 2 | C2 |
| Course 3 | C3 |
And I am on the "C1" "Course" page logged in as "admin"
# Open a new tab on the same page.
When I open a tab named "CourseViewer1" on the current page
And I should see "Course 1" in the "h1" "css_element"
And I am on the "C2" "Course" page
# Open new tab for specified page with identifier.
And I open a tab named "CourseViewer2" on the "C3" "Course" page
# And for a specified page without identifier.
And I open a tab named "CourseViewer4" on the "My courses" page
# Switch between all the tabs and confirm their different contents.
Then I should see "You're not enrolled in any course"
And I switch to "CourseViewer2" tab
And I should see "Course 3" in the "h1" "css_element"
And I switch to "CourseViewer1" tab
And I should see "Course 2" in the "h1" "css_element"
And I switch to the main tab
And I should see "Course 1" in the "h1" "css_element"
@@ -0,0 +1,53 @@
@core @core_badges
Feature: Delete course badge already awarded
As a teacher
I can delete awarded course badge
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
And the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
@javascript
Scenario Outline: Delete course badge already awarded to student
Given the following "core_badges > Badges" exist:
| name | course | description | image | status | type |
| <badgename> | C1 | <badgename> description | badges/tests/behat/badge.png | active | 2 |
And the following "core_badges > Criterias" exist:
| badge | role |
| <badgename> | editingteacher |
And the following "core_badges > Issued badges" exist:
| badge | user |
| <badgename> | student1 |
When I am on the "Course 1" "enrolled users" page logged in as "teacher1"
And I click on "Student 1" "link"
Then I should see "<badgename>"
And I am on the "Course 1" course page
# Navigate to Manage Badges page in order to delete the badge
And I navigate to "Badges > Manage badges" in current page administration
# Delete the badge
And I open the action menu in "<badgename>" "table_row"
And I choose "Delete" in the open action menu
And I press "<deleteoption>"
And I am on the "Course 1" "enrolled users" page
And I click on "Student 1" "link"
# Confirm that Badge 1 is retained as awarded badge but Badge 2 is not
And I <visibility> see "<badgename>"
And I am on the "Course 1" course page
# Navigate to Badges page to confirm that no badges exist, hence, Manage badges would not exist
And I navigate to "Badges" in current page administration
# Confirm that badges are sucessfully deleted
And I should see "There are currently no badges available for users to earn."
Examples:
| badgename | deleteoption | visibility |
| Badge 1 | Delete and keep existing issued badges | should |
| Badge 2 | Delete and remove existing issued badges | should not |
@@ -25,11 +25,16 @@ Feature: Add a comment to the comments block
Scenario: Add a comment with Javascript enabled
When I add "I'm a comment from student1" comment to comments block
Then I should see "I'm a comment from student1"
And I am on the "Course 1" course page logged in as teacher1
And I should see "I'm a comment from student1"
Scenario: Add a comment with Javascript disabled
When I follow "Show comments"
And I add "I'm a comment from student1" comment to comments block
Then I should see "I'm a comment from student1"
And I am on the "Course 1" course page logged in as teacher1
And I follow "Show comments"
And I should see "I'm a comment from student1"
@javascript
Scenario: Test comment block pagination
@@ -1,27 +0,0 @@
@block @block_comments
Feature: Enable Block comments on a course page and view comments
In order to enable the comments block on a course page
As a teacher
I can add the comments block to the course page
Scenario: Add the comments block on the course page and add comments
Given the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | Frist | teacher1@example.com |
| student1 | Student | First | student1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "blocks" exist:
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
| comments | Course | C1 | course-view-* | side-pre |
And I am on the "Course 1" course page logged in as teacher1
And I follow "Show comments"
And I add "I'm a comment from the teacher" comment to comments block
When I am on the "Course 1" course page logged in as student1
And I follow "Show comments"
Then I should see "I'm a comment from the teacher"
+17 -1
View File
@@ -176,7 +176,23 @@ class behat_calendar extends behat_base {
* @return void
*/
public function i_am_viewing_site_calendar() {
$url = new moodle_url('/calendar/view.php', ['view' => 'month']);
$this->i_am_viewing_calendar_in_view('month');
}
/**
* Navigate to a specific view in the calendar.
*
* @Given /^I am viewing calendar in "([^"]+)" view$/
* @param string $view The calendar view ('month', 'day' and 'upcoming') to navigate to.
* @return void
*/
public function i_am_viewing_calendar_in_view(string $view): void {
if (!in_array($view, ['month', 'day', 'upcoming'])) {
throw new Exception("Invalid calendar view. Allowed values are: 'month', 'day' and 'upcoming'");
}
$url = new moodle_url('/calendar/view.php', ['view' => $view]);
$this->execute('behat_general::i_visit', [$url]);
}
}
+1 -1
View File
@@ -528,7 +528,7 @@ class manager {
$data['customrules'] = $customdata ? json_encode($customdata) : null;
$defaults['customrules'] = null;
}
$data = array_intersect_key($data, $defaults);
$data = array_merge($defaults, $data);
// Get names of the affected modules.
list($modidssql, $params) = $DB->get_in_or_equal($modids);
@@ -29,13 +29,19 @@ Feature: Allow teachers to edit the default activity completion rules in a cours
And I set the field "Course completion tertiary navigation" to "Default activity completion"
And I click on "Expand Assignment" "button"
And I set the following fields to these values:
| Add requirements | 1 |
| completionview_assign | 0 |
| completionusegrade_assign | 1 |
| completionsubmit_assign | 1 |
| Add requirements | 1 |
| completionview_assign | 1 |
| completionusegrade_assign | 1 |
| completionsubmit_assign | 1 |
And I should not see "Cancel" in the "[data-region='activitycompletion-forum']" "css_element"
And I click on "Save changes" "button" in the "[data-region='activitycompletion-assign']" "css_element"
Then I should see "Changes saved"
And I navigate to "Course completion" in current page administration
And I set the field "Course completion tertiary navigation" to "Default activity completion"
And I click on "Expand Assignment" "button"
And I set the following fields to these values:
| completionview_assign | 0 |
And I click on "Save changes" "button" in the "[data-region='activitycompletion-assign']" "css_element"
And I am on "Course 1" course homepage with editing mode on
And I press "Add an activity or resource"
And I click on "Add a new Assignment" "link" in the "Add an activity or resource" "dialogue"
@@ -25,6 +25,7 @@
namespace core_courseformat\output\local\content;
use cm_info;
use context_course;
use core\output\named_templatable;
use core_availability\info_module;
use core_courseformat\base as course_format;
@@ -300,7 +301,13 @@ class cm implements named_templatable, renderable {
* @return bool if the cm has editor data
*/
protected function add_editor_data(stdClass &$data, renderer_base $output): bool {
if (!$this->format->show_editor()) {
$course = $this->format->get_course();
$coursecontext = context_course::instance($course->id);
$editcaps = [];
if (has_capability('moodle/course:activityvisibility', $coursecontext)) {
$editcaps = ['moodle/course:activityvisibility'];
}
if (!$this->format->show_editor($editcaps)) {
return false;
}
$returnsection = $this->format->get_section_number();
@@ -283,11 +283,16 @@ class section implements named_templatable, renderable {
* @return bool if the cm has name data
*/
protected function add_editor_data(stdClass &$data, renderer_base $output): bool {
if (!$this->format->show_editor()) {
$course = $this->format->get_course();
$coursecontext = context_course::instance($course->id);
$editcaps = [];
if (has_capability('moodle/course:sectionvisibility', $coursecontext)) {
$editcaps = ['moodle/course:sectionvisibility'];
}
if (!$this->format->show_editor($editcaps)) {
return false;
}
$course = $this->format->get_course();
if (empty($this->hidecontrols)) {
$controlmenu = new $this->controlmenuclass($this->format, $this->section);
$data->controlmenu = $controlmenu->export_for_template($output);
+1 -1
View File
@@ -1697,7 +1697,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
}
// Move (only for component compatible formats).
if ($usecomponents) {
if ($hasmanageactivities && $usecomponents) {
$actions['move'] = new action_menu_link_secondary(
new moodle_url($baseurl, [
'sesskey' => sesskey(),
+3 -7
View File
@@ -183,12 +183,7 @@ class graded_users_iterator {
}
}
$userfieldsapi = \core_user\fields::for_identity($coursecontext, false)->with_userpic();
$userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
// This need to be fixed - webservices in grade/report/user/classes/external/user.php don't check permission properly.
$userfields .= ', u.idnumber, u.institution, u.department';
$userfields = 'u.*';
$customfieldssql = '';
if ($this->allowusercustomfields && !empty($CFG->grade_export_customprofilefields)) {
$customfieldscount = 0;
@@ -222,7 +217,8 @@ class graded_users_iterator {
$this->users_rs = $DB->get_recordset_sql($users_sql, $params);
if (!$this->onlyactive) {
$this->suspendedusers = get_suspended_userids($coursecontext);
$context = context_course::instance($this->course->id);
$this->suspendedusers = get_suspended_userids($context);
} else {
$this->suspendedusers = array();
}
+33
View File
@@ -0,0 +1,33 @@
<?php
// This file is part of Moodle - https://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 <https://www.gnu.org/licenses/>.
/**
* Automatically generated strings for Moodle installer
*
* Do not edit this file manually! It contains just a subset of strings
* needed during the very first steps of installation. This file was
* generated automatically by export-installer.php (which is part of AMOS
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
* list of strings defined in /install/stringnames.txt.
*
* @package installer
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$string['language'] = 'Lingua';
$string['moodlelogo'] = 'Mercu Moodle';
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -1,4 +1,4 @@
define("core/str",["exports","jquery","core/ajax","core/localstorage"],(function(_exports,_jquery,_ajax,_localstorage){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
define("core/str",["exports","jquery","core/ajax","core/config","core/localstorage"],(function(_exports,_jquery,_ajax,_config,_localstorage){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
/**
* Fetch and return language strings.
*
@@ -7,6 +7,6 @@ define("core/str",["exports","jquery","core/ajax","core/localstorage"],(function
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 2.9
*
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.get_strings=_exports.get_string=_exports.getStrings=_exports.getString=_exports.cache_strings=void 0,_jquery=_interopRequireDefault(_jquery),_ajax=_interopRequireDefault(_ajax),_localstorage=_interopRequireDefault(_localstorage);let promiseCache=[];_exports.get_string=(key,component,param,lang)=>get_strings([{key:key,component:component,param:param,lang:lang}]).then((results=>results[0]));_exports.getString=(key,component,param,lang)=>getRequestedStrings([{key:key,component:component,param:param,lang:lang}])[0];_exports.getStrings=requests=>Promise.all(getRequestedStrings(requests));const getRequestedStrings=requests=>{let requestData=[];const pageLang=(0,_jquery.default)("html").attr("lang").replace(/-/g,"_"),stringPromises=requests.map((request=>{let{component:component,key:key,param:param,lang:lang=pageLang}=request;component||(component="core");const cacheKey=(_ref=>{let{key:key,component:component,lang:lang=pageLang}=_ref;return"core_str/".concat(key,"/").concat(component,"/").concat(lang)})({key:key,component:component,lang:lang}),buildReturn=promise=>(promiseCache[cacheKey]=promise,promise);if(component in M.str&&key in M.str[component])return buildReturn(new Promise((resolve=>{resolve(M.util.get_string(key,component,param,lang))})));const cached=_localstorage.default.get(cacheKey);return cached?(M.str[component]={...M.str[component],[key]:cached},buildReturn(new Promise((resolve=>{resolve(M.util.get_string(key,component,param,lang))})))):cacheKey in promiseCache?buildReturn(promiseCache[cacheKey]).then((()=>M.util.get_string(key,component,param,lang))):buildReturn(new Promise(((resolve,reject)=>{requestData.push({methodname:"core_get_string",args:{stringid:key,stringparams:[],component:component,lang:lang},done:str=>{M.str[component]={...M.str[component],[key]:str},_localstorage.default.set(cacheKey,str),resolve(M.util.get_string(key,component,param,lang))},fail:reject})})))}));return requestData.length&&_ajax.default.call(requestData,!0,!1,!1,0,M.cfg.langrev),stringPromises},get_strings=requests=>_jquery.default.when.apply(_jquery.default,getRequestedStrings(requests)).then((function(){for(var _len=arguments.length,strings=new Array(_len),_key=0;_key<_len;_key++)strings[_key]=arguments[_key];return strings}));_exports.get_strings=get_strings;_exports.cache_strings=strings=>{const defaultLang=(0,_jquery.default)("html").attr("lang").replace(/-/g,"_");strings.forEach((_ref2=>{let{key:key,component:component,value:value,lang:lang=defaultLang}=_ref2;const cacheKey=["core_str",key,component,lang].join("/");component in M.str&&key in M.str[component]||(component in M.str||(M.str[component]={}),M.str[component][key]=value),_localstorage.default.get(cacheKey)||_localstorage.default.set(cacheKey,value),cacheKey in promiseCache||(promiseCache[cacheKey]=_jquery.default.Deferred().resolve(value).promise())}))}}));
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.get_strings=_exports.get_string=_exports.getStrings=_exports.getString=_exports.cache_strings=void 0,_jquery=_interopRequireDefault(_jquery),_ajax=_interopRequireDefault(_ajax),_config=_interopRequireDefault(_config),_localstorage=_interopRequireDefault(_localstorage);let promiseCache=[];_exports.get_string=(key,component,param,lang)=>get_strings([{key:key,component:component,param:param,lang:lang}]).then((results=>results[0]));_exports.getString=(key,component,param,lang)=>getRequestedStrings([{key:key,component:component,param:param,lang:lang}])[0];_exports.getStrings=requests=>Promise.all(getRequestedStrings(requests));const getRequestedStrings=requests=>{let requestData=[];const pageLang=_config.default.language,stringPromises=requests.map((request=>{let{component:component,key:key,param:param,lang:lang=pageLang}=request;component||(component="core");const cacheKey=(_ref=>{let{key:key,component:component,lang:lang=pageLang}=_ref;return"core_str/".concat(key,"/").concat(component,"/").concat(lang)})({key:key,component:component,lang:lang}),buildReturn=promise=>(promiseCache[cacheKey]=promise,promise);if(component in M.str&&key in M.str[component])return buildReturn(new Promise((resolve=>{resolve(M.util.get_string(key,component,param,lang))})));const cached=_localstorage.default.get(cacheKey);return cached?(M.str[component]={...M.str[component],[key]:cached},buildReturn(new Promise((resolve=>{resolve(M.util.get_string(key,component,param,lang))})))):cacheKey in promiseCache?buildReturn(promiseCache[cacheKey]).then((()=>M.util.get_string(key,component,param,lang))):buildReturn(new Promise(((resolve,reject)=>{requestData.push({methodname:"core_get_string",args:{stringid:key,stringparams:[],component:component,lang:lang},done:str=>{M.str[component]={...M.str[component],[key]:str},_localstorage.default.set(cacheKey,str),resolve(M.util.get_string(key,component,param,lang))},fail:reject})})))}));return requestData.length&&_ajax.default.call(requestData,!0,!1,!1,0,M.cfg.langrev),stringPromises},get_strings=requests=>_jquery.default.when.apply(_jquery.default,getRequestedStrings(requests)).then((function(){for(var _len=arguments.length,strings=new Array(_len),_key=0;_key<_len;_key++)strings[_key]=arguments[_key];return strings}));_exports.get_strings=get_strings;_exports.cache_strings=strings=>{strings.forEach((_ref2=>{let{key:key,component:component,value:value,lang:lang=_config.default.language}=_ref2;const cacheKey=["core_str",key,component,lang].join("/");component in M.str&&key in M.str[component]||(component in M.str||(M.str[component]={}),M.str[component][key]=value),_localstorage.default.get(cacheKey)||_localstorage.default.set(cacheKey,value),cacheKey in promiseCache||(promiseCache[cacheKey]=_jquery.default.Deferred().resolve(value).promise())}))}}));
//# sourceMappingURL=str.min.js.map
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -5,6 +5,6 @@
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("core/user_date",["jquery","core/ajax","core/sessionstorage","core/config"],(function($,Ajax,Storage,Config){var promisesCache={},getKey=function(request){return"core_user_date/"+$("html").attr("lang").replace(/-/g,"_")+"/"+Config.usertimezone+"/"+request.timestamp+"/"+request.format},loadDatesFromServer=function(dates){var args=dates.map((function(data){var fixDay=data.hasOwnProperty("fixday")?data.fixday:1,fixHour=data.hasOwnProperty("fixhour")?data.fixhour:1;return{timestamp:data.timestamp,format:data.format,type:data.type||null,fixday:fixDay,fixhour:fixHour}})),request={methodname:"core_get_user_dates",args:{contextid:Config.contextid,timestamps:args}};return Ajax.call([request],!0,!0)[0].then((function(results){results.dates.forEach((function(value,index){var date=dates[index];!function(key,value){Storage.set(key,value)}(getKey(date),value),date.deferred.resolve(value)}))})).catch((function(ex){dates.forEach((function(date){date.deferred.reject(ex)}))}))};return{get:function(requests){var ajaxRequests=[],promises=[];return requests.forEach((function(request){var key=getKey(request);if(function(key){return void 0!==promisesCache[key]}(key))promises.push(function(key){return promisesCache[key]}(key));else{var deferred=$.Deferred(),cached=function(key){return Storage.get(key)}(key);cached?deferred.resolve(cached):(request.deferred=deferred,ajaxRequests.push(request)),function(key,promise){promisesCache[key]=promise}(key,deferred.promise()),promises.push(deferred.promise())}})),ajaxRequests.length&&loadDatesFromServer(ajaxRequests),$.when.apply($,promises).then((function(){return 1===arguments.length?[arguments[0]]:Array.apply(null,arguments)}))},getUserMidnightForTimestamp:function(timestamp,todayMidnight){var future=timestamp>todayMidnight,diffSeconds=Math.abs(timestamp-todayMidnight),diffDaysInSeconds=86400*(future?Math.floor(diffSeconds/86400):Math.ceil(diffSeconds/86400));return future?todayMidnight+diffDaysInSeconds:todayMidnight-diffDaysInSeconds}}}));
define("core/user_date",["jquery","core/ajax","core/sessionstorage","core/config"],(function($,Ajax,Storage,Config){var promisesCache={},getKey=function(request){return"core_user_date/"+Config.language+"/"+Config.usertimezone+"/"+request.timestamp+"/"+request.format},loadDatesFromServer=function(dates){var args=dates.map((function(data){var fixDay=data.hasOwnProperty("fixday")?data.fixday:1,fixHour=data.hasOwnProperty("fixhour")?data.fixhour:1;return{timestamp:data.timestamp,format:data.format,type:data.type||null,fixday:fixDay,fixhour:fixHour}})),request={methodname:"core_get_user_dates",args:{contextid:Config.contextid,timestamps:args}};return Ajax.call([request],!0,!0)[0].then((function(results){results.dates.forEach((function(value,index){var date=dates[index];!function(key,value){Storage.set(key,value)}(getKey(date),value),date.deferred.resolve(value)}))})).catch((function(ex){dates.forEach((function(date){date.deferred.reject(ex)}))}))};return{get:function(requests){var ajaxRequests=[],promises=[];return requests.forEach((function(request){var key=getKey(request);if(function(key){return void 0!==promisesCache[key]}(key))promises.push(function(key){return promisesCache[key]}(key));else{var deferred=$.Deferred(),cached=function(key){return Storage.get(key)}(key);cached?deferred.resolve(cached):(request.deferred=deferred,ajaxRequests.push(request)),function(key,promise){promisesCache[key]=promise}(key,deferred.promise()),promises.push(deferred.promise())}})),ajaxRequests.length&&loadDatesFromServer(ajaxRequests),$.when.apply($,promises).then((function(){return 1===arguments.length?[arguments[0]]:Array.apply(null,arguments)}))},getUserMidnightForTimestamp:function(timestamp,todayMidnight){var future=timestamp>todayMidnight,diffSeconds=Math.abs(timestamp-todayMidnight),diffDaysInSeconds=86400*(future?Math.floor(diffSeconds/86400):Math.ceil(diffSeconds/86400));return future?todayMidnight+diffDaysInSeconds:todayMidnight-diffDaysInSeconds}}}));
//# sourceMappingURL=user_date.min.js.map
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -346,7 +346,7 @@ export default class Loader {
component,
template: name,
themename: theme,
lang: $('html').attr('lang').replace(/-/g, '_')
lang: config.language,
}
});
// Remember the index in the requests list for this template so that
+4 -5
View File
@@ -24,6 +24,7 @@
*/
import $ from 'jquery';
import Ajax from 'core/ajax';
import Config from 'core/config';
import LocalStorage from 'core/localstorage';
/**
@@ -129,7 +130,7 @@ export const getStrings = (requests) => Promise.all(getRequestedStrings(requests
*/
const getRequestedStrings = (requests) => {
let requestData = [];
const pageLang = $('html').attr('lang').replace(/-/g, '_');
const pageLang = Config.language;
// Helper function to construct the cache key.
const getCacheKey = ({key, component, lang = pageLang}) => `core_str/${key}/${component}/${lang}`;
@@ -256,13 +257,11 @@ export const get_strings = (requests) => {
* @param {string} strings.key The string identifer to fetch
* @param {string} strings.value The string value
* @param {string} [strings.component='core'] The componet to fetch from
* @param {string} [strings.lang] The language to fetch a string for. Defaults to current page language.
* @param {string} [strings.lang=Config.language] The language to fetch a string for. Defaults to current page language.
*/
// eslint-disable-next-line camelcase
export const cache_strings = (strings) => {
const defaultLang = $('html').attr('lang').replace(/-/g, '_');
strings.forEach(({key, component, value, lang = defaultLang}) => {
strings.forEach(({key, component, value, lang = Config.language}) => {
const cacheKey = ['core_str', key, component, lang].join('/');
// Check M.str caching.
+1 -2
View File
@@ -36,9 +36,8 @@ define(['jquery', 'core/ajax', 'core/sessionstorage', 'core/config'],
* @return {string}
*/
var getKey = function(request) {
var language = $('html').attr('lang').replace(/-/g, '_');
return 'core_user_date/' +
language + '/' +
Config.language + '/' +
Config.usertimezone + '/' +
request.timestamp + '/' +
request.format;
+1 -7
View File
@@ -470,13 +470,7 @@ class behat_core_generator extends behat_generator_base {
}
}
// Custom exception.
try {
$this->datagenerator->create_module($activityname, $data, $cmoptions);
} catch (coding_exception $e) {
throw new Exception('\'' . $activityname . '\' activity can not be added using this step,' .
' use the step \'I add a "ACTIVITY_OR_RESOURCE_NAME_STRING" to section "SECTION_NUMBER"\' instead');
}
$this->datagenerator->create_module($activityname, $data, $cmoptions);
}
/**
+1 -3
View File
@@ -144,10 +144,8 @@ class repository extends base {
if ($hassiteconfig && $this->is_enabled()) {
// Completely no access to repository setting when it is not enabled.
$sectionname = $this->get_settings_section_name();
$settingsurl = new moodle_url('/admin/repository.php',
array('sesskey' => sesskey(), 'action' => 'edit', 'repos' => $this->name));
$settings = new admin_externalpage($sectionname, $this->displayname,
$settingsurl, 'moodle/site:config', false);
new moodle_url('/admin/repository.php', ['action' => 'edit', 'repos' => $this->name]), 'moodle/site:config', false);
$adminroot->add($parentnodename, $settings);
}
}
+1
View File
@@ -324,6 +324,7 @@ class page_requirements_manager {
'admin' => $CFG->admin,
'svgicons' => $page->theme->use_svg_icons(),
'usertimezone' => usertimezone(),
'language' => current_language(),
'courseId' => isset($courseid) ? (int) $courseid : 0,
'courseContextId' => isset($coursecontext) ? $coursecontext->id : 0,
'contextid' => $contextid,
+2 -2
View File
@@ -200,7 +200,7 @@ class behat_general extends behat_base {
/**
* Switches to the specified window. Useful when interacting with popup windows.
*
* @Given /^I switch to "(?P<window_name_string>(?:[^"]|\\")*)" window$/
* @Given /^I switch to "(?P<window_name_string>(?:[^"]|\\")*)" (window|tab)$/
* @param string $windowname
*/
public function switch_to_window($windowname) {
@@ -232,7 +232,7 @@ class behat_general extends behat_base {
/**
* Switches to the main Moodle window. Useful when you finish interacting with popup windows.
*
* @Given /^I switch to the main window$/
* @Given /^I switch to the main (window|tab)$/
*/
public function switch_to_the_main_window() {
$this->switch_to_window(self::MAIN_WINDOW_NAME);
+57
View File
@@ -965,6 +965,63 @@ class behat_navigation extends behat_base {
throw new Exception('Unrecognised core page type "' . $type . '."');
}
/**
* Opens a new tab with given name on the same URL as current page and switches to it.
*
* @param string $name Tab name that can be used for switching later (no whitespace)
* @When /^I open a tab named "(?<name>[^"]*)" on the current page$/
*/
public function i_open_a_tab_on_the_current_page(string $name): void {
$this->open_tab($name, 'location.href');
}
/**
* Opens a new tab with given name on specified page, and switches to it.
*
* @param string $name Tab name that can be used for switching later (no whitespace)
* @param string $page Page name
* @When /^I open a tab named "(?<name>[^"]*)" on the "(?<page>[^"]*)" page$/
*/
public function i_open_a_tab_on_the_page(string $name, string $page): void {
if ($page === 'current') {
$jstarget = 'location.href';
} else {
$jstarget = '"' . addslashes_js($this->resolve_page_helper($page)->out(false)) . '"';
}
$this->open_tab($name, $jstarget);
}
/**
* Opens a new tab with given name (on specified page), and switches to it.
*
* @param string $name Tab name that can be used for switching later (no whitespace)
* @param string $identifier Page identifier
* @param string $page Page type
* @When /^I open a tab named "(?<name>[^"]*)" on the "(?<identifier>[^"]*)" "(?<page>[^"]*)" page$/
*/
public function i_open_a_tab_on_the_page_instance(string $name, string $identifier, string $page): void {
$this->open_tab($name, '"' . addslashes_js(
$this->resolve_page_instance_helper($identifier, $page)->out(false)) . '"');
}
/**
* Opens a new tab at the given target URL.
*
* @param string $name Name for tab
* @param string $jstarget Target in JavaScript syntax, i.e. if a string, must be quoted
*/
protected function open_tab(string $name, string $jstarget): void {
// Tab names aren't allowed spaces, and our JavaScript below doesn't do any escaping.
if (clean_param($name, PARAM_ALPHANUMEXT) !== $name) {
throw new Exception('Tab name may not contain whitespace or special characters: "' . $name . '"');
}
// Normally you can't open a tab unless in response to a user action, but presumably Behat
// is exempt from this restriction, because it works to just open it directly.
$this->execute_script('window.open(' . $jstarget . ', "' . $name . '");');
$this->execute('behat_general::switch_to_window', [$name]);
}
/**
* Opens the course homepage. (Consider using 'I am on the "shortname" "Course" page' step instead.)
*
+4
View File
@@ -1,6 +1,10 @@
This files describes API changes in core libraries and APIs,
information provided here is intended especially for developers.
=== 4.3.2 ===
* The current page language is available in new `core/config` language property for Javascript modules
=== 4.3.1 ===
* Added modalform config object `moduleName` param that can be used to define alternative modal type for the modalform. By default 'core/modal_save_cancel' is used.
@@ -17,18 +17,19 @@ Feature: Chat calendar entries
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
Scenario: Create a chat activity and do not publish the start date to the calendar
Scenario Outline: Create a chat activity with repeated chat times set
# Create an activity with repeated chat times
Given the following "activities" exist:
| activity | name | intro | course | idnumber | schedule |
| chat | Test chat name | Test chat description | C1 | chat1 | 0 |
| activity | course | name | schedule |
| chat | C1 | Chat 1 | <scheduleset> |
And I log in as "teacher1"
When I follow "Calendar" in the user menu
Then I should not see "Test chat name"
# Confirm Chat activity visibility based on schedule
When I am viewing calendar in "upcoming" view
Then I <chatvisibility> see "Chat 1"
Scenario: Create a chat activity and publish the start date to the calendar
Given the following "activities" exist:
| activity | name | intro | course | idnumber | schedule |
| chat | Test chat name | Test chat description | C1 | chat1 | 1 |
And I log in as "teacher1"
When I follow "Calendar" in the user menu
Then I should see "Test chat name"
Examples:
| scheduleset | chatvisibility |
| 0 | should not |
| 1 | should |
| 2 | should |
| 3 | should |
@@ -0,0 +1,47 @@
@mod @mod_chat @javascript
Feature: View past chat sessions
In order for students to view past chat sessions
As a teacher
I need to be able to change the mod/chat:readlog permission for students
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 "course" exist:
| fullname | shortname |
| Course 1 | C1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
Scenario Outline: View past chat sessions
# Display or hide past chat session based on example data
Given the following "activities" exist:
| activity | course | name | studentlogs |
| chat | C1 | Chat 1 | <studentlogvalue> |
And I am on the "Chat 1" "chat activity" page logged in as teacher1
# Display or hide past chat session by default based on mod/chat:readlog setting
And the following "role capability" exists:
| role | student |
| mod/chat:readlog | <readpermission> |
# Enter chat activity to create a session
And I click on "Enter the chat" "link"
# Close chat window
When I close all opened windows
And I reload the page
# Confirm that past chat sessions is always visible for teacher
Then I should see "Past sessions"
# Confirm past chat sessions visibility for student based on settings
And I am on the "Chat 1" "chat activity" page logged in as student1
And I <sessionvisibility> see "Past sessions"
# Regardless of studentlogvalue value if readpermission is allowed, then Past sessions will be visible for students
Examples:
| studentlogvalue | readpermission | sessionvisibility |
| 0 | allow | should |
| 1 | allow | should |
| 0 | prohibit | should not |
| 1 | prohibit | should |
@@ -0,0 +1,82 @@
@mod @mod_lesson
Feature: Retake lesson activity
In order for student to retake a lesson activity
As a teacher
I should be able to allow retakes
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
And the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
# Generate lesson with retakes enabled, maxgrade = 100 and handling of re-takes = use mean
And the following "activities" exist:
| activity | name | course | retake | grade[modgrade_point] | usemaxgrade |
| lesson | Test lesson name | C1 | 1 | 100 | 0 |
# Generate question pages
And the following "mod_lesson > page" exist:
| lesson | qtype | title | content |
| Test lesson name | multichoice | Question 1 | Which is not a plant? |
| Test lesson name | multichoice | Question 2 | Which is a plant? |
| Test lesson name | multichoice | Question 3 | Which is a plant and a colour? |
# Generate question answers
And the following "mod_lesson > answers" exist:
| page | answer | jumpto | score |
| Question 1 | Brown | Next page | 1 |
| Question 1 | Lavender | Next page | 0 |
| Question 2 | Brown | Next page | 0 |
| Question 2 | Lavender | Next page | 1 |
| Question 3 | Brown | Next page | 0 |
| Question 3 | Lavender | Next page | 1 |
Scenario: A student can retake a lesson
# First attempt - all correct
Given I am on the "Test lesson name" "lesson activity" page logged in as student1
And I set the following fields to these values:
| Brown | 1 |
And I press "Submit"
And I set the following fields to these values:
| Lavender | 1 |
And I press "Submit"
And I set the following fields to these values:
| Lavender | 1 |
And I press "Submit"
# Confirm that lesson can be retaken
When I am on the "Test lesson name" "lesson activity" page
Then I should see "Which is not a plant?"
# Second attempt - only 1 correct
And I set the following fields to these values:
| Lavender | 1 |
And I press "Submit"
And I set the following fields to these values:
| Brown | 1 |
And I press "Submit"
And I set the following fields to these values:
| Lavender | 1 |
And I press "Submit"
# Check that grade is 66.67 (mean of the 2 attempts)
And I click on "View grades" "link"
And "Test lesson name" row "Grade" column of "generaltable" table should contain "66.67"
# Change handling of re-takes = use maximum
And I am on the "Test lesson name" "lesson activity editing" page logged in as teacher1
And I set the following fields to these values:
| Handling of re-takes | Use maximum |
And I press "Save and display"
# Confirm that lesson grade is the maximum of the 2 attempts (100)
And I am on the "Course 1" "grades > user > View" page logged in as student1
And "Test lesson name" row "Grade" column of "generaltable" table should contain "100.00"
# Disable lesson retake
And I am on the "Test lesson name" "lesson activity editing" page logged in as teacher1
And I set the following fields to these values:
| Re-takes allowed | No |
And I press "Save and display"
# Confirm lesson cannot be retaken
And I am on the "Test lesson name" "lesson activity" page logged in as student1
And I should see "You are not allowed to retake this lesson."
+43 -18
View File
@@ -16,51 +16,76 @@ Feature: In a lesson activity, students can review the answers they gave to ques
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activity" exist:
| activity | name | course | idnumber |
| lesson | Test lesson name | C1 | lesson1 |
And the following "activity" exists:
| activity | lesson |
| name | Test lesson name |
| course | C1 |
| idnumber | lesson1 |
# Display ongoing score = Yes
| ongoing | 1 |
# Slideshow = Yes
| slideshow | 1 |
# Maximum number of answers = 10
| maxanswers | 10 |
# Allow student review = Yes
| modattempts | 1 |
# Maximum number of attempts per question
| maxattempts | 3 |
# Custom scoring = No
| custom | 0 |
# Re-takes allowed = Yes
| retake | 1 |
And the following "mod_lesson > pages" exist:
| lesson | qtype | title | content |
| Test lesson name | numeric | Hardest question ever | 1 + 1? |
| Test lesson name | truefalse | Next question | Paper is made from trees. |
And the following "mod_lesson > answers" exist:
| page | answer | response | jumpto | score |
| Hardest question ever | 2 | Correct answer | Next page | 1 |
| Hardest question ever | 1 | Incorrect answer | This page | 0 |
| Next question | True | Correct | Next page | 1 |
| Next question | False | Wrong | This page | 0 |
And I am on the "Test lesson name" "lesson activity editing" page logged in as teacher1
And I set the following fields to these values:
| Display ongoing score | Yes |
| Slideshow | Yes |
| Maximum number of answers | 10 |
| Allow student review | Yes |
| Maximum number of attempts per question | 3 |
| Custom scoring | No |
| Re-takes allowed | Yes |
And I press "Save and display"
| page | answer | response | jumpto | score |
| Hardest question ever | 2 | Correct answer 1 | Next page | 1 |
| Hardest question ever | 1 | Incorrect answer 1 | This page | 0 |
| Next question | True | Correct answer 2 | Next page | 1 |
| Next question | False | Incorrect answer 2 | This page | 0 |
And I am on the "Test lesson name" "lesson activity" page logged in as student1
And I should see "You have answered 0 correctly out of 0 attempts."
And I should see "1 + 1?"
And I wait "1" seconds
And I set the following fields to these values:
| Your answer | 1 |
And I press "Submit"
And I should see "You have answered 0 correctly out of 1 attempts."
And I press "Continue"
And I should see "1 + 1?"
And I wait "1" seconds
And I set the following fields to these values:
| Your answer | 2 |
And I press "Submit"
And I should see "You have answered 1 correctly out of 2 attempts."
And I press "Continue"
And I should see "Paper is made from trees."
And I wait "1" seconds
And I set the following fields to these values:
| True | 1 |
And I press "Submit"
And I should see "You have answered 2 correctly out of 3 attempts."
And I should see "Paper is made from trees."
And I press "Continue"
And I should see "Congratulations - end of lesson reached"
And I should see "Number of questions answered: 2"
And I should see "Number of correct answers: 2"
And I should see "Your score is 2 (out of 3)."
When I follow "Review lesson"
Then I should see "You have answered 2 correctly out of 3 attempts."
And I should see "1 + 1?"
And I press "Next page"
And I should see "You have answered 2 correctly out of 3 attempts."
And I should see "1 + 1?"
And I should see "Correct answer 1"
And I press "Continue"
And I should see "You have answered 2 correctly out of 3 attempts."
And I should see "Paper is made from trees."
And I press "Next page"
And I should see "You have answered 2 correctly out of 3 attempts."
And I should see "Paper is made from trees."
And I should see "Correct answer 2"
And I press "Continue"
And I should see "Congratulations - end of lesson reached"
@@ -274,7 +274,12 @@ M.mod_quiz.autosave = {
window.tinyMCE.on('AddEditor', function(event) {
event.editor.on('Change Undo Redo keydown', startSaveTimer);
});
}
// One or more editors might already have been added, so we have to attach
// the event handlers to these as well.
window.tinyMCE.get().forEach(function(editor) {
editor.on('Change Undo Redo keydown', startSaveTimer);
});
}
},
/**
@@ -1 +1 @@
YUI.add("moodle-mod_quiz-autosave",function(s,e){M.mod_quiz=M.mod_quiz||{},M.mod_quiz.autosave={TINYMCE_DETECTION_DELAY:500,TINYMCE_DETECTION_REPEATS:20,WATCH_HIDDEN_DELAY:1e3,FAILURES_BEFORE_NOTIFY:1,FIRST_SUCCESSFUL_SAVE:-1,SELECTORS:{QUIZ_FORM:"#responseform",VALUE_CHANGE_ELEMENTS:'input, textarea, [contenteditable="true"]',CHANGE_ELEMENTS:"input, select",HIDDEN_INPUTS:"input[type=hidden]",CONNECTION_ERROR:"#connection-error",CONNECTION_OK:"#connection-ok"},AUTOSAVE_HANDLER:M.cfg.wwwroot+"/mod/quiz/autosave.ajax.php",delay:12e4,form:null,dirty:!1,delay_timer:null,save_transaction:null,savefailures:0,editor_change_handler:null,hidden_field_values:{},init:function(e){this.form=s.one(this.SELECTORS.QUIZ_FORM),this.form&&(this.delay=1e3*e,this.form.delegate("valuechange",this.value_changed,this.SELECTORS.VALUE_CHANGE_ELEMENTS,this),this.form.delegate("change",this.value_changed,this.SELECTORS.CHANGE_ELEMENTS,this),this.form.on("submit",this.stop_autosaving,this),require(["core_form/events"],function(e){window.addEventListener(e.eventTypes.uploadChanged,this.value_changed.bind(this))}.bind(this)),this.init_tinymce(this.TINYMCE_DETECTION_REPEATS),this.save_hidden_field_values(),this.watch_hidden_fields())},save_hidden_field_values:function(){this.form.all(this.SELECTORS.HIDDEN_INPUTS).each(function(e){var t=e.get("name");t&&(this.hidden_field_values[t]=e.get("value"))},this)},watch_hidden_fields:function(){this.detect_hidden_field_changes(),s.later(this.WATCH_HIDDEN_DELAY,this,this.watch_hidden_fields)},detect_hidden_field_changes:function(){this.form.all(this.SELECTORS.HIDDEN_INPUTS).each(function(e){var t=e.get("name"),i=e.get("value");t&&(t in this.hidden_field_values&&i===this.hidden_field_values[t]||(this.hidden_field_values[t]=i,this.value_changed({target:e})))},this)},init_tinymce:function(e){var t;"undefined"!=typeof window.tinyMCE?(this.editor_change_handler=s.bind(this.editor_changed,this),window.tinyMCE.onAddEditor?window.tinyMCE.onAddEditor.add(s.bind(this.init_tinymce_editor,this)):window.tinyMCE.on&&(t=this.start_save_timer_if_necessary.bind(this),window.tinyMCE.on("AddEditor",function(e){e.editor.on("Change Undo Redo keydown",t)}))):0<e&&s.later(this.TINYMCE_DETECTION_DELAY,this,this.init_tinymce,[e-1])},init_tinymce_editor:function(e,t){t.onChange.add(this.editor_change_handler),t.onRedo.add(this.editor_change_handler),t.onUndo.add(this.editor_change_handler),t.onKeyDown.add(this.editor_change_handler)},value_changed:function(e){var t=e.target.getAttribute("name");"thispage"===t||"scrollpos"===t||t&&t.match(/_:flagged$/)||(t=t||"#"+e.target.getAttribute("id"),this.start_save_timer_if_necessary())},editor_changed:function(e){this.start_save_timer_if_necessary()},start_save_timer_if_necessary:function(){this.dirty=!0,this.delay_timer||this.save_transaction||this.start_save_timer()},start_save_timer:function(){this.cancel_delay(),this.delay_timer=s.later(this.delay,this,this.save_changes)},cancel_delay:function(){this.delay_timer&&!0!==this.delay_timer&&this.delay_timer.cancel(),this.delay_timer=null},save_changes:function(){var e;this.cancel_delay(),this.dirty=!1,this.is_time_nearly_over()?this.stop_autosaving():("undefined"!=typeof window.tinyMCE&&window.tinyMCE.triggerSave(),(e=this.form.all("input[type=submit], button[type=submit]")).setAttribute("type","button"),this.save_transaction=s.io(this.AUTOSAVE_HANDLER,{method:"POST",form:{id:this.form},on:{success:this.save_done,failure:this.save_failed},context:this}),e.setAttribute("type","submit"))},save_done:function(e,t){var i=JSON.parse(t.responseText);"OK"===i.status?("undefined"!=typeof i.timeleft&&M.mod_quiz.timer.updateEndTime(i.timeleft),this.save_transaction=null,this.dirty&&this.start_save_timer(),0<this.savefailures?(s.one(this.SELECTORS.CONNECTION_ERROR).hide(),s.one(this.SELECTORS.CONNECTION_OK).show(),this.savefailures=this.FIRST_SUCCESSFUL_SAVE):this.savefailures===this.FIRST_SUCCESSFUL_SAVE&&(s.one(this.SELECTORS.CONNECTION_OK).hide(),this.savefailures=0)):this.save_failed(e,t)},save_failed:function(){this.save_transaction=null,this.start_save_timer(),this.savefailures=Math.max(1,this.savefailures+1),this.savefailures===this.FAILURES_BEFORE_NOTIFY&&(s.one(this.SELECTORS.CONNECTION_ERROR).show(),s.one(this.SELECTORS.CONNECTION_OK).hide())},is_time_nearly_over:function(){return M.mod_quiz.timer&&M.mod_quiz.timer.endtime&&(new Date).getTime()+2*this.delay>M.mod_quiz.timer.endtime},stop_autosaving:function(){this.cancel_delay(),this.delay_timer=!0,this.save_transaction&&this.save_transaction.abort()}}},"@VERSION@",{requires:["base","node","event","event-valuechange","node-event-delegate","io-form"]});
YUI.add("moodle-mod_quiz-autosave",function(n,e){M.mod_quiz=M.mod_quiz||{},M.mod_quiz.autosave={TINYMCE_DETECTION_DELAY:500,TINYMCE_DETECTION_REPEATS:20,WATCH_HIDDEN_DELAY:1e3,FAILURES_BEFORE_NOTIFY:1,FIRST_SUCCESSFUL_SAVE:-1,SELECTORS:{QUIZ_FORM:"#responseform",VALUE_CHANGE_ELEMENTS:'input, textarea, [contenteditable="true"]',CHANGE_ELEMENTS:"input, select",HIDDEN_INPUTS:"input[type=hidden]",CONNECTION_ERROR:"#connection-error",CONNECTION_OK:"#connection-ok"},AUTOSAVE_HANDLER:M.cfg.wwwroot+"/mod/quiz/autosave.ajax.php",delay:12e4,form:null,dirty:!1,delay_timer:null,save_transaction:null,savefailures:0,editor_change_handler:null,hidden_field_values:{},init:function(e){this.form=n.one(this.SELECTORS.QUIZ_FORM),this.form&&(this.delay=1e3*e,this.form.delegate("valuechange",this.value_changed,this.SELECTORS.VALUE_CHANGE_ELEMENTS,this),this.form.delegate("change",this.value_changed,this.SELECTORS.CHANGE_ELEMENTS,this),this.form.on("submit",this.stop_autosaving,this),require(["core_form/events"],function(e){window.addEventListener(e.eventTypes.uploadChanged,this.value_changed.bind(this))}.bind(this)),this.init_tinymce(this.TINYMCE_DETECTION_REPEATS),this.save_hidden_field_values(),this.watch_hidden_fields())},save_hidden_field_values:function(){this.form.all(this.SELECTORS.HIDDEN_INPUTS).each(function(e){var t=e.get("name");t&&(this.hidden_field_values[t]=e.get("value"))},this)},watch_hidden_fields:function(){this.detect_hidden_field_changes(),n.later(this.WATCH_HIDDEN_DELAY,this,this.watch_hidden_fields)},detect_hidden_field_changes:function(){this.form.all(this.SELECTORS.HIDDEN_INPUTS).each(function(e){var t=e.get("name"),i=e.get("value");t&&(t in this.hidden_field_values&&i===this.hidden_field_values[t]||(this.hidden_field_values[t]=i,this.value_changed({target:e})))},this)},init_tinymce:function(e){var t;"undefined"!=typeof window.tinyMCE?(this.editor_change_handler=n.bind(this.editor_changed,this),window.tinyMCE.onAddEditor?window.tinyMCE.onAddEditor.add(n.bind(this.init_tinymce_editor,this)):window.tinyMCE.on&&(t=this.start_save_timer_if_necessary.bind(this),window.tinyMCE.on("AddEditor",function(e){e.editor.on("Change Undo Redo keydown",t)}),window.tinyMCE.get().forEach(function(e){e.on("Change Undo Redo keydown",t)}))):0<e&&n.later(this.TINYMCE_DETECTION_DELAY,this,this.init_tinymce,[e-1])},init_tinymce_editor:function(e,t){t.onChange.add(this.editor_change_handler),t.onRedo.add(this.editor_change_handler),t.onUndo.add(this.editor_change_handler),t.onKeyDown.add(this.editor_change_handler)},value_changed:function(e){var t=e.target.getAttribute("name");"thispage"===t||"scrollpos"===t||t&&t.match(/_:flagged$/)||(t=t||"#"+e.target.getAttribute("id"),this.start_save_timer_if_necessary())},editor_changed:function(e){this.start_save_timer_if_necessary()},start_save_timer_if_necessary:function(){this.dirty=!0,this.delay_timer||this.save_transaction||this.start_save_timer()},start_save_timer:function(){this.cancel_delay(),this.delay_timer=n.later(this.delay,this,this.save_changes)},cancel_delay:function(){this.delay_timer&&!0!==this.delay_timer&&this.delay_timer.cancel(),this.delay_timer=null},save_changes:function(){var e;this.cancel_delay(),this.dirty=!1,this.is_time_nearly_over()?this.stop_autosaving():("undefined"!=typeof window.tinyMCE&&window.tinyMCE.triggerSave(),(e=this.form.all("input[type=submit], button[type=submit]")).setAttribute("type","button"),this.save_transaction=n.io(this.AUTOSAVE_HANDLER,{method:"POST",form:{id:this.form},on:{success:this.save_done,failure:this.save_failed},context:this}),e.setAttribute("type","submit"))},save_done:function(e,t){var i=JSON.parse(t.responseText);"OK"===i.status?("undefined"!=typeof i.timeleft&&M.mod_quiz.timer.updateEndTime(i.timeleft),this.save_transaction=null,this.dirty&&this.start_save_timer(),0<this.savefailures?(n.one(this.SELECTORS.CONNECTION_ERROR).hide(),n.one(this.SELECTORS.CONNECTION_OK).show(),this.savefailures=this.FIRST_SUCCESSFUL_SAVE):this.savefailures===this.FIRST_SUCCESSFUL_SAVE&&(n.one(this.SELECTORS.CONNECTION_OK).hide(),this.savefailures=0)):this.save_failed(e,t)},save_failed:function(){this.save_transaction=null,this.start_save_timer(),this.savefailures=Math.max(1,this.savefailures+1),this.savefailures===this.FAILURES_BEFORE_NOTIFY&&(n.one(this.SELECTORS.CONNECTION_ERROR).show(),n.one(this.SELECTORS.CONNECTION_OK).hide())},is_time_nearly_over:function(){return M.mod_quiz.timer&&M.mod_quiz.timer.endtime&&(new Date).getTime()+2*this.delay>M.mod_quiz.timer.endtime},stop_autosaving:function(){this.cancel_delay(),this.delay_timer=!0,this.save_transaction&&this.save_transaction.abort()}}},"@VERSION@",{requires:["base","node","event","event-valuechange","node-event-delegate","io-form"]});
@@ -271,7 +271,12 @@ M.mod_quiz.autosave = {
window.tinyMCE.on('AddEditor', function(event) {
event.editor.on('Change Undo Redo keydown', startSaveTimer);
});
}
// One or more editors might already have been added, so we have to attach
// the event handlers to these as well.
window.tinyMCE.get().forEach(function(editor) {
editor.on('Change Undo Redo keydown', startSaveTimer);
});
}
},
/**
+6 -1
View File
@@ -272,7 +272,12 @@ M.mod_quiz.autosave = {
window.tinyMCE.on('AddEditor', function(event) {
event.editor.on('Change Undo Redo keydown', startSaveTimer);
});
}
// One or more editors might already have been added, so we have to attach
// the event handlers to these as well.
window.tinyMCE.get().forEach(function(editor) {
editor.on('Change Undo Redo keydown', startSaveTimer);
});
}
},
/**
+1
View File
@@ -228,6 +228,7 @@ function resource_get_coursemodule_info($coursemodule) {
$mainfile = reset($files);
$resource->mainfile = $mainfile->get_filename();
$info->icon = file_file_icon($mainfile);
$info->customdata['filtericon'] = true;
}
$display = resource_get_final_display_type($resource);
+2 -2
View File
@@ -1497,10 +1497,10 @@ abstract class repository implements cacheable_object {
//if the context is SYSTEM, so we call it from administration page
$admin = ($context->id == SYSCONTEXTID) ? true : false;
if ($admin) {
$baseurl = new moodle_url('/'.$CFG->admin.'/repositoryinstance.php', array('sesskey'=>sesskey()));
$baseurl = new moodle_url('/admin/repositoryinstance.php');
$output .= $OUTPUT->heading(get_string('siteinstances', 'repository'));
} else {
$baseurl = new moodle_url('/repository/manage_instances.php', array('contextid'=>$context->id, 'sesskey'=>sesskey()));
$baseurl = new moodle_url('/repository/manage_instances.php', ['contextid' => $context->id]);
}
$namestr = get_string('name');
+1 -8
View File
@@ -37,9 +37,7 @@ $contextid = optional_param('contextid', 0, PARAM_INT);
$usercourseid = optional_param('usercourseid', SITEID, PARAM_INT); // Extra: used for user context only
$url = new moodle_url('/repository/manage_instances.php');
$baseurl = new moodle_url('/repository/manage_instances.php');
$baseurl->param('sesskey', sesskey());
if ($edit){
$url->param('edit', $edit);
@@ -168,9 +166,6 @@ if (!empty($edit) || !empty($new)) {
exit;
} else if ($fromform = $mform->get_data()){
if (!confirm_sesskey()) {
throw new \moodle_exception('confirmsesskeybad', '', $baseurl);
}
if ($edit) {
$settings = array();
$settings['name'] = $fromform->name;
@@ -199,9 +194,7 @@ if (!empty($edit) || !empty($new)) {
}
} else if (!empty($delete)) {
if ($sure) {
if (!confirm_sesskey()) {
throw new \moodle_exception('confirmsesskeybad', '', $baseurl);
}
require_sesskey();
if ($instance->delete()) {
$deletedstr = get_string('instancedeleted', 'repository');
redirect($baseurl, $deletedstr, 3);
+2 -2
View File
@@ -29,9 +29,9 @@
defined('MOODLE_INTERNAL') || die();
$version = 2023100901.00; // 20231009 = branching date YYYYMMDD - do not modify!
$version = 2023100902.00; // 20231009 = branching date YYYYMMDD - do not modify!
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
$release = '4.3.1 (Build: 20231211)'; // Human-friendly version name
$release = '4.3.2 (Build: 20231222)'; // Human-friendly version name
$branch = '403'; // This version's branch.
$maturity = MATURITY_STABLE; // This version's maturity level.