--- /dev/null
+@core @core_admin
+Feature: Enable multiple accounts to have the same email address
+ In order to have multiple accounts registerd on the system with the same email address
+ As an admin
+ I need to enable multiple accounts to be registered with the same email address and verify it is applied
+
+ Background:
+ Given I log in as "admin"
+
+ Scenario: Enable registration of multiple accounts with the same email address
+ Given the following config values are set as admin:
+ | allowaccountssameemail | 1 |
+ When I navigate to "Add a new user" node in "Site administration>Users>Accounts"
+ And I set the following fields to these values:
+ | Username | testmultiemailuser1 |
+ | Choose an authentication method | Manual accounts |
+ | New password | test@User1 |
+ | First name | Test |
+ | Surname | Multi1 |
+ | Email address | testmultiemailuser@example.com |
+ And I press "Create user"
+ And I should see "Test Multi1"
+ And I press "Add a new user"
+ And I set the following fields to these values:
+ | Username | testmultiemailuser2 |
+ | Choose an authentication method | Manual accounts |
+ | New password | test@User2 |
+ | First name | Test |
+ | Surname | Multi2 |
+ | Email address | testmultiemailuser@example.com |
+ And I press "Create user"
+ Then I should see "Test Multi2"
+ And I should not see "This email address is already registered"
+
+ Scenario: Disable registration of multiple accounts with the same email address
+ Given the following config values are set as admin:
+ | allowaccountssameemail | 0 |
+ When I navigate to "Add a new user" node in "Site administration>Users>Accounts"
+ And I set the following fields to these values:
+ | Username | testmultiemailuser1 |
+ | Choose an authentication method | Manual accounts |
+ | New password | test@User1 |
+ | First name | Test |
+ | Surname | Multi1 |
+ | Email address | testmultiemailuser@example.com |
+ And I press "Create user"
+ And I should see "Test Multi1"
+ And I press "Add a new user"
+ And I set the following fields to these values:
+ | Username | testmultiemailuser2 |
+ | Choose an authentication method | Manual accounts |
+ | New password | test@User2 |
+ | First name | Test |
+ | Surname | Multi2 |
+ | Email address | testmultiemailuser@example.com |
+ And I press "Create user"
+ Then I should see "This email address is already registered"
\ No newline at end of file
up: 38,
right: 39,
down: 40,
+ eight: 56,
asterisk: 106
};
Tree.prototype.handleKeyDown = function(item, e) {
var currentIndex = this.visibleItems.index(item);
var newItem = null;
+ var hasKeyModifier = e.shiftKey || e.ctrlKey || e.metaKey || e.altKey;
+ var thisObj = this;
switch (e.keyCode) {
case this.keys.home: {
newItem.focus();
if (e.shiftKey) {
this.multiSelectItem(newItem);
- } else if (!e.ctrlKey) {
+ } else if (!hasKeyModifier) {
this.selectItem(newItem);
}
newItem.focus();
if (e.shiftKey) {
this.multiSelectItem(newItem);
- } else if (!e.ctrlKey) {
+ } else if (!hasKeyModifier) {
this.selectItem(newItem);
}
if (e.shiftKey) {
this.multiSelectItem(item);
- } else if (e.ctrlKey) {
+ } else if (e.metaKey || e.ctrlKey) {
this.toggleItem(item);
} else {
this.selectItem(item);
itemParent.focus();
if (e.shiftKey) {
this.multiSelectItem(itemParent);
- } else if (!e.ctrlKey) {
+ } else if (!hasKeyModifier) {
this.selectItem(itemParent);
}
}
newItem.focus();
if (e.shiftKey) {
this.multiSelectItem(newItem);
- } else if (!e.ctrlKey) {
+ } else if (!hasKeyModifier) {
this.selectItem(newItem);
}
}
prev.focus();
if (e.shiftKey) {
this.multiSelectItem(prev);
- } else if (!e.ctrlKey) {
+ } else if (!hasKeyModifier) {
this.selectItem(prev);
}
}
next.focus();
if (e.shiftKey) {
this.multiSelectItem(next);
- } else if (!e.ctrlKey) {
+ } else if (!hasKeyModifier) {
this.selectItem(next);
}
}
}
case this.keys.asterisk: {
// Expand all groups.
-
- var thisObj = this;
-
this.parents.each(function() {
thisObj.expandGroup($(this));
});
e.stopPropagation();
return false;
}
+ case this.keys.eight: {
+ if (e.shiftKey) {
+ // Expand all groups.
+ this.parents.each(function() {
+ thisObj.expandGroup($(this));
+ });
+
+ e.stopPropagation();
+ }
+
+ return false;
+ }
}
return true;
* @param {Event} e The event.
*/
Tree.prototype.handleKeyPress = function(item, e) {
- if (e.altKey || e.ctrlKey || e.shiftKey) {
+ if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {
// Do nothing.
return true;
}
*/
Tree.prototype.handleDblClick = function(item, e) {
- if (e.altKey || e.ctrlKey || e.shiftKey) {
+ if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {
// Do nothing.
return true;
}
if (e.shiftKey) {
this.multiSelectItem(item);
- } else if (e.ctrlKey) {
+ } else if (e.metaKey || e.ctrlKey) {
this.toggleItem(item);
} else {
this.selectItem(item);
this._userId = userId + '';
this._competencyId = competencyId + '';
this._courseId = courseId;
- this._ignoreFirstUser = true;
- this._ignoreFirstCompetency = true;
$(userSelector).on('change', this._userChanged.bind(this));
$(competencySelector).on('change', this._competencyChanged.bind(this));
* @param {Event} e
*/
UserCompetencyCourseNavigation.prototype._userChanged = function(e) {
- if (this._ignoreFirstUser) {
- this._ignoreFirstUser = false;
- return;
- }
-
var newUserId = $(e.target).val();
var queryStr = '?userid=' + newUserId + '&courseid=' + this._courseId + '&competencyid=' + this._competencyId;
document.location = this._baseUrl + queryStr;
* @param {Event} e
*/
UserCompetencyCourseNavigation.prototype._competencyChanged = function(e) {
- if (this._ignoreFirstCompetency) {
- this._ignoreFirstCompetency = false;
- return;
- }
var newCompetencyId = $(e.target).val();
var queryStr = '?userid=' + this._userId + '&courseid=' + this._courseId + '&competencyid=' + newCompetencyId;
document.location = this._baseUrl + queryStr;
namespace tool_lp\external;
use core_competency\api;
+use core_competency\user_competency;
use context_course;
use renderer_base;
use stdClass;
$related['usercompetency'] = null;
$exporter = new user_competency_summary_exporter(null, $related);
$result->usercompetencysummary = $exporter->export($output);
+ $result->usercompetencysummary->cangrade = user_competency::can_grade_user_in_course($this->related['user']->id,
+ $this->related['course']->id);
$context = context_course::instance($this->related['course']->id);
$exporter = new course_summary_exporter($this->related['course'], array('context' => $context));
'type' => 'read',
'capabilities' => 'moodle/competency:coursecompetencyview',
'ajax' => true,
+ 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'tool_lp_data_for_template_competencies_page' => array(
'classname' => 'tool_lp\external',
'type' => 'read',
'capabilities' => 'moodle/competency:planviewown',
'ajax' => true,
+ 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'tool_lp_data_for_plan_page' => array(
'classname' => 'tool_lp\external',
'type' => 'read',
'capabilities' => 'moodle/competency:planview',
'ajax' => true,
+ 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'tool_lp_data_for_related_competencies_section' => array(
'classname' => 'tool_lp\external',
'type' => 'read',
'capabilities' => 'moodle/competency:userevidenceview',
'ajax' => true,
+ 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'tool_lp_data_for_user_evidence_page' => array(
'classname' => 'tool_lp\external',
'type' => 'read',
'capabilities' => 'moodle/competency:userevidenceview',
'ajax' => true,
+ 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
// User competency.
'type' => 'read',
'capabilities' => 'moodle/competency:planview',
'ajax' => true,
+ 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'tool_lp_data_for_user_competency_summary_in_plan' => array(
'classname' => 'tool_lp\external',
'type' => 'read',
'capabilities' => 'moodle/competency:planview',
'ajax' => true,
+ 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'tool_lp_data_for_user_competency_summary_in_course' => array(
'classname' => 'tool_lp\external',
'type' => 'read',
'capabilities' => 'moodle/competency:coursecompetencyview',
'ajax' => true,
+ 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
);
display: table-cell;
}
.path-admin-tool-lp [data-region="competencyruleconfig"] [data-region="rule-outcome"] select,
-.path-admin-tool-lp [data-region="competencyruleconfig"] [data-region="rule-type"] select {
+.path-admin-tool-lp [data-region="competencyruleconfig"] [data-region="rule-type"] select,
+.path-admin-tool-lp [data-region="competencylinktree"] select {
width: 100%;
}
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2016020925; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->version = 2016050400; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2014110400; // Requires this Moodle version.
$plugin->component = 'tool_lp'; // Full name of the plugin (used for diagnostics).
public function test_restore() {
global $DB;
+ $startcount = $DB->count_records('course_modules');
+
// Delete the course module.
course_delete_module($this->quiz->cmid);
}
// Check that it was restored and removed from the recycle bin.
- $this->assertEquals(1, $DB->count_records('course_modules'));
+ $this->assertEquals($startcount, $DB->count_records('course_modules'));
$this->assertEquals(0, count($recyclebin->get_items()));
}
public function test_delete() {
global $DB;
+ $startcount = $DB->count_records('course_modules');
+
// Delete the course module.
course_delete_module($this->quiz->cmid);
}
// Item was deleted, so no course module was restored.
- $this->assertEquals(0, $DB->count_records('course_modules'));
+ $this->assertEquals($startcount - 1, $DB->count_records('course_modules'));
$this->assertEquals(0, count($recyclebin->get_items()));
}
}
// Front channel logout.
+$inputstream = file_get_contents("php://input");
if ($action == 'logout' && !empty($redirect)) {
if ($USER->auth == 'shibboleth') {
redirect($redirect);
}
-} else if (!file_get_contents("php://input")) {
+} else if (!empty($inputstream)) {
// Back channel logout.
// Set SOAP header.
$groups = new backup_groups_setting('groups', base_setting::IS_BOOLEAN, true);
$groups->set_ui(new backup_setting_ui_checkbox($groups, get_string('rootsettinggroups', 'backup')));
$this->add_setting($groups);
+
+ // Define competencies inclusion setting if competencies are enabled.
+ $competencies = new backup_competencies_setting();
+ $competencies->set_ui(new backup_setting_ui_checkbox($competencies, get_string('rootsettingcompetencies', 'backup')));
+ $this->add_setting($competencies);
}
}
*/
class backup_userscompletion_setting extends backup_anonymize_setting {}
+/**
+ * root setting to control if backup will include competencies or not.
+ */
+class backup_competencies_setting extends backup_generic_setting {
+
+ /**
+ * backup_competencies_setting constructor.
+ */
+ public function __construct() {
+ $defaultvalue = false;
+ $visibility = base_setting::HIDDEN;
+ $status = base_setting::LOCKED_BY_CONFIG;
+ if (\core_competency\api::is_enabled()) {
+ $defaultvalue = true;
+ $visibility = base_setting::VISIBLE;
+ $status = base_setting::NOT_LOCKED;
+ }
+ parent::__construct('competencies', base_setting::IS_BOOLEAN, $defaultvalue, $visibility, $status);
+ }
+}
+
// Section backup settings
/**
return $wrapper;
}
+
+ /**
+ * Execute conditions.
+ *
+ * @return bool
+ */
+ protected function execute_condition() {
+
+ // Do not execute if competencies are not included.
+ if (!$this->get_setting_value('competencies')) {
+ return false;
+ }
+
+ return true;
+ }
}
/**
return $wrapper;
}
+
+ /**
+ * Execute conditions.
+ *
+ * @return bool
+ */
+ protected function execute_condition() {
+
+ // Do not execute if competencies are not included.
+ if (!$this->get_setting_value('competencies')) {
+ return false;
+ }
+
+ return true;
+ }
}
/**
$groups->set_ui(new backup_setting_ui_checkbox($groups, get_string('rootsettinggroups', 'backup')));
$groups->get_ui()->set_changeable($changeable);
$this->add_setting($groups);
+
+ // Competencies restore setting. Show when competencies is enabled and the setting is available.
+ $hascompetencies = !empty($rootsettings['competencies']);
+ $competencies = new restore_competencies_setting($hascompetencies);
+ $competencies->set_ui(new backup_setting_ui_checkbox($competencies, get_string('rootsettingcompetencies', 'backup')));
+ $this->add_setting($competencies);
}
}
*/
class restore_badges_setting extends restore_generic_setting {}
+/**
+ * root setting to control if competencies will also be restored.
+ */
+class restore_competencies_setting extends restore_generic_setting {
+
+ /**
+ * restore_competencies_setting constructor.
+ * @param bool $hascompetencies Flag whether to set the restore setting as checked and unlocked.
+ */
+ public function __construct($hascompetencies) {
+ $defaultvalue = false;
+ $visibility = base_setting::HIDDEN;
+ $status = base_setting::LOCKED_BY_CONFIG;
+ if (\core_competency\api::is_enabled()) {
+ $visibility = base_setting::VISIBLE;
+ if ($hascompetencies) {
+ $defaultvalue = true;
+ $status = base_setting::NOT_LOCKED;
+ }
+ }
+ parent::__construct('competencies', base_setting::IS_BOOLEAN, $defaultvalue, $visibility, $status);
+ }
+}
+
/**
* root setting to control if restore will create
* events or no, depends of @restore_users_setting
*/
public function process_course($data) {
global $CFG, $DB;
+ $context = context::instance_by_id($this->task->get_contextid());
+ $userid = $this->task->get_userid();
+ $target = $this->get_task()->get_target();
+ $isnewcourse = $target != backup::TARGET_CURRENT_ADDING && $target != backup::TARGET_EXISTING_ADDING;
+
+ // When restoring to a new course we can set all the things except for the ID number.
+ $canchangeidnumber = $isnewcourse || has_capability('moodle/course:changeidnumber', $context, $userid);
+ $canchangeshortname = $isnewcourse || has_capability('moodle/course:changeshortname', $context, $userid);
+ $canchangefullname = $isnewcourse || has_capability('moodle/course:changefullname', $context, $userid);
+ $canchangesummary = $isnewcourse || has_capability('moodle/course:changesummary', $context, $userid);
$data = (object)$data;
+ $data->id = $this->get_courseid();
$fullname = $this->get_setting_value('course_fullname');
$shortname = $this->get_setting_value('course_shortname');
$startdate = $this->get_setting_value('course_startdate');
- // Calculate final course names, to avoid dupes
+ // Calculate final course names, to avoid dupes.
list($fullname, $shortname) = restore_dbops::calculate_course_names($this->get_courseid(), $fullname, $shortname);
- // Need to change some fields before updating the course record
- $data->id = $this->get_courseid();
- $data->fullname = $fullname;
- $data->shortname= $shortname;
+ if ($canchangefullname) {
+ $data->fullname = $fullname;
+ } else {
+ unset($data->fullname);
+ }
+
+ if ($canchangeshortname) {
+ $data->shortname = $shortname;
+ } else {
+ unset($data->shortname);
+ }
+
+ if (!$canchangesummary) {
+ unset($data->summary);
+ unset($data->summaryformat);
+ }
// Only allow the idnumber to be set if the user has permission and the idnumber is not already in use by
// another course on this site.
- $context = context::instance_by_id($this->task->get_contextid());
- if (!empty($data->idnumber) && has_capability('moodle/course:changeidnumber', $context, $this->task->get_userid()) &&
- $this->task->is_samesite() && !$DB->record_exists('course', array('idnumber' => $data->idnumber))) {
+ if (!empty($data->idnumber) && $canchangeidnumber && $this->task->is_samesite()
+ && !$DB->record_exists('course', array('idnumber' => $data->idnumber))) {
// Do not reset idnumber.
+
+ } else if (!$isnewcourse) {
+ // Prevent override when restoring as merge.
+ unset($data->idnumber);
+
} else {
$data->idnumber = '';
}
*/
protected function execute_condition() {
- // Do not restore when competencies are disabled.
- if (!\core_competency\api::is_enabled()) {
+ // Do not execute if competencies are not included.
+ if (!$this->get_setting_value('competencies')) {
return false;
}
*/
protected function execute_condition() {
- // Do not restore when competencies are disabled.
- if (!\core_competency\api::is_enabled()) {
+ // Do not execute if competencies are not included.
+ if (!$this->get_setting_value('competencies')) {
return false;
}
// Add category. This node should appear after 'contact' so that administration block appears towards the end. Refer MDL-49928.
$category = new core_user\output\myprofile\category('badges', get_string('badges', 'badges'), 'contact');
$tree->add_category($category);
-
- // Determine context.
- if (isloggedin()) {
- $context = context_user::instance($USER->id);
- } else {
- $context = context_system::instance();
- }
+ $context = context_user::instance($user->id);
$courseid = empty($course) ? 0 : $course->id;
if ($USER->id == $user->id || has_capability('moodle/badges:viewotherbadges', $context)) {
}
}
}
-}
\ No newline at end of file
+}
--- /dev/null
+@block @block_badges
+Feature: Enable Block Badges in a course without badges
+ In order to view the badges block in a course
+ As a teacher
+ I can add badges block to a course and view the contents
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email | idnumber |
+ | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+ And the following "courses" exist:
+ | fullname | shortname | category |
+ | Course 1 | C1 | 0 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+
+ Scenario: Add the block to a the course when badges are disabled
+ Given I log in as "admin"
+ And the following config values are set as admin:
+ | enablebadges | 0 |
+ And I log out
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ When I add the "Latest badges" block
+ Then I should see "Badges are not enabled on this site." in the "Latest badges" "block"
+
+ Scenario: Add the block to a the course when badges are enabled
+ Given I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ When I add the "Latest badges" block
+ Then I should see "You have no badges to display" in the "Latest badges" "block"
--- /dev/null
+@block @block_badges @core_badges @_file_upload @javascript
+Feature: Enable Block Badges in a course
+ In order to enable the badges block in a course
+ As a teacher
+ I can add badges block to a course
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email | idnumber |
+ | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+ And the following "courses" exist:
+ | fullname | shortname | category |
+ | Course 1 | C1 | 0 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ # Issue badge 1 of 2
+ And I navigate to "Add a new badge" node in "Badges"
+ And I set the following fields to these values:
+ | id_name | Badge 1 |
+ | id_description | Badge 1 |
+ | id_issuername | Teacher 1 |
+ And I upload "blocks/badges/tests/fixtures/badge.png" file to "Image" filemanager
+ And I press "Create badge"
+ And I select "Manual issue by role" from the "Add badge criteria" singleselect
+ And I set the field "Teacher" to "1"
+ And I press "Save"
+ And I press "Enable access"
+ And I press "Continue"
+ And I follow "Recipients (0)"
+ And I press "Award badge"
+ And I set the field "potentialrecipients[]" to "Teacher 1 (teacher1@example.com)"
+ And I press "Award badge"
+ # Issue Badge 2 of 2
+ And I navigate to "Add a new badge" node in "Badges"
+ And I set the following fields to these values:
+ | id_name | Badge 2 |
+ | id_description | Badge 2 |
+ | id_issuername | Teacher 1 |
+ And I upload "blocks/badges/tests/fixtures/badge.png" file to "Image" filemanager
+ And I press "Create badge"
+ And I select "Manual issue by role" from the "Add badge criteria" singleselect
+ And I set the field "Teacher" to "1"
+ And I press "Save"
+ And I press "Enable access"
+ And I press "Continue"
+ And I follow "Recipients (0)"
+ And I press "Award badge"
+ And I set the field "potentialrecipients[]" to "Teacher 1 (teacher1@example.com)"
+ And I press "Award badge"
+ And I log out
+
+ Scenario: Add the recent badges block to a course.
+ Given I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ When I add the "Latest badges" block
+ Then I should see "Badge 1" in the "Latest badges" "block"
+ And I should see "Badge 2" in the "Latest badges" "block"
+
+ Scenario: Add the recent badges block to a course and limit it to only display 1 badge.
+ Given I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ When I add the "Latest badges" block
+ And I configure the "Latest badges" block
+ And I set the following fields to these values:
+ | id_config_numberofbadges | 1 |
+ And I press "Save changes"
+ Then I should see "Badge 2" in the "Latest badges" "block"
+ And I should not see "Badge 1" in the "Latest badges" "block"
--- /dev/null
+@block @block_badges @core_badges @_file_upload @javascript
+Feature: Enable Block Badges on the dashboard and view awarded badges
+ In order to view recent badges on the dashboard
+ As a teacher
+ I can add badges block to the dashboard
+
+ Scenario: Add the recent badges block to a course.
+ Given the following "users" exist:
+ | username | firstname | lastname | email | idnumber |
+ | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+ And the following "courses" exist:
+ | fullname | shortname | category |
+ | Course 1 | C1 | 0 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ # Issue badge 1 of 2
+ And I navigate to "Add a new badge" node in "Badges"
+ And I set the following fields to these values:
+ | id_name | Badge 1 |
+ | id_description | Badge 1 |
+ | id_issuername | Teacher 1 |
+ And I upload "blocks/badges/tests/fixtures/badge.png" file to "Image" filemanager
+ And I press "Create badge"
+ And I select "Manual issue by role" from the "Add badge criteria" singleselect
+ And I set the field "Teacher" to "1"
+ And I press "Save"
+ And I press "Enable access"
+ And I press "Continue"
+ And I follow "Recipients (0)"
+ And I press "Award badge"
+ And I set the field "potentialrecipients[]" to "Teacher 1 (teacher1@example.com)"
+ And I press "Award badge"
+ And I log out
+ When I log in as "teacher1"
+ And I click on "Dashboard" "link" in the "Navigation" "block"
+ Then I should see "Badge 1" in the "Latest badges" "block"
--- /dev/null
+@block @block_badges @core_badges @_file_upload @javascript
+Feature: Enable Block Badges on the frontpage and view awarded badges
+ In order to enable the badges block on the frontpage
+ As a admin
+ I can add badges block to the frontpage
+
+ Scenario: Add the recent badges block on the frontpage and view recent badges
+ Given the following "users" exist:
+ | username | firstname | lastname | email | idnumber |
+ | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+ And the following "courses" exist:
+ | fullname | shortname | category |
+ | Course 1 | C1 | 0 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ And I log in as "admin"
+ And I am on site homepage
+ And I navigate to "Turn editing on" node in "Front page settings"
+ And I add the "Latest badges" block
+ And I log out
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ # Issue badge 1 of 2
+ And I navigate to "Add a new badge" node in "Badges"
+ And I set the following fields to these values:
+ | id_name | Badge 1 |
+ | id_description | Badge 1 |
+ | id_issuername | Teacher 1 |
+ And I upload "blocks/badges/tests/fixtures/badge.png" file to "Image" filemanager
+ And I press "Create badge"
+ And I select "Manual issue by role" from the "Add badge criteria" singleselect
+ And I set the field "Teacher" to "1"
+ And I press "Save"
+ And I press "Enable access"
+ And I press "Continue"
+ And I follow "Recipients (0)"
+ And I press "Award badge"
+ And I set the field "potentialrecipients[]" to "Teacher 1 (teacher1@example.com)"
+ And I press "Award badge"
+ And I log out
+ When I log in as "teacher1"
+ And I am on site homepage
+ Then I should see "Badge 1" in the "Latest badges" "block"
--- /dev/null
+@block @block_calendar_month
+Feature: Enable the calendar block in a course and test it's functionality
+ In order to enable the calendar block in a course
+ As a teacher
+ I can add the calendar block to a course
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email | idnumber |
+ | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+ | student1 | Student | 1 | student1@example.com | S1 |
+ | student2 | Student | 2 | student2@example.com | S2 |
+ And the following "courses" exist:
+ | fullname | shortname | category |
+ | Course 1 | C1 | 0 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ | student1 | C1 | student |
+ | student2 | C1 | student |
+
+ Scenario: Add the block to a the course
+ Given I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ When I add the "Calendar" block
+ Then I should see "Events key" in the "Calendar" "block"
+
+ @javascript
+ Scenario: View a global event in the calendar block
+ Given I log in as "admin"
+ And I create a calendar event with form data:
+ | id_eventtype | Site |
+ | id_name | Site Event |
+ And I log out
+ When I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ And I add the "Calendar" block
+ And I hover over today in the calendar
+ Then I should see "Site Event"
+
+ @javascript
+ Scenario: Filter site events in the calendar block
+ Given I log in as "admin"
+ And I create a calendar event with form data:
+ | id_eventtype | Site |
+ | id_name | Site Event |
+ And I log out
+ When I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ And I add the "Calendar" block
+ And I create a calendar event with form data:
+ | id_eventtype | Course |
+ | id_name | Course Event |
+ And I follow "Course 1"
+ And I follow "Hide global events"
+ And I hover over today in the calendar
+ Then I should not see "Site Event"
+ And I should see "Course Event"
+
+ @javascript
+ Scenario: View a course event in the calendar block
+ Given I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ And I add the "Calendar" block
+ And I create a calendar event with form data:
+ | id_eventtype | Course |
+ | id_name | Course Event |
+ When I follow "Course 1"
+ And I hover over today in the calendar
+ Then I should see "Course Event"
+
+ @javascript
+ Scenario: Filter course events in the calendar block
+ Given I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ And I add the "Calendar" block
+ And I create a calendar event with form data:
+ | id_eventtype | Course |
+ | id_name | Course Event |
+ And I follow "Course 1"
+ And I create a calendar event with form data:
+ | id_eventtype | User |
+ | id_name | User Event |
+ When I click on "Dashboard" "link" in the "Navigation" "block"
+ And I follow "Course 1"
+ And I follow "Hide course events"
+ And I hover over today in the calendar
+ Then I should not see "Course Event"
+ And I should see "User Event"
+
+ @javascript
+ Scenario: View a user event in the calendar block
+ Given I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ And I add the "Calendar" block
+ And I create a calendar event with form data:
+ | id_eventtype | User |
+ | id_name | User Event |
+ When I click on "Dashboard" "link" in the "Navigation" "block"
+ And I follow "Course 1"
+ And I hover over today in the calendar
+ Then I should see "User Event"
+
+ @javascript
+ Scenario: Filter user events in the calendar block
+ Given I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ And I add the "Calendar" block
+ And I create a calendar event with form data:
+ | id_eventtype | Course |
+ | id_name | Course Event |
+ And I follow "Course 1"
+ And I create a calendar event with form data:
+ | id_eventtype | User |
+ | id_name | User Event |
+ When I click on "Dashboard" "link" in the "Navigation" "block"
+ And I follow "Course 1"
+ And I follow "Hide user events"
+ And I hover over today in the calendar
+ Then I should not see "User Event"
+ And I should see "Course Event"
+
+ @javascript
+ Scenario: View a group event in the calendar block
+ Given the following "groups" exist:
+ | name | course | idnumber |
+ | Group 1 | C1 | G1 |
+ | Group 2 | C1 | G2 |
+ And the following "group members" exist:
+ | user | group |
+ | student1 | G1 |
+ | student2 | G2 |
+ When I log in as "teacher1"
+ And I follow "Course 1"
+ And I navigate to "Edit settings" node in "Course administration"
+ And I set the following fields to these values:
+ | id_groupmode | Separate groups |
+ | id_groupmodeforce | Yes |
+ And I press "Save and display"
+ And I turn editing mode on
+ And I add the "Calendar" block
+ And I create a calendar event with form data:
+ | id_eventtype | Group |
+ | id_groupid | Group 1 |
+ | id_name | Group Event |
+ And I log out
+ Then I log in as "student1"
+ And I follow "Course 1"
+ And I hover over today in the calendar
+ And I should see "Group Event"
+ And I log out
+ And I log in as "student2"
+ And I follow "Course 1"
+ And I hover over today in the calendar
+ And I should not see "Group Event"
+
+ @javascript
+ Scenario: Filter group events in the calendar block
+ Given the following "groups" exist:
+ | name | course | idnumber |
+ | Group 1 | C1 | G1 |
+ | Group 2 | C1 | G2 |
+ And the following "group members" exist:
+ | user | group |
+ | student1 | G1 |
+ | student2 | G2 |
+ When I log in as "teacher1"
+ And I follow "Course 1"
+ And I navigate to "Edit settings" node in "Course administration"
+ And I set the following fields to these values:
+ | id_groupmode | Separate groups |
+ | id_groupmodeforce | Yes |
+ And I press "Save and display"
+ And I turn editing mode on
+ And I add the "Calendar" block
+ And I create a calendar event with form data:
+ | id_eventtype | Course |
+ | id_name | Course Event 1 |
+ And I follow "Course 1"
+ And I create a calendar event with form data:
+ | id_eventtype | Group |
+ | id_groupid | Group 1 |
+ | id_name | Group Event 1 |
+ And I log out
+ Then I log in as "student1"
+ And I follow "Course 1"
+ And I follow "Hide group events"
+ And I hover over today in the calendar
+ And I should not see "Group Event 1"
+ And I should see "Course Event 1"
--- /dev/null
+@block @block_calendar_month
+Feature: Enable the calendar block in a course
+ In order to enable the calendar block in a course
+ As a teacher
+ I can add the calendar block to a course
+
+ @javascript
+ Scenario: View a global event in the calendar block in a course
+ Given the following "users" exist:
+ | username | firstname | lastname | email | idnumber |
+ | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+ And the following "courses" exist:
+ | fullname | shortname | category |
+ | Course 1 | C1 | 0 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ When I log in as "admin"
+ And I create a calendar event with form data:
+ | id_eventtype | Site |
+ | id_name | Site Event |
+ And I log out
+ Then I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ And I add the "Calendar" block
+ And I hover over today in the calendar
+ And I should see "Site Event"
--- /dev/null
+@block @block_calendar_month
+Feature: View a site event on the dashboard
+ In order to view a site event
+ As a student
+ I can view the event in the calendar
+
+ @javascript
+ Scenario: View a global event in the calendar block on the dashboard
+ Given the following "users" exist:
+ | username | firstname | lastname | email | idnumber |
+ | student1 | Student | 1 | student1@example.com | S1 |
+ And I log in as "admin"
+ And I create a calendar event with form data:
+ | id_eventtype | Site |
+ | id_name | Site Event |
+ And I log out
+ When I log in as "student1"
+ And I hover over today in the calendar
+ Then I should see "Site Event"
--- /dev/null
+@block @block_calendar_month
+Feature: Enable the calendar block on the site front page
+ In order to enable the calendar block on the site front page
+ As an admin
+ I can add the calendar block on the site front page
+
+ @javascript
+ Scenario: View a global event in the calendar block on the front page
+ Given the following "users" exist:
+ | username | firstname | lastname | email | idnumber |
+ | student1 | Student | 1 | student1@example.com | S1 |
+ And I log in as "admin"
+ And I am on site homepage
+ And I create a calendar event with form data:
+ | id_eventtype | Site |
+ | id_name | Site Event |
+ And I log out
+ When I log in as "student1"
+ And I am on site homepage
+ And I hover over today in the calendar
+ Then I should see "Site Event"
--- /dev/null
+@block @block_calendar_upcoming
+Feature: Enable the upcoming events block in a course
+ In order to enable the calendar block in a course
+ As a teacher
+ I can view the event in the upcoming events block
+
+ Scenario: View a global event in the upcoming events block in a course
+ Given the following "users" exist:
+ | username | firstname | lastname | email | idnumber |
+ | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+ And the following "courses" exist:
+ | fullname | shortname | category |
+ | Course 1 | C1 | 0 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ When I log in as "admin"
+ And I create a calendar event with form data:
+ | id_eventtype | Site |
+ | id_name | My Site Event |
+ And I log out
+ When I log in as "teacher1"
+ Then I should see "My Site Event" in the "Upcoming events" "block"
--- /dev/null
+@block @block_calendar_upcoming
+Feature: View a site event on the dashboard
+ In order to view a site event
+ As a student
+ I can view the event in the upcoming events block
+
+ Scenario: View a global event in the upcoming events block on the dashboard
+ Given the following "users" exist:
+ | username | firstname | lastname | email | idnumber |
+ | student1 | Student | 1 | student1@example.com | S1 |
+ And I log in as "admin"
+ And I create a calendar event with form data:
+ | id_eventtype | Site |
+ | id_name | My Site Event |
+ And I log out
+ When I log in as "student1"
+ Then I should see "My Site Event"
--- /dev/null
+@block @block_calendar_upcoming
+Feature: View a site event on the frontpage
+ In order to view a site event
+ As a teacher
+ I can view the event in the upcoming events block
+
+ Scenario: View a global event in the upcoming events block on the frontpage
+ Given the following "users" exist:
+ | username | firstname | lastname | email | idnumber |
+ | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+ And I log in as "admin"
+ And I create a calendar event with form data:
+ | id_eventtype | Site |
+ | id_name | My Site Event |
+ And I am on site homepage
+ And I navigate to "Turn editing on" node in "Front page settings"
+ And I add the "Upcoming events" block
+ And I log out
+ When I log in as "teacher1"
+ And I am on site homepage
+ Then I should see "My Site Event" in the "Upcoming events" "block"
--- /dev/null
+@block @block_comments
+Feature: Enable Block comments on an activity page and view comments
+ In order to enable the comments block on an activity page
+ As a teacher
+ I can add the comments block to an activity page
+
+ Scenario: Add the comments block on an activity 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 "activities" exist:
+ | activity | course | idnumber | name | intro |
+ | page | C1 | page1 | Test page name | Test page description |
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ And I follow "Test page name"
+ And I add the "Comments" block
+ And I follow "Show comments"
+ And I add "I'm a comment from the teacher" comment to comments block
+ And I log out
+ When I log in as "student1"
+ And I follow "Course 1"
+ And I follow "Test page name"
+ And I follow "Show comments"
+ Then I should see "I'm a comment from the teacher"
--- /dev/null
+@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 I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ And I add the "Comments" block
+ And I follow "Show comments"
+ And I add "I'm a comment from the teacher" comment to comments block
+ And I log out
+ When I log in as "student1"
+ And I follow "Course 1"
+ And I follow "Show comments"
+ Then I should see "I'm a comment from the teacher"
--- /dev/null
+@block @block_comments
+Feature: Enable Block comments on the frontpage and view comments
+ In order to enable the comments block on the frontpage
+ As a admin
+ I can add the comments block to the frontpage
+
+ Scenario: Add the comments block on the frontpage and add comments
+ Given the following "users" exist:
+ | username | firstname | lastname | email | idnumber |
+ | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+ And I log in as "admin"
+ And I am on site homepage
+ And I navigate to "Turn editing on" node in "Front page settings"
+ And I add the "Comments" block
+ And I follow "Show comments"
+ And I add "I'm a comment from admin" comment to comments block
+ And I log out
+ When I log in as "teacher1"
+ And I am on site homepage
+ And I follow "Show comments"
+ Then I should see "I'm a comment from admin"
--- /dev/null
+.block_globalsearch .searchform {text-align: center;}
+.block_globalsearch .footer {text-align: center;}
$lpg = $this->getDataGenerator()->get_plugin_generator('core_competency');
$user = $dg->create_user();
- $dg->create_scale(array("id" => "1", "scale" => "value1, value2"));
- $dg->create_scale(array("id" => "2", "scale" => "value3, value4, value5, value6"));
+ $s1 = $dg->create_scale(array("scale" => "value1, value2"));
+ $s2 = $dg->create_scale(array("scale" => "value3, value4, value5, value6"));
- $scaleconfiguration1 = '[{"scaleid":"1"},{"name":"value1","id":1,"scaledefault":1,"proficient":0},' .
+ $scaleconfiguration1 = '[{"scaleid":"'.$s1->id.'"},{"name":"value1","id":1,"scaledefault":1,"proficient":0},' .
'{"name":"value2","id":2,"scaledefault":0,"proficient":1}]';
- $scaleconfiguration2 = '[{"scaleid":"2"},{"name":"value3","id":1,"scaledefault":1,"proficient":0},'
+ $scaleconfiguration2 = '[{"scaleid":"'.$s2->id.'"},{"name":"value3","id":1,"scaledefault":1,"proficient":0},'
. '{"name":"value4","id":2,"scaledefault":0,"proficient":1}]';
// Create a framework with scale configuration1.
$frm = array(
- 'scaleid' => 1,
+ 'scaleid' => $s1->id,
'scaleconfiguration' => $scaleconfiguration1
);
$framework = $lpg->create_framework($frm);
// Create competency with its own scale configuration.
$c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get_id(),
- 'scaleid' => 2,
+ 'scaleid' => $s2->id,
'scaleconfiguration' => $scaleconfiguration2
));
/** @var int User role id */
protected $userrole = null;
+ /** @var stdClass $scale1 Scale */
+ protected $scale1 = null;
+
+ /** @var stdClass $scale2 Scale */
+ protected $scale2 = null;
+
+ /** @var stdClass $scale3 Scale */
+ protected $scale3 = null;
+
+ /** @var stdClass $scale4 Scale */
+ protected $scale4 = null;
+
/** @var string scaleconfiguration */
protected $scaleconfiguration1 = null;
$this->category = $category;
$this->othercategory = $othercategory;
- $this->getDataGenerator()->create_scale(array("id" => "1", "scale" => "value1, value2"));
- $this->getDataGenerator()->create_scale(array("id" => "2", "scale" => "value3, value4"));
- $this->getDataGenerator()->create_scale(array("id" => "3", "scale" => "value5, value6"));
- $this->getDataGenerator()->create_scale(array("id" => "4", "scale" => "value7, value8"));
+ $this->scale1 = $this->getDataGenerator()->create_scale(array("scale" => "value1, value2"));
+ $this->scale2 = $this->getDataGenerator()->create_scale(array("scale" => "value3, value4"));
+ $this->scale3 = $this->getDataGenerator()->create_scale(array("scale" => "value5, value6"));
+ $this->scale4 = $this->getDataGenerator()->create_scale(array("scale" => "value7, value8"));
- $this->scaleconfiguration1 = '[{"scaleid":"1"},{"name":"value1","id":1,"scaledefault":1,"proficient":0},' .
+ $this->scaleconfiguration1 = '[{"scaleid":"'.$this->scale1->id.'"},' .
+ '{"name":"value1","id":1,"scaledefault":1,"proficient":0},' .
'{"name":"value2","id":2,"scaledefault":0,"proficient":1}]';
- $this->scaleconfiguration2 = '[{"scaleid":"2"},{"name":"value3","id":1,"scaledefault":1,"proficient":0},' .
+ $this->scaleconfiguration2 = '[{"scaleid":"'.$this->scale2->id.'"},' .
+ '{"name":"value3","id":1,"scaledefault":1,"proficient":0},' .
'{"name":"value4","id":2,"scaledefault":0,"proficient":1}]';
- $this->scaleconfiguration3 = '[{"scaleid":"3"},{"name":"value5","id":1,"scaledefault":1,"proficient":0},' .
+ $this->scaleconfiguration3 = '[{"scaleid":"'.$this->scale3->id.'"},' .
+ '{"name":"value5","id":1,"scaledefault":1,"proficient":0},' .
'{"name":"value6","id":2,"scaledefault":0,"proficient":1}]';
- $this->scaleconfiguration4 = '[{"scaleid":"4"},{"name":"value8","id":1,"scaledefault":1,"proficient":0},' .
+ $this->scaleconfiguration4 = '[{"scaleid":"'.$this->scale4->id.'"},'.
+ '{"name":"value8","id":1,"scaledefault":1,"proficient":0},' .
'{"name":"value8","id":2,"scaledefault":0,"proficient":1}]';
accesslib_clear_all_caches_for_unit_testing();
}
protected function create_competency_framework($number = 1, $system = true) {
+ $scalename = 'scale' . $number;
$scalepropname = 'scaleconfiguration' . $number;
$framework = array(
'shortname' => 'shortname' . $number,
'idnumber' => 'idnumber' . $number,
'description' => 'description' . $number,
'descriptionformat' => FORMAT_HTML,
- 'scaleid' => $number,
+ 'scaleid' => $this->$scalename->id,
'scaleconfiguration' => $this->$scalepropname,
'visible' => true,
'contextid' => $system ? context_system::instance()->id : context_coursecat::instance($this->category->id)->id
}
protected function update_competency_framework($id, $number = 1, $system = true) {
+ $scalename = 'scale' . $number;
$scalepropname = 'scaleconfiguration' . $number;
$framework = array(
'id' => $id,
'idnumber' => 'idnumber' . $number,
'description' => 'description' . $number,
'descriptionformat' => FORMAT_HTML,
- 'scaleid' => $number,
+ 'scaleid' => $this->$scalename->id,
'scaleconfiguration' => $this->$scalepropname,
'visible' => true,
'contextid' => $system ? context_system::instance()->id : context_coursecat::instance($this->category->id)->id
$this->assertEquals('idnumber1', $result->idnumber);
$this->assertEquals('description1', $result->description);
$this->assertEquals(FORMAT_HTML, $result->descriptionformat);
- $this->assertEquals(1, $result->scaleid);
+ $this->assertEquals($this->scale1->id, $result->scaleid);
$this->assertEquals($this->scaleconfiguration1, $result->scaleconfiguration);
$this->assertEquals(true, $result->visible);
}
$this->assertEquals('idnumber1', $result->idnumber);
$this->assertEquals('description1', $result->description);
$this->assertEquals(FORMAT_HTML, $result->descriptionformat);
- $this->assertEquals(1, $result->scaleid);
+ $this->assertEquals($this->scale1->id, $result->scaleid);
$this->assertEquals($this->scaleconfiguration1, $result->scaleconfiguration);
$this->assertEquals(true, $result->visible);
'idnumber' => 'id;"number',
'description' => 'de<>\\..scription',
'descriptionformat' => FORMAT_HTML,
- 'scaleid' => 1,
+ 'scaleid' => $this->scale1->id,
'scaleconfiguration' => $this->scaleconfiguration1,
'visible' => true,
'contextid' => context_system::instance()->id
$this->assertEquals('idnumber1', $result->idnumber);
$this->assertEquals('description1', $result->description);
$this->assertEquals(FORMAT_HTML, $result->descriptionformat);
- $this->assertEquals(1, $result->scaleid);
+ $this->assertEquals($this->scale1->id, $result->scaleid);
$this->assertEquals($this->scaleconfiguration1, $result->scaleconfiguration);
$this->assertEquals(true, $result->visible);
}
$this->assertEquals('idnumber2', $result->idnumber);
$this->assertEquals('description2', $result->description);
$this->assertEquals(FORMAT_HTML, $result->descriptionformat);
- $this->assertEquals(2, $result->scaleid);
+ $this->assertEquals($this->scale2->id, $result->scaleid);
$this->assertEquals($this->scaleconfiguration2, $result->scaleconfiguration);
$this->assertEquals(true, $result->visible);
$this->assertEquals('idnumber1', $result->idnumber);
$this->assertEquals('description1', $result->description);
$this->assertEquals(FORMAT_HTML, $result->descriptionformat);
- $this->assertEquals(1, $result->scaleid);
+ $this->assertEquals($this->scale1->id, $result->scaleid);
$this->assertEquals($this->scaleconfiguration1, $result->scaleconfiguration);
$this->assertEquals(true, $result->visible);
}
$this->assertEquals('idnumber2', $result->idnumber);
$this->assertEquals('description2', $result->description);
$this->assertEquals(FORMAT_HTML, $result->descriptionformat);
- $this->assertEquals(2, $result->scaleid);
+ $this->assertEquals($this->scale2->id, $result->scaleid);
$this->assertEquals($this->scaleconfiguration2, $result->scaleconfiguration);
$this->assertEquals(true, $result->visible);
$s1 = $this->getDataGenerator()->create_scale();
- $f1 = $lpg->create_framework(array('scaleid' => 1));
- $f2 = $lpg->create_framework(array('scaleid' => 1));
+ $f1 = $lpg->create_framework(array('scaleid' => $s1->id));
+ $f2 = $lpg->create_framework(array('scaleid' => $s1->id));
$c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get_id()));
$c2 = $lpg->create_competency(array('competencyframeworkid' => $f2->get_id()));
- $this->assertEquals(1, $f1->get_scaleid());
+ $this->assertEquals($s1->id, $f1->get_scaleid());
// Make the scale of f2 being used.
$lpg->create_user_competency(array('userid' => $this->user->id, 'competencyid' => $c2->get_id()));
$result = $this->update_competency_framework($f1->get_id(), 3, true);
$f1 = new \core_competency\competency_framework($f1->get_id());
- $this->assertEquals(3, $f1->get_scaleid());
+ $this->assertEquals($this->scale3->id, $f1->get_scaleid());
// Changing the framework where the scale is used.
try {
$this->assertEquals('idnumber1', $result->idnumber);
$this->assertEquals('description1', $result->description);
$this->assertEquals(FORMAT_HTML, $result->descriptionformat);
- $this->assertEquals(1, $result->scaleid);
+ $this->assertEquals($this->scale1->id, $result->scaleid);
$this->assertEquals($this->scaleconfiguration1, $result->scaleconfiguration);
$this->assertEquals(true, $result->visible);
}
$this->assertEquals('idnumber1', $result->idnumber);
$this->assertEquals('description1', $result->description);
$this->assertEquals(FORMAT_HTML, $result->descriptionformat);
- $this->assertEquals(1, $result->scaleid);
+ $this->assertEquals($this->scale1->id, $result->scaleid);
$this->assertEquals($this->scaleconfiguration1, $result->scaleconfiguration);
$this->assertEquals(true, $result->visible);
}
"require-dev": {
"phpunit/phpunit": "4.8.*",
"phpunit/dbUnit": "1.4.*",
- "moodlehq/behat-extension": "3.31.1"
+ "moodlehq/behat-extension": "3.31.2"
}
}
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "hash": "08ee36172d6de7fe083e753b44255ed7",
- "content-hash": "2bc89ce1a925ac037c899ae6f02eaa26",
+ "hash": "ccba8f24cd70bd4ca9b78873fc4be17f",
+ "content-hash": "cf7a848add8e3de854561718a0d18986",
"packages": [],
"packages-dev": [
{
},
{
"name": "moodlehq/behat-extension",
- "version": "v3.31.1",
+ "version": "v3.31.2",
"source": {
"type": "git",
"url": "https://github.com/moodlehq/moodle-behat-extension.git",
- "reference": "d876ea5940e7ad115318140ae37f228c70450225"
+ "reference": "f0b6a44de9111fd4fa82796aca712b9e9772d07e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/moodlehq/moodle-behat-extension/zipball/d876ea5940e7ad115318140ae37f228c70450225",
- "reference": "d876ea5940e7ad115318140ae37f228c70450225",
+ "url": "https://api.github.com/repos/moodlehq/moodle-behat-extension/zipball/f0b6a44de9111fd4fa82796aca712b9e9772d07e",
+ "reference": "f0b6a44de9111fd4fa82796aca712b9e9772d07e",
"shasum": ""
},
"require": {
"Behat",
"moodle"
],
- "time": "2016-04-01 01:57:33"
+ "time": "2016-05-09 03:32:06"
},
{
"name": "phpdocumentor/reflection-docblock",
},
{
"name": "react/promise",
- "version": "v2.4.0",
+ "version": "v2.4.1",
"source": {
"type": "git",
"url": "https://github.com/reactphp/promise.git",
- "reference": "f942da7b505d1a294284ab343d05df42d02ad6d9"
+ "reference": "8025426794f1944de806618671d4fa476dc7626f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/reactphp/promise/zipball/f942da7b505d1a294284ab343d05df42d02ad6d9",
- "reference": "f942da7b505d1a294284ab343d05df42d02ad6d9",
+ "url": "https://api.github.com/repos/reactphp/promise/zipball/8025426794f1944de806618671d4fa476dc7626f",
+ "reference": "8025426794f1944de806618671d4fa476dc7626f",
"shasum": ""
},
"require": {
}
],
"description": "A lightweight implementation of CommonJS Promises/A for PHP",
- "time": "2016-03-31 13:10:33"
+ "time": "2016-05-03 17:50:52"
},
{
"name": "sebastian/comparator",
},
{
"name": "sebastian/environment",
- "version": "1.3.5",
+ "version": "1.3.6",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/environment.git",
- "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf"
+ "reference": "2292b116f43c272ff4328083096114f84ea46a56"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf",
- "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/2292b116f43c272ff4328083096114f84ea46a56",
+ "reference": "2292b116f43c272ff4328083096114f84ea46a56",
"shasum": ""
},
"require": {
"environment",
"hhvm"
],
- "time": "2016-02-26 18:40:46"
+ "time": "2016-05-04 07:59:13"
},
{
"name": "sebastian/exporter",
},
{
"name": "symfony/browser-kit",
- "version": "v2.8.4",
+ "version": "v2.8.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/browser-kit.git",
},
{
"name": "symfony/class-loader",
- "version": "v2.8.4",
+ "version": "v2.8.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/class-loader.git",
- "reference": "7d362c22710980730d46a5d039e788946a2938cb"
+ "reference": "f1cf312c81c7b4f0f11431e6fd37b66890f5e27b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/class-loader/zipball/7d362c22710980730d46a5d039e788946a2938cb",
- "reference": "7d362c22710980730d46a5d039e788946a2938cb",
+ "url": "https://api.github.com/repos/symfony/class-loader/zipball/f1cf312c81c7b4f0f11431e6fd37b66890f5e27b",
+ "reference": "f1cf312c81c7b4f0f11431e6fd37b66890f5e27b",
"shasum": ""
},
"require": {
],
"description": "Symfony ClassLoader Component",
"homepage": "https://symfony.com",
- "time": "2016-03-10 19:33:53"
+ "time": "2016-03-30 10:37:34"
},
{
"name": "symfony/config",
- "version": "v2.8.4",
+ "version": "v2.8.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
- "reference": "5273f4724dc5288fe7a33cb08077ab9852621f2c"
+ "reference": "edbbcf33cffa2a85104fc80de8dc052cc51596bb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/config/zipball/5273f4724dc5288fe7a33cb08077ab9852621f2c",
- "reference": "5273f4724dc5288fe7a33cb08077ab9852621f2c",
+ "url": "https://api.github.com/repos/symfony/config/zipball/edbbcf33cffa2a85104fc80de8dc052cc51596bb",
+ "reference": "edbbcf33cffa2a85104fc80de8dc052cc51596bb",
"shasum": ""
},
"require": {
],
"description": "Symfony Config Component",
"homepage": "https://symfony.com",
- "time": "2016-03-04 07:54:35"
+ "time": "2016-04-20 18:52:26"
},
{
"name": "symfony/console",
- "version": "v2.8.4",
+ "version": "v2.8.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "9a5aef5fc0d4eff86853d44202b02be8d5a20154"
+ "reference": "48221d3de4dc22d2cd57c97e8b9361821da86609"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/9a5aef5fc0d4eff86853d44202b02be8d5a20154",
- "reference": "9a5aef5fc0d4eff86853d44202b02be8d5a20154",
+ "url": "https://api.github.com/repos/symfony/console/zipball/48221d3de4dc22d2cd57c97e8b9361821da86609",
+ "reference": "48221d3de4dc22d2cd57c97e8b9361821da86609",
"shasum": ""
},
"require": {
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
- "time": "2016-03-17 09:19:04"
+ "time": "2016-04-26 12:00:47"
},
{
"name": "symfony/css-selector",
- "version": "v2.8.4",
+ "version": "v2.8.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
},
{
"name": "symfony/dependency-injection",
- "version": "v2.8.4",
+ "version": "v2.8.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/dependency-injection.git",
- "reference": "f7b4a498e679fa440b16facb934680a1527ed48c"
+ "reference": "35ac8cd26e4477d79e5cbd4f11d41dc92fed4d8d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/f7b4a498e679fa440b16facb934680a1527ed48c",
- "reference": "f7b4a498e679fa440b16facb934680a1527ed48c",
+ "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/35ac8cd26e4477d79e5cbd4f11d41dc92fed4d8d",
+ "reference": "35ac8cd26e4477d79e5cbd4f11d41dc92fed4d8d",
"shasum": ""
},
"require": {
],
"description": "Symfony DependencyInjection Component",
"homepage": "https://symfony.com",
- "time": "2016-03-21 07:27:21"
+ "time": "2016-04-20 14:12:37"
},
{
"name": "symfony/dom-crawler",
- "version": "v2.8.4",
+ "version": "v2.8.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/dom-crawler.git",
- "reference": "aae5c37d243c6ec11db62221aaff37e7f8005926"
+ "reference": "f282b08f6bbbc72e7af2e9e0c2f896221053f791"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/aae5c37d243c6ec11db62221aaff37e7f8005926",
- "reference": "aae5c37d243c6ec11db62221aaff37e7f8005926",
+ "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/f282b08f6bbbc72e7af2e9e0c2f896221053f791",
+ "reference": "f282b08f6bbbc72e7af2e9e0c2f896221053f791",
"shasum": ""
},
"require": {
],
"description": "Symfony DomCrawler Component",
"homepage": "https://symfony.com",
- "time": "2016-03-23 13:11:46"
+ "time": "2016-04-12 18:01:21"
},
{
"name": "symfony/event-dispatcher",
- "version": "v2.8.4",
+ "version": "v2.8.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
- "reference": "47d2d8cade9b1c3987573d2943bb9352536cdb87"
+ "reference": "81c4c51f7fd6d0d40961bd53dd60cade32db6ed6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/47d2d8cade9b1c3987573d2943bb9352536cdb87",
- "reference": "47d2d8cade9b1c3987573d2943bb9352536cdb87",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/81c4c51f7fd6d0d40961bd53dd60cade32db6ed6",
+ "reference": "81c4c51f7fd6d0d40961bd53dd60cade32db6ed6",
"shasum": ""
},
"require": {
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
- "time": "2016-03-07 14:04:32"
+ "time": "2016-04-05 16:36:54"
},
{
"name": "symfony/filesystem",
- "version": "v2.8.4",
+ "version": "v2.8.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
- "reference": "f08ffdf229252cd2745558cb2112df43903bcae4"
+ "reference": "dee379131dceed90a429e951546b33edfe7dccbb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/filesystem/zipball/f08ffdf229252cd2745558cb2112df43903bcae4",
- "reference": "f08ffdf229252cd2745558cb2112df43903bcae4",
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/dee379131dceed90a429e951546b33edfe7dccbb",
+ "reference": "dee379131dceed90a429e951546b33edfe7dccbb",
"shasum": ""
},
"require": {
],
"description": "Symfony Filesystem Component",
"homepage": "https://symfony.com",
- "time": "2016-03-27 10:20:16"
+ "time": "2016-04-12 18:01:21"
},
{
"name": "symfony/polyfill-apcu",
},
{
"name": "symfony/process",
- "version": "v2.8.4",
+ "version": "v2.8.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
- "reference": "fb467471952ef5cf8497c029980e556b47545333"
+ "reference": "1276bd9be89be039748cf753a2137f4ef149cd74"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/fb467471952ef5cf8497c029980e556b47545333",
- "reference": "fb467471952ef5cf8497c029980e556b47545333",
+ "url": "https://api.github.com/repos/symfony/process/zipball/1276bd9be89be039748cf753a2137f4ef149cd74",
+ "reference": "1276bd9be89be039748cf753a2137f4ef149cd74",
"shasum": ""
},
"require": {
],
"description": "Symfony Process Component",
"homepage": "https://symfony.com",
- "time": "2016-03-23 13:11:46"
+ "time": "2016-04-14 15:22:22"
},
{
"name": "symfony/translation",
- "version": "v2.8.4",
+ "version": "v2.8.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
},
{
"name": "symfony/yaml",
- "version": "v2.8.4",
+ "version": "v2.8.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
- "reference": "584e52cb8f788a887553ba82db6caacb1d6260bb"
+ "reference": "e4fbcc65f90909c999ac3b4dfa699ee6563a9940"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/584e52cb8f788a887553ba82db6caacb1d6260bb",
- "reference": "584e52cb8f788a887553ba82db6caacb1d6260bb",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/e4fbcc65f90909c999ac3b4dfa699ee6563a9940",
+ "reference": "e4fbcc65f90909c999ac3b4dfa699ee6563a9940",
"shasum": ""
},
"require": {
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
- "time": "2016-03-04 07:54:35"
+ "time": "2016-03-29 19:00:15"
}
],
"aliases": [],
// $CFG->session_handler_class = '\core\session\file';
// $CFG->session_file_save_path = $CFG->dataroot.'/sessions';
//
-// Redis session handler (requires redis server and redis extension):
-// $CFG->session_handler_class = '\core\session\redis';
-// $CFG->session_redis_save_path = 'tcp://127.0.0.1'
-//
// Memcached session handler (requires memcached server and extension):
// $CFG->session_handler_class = '\core\session\memcached';
// $CFG->session_memcached_save_path = '127.0.0.1:11211';
$courseinfo['id'] = $course->id;
$courseinfo['fullname'] = $course->fullname;
$courseinfo['shortname'] = $course->shortname;
+ $courseinfo['displayname'] = external_format_string(get_course_display_name_for_list($course), $context->id);
$courseinfo['categoryid'] = $course->category;
list($courseinfo['summary'], $courseinfo['summaryformat']) =
external_format_text($course->summary, $course->summaryformat, $context->id, 'course', 'summary', 0);
'categorysortorder' => new external_value(PARAM_INT,
'sort order into the category', VALUE_OPTIONAL),
'fullname' => new external_value(PARAM_TEXT, 'full name'),
+ 'displayname' => new external_value(PARAM_TEXT, 'course display name'),
'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
'summary' => new external_value(PARAM_RAW, 'summary'),
'summaryformat' => new external_format_value('summary'),
'requiredcapabilities' => $requiredcapabilities
);
$params = self::validate_parameters(self::search_courses_parameters(), $parameters);
+ self::validate_context(context_system::instance());
$allowedcriterianames = array('search', 'modulelist', 'blocklist', 'tagid');
if (!in_array($params['criterianame'], $allowedcriterianames)) {
$dbcourse = $generatedcourses[$course['id']];
$this->assertEquals($course['idnumber'], $dbcourse->idnumber);
$this->assertEquals($course['fullname'], $dbcourse->fullname);
+ $this->assertEquals($course['displayname'], get_course_display_name_for_list($dbcourse));
// Summary was converted to the HTML format.
$this->assertEquals($course['summary'], format_text($dbcourse->summary, FORMAT_MOODLE, array('para' => false)));
$this->assertEquals($course['summaryformat'], FORMAT_HTML);
--- /dev/null
+<?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/>.
+
+/**
+ * Course restore tests.
+ *
+ * @package core_course
+ * @copyright 2016 Frédéric Massart - FMCorz.net
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+global $CFG;
+
+require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
+require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
+
+/**
+ * Course restore testcase.
+ *
+ * @package core_course
+ * @copyright 2016 Frédéric Massart - FMCorz.net
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_restore_backup_testcase extends advanced_testcase {
+
+ /**
+ * Backup a course and return its backup ID.
+ *
+ * @param int $courseid The course ID.
+ * @param int $userid The user doing the backup.
+ * @return string
+ */
+ protected function backup_course($courseid, $userid = 2) {
+ globaL $CFG;
+ $packer = get_file_packer('application/vnd.moodle.backup');
+
+ $bc = new backup_controller(backup::TYPE_1COURSE, $courseid, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO,
+ backup::MODE_GENERAL, $userid);
+ $bc->execute_plan();
+
+ $results = $bc->get_results();
+ $results['backup_destination']->extract_to_pathname($packer, "$CFG->tempdir/backup/core_course_testcase");
+
+ $bc->destroy();
+ unset($bc);
+ return 'core_course_testcase';
+ }
+
+ /**
+ * Create a role with capabilities and permissions.
+ *
+ * @param string|array $caps Capability names.
+ * @param int $perm Constant CAP_* to apply to the capabilities.
+ * @return int The new role ID.
+ */
+ protected function create_role_with_caps($caps, $perm) {
+ $caps = (array) $caps;
+ $dg = $this->getDataGenerator();
+ $roleid = $dg->create_role();
+ foreach ($caps as $cap) {
+ assign_capability($cap, $perm, $roleid, context_system::instance()->id, true);
+ }
+ accesslib_clear_all_caches_for_unit_testing();
+ return $roleid;
+ }
+
+ /**
+ * Restore a course.
+ *
+ * @param int $backupid The backup ID.
+ * @param int $courseid The course ID to restore in, or 0.
+ * @param int $userid The ID of the user performing the restore.
+ * @return stdClass The updated course object.
+ */
+ protected function restore_course($backupid, $courseid, $userid) {
+ global $DB;
+
+ $target = backup::TARGET_CURRENT_ADDING;
+ if (!$courseid) {
+ $target = backup::TARGET_NEW_COURSE;
+ $categoryid = $DB->get_field_sql("SELECT MIN(id) FROM {course_categories}");
+ $courseid = restore_dbops::create_new_course('Tmp', 'tmp', $categoryid);
+ }
+
+ $rc = new restore_controller($backupid, $courseid, backup::INTERACTIVE_NO, backup::MODE_GENERAL, $userid, $target);
+ $target == backup::TARGET_NEW_COURSE ?: $rc->get_plan()->get_setting('overwrite_conf')->set_value(true);
+ $rc->execute_precheck();
+ $rc->execute_plan();
+
+ $course = $DB->get_record('course', array('id' => $rc->get_courseid()));
+
+ $rc->destroy();
+ unset($rc);
+ return $course;
+ }
+
+ /**
+ * Restore a course to an existing course.
+ *
+ * @param int $backupid The backup ID.
+ * @param int $courseid The course ID to restore in.
+ * @param int $userid The ID of the user performing the restore.
+ * @return stdClass The updated course object.
+ */
+ protected function restore_to_existing_course($backupid, $courseid, $userid = 2) {
+ return $this->restore_course($backupid, $courseid, $userid);
+ }
+
+ /**
+ * Restore a course to a new course.
+ *
+ * @param int $backupid The backup ID.
+ * @param int $userid The ID of the user performing the restore.
+ * @return stdClass The new course object.
+ */
+ protected function restore_to_new_course($backupid, $userid = 2) {
+ return $this->restore_course($backupid, 0, $userid);
+ }
+
+ public function test_restore_existing_idnumber_in_new_course() {
+ $this->resetAfterTest();
+
+ $dg = $this->getDataGenerator();
+ $c1 = $dg->create_course(['idnumber' => 'ABC']);
+ $backupid = $this->backup_course($c1->id);
+ $c2 = $this->restore_to_new_course($backupid);
+
+ // The ID number is set empty.
+ $this->assertEquals('', $c2->idnumber);
+ }
+
+ public function test_restore_non_existing_idnumber_in_new_course() {
+ global $DB;
+ $this->resetAfterTest();
+
+ $dg = $this->getDataGenerator();
+ $c1 = $dg->create_course(['idnumber' => 'ABC']);
+ $backupid = $this->backup_course($c1->id);
+
+ $c1->idnumber = 'BCD';
+ $DB->update_record('course', $c1);
+
+ // The ID number changed.
+ $c2 = $this->restore_to_new_course($backupid);
+ $this->assertEquals('ABC', $c2->idnumber);
+ }
+
+ public function test_restore_existing_idnumber_in_existing_course() {
+ global $DB;
+ $this->resetAfterTest();
+
+ $dg = $this->getDataGenerator();
+ $c1 = $dg->create_course(['idnumber' => 'ABC']);
+ $c2 = $dg->create_course(['idnumber' => 'DEF']);
+ $backupid = $this->backup_course($c1->id);
+
+ // The ID number does not change.
+ $c2 = $this->restore_to_existing_course($backupid, $c2->id);
+ $this->assertEquals('DEF', $c2->idnumber);
+
+ $c1 = $DB->get_record('course', array('id' => $c1->id));
+ $this->assertEquals('ABC', $c1->idnumber);
+ }
+
+ public function test_restore_non_existing_idnumber_in_existing_course() {
+ global $DB;
+ $this->resetAfterTest();
+
+ $dg = $this->getDataGenerator();
+ $c1 = $dg->create_course(['idnumber' => 'ABC']);
+ $c2 = $dg->create_course(['idnumber' => 'DEF']);
+ $backupid = $this->backup_course($c1->id);
+
+ $c1->idnumber = 'XXX';
+ $DB->update_record('course', $c1);
+
+ // The ID number has changed.
+ $c2 = $this->restore_to_existing_course($backupid, $c2->id);
+ $this->assertEquals('ABC', $c2->idnumber);
+ }
+
+ public function test_restore_idnumber_in_existing_course_without_permissions() {
+ global $DB;
+ $this->resetAfterTest();
+ $dg = $this->getDataGenerator();
+ $u1 = $dg->create_user();
+
+ $managers = get_archetype_roles('manager');
+ $manager = array_shift($managers);
+ $roleid = $this->create_role_with_caps('moodle/course:changeidnumber', CAP_PROHIBIT);
+ $dg->role_assign($manager->id, $u1->id);
+ $dg->role_assign($roleid, $u1->id);
+
+ $c1 = $dg->create_course(['idnumber' => 'ABC']);
+ $c2 = $dg->create_course(['idnumber' => 'DEF']);
+ $backupid = $this->backup_course($c1->id);
+
+ $c1->idnumber = 'XXX';
+ $DB->update_record('course', $c1);
+
+ // The ID number does not change.
+ $c2 = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
+ $this->assertEquals('DEF', $c2->idnumber);
+ }
+
+ public function test_restore_course_info_in_new_course() {
+ global $DB;
+ $this->resetAfterTest();
+ $dg = $this->getDataGenerator();
+
+ $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
+ $backupid = $this->backup_course($c1->id);
+
+ // The information is restored but adapted because names are already taken.
+ $c2 = $this->restore_to_new_course($backupid);
+ $this->assertEquals('SN_1', $c2->shortname);
+ $this->assertEquals('FN copy 1', $c2->fullname);
+ $this->assertEquals('DESC', $c2->summary);
+ $this->assertEquals(FORMAT_MOODLE, $c2->summaryformat);
+ }
+
+ public function test_restore_course_info_in_existing_course() {
+ global $DB;
+ $this->resetAfterTest();
+ $dg = $this->getDataGenerator();
+
+ $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
+ $c2 = $dg->create_course(['shortname' => 'A', 'fullname' => 'B', 'summary' => 'C', 'summaryformat' => FORMAT_PLAIN]);
+ $backupid = $this->backup_course($c1->id);
+
+ // The information is restored but adapted because names are already taken.
+ $c2 = $this->restore_to_existing_course($backupid, $c2->id);
+ $this->assertEquals('SN_1', $c2->shortname);
+ $this->assertEquals('FN copy 1', $c2->fullname);
+ $this->assertEquals('DESC', $c2->summary);
+ $this->assertEquals(FORMAT_MOODLE, $c2->summaryformat);
+ }
+
+ public function test_restore_course_shortname_in_existing_course_without_permissions() {
+ global $DB;
+ $this->resetAfterTest();
+ $dg = $this->getDataGenerator();
+ $u1 = $dg->create_user();
+
+ $managers = get_archetype_roles('manager');
+ $manager = array_shift($managers);
+ $roleid = $this->create_role_with_caps('moodle/course:changeshortname', CAP_PROHIBIT);
+ $dg->role_assign($manager->id, $u1->id);
+ $dg->role_assign($roleid, $u1->id);
+
+ $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
+ $c2 = $dg->create_course(['shortname' => 'A1', 'fullname' => 'B1', 'summary' => 'C1', 'summaryformat' => FORMAT_PLAIN]);
+
+ // The shortname does not change.
+ $backupid = $this->backup_course($c1->id);
+ $restored = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
+ $this->assertEquals($c2->shortname, $restored->shortname);
+ $this->assertEquals('FN copy 1', $restored->fullname);
+ $this->assertEquals('DESC', $restored->summary);
+ $this->assertEquals(FORMAT_MOODLE, $restored->summaryformat);
+ }
+
+ public function test_restore_course_fullname_in_existing_course_without_permissions() {
+ global $DB;
+ $this->resetAfterTest();
+ $dg = $this->getDataGenerator();
+ $u1 = $dg->create_user();
+
+ $managers = get_archetype_roles('manager');
+ $manager = array_shift($managers);
+ $roleid = $this->create_role_with_caps('moodle/course:changefullname', CAP_PROHIBIT);
+ $dg->role_assign($manager->id, $u1->id);
+ $dg->role_assign($roleid, $u1->id);
+
+ $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
+ $c2 = $dg->create_course(['shortname' => 'A1', 'fullname' => 'B1', 'summary' => 'C1', 'summaryformat' => FORMAT_PLAIN]);
+
+ // The fullname does not change.
+ $backupid = $this->backup_course($c1->id);
+ $restored = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
+ $this->assertEquals('SN_1', $restored->shortname);
+ $this->assertEquals($c2->fullname, $restored->fullname);
+ $this->assertEquals('DESC', $restored->summary);
+ $this->assertEquals(FORMAT_MOODLE, $restored->summaryformat);
+ }
+
+ public function test_restore_course_summary_in_existing_course_without_permissions() {
+ global $DB;
+ $this->resetAfterTest();
+ $dg = $this->getDataGenerator();
+ $u1 = $dg->create_user();
+
+ $managers = get_archetype_roles('manager');
+ $manager = array_shift($managers);
+ $roleid = $this->create_role_with_caps('moodle/course:changesummary', CAP_PROHIBIT);
+ $dg->role_assign($manager->id, $u1->id);
+ $dg->role_assign($roleid, $u1->id);
+
+ $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
+ $c2 = $dg->create_course(['shortname' => 'A1', 'fullname' => 'B1', 'summary' => 'C1', 'summaryformat' => FORMAT_PLAIN]);
+
+ // The summary and format do not change.
+ $backupid = $this->backup_course($c1->id);
+ $restored = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
+ $this->assertEquals('SN_1', $restored->shortname);
+ $this->assertEquals('FN copy 1', $restored->fullname);
+ $this->assertEquals($c2->summary, $restored->summary);
+ $this->assertEquals($c2->summaryformat, $restored->summaryformat);
+ }
+}
$PAGE->set_url('/enrol/editinstance.php', array('courseid' => $course->id, 'id' => $instanceid, 'type' => $type));
$PAGE->set_pagelayout('admin');
+$PAGE->set_docs_path('enrol/' . $type . '/edit');
if (empty($return)) {
$return = new moodle_url('/enrol/instances.php', array('id' => $course->id));
$instance->enrol = 'lti';
$instance->status = $tool->status;
$ltienrol = enrol_get_plugin('lti');
- $ltienrol->enrol_user($instance, $userid, null, time(), $timeend);
+
+ // Hack - need to do this to workaround DB caching hack. See MDL-53977.
+ $timestart = intval(substr(time(), 0, 8) . '00') - 1;
+ $ltienrol->enrol_user($instance, $userid, null, $timestart, $timeend);
}
return self::ENROLMENT_SUCCESSFUL;
public function unenrol_user(stdClass $instance, $userid) {
global $DB;
- // Get the tool associated with this instance.
- $tool = $DB->get_record('enrol_lti_tools', array('enrolid' => $instance->id), 'id', MUST_EXIST);
-
- // Need to remove the user from the users table.
- $DB->delete_records('enrol_lti_users', array('userid' => $userid, 'toolid' => $tool->id));
+ // Get the tool associated with this instance. Note - it may not exist if we have deleted
+ // the tool. This is fine because we have already cleaned the 'enrol_lti_users' table.
+ if ($tool = $DB->get_record('enrol_lti_tools', array('enrolid' => $instance->id), 'id')) {
+ // Need to remove the user from the users table.
+ $DB->delete_records('enrol_lti_users', array('userid' => $userid, 'toolid' => $tool->id));
+ }
parent::unenrol_user($instance, $userid);
}
// Make sure sorting and columns work.
$basefields = array('id', 'category', 'sortorder', 'shortname', 'fullname', 'idnumber',
- 'startdate', 'visible', 'groupmode', 'groupmodeforce');
+ 'startdate', 'visible', 'groupmode', 'groupmodeforce', 'defaultgroupingid');
$courses = enrol_get_all_users_courses($user2->id, true);
$course = reset($courses);
$stepusage = '"I grade by filling the rubric with:" step needs you to provide a table where each row is a criterion' .
' and each criterion has 3 different values: | Criterion name | Number of points | Remark text |';
+ // If running Javascript, ensure we zoom in before filling the grades.
+ if ($this->running_javascript()) {
+ $this->execute('behat_general::click_link', get_string('togglezoom', 'mod_assign'));
+ }
+
// First element -> name, second -> points, third -> Remark.
foreach ($criteria as $name => $criterion) {
$textarea = $this->get_node_in_container('css_element', 'textarea', 'table_row', $name);
$this->execute('behat_forms::i_set_the_field_to', array($textarea->getAttribute('name'), $criterion[1]));
}
+
+ // If running Javascript, then ensure to close zoomed rubric.
+ if ($this->running_javascript()) {
+ $this->execute('behat_general::click_link', get_string('togglezoom', 'mod_assign'));
+ }
}
/**
{$a}.
Utilisez l\'option --help.';
$string['cliyesnoprompt'] = 'Tapez y (pour oui) ou n (pour non)';
-$string['environmentrequireinstall'] = 'doit être installé et activé';
-$string['environmentrequireversion'] = 'la version {$a->needed} est requise ; vous utilisez actuellement la version {$a->current}';
+$string['environmentrequireinstall'] = 'L\'extension doit être installée et activée';
+$string['environmentrequireversion'] = 'La version {$a->needed} est requise ; vous utilisez actuellement la version {$a->current}';
$string['upgradekeyset'] = 'Mettre à jour la clef (laisser vide pour ne pas la définir)';
$string['cannotsavezipfile'] = 'Enregistrament del fichièr ZIP impossible';
$string['cannotunzipfile'] = 'Descompression del fichièr ZIP impossibla';
$string['componentisuptodate'] = 'Lo component es a jorn';
-$string['dmlexceptiononinstall'] = '<p>Una error de banca de donadas s\'es producha [{$a->errorcode}].<br />{$a->debuginfo}</p>';
+$string['dmlexceptiononinstall'] = '<p>Una error de banca de donadas s\'es produita [{$a->errorcode}].<br />{$a->debuginfo}</p>';
$string['downloadedfilecheckfailed'] = 'La verificacion del fichièr telecargat a fracassat';
-$string['invalidmd5'] = 'Lo còde de contraròtle md5 es pas valid';
+$string['invalidmd5'] = 'Lo còdi de contraròtle md5 es pas valid';
$string['missingrequiredfield'] = 'Un camp obligatòri es pas completat';
$string['remotedownloaderror'] = '<p>Lo telecargament del component sus vòstre servidor a fracassat. Verificatz los reglatges de proxy. L\'extension cURL de PHP es bravament recomandada.</p>
-<p>Vos cal telecargar manualament lo fichièr <a href="{$a->url}">{$a->url}</a>, lo copiar sus vòstre servidor a l\'emplaçament « {$a->dest} » e lo descompressar a aqueste endrech.</p>';
+<p>Vos cal telecargar manualament lo fichièr <a href="{$a->url}">{$a->url}</a>, lo copiar sus vòstre servidor a l\'emplaçament « {$a->dest} » e lo descompressar a aqueste endreit.</p>';
$string['wrongdestpath'] = 'Camin de destinacion incorrècte';
$string['wrongsourcebase'] = 'Adreça URL de basa de la font incorrècta';
$string['wrongzipfilename'] = 'Nom de fichièr ZIP incorrècte';
$string['rootsettingactivities'] = 'Include activities and resources';
$string['rootsettingbadges'] = 'Include badges';
$string['rootsettingblocks'] = 'Include blocks';
+$string['rootsettingcompetencies'] = 'Include competencies';
$string['rootsettingfilters'] = 'Include filters';
$string['rootsettingcomments'] = 'Include comments';
$string['rootsettingcalendarevents'] = 'Include calendar events';
public function output_html($data, $query='') {
global $CFG;
$default = $this->get_defaultsetting();
+ require_once("$CFG->libdir/filelib.php");
if ($data) {
- if (file_exists($data) and !is_dir($data) and is_executable($data)) {
+ if (file_exists($data) and !is_dir($data) and file_is_executable($data)) {
$executable = '<span class="pathok">✔</span>';
} else {
$executable = '<span class="patherror">✘</span>';
}
$responses = array();
+// Defines the external settings required for Ajax processing.
+$settings = external_settings::get_instance();
+$settings->set_file('pluginfile.php');
+$settings->set_fileurl(true);
+$settings->set_filter(true);
+$settings->set_raw(false);
+
foreach ($requests as $request) {
$response = array();
$methodname = clean_param($request['methodname'], PARAM_ALPHANUMEXT);
});
}
}).fail(notification.exception);
- // Because this function get's called after changing the selection, this is a good place
- // to trigger a change notification.
- if (typeof M.core_formchangechecker != 'undefined') {
+ };
+
+ /**
+ * Notify of a change in the selection.
+ *
+ * @param {jQuery} originalSelect The jQuery object matching the hidden select list.
+ * @return {Void}
+ */
+ var notifyChange = function(originalSelect) {
+ if (typeof M.core_formchangechecker !== 'undefined') {
M.core_formchangechecker.set_form_changed();
}
originalSelect.change();
};
-
/**
* Remove the given item from the list of selected things.
*
}
// Rerender the selection list.
updateSelectionList(options, state, originalSelect);
+ // Notifiy that the selection changed.
+ notifyChange(originalSelect);
};
/**
});
updateSelectionList(options, state, originalSelect);
-
+ // Notifiy that the selection changed.
+ notifyChange(originalSelect);
// Clear the input field.
inputElement.val('');
// Close the suggestions list.
});
// Rerender the selection list.
updateSelectionList(options, state, originalSelect);
+ // Notifiy that the selection changed.
+ notifyChange(originalSelect);
// Clear the input element.
inputElement.val('');
// Close the list of suggestions.
},
transport: function(selector, query, success, failure) {
+ var el = $(selector);
+
// Parse some data-attributes from the form element.
- var requiredcapabilities = $(selector).data('requiredcapabilities');
+ var requiredcapabilities = el.data('requiredcapabilities');
if (requiredcapabilities.trim() !== "") {
requiredcapabilities = requiredcapabilities.split(',');
} else {
requiredcapabilities = [];
}
- var limittoenrolled = $(selector).data('limittoenrolled');
+ var limittoenrolled = el.data('limittoenrolled');
+ var includefrontpage = el.data('includefrontpage');
// Build the query.
- var promise = null;
+ var promises = null;
if (typeof query === "undefined") {
query = '';
requiredcapabilities: requiredcapabilities,
limittoenrolled: limittoenrolled
};
- // Go go go!
- promise = ajax.call([{
- methodname: 'core_course_search_courses', args: searchargs
- }]);
- promise[0].done(success);
- promise[0].fail(failure);
+ var calls = [{
+ methodname: 'core_course_search_courses', args: searchargs
+ }];
+ if (includefrontpage) {
+ calls.push({
+ methodname: 'core_course_get_courses',
+ args: {
+ options: {
+ ids: [includefrontpage]
+ }
+ }
+ });
+ }
- return promise;
+ // Go go go!
+ promises = ajax.call(calls);
+ $.when.apply($.when, promises).done(function(data, site) {
+ if (site && site.length == 1) {
+ var frontpage = site.pop();
+ var matches = query === ''
+ || frontpage.fullname.toUpperCase().indexOf(query.toUpperCase()) > -1
+ || frontpage.shortname.toUpperCase().indexOf(query.toUpperCase()) > -1;
+ if (matches) {
+ data.courses.splice(0, 0, frontpage);
+ }
+ }
+ success(data);
+ }).fail(failure);
}
};
});
Tree.prototype.handleKeyDown = function(item, e) {
var currentIndex = this.getVisibleItems().index(item);
- if ((e.altKey || e.ctrlKey) || (e.shiftKey && e.keyCode != this.keys.tab)) {
+ if ((e.altKey || e.ctrlKey || e.metaKey) || (e.shiftKey && e.keyCode != this.keys.tab)) {
// Do nothing.
return true;
}
*/
Tree.prototype.handleClick = function(item, e) {
- if (e.altKey || e.ctrlKey || e.shiftKey) {
+ if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {
// Do nothing.
return true;
}
try {
self::$handler->init();
self::prepare_cookies();
- $newsid = empty($_COOKIE[session_name()]);
+ $isnewsession = empty($_COOKIE[session_name()]);
- self::$handler->start();
+ if (!self::$handler->start()) {
+ // Could not successfully start/recover session.
+ throw new \core\session\exception(get_string('servererror'));
+ }
- self::initialise_user_session($newsid);
+ self::initialise_user_session($isnewsession);
self::check_security();
// Link global $USER and $SESSION,
$_SESSION['SESSION'] =& $GLOBALS['SESSION'];
} catch (\Exception $ex) {
- @session_write_close();
self::init_empty_session();
self::$sessionactive = false;
throw $ex;
* Do various session security checks.
*
* WARNING: $USER and $SESSION are set up later, do not use them yet!
+ * @throws \core\session\exception
*/
protected static function check_security() {
global $CFG;
* Unblocks the sessions, other scripts may start executing in parallel.
*/
public static function write_close() {
- if (self::$sessionactive) {
- session_write_close();
+ if (version_compare(PHP_VERSION, '5.6.0', '>=')) {
+ // More control over whether session data
+ // is persisted or not.
+ if (self::$sessionactive && session_id()) {
+ // Write session and release lock only if
+ // indication session start was clean.
+ session_write_close();
+ } else {
+ // Otherwise, if possibile lock exists want
+ // to clear it, but do not write session.
+ @session_abort();
+ }
} else {
- if (session_id()) {
- @session_write_close();
+ // Any indication session was started, attempt
+ // to close it.
+ if (self::$sessionactive || session_id()) {
+ session_write_close();
}
}
self::$sessionactive = false;
$default = ini_get('max_execution_time');
set_time_limit($this->acquiretimeout);
+ $isnewsession = empty($_COOKIE[session_name()]);
+ $starttimer = microtime(true);
+
$result = parent::start();
+ // If session_start returned TRUE, but it took as long
+ // as the timeout value, and the $_SESSION returned is
+ // empty when should not have been (isnewsession false)
+ // then assume it did timeout and is invalid.
+ // Add 1 second to elapsed time to account for inexact
+ // timings in php_memcached_session.c.
+ // @TODO Remove this check when php-memcached is fixed
+ // to return false after key lock acquisition timeout.
+ if (!$isnewsession && $result && count($_SESSION) == 0
+ && (microtime(true) - $starttimer + 1) >= floatval($this->acquiretimeout)) {
+ $result = false;
+ }
+
set_time_limit($default);
return $result;
}
+++ /dev/null
-<?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/>.
-
-/**
- * Redis based session handler.
- *
- * @package core
- * @copyright 2016 Nicholas Hoobin <nicholashoobin@catalyst-au.net>
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-namespace core\session;
-
-defined('MOODLE_INTERNAL') || die();
-
-/**
- * Redis based session handler.
- *
- * @package core
- * @copyright 2016 Nicholas Hoobin <nicholashoobin@catalyst-au.net>
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class redis extends handler {
- /** @var string $savepath save_path string */
- protected $savepath;
-
- /** @var array $servers list of servers parsed from save_path */
- protected $servers;
-
- /** @var int $acquiretimeout how long to wait for session lock */
- protected $acquiretimeout = 120;
-
- /**
- * Create new instance of handler.
- */
- public function __construct() {
- global $CFG;
-
- if (!empty($CFG->session_redis_acquire_lock_timeout)) {
- $this->acquiretimeout = $CFG->session_redis_acquire_lock_timeout;
- }
-
- if (empty($CFG->session_redis_save_path)) {
- $this->savepath = '';
- } else {
- $this->savepath = $CFG->session_redis_save_path;
- }
-
- if (empty($this->savepath)) {
- $this->servers = array();
- } else {
- $this->servers = $this->connection_string_to_redis_servers($this->savepath);
- }
-
- }
-
- /**
- * Start the session.
- * @return bool success
- */
- public function start() {
- $default = ini_get('max_execution_time');
- set_time_limit($this->acquiretimeout);
-
- $result = parent::start();
-
- set_time_limit($default);
-
- return $result;
- }
-
- /**
- * Init session handler.
- */
- public function init() {
- if (!extension_loaded('Redis')) {
- throw new exception('sessionhandlerproblem', 'error', '', null, 'redis extension is not loaded');
- }
-
- // The session handler requires a version of Redis with the SETEX command (at least 2.0).
- $version = phpversion('Redis');
- if (!$version or version_compare($version, '2.0') <= 0) {
- throw new exception('sessionhandlerproblem', 'error', '', null, 'redis extension version must be at least 2.0');
- }
-
- if (empty($this->savepath)) {
- throw new exception('sessionhandlerproblem', 'error', '', null,
- '$CFG->session_redis_save_path must be specified in config.php');
- }
-
- ini_set('session.save_handler', 'redis');
- ini_set('session.save_path', $this->savepath);
- }
-
- /**
- * Check the backend contains data for this session id.
- *
- * Note: this is intended to be called from manager::session_exists() only.
- *
- * @param string $sid
- * @return bool true if session found.
- */
- public function session_exists($sid) {
- if (!$this->servers) {
- return false;
- }
-
- foreach ($this->servers as $server) {
- if ($redis = $this->redis_connect($server)) {
- $value = $redis->get($server['prefix'] . $sid);
- $redis->close();
- }
-
- if ($value !== false) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Kill all active sessions, the core sessions table is
- * purged afterwards.
- */
- public function kill_all_sessions() {
- global $DB;
- if (!$this->servers) {
- return false;
- }
-
- $serverlist = array();
- foreach ($this->servers as $server) {
- if ($redis = $this->redis_connect($server)) {
- $serverlist[] = array($redis, $server['prefix']);
- }
- }
-
- $rs = $DB->get_recordset('sessions', array(), 'id DESC', 'id, sid');
- foreach ($rs as $record) {
- foreach ($serverlist as $arr) {
- list($server, $prefix) = $arr;
- $server->delete($prefix . $sid);
- }
- }
-
- foreach ($serverlist as $arr) {
- list($server, $prefix) = $arr;
- $server->close();
- }
- }
-
- /**
- * Kill one session, the session record is removed afterwards.
- * @param string $sid
- */
- public function kill_session($sid) {
- if (!$this->servers) {
- return false;
- }
-
- // Go through the list of all servers because
- // we do not know where the session handler put the
- // data.
-
- foreach ($this->servers as $server) {
- if ($redis = $this->redis_connect($server)) {
- $redis->delete($server['prefix'] . $sid);
- $redis->close();
- }
- }
- }
-
- /**
- * Convert a connection string to an array of servers
- *
- * Example conversion,
- * "tcp://host1:123?database=0, unix:///var/run/redis/redis.sock?database=0" to
- *
- * array(
- * (
- * [scheme] => 'tcp',
- * [host] => 'host1',
- * [port] => 123,
- * [database] => 0,
- * [prefix] => 'PHPREDIS_SESSION:'
- * ),
- * (
- * [scheme] => 'unix',
- * [path] => '/var/run/redis/redis.sock',
- * [database] => 0,
- * [prefix] => 'PHPREDIS_SESSION:'
- * )
- * )
- *
- * @copyright 2016 Nicholas Hoobin <nicholashoobin@catalyst-au.net>
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- * @author Nicholas Hoobin
- *
- * @param string $str save_path value containing redis connection string
- * @return array
- */
- public function connection_string_to_redis_servers($str) {
- $servers = array();
- $connections = array_map('trim', explode(',', $str));
-
- foreach ($connections as $con) {
- if (strpos($con, "unix:///") !== false) {
- $fields = $this->parse_unix_sock($con);
-
- } else if (strpos($con, "tcp://") !== false) {
- $fields = $this->parse_url_tcp($con);
-
- } else {
- $fields = false;
- debugging("Invalid Redis schema in connection savepath");
-
- }
-
- // Parsing failed.
- if ($fields === false) {
- continue;
- }
-
- // Setting the default prefix.
- if (!isset($fields['prefix'])) {
- $fields['prefix'] = 'PHPREDIS_SESSION:';
- }
-
- // Setting the default database.
- if (!isset($fields['database'])) {
- $fields['database'] = 0;
- }
-
- // Setting the default timeout.
- if (!isset($fields['timeout'])) {
- $fields['timeout'] = 86400;
- }
-
- $servers[] = $fields;
- }
-
- return $servers;
- }
-
- /**
- * Parses the tcp connection string and returns an object.
- * @param string $con connection string
- * @return object $con connection data object
- */
- private function parse_url_tcp($con) {
- $con = parse_url($con);
-
- // Seriously wrong url, parsing failed.
- if ($con === false) {
- return false;
- }
-
- // Parsing the query string.
- if (isset($con['query'])) {
- $query = $con['query'];
- $parts = explode('&', $query);
-
- foreach ($parts as $part) {
- list($key, $value) = explode('=', $part);
- $con[$key] = $value;
- }
- }
-
- // Setting the default port.
- if (!isset($con['port'])) {
- $con['port'] = 6379;
- }
-
- return $con;
- }
-
- /**
- * Parses the unix domain socket connection string and returns an object.
- * @param string $con connection string
- * @return object $con connection data object
- */
- private function parse_unix_sock($con) {
- // Lets use parse_url to get the bits we need.
- // To use this, replace the three slashes with two slashes.
- $con = str_replace(":///", "://", $con);
- $con = parse_url($con);
-
- // Seriously wrong url, parsing failed.
- if ($con === false) {
- return false;
- }
-
- /* Eg. host = var
- path = run/redis/redis.sock
- new path = /var/run/redis/redis.sock
- */
- $con['path'] = '/' . $con['host'] . $con['path'];
- unset($con['host']);
-
- // Parsing the query string.
- if (isset($con['query'])) {
- $query = $con['query'];
- $parts = explode('&', $query);
-
- foreach ($parts as $part) {
- list($key, $value) = explode('=', $part);
- $con[$key] = $value;
- }
- }
-
- return $con;
- }
-
- /**
- * Connects to the Redis server with the details from the connection object.
- * @param object $con connection details object
- * @return redis $redis redis connection
- */
- private function redis_connect($con) {
- $redis = new \Redis();
-
- $func = isset($con['persistent']) ? 'pconnect' : 'connect';
-
- if ($con['scheme'] === 'tcp') {
- // Only TCP connections will have a port, default 6379.
- $result = $redis->$func($con['host'], $con['port'], $con['timeout']);
- } else if ($con['scheme'] === 'unix') {
- // Unix domain socket.
- $result = $redis->$func($con['path']);
- }
-
- $result = true ? $redis->select($con['database']) : false;
-
- return $redis;
- }
-}
-
'type' => 'read',
'capabilities' => 'moodle/competency:competencyview',
'ajax' => true,
+ 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'core_competency_delete_competency' => array(
'classname' => 'core_competency\external',
'type' => 'read',
'capabilities' => 'moodle/competency:coursecompetencyview',
'ajax' => true,
+ 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'core_competency_count_competencies_in_course' => array(
'classname' => 'core_competency\external',
'type' => 'read',
'capabilities' => 'moodle/competency:competencymanage',
'ajax' => true,
+ 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'core_competency_add_related_competency' => array(
'classname' => 'core_competency\external',
'type' => 'read',
'capabilities' => 'moodle/competency:usercompetencyview',
'ajax' => true,
+ 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'core_competency_user_competency_viewed_in_plan' => array(
'classname' => 'core_competency\external',
'type' => 'read',
'capabilities' => 'moodle/competency:usercompetencyview',
'ajax' => true,
+ 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'core_competency_user_competency_viewed_in_course' => array(
'classname' => 'core_competency\external',
'type' => 'read',
'capabilities' => 'moodle/competency:usercompetencyview',
'ajax' => true,
+ 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'core_competency_user_competency_plan_viewed' => array(
'classname' => 'core_competency\external',
'type' => 'read',
'capabilities' => 'moodle/competency:usercompetencyview',
'ajax' => true,
+ 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'core_competency_grade_competency' => array(
'classname' => 'core_competency\external',
'type' => 'write',
'capabilities' => 'moodle/competency:competencygrade',
'ajax' => true,
+ 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'core_competency_unlink_plan_from_template' => array(
'classname' => 'core_competency\external',
'type' => 'write',
'capabilities' => 'moodle/competency:evidencedelete',
'ajax' => true,
+ 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'core_webservice_get_site_info' => array(
}
public function sql_cast_char2real($fieldname, $text=false) {
- return ' CAST(' . $fieldname . ' AS DECIMAL) ';
+ // Set to 65 (max mysql 5.5 precision) with 7 as scale
+ // because we must ensure at least 6 decimal positions
+ // per casting given that postgres is casting to that scale (::real::).
+ // Can be raised easily but that must be done in all DBs and tests.
+ return ' CAST(' . $fieldname . ' AS DECIMAL(65,7)) ';
}
/**
$DB->insert_record($tablename, array('name'=>'10.10', 'nametext'=>'10.10', 'res'=>5.1));
$DB->insert_record($tablename, array('name'=>'91.10', 'nametext'=>'91.10', 'res'=>666));
- $DB->insert_record($tablename, array('name'=>'011.10', 'nametext'=>'011.10', 'res'=>10.1));
+ $DB->insert_record($tablename, array('name'=>'011.13333333', 'nametext'=>'011.13333333', 'res'=>10.1));
// Casting varchar field.
$sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_cast_char2real('name')." > res";
$records = $DB->get_records_sql($sql);
$this->assertCount(3, $records);
$this->assertSame('10.10', reset($records)->name);
- $this->assertSame('011.10', next($records)->name);
+ $this->assertSame('011.13333333', next($records)->name);
$this->assertSame('91.10', next($records)->name);
+ // And verify we can operate with them without too much problem with at least 6 decimals scale accuracy.
+ $sql = "SELECT AVG(" . $DB->sql_cast_char2real('name') . ") FROM {{$tablename}}";
+ $this->assertEquals(37.44444443333333, (float)$DB->get_field_sql($sql), '', 1.0E-6);
// Casting text field.
$sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_cast_char2real('nametext', true)." > res";
$records = $DB->get_records_sql($sql);
$this->assertCount(3, $records);
$this->assertSame('10.10', reset($records)->nametext);
- $this->assertSame('011.10', next($records)->nametext);
+ $this->assertSame('011.13333333', next($records)->nametext);
$this->assertSame('91.10', next($records)->nametext);
+ // And verify we can operate with them without too much problem with at least 6 decimals scale accuracy.
+ $sql = "SELECT AVG(" . $DB->sql_cast_char2real('nametext', true) . ") FROM {{$tablename}}";
+ $this->assertEquals(37.44444443333333, (float)$DB->get_field_sql($sql), '', 1.0E-6);
+
+ // Check it works with values passed as param.
+ $sql = "SELECT name FROM {{$tablename}} WHERE FLOOR(res - " . $DB->sql_cast_char2real(':param') . ") = 0";
+ $this->assertEquals('011.13333333', $DB->get_field_sql($sql, array('param' => '10.09999')));
+
+ // And also, although not recommended, with directly passed values.
+ $sql = "SELECT name FROM {{$tablename}} WHERE FLOOR(res - " . $DB->sql_cast_char2real('10.09999') . ") = 0";
+ $this->assertEquals('011.13333333', $DB->get_field_sql($sql));
}
public function test_sql_compare_text() {
$basefields = array('id', 'category', 'sortorder',
'shortname', 'fullname', 'idnumber',
'startdate', 'visible',
+ 'defaultgroupingid',
'groupmode', 'groupmodeforce');
if (empty($fields)) {
* The caller can change the format (raw) with the external_settings singleton
* All web service servers must set this singleton when parsing the $_GET and $_POST.
*
+ * <pre>
+ * Options are the same that in {@link format_string()} with some changes:
+ * filter : Can be set to false to force filters off, else observes {@link external_settings}.
+ * </pre>
+ *
* @param string $str The string to be filtered. Should be plain text, expect
* possibly for multilang tags.
* @param boolean $striplinks To strip any link in the result text. Moodle 1.8 default changed from false to true! MDL-8713
if (!$settings->get_raw()) {
$context = context::instance_by_id($contextid);
$options['context'] = $context;
- $options['filter'] = $settings->get_filter();
+ $options['filter'] = isset($options['filter']) && !$options['filter'] ? false : $settings->get_filter();
$str = format_string($str, $striplinks, $options);
}
* trusted : If true the string won't be cleaned. Default false.
* noclean : If true the string won't be cleaned only if trusted is also true. Default false.
* nocache : If true the string will not be cached and will be formatted every call. Default false.
- * filter : If true the string will be run through applicable filters as well. Default (different from format_text)
- * got form settings.
+ * filter : Can be set to false to force filters off, else observes {@link external_settings}.
* para : If true then the returned string will be wrapped in div tags. Default (different from format_text) false.
* Default changed because div tags are not commonly needed.
* newlines : If true then lines newline breaks will be converted to HTML newline breaks. Default true.
}
}
- $options['filter'] = isset($options['filter']) ? $options['filter'] : $settings->get_filter();
+ $options['filter'] = isset($options['filter']) && !$options['filter'] ? false : $settings->get_filter();
$options['para'] = isset($options['para']) ? $options['para'] : false;
$options['context'] = context::instance_by_id($contextid);
$options['allowid'] = isset($options['allowid']) ? $options['allowid'] : true;
return $text;
}
+/**
+ * Tells whether the filename is executable.
+ *
+ * @link http://php.net/manual/en/function.is-executable.php
+ * @link https://bugs.php.net/bug.php?id=41062
+ * @param string $filename Path to the file.
+ * @return bool True if the filename exists and is executable; otherwise, false.
+ */
+function file_is_executable($filename) {
+ if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
+ if (is_executable($filename)) {
+ return true;
+ } else {
+ $fileext = strrchr($filename, '.');
+ // If we have an extension we can check if it is listed as executable.
+ if ($fileext && file_exists($filename) && !is_dir($filename)) {
+ $winpathext = strtolower(getenv('PATHEXT'));
+ $winpathexts = explode(';', $winpathext);
+
+ return in_array(strtolower($fileext), $winpathexts);
+ }
+
+ return false;
+ }
+ } else {
+ return is_executable($filename);
+ }
+}
+
/**
* RESTful cURL class
*
protected function create_converted_document(stored_file $file, $format) {
global $CFG;
- if (empty($CFG->pathtounoconv) || !is_executable(trim($CFG->pathtounoconv))) {
+ if (empty($CFG->pathtounoconv) || !file_is_executable(trim($CFG->pathtounoconv))) {
// No conversions are possible, sorry.
return false;
}
* 'multiple' - boolean multi select
* 'exclude' - array or int, list of course ids to never show
* 'requiredcapabilities' - array of capabilities. Uses ANY to combine them.
+ * 'limittoenrolled' - boolean Limits to enrolled courses.
+ * 'includefrontpage' - boolean Enables the frontpage to be selected.
*/
public function __construct($elementname = null, $elementlabel = null, $options = array()) {
if (isset($options['multiple'])) {
if (isset($options['placeholder'])) {
$validattributes['placeholder'] = $options['placeholder'];
}
+ if (!empty($options['includefrontpage'])) {
+ $validattributes['data-includefrontpage'] = SITEID;
+ }
parent::__construct($elementname, $elementlabel, array(), $validattributes);
}
}
if (empty($coursestofetch)) {
- return $this->setSelected(array());
+ return $this->setSelected($values);
}
// There is no API function to load a list of course from a list of ids.
$coursestoselect = array();
foreach ($list as $course) {
context_helper::preload_from_record($course);
+ $context = context_course::instance($course->id);
// Make sure we can see the course.
- if (!$course->visible && !has_capability('moodle/course:viewhiddencourses', context_course::instance($course->id))) {
+ if (!$course->visible && !has_capability('moodle/course:viewhiddencourses', $context)) {
continue;
}
- $label = get_course_display_name_for_list($course);
+ $label = format_string(get_course_display_name_for_list($course), true, ['context' => $context]);
$this->addOption($label, $course->id);
array_push($coursestoselect, $course->id);
}
- return $this->setSelected($coursestoselect);
+
+ return $this->setSelected($values);
}
}
Y.on('click', function(e, client_id) {
e.preventDefault();
M.core_filepicker.instances[client_id].show();
- }, '#filepicker-button-'+options.client_id, null, options.client_id);
-
+ }, '#filepicker-button-js-'+options.client_id, null, options.client_id);
};
M.form_url.callback = function (params) {
$calendar = \core_calendar\type_factory::get_calendar_instance();
$module = 'moodle-form-dateselector';
$function = 'M.form.dateselector.init_date_selectors';
+ $defaulttimezone = date_default_timezone_get();
+
$config = array(array(
'firstdayofweek' => $calendar->get_starting_weekday(),
- 'mon' => strftime('%a', strtotime("Monday")),
- 'tue' => strftime('%a', strtotime("Tuesday")),
- 'wed' => strftime('%a', strtotime("Wednesday")),
- 'thu' => strftime('%a', strtotime("Thursday")),
- 'fri' => strftime('%a', strtotime("Friday")),
- 'sat' => strftime('%a', strtotime("Saturday")),
- 'sun' => strftime('%a', strtotime("Sunday")),
- 'january' => strftime('%B', strtotime("January 1")),
- 'february' => strftime('%B', strtotime("February 1")),
- 'march' => strftime('%B', strtotime("March 1")),
- 'april' => strftime('%B', strtotime("April 1")),
- 'may' => strftime('%B', strtotime("May 1")),
- 'june' => strftime('%B', strtotime("June 1")),
- 'july' => strftime('%B', strtotime("July 1")),
- 'august' => strftime('%B', strtotime("August 1")),
- 'september' => strftime('%B', strtotime("September 1")),
- 'october' => strftime('%B', strtotime("October 1")),
- 'november' => strftime('%B', strtotime("November 1")),
- 'december' => strftime('%B', strtotime("December 1"))
+ 'mon' => date_format_string(strtotime("Monday"), '%a', $defaulttimezone),
+ 'tue' => date_format_string(strtotime("Tuesday"), '%a', $defaulttimezone),
+ 'wed' => date_format_string(strtotime("Wednesday"), '%a', $defaulttimezone),
+ 'thu' => date_format_string(strtotime("Thursday"), '%a', $defaulttimezone),
+ 'fri' => date_format_string(strtotime("Friday"), '%a', $defaulttimezone),
+ 'sat' => date_format_string(strtotime("Saturday"), '%a', $defaulttimezone),
+ 'sun' => date_format_string(strtotime("Sunday"), '%a', $defaulttimezone),
+ 'january' => date_format_string(strtotime("January 1"), '%B', $defaulttimezone),
+ 'february' => date_format_string(strtotime("February 1"), '%B', $defaulttimezone),
+ 'march' => date_format_string(strtotime("March 1"), '%B', $defaulttimezone),
+ 'april' => date_format_string(strtotime("April 1"), '%B', $defaulttimezone),
+ 'may' => date_format_string(strtotime("May 1"), '%B', $defaulttimezone),
+ 'june' => date_format_string(strtotime("June 1"), '%B', $defaulttimezone),
+ 'july' => date_format_string(strtotime("July 1"), '%B', $defaulttimezone),
+ 'august' => date_format_string(strtotime("August 1"), '%B', $defaulttimezone),
+ 'september' => date_format_string(strtotime("September 1"), '%B', $defaulttimezone),
+ 'october' => date_format_string(strtotime("October 1"), '%B', $defaulttimezone),
+ 'november' => date_format_string(strtotime("November 1"), '%B', $defaulttimezone),
+ 'december' => date_format_string(strtotime("December 1"), '%B', $defaulttimezone)
));
$PAGE->requires->yui_module($module, $function, $config);
$done = true;
</span></span>', FORMAT_HTML);
$this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0), $correct);
+ // Filters can be opted out from by the developer.
+ $test = '$$ \pi $$';
+ $testformat = FORMAT_MARKDOWN;
+ $correct = array('<p>$$ \pi $$</p>
+', FORMAT_HTML);
+ $this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0, ['filter' => false]), $correct);
+
$test = '<p><a id="test"></a><a href="#test">Text</a></p>';
$testformat = FORMAT_HTML;
$correct = array($test, FORMAT_HTML);
}
public function test_external_format_string() {
+ $this->resetAfterTest();
$settings = external_settings::get_instance();
-
$currentraw = $settings->get_raw();
$currentfilter = $settings->get_filter();
+ // Enable multilang filter to on content and heading.
+ filter_set_global_state('multilang', TEXTFILTER_ON);
+ filter_set_applies_to_strings('multilang', 1);
+ $filtermanager = filter_manager::instance();
+ $filtermanager->reset_caches();
+
$settings->set_raw(true);
+ $settings->set_filter(true);
$context = context_system::instance();
- $test = '$$ \pi $$ <script>hi</script> <h3>there</h3>';
+ $test = '<span lang="en" class="multilang">EN</span><span lang="fr" class="multilang">FR</span> ' .
+ '<script>hi</script> <h3>there</h3>!';
$correct = $test;
- $this->assertSame(external_format_string($test, $context->id), $correct);
+ $this->assertSame($correct, external_format_string($test, $context->id));
$settings->set_raw(false);
+ $settings->set_filter(false);
+
+ $test = '<span lang="en" class="multilang">EN</span><span lang="fr" class="multilang">FR</span> ' .
+ '<script>hi</script> <h3>there</h3>?';
+ $correct = 'ENFR hi there?';
+ $this->assertSame($correct, external_format_string($test, $context->id));
+
+ $settings->set_filter(true);
+
+ $test = '<span lang="en" class="multilang">EN</span><span lang="fr" class="multilang">FR</span> ' .
+ '<script>hi</script> <h3>there</h3>@';
+ $correct = 'EN hi there@';
+ $this->assertSame($correct, external_format_string($test, $context->id));
+
+ // Filters can be opted out.
+ $test = '<span lang="en" class="multilang">EN</span><span lang="fr" class="multilang">FR</span> ' .
+ '<script>hi</script> <h3>there</h3>%';
+ $correct = 'ENFR hi there%';
+ $this->assertSame($correct, external_format_string($test, $context->id, false, ['filter' => false]));
- $test = '$$ \pi $$<script>hi</script> <h3>there</h3>';
- $correct = '$$ \pi $$hi there';
- $this->assertSame(external_format_string($test, $context->id), $correct);
$settings->set_raw($currentraw);
$settings->set_filter($currentfilter);
+++ /dev/null
-<?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/>.
-
-/**
- * Tests redis session handler
- *
- * @package core
- * @copyright 2016 Nicholas Hoobin (nicholashoobin@catalyst-au.net)
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-/**
- * Tests redis session handler class
- *
- * @package core
- * @copyright 2016 Nicholas Hoobin (nicholashoobin@catalyst-au.net)
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class redis_session_testcase extends advanced_testcase {
-
- /**
- * Test test_redis_connection_parser()
- * @param string $constring The connection string, 'savepath'.
- * @param array $expected An array of expected results.
- * @param int $count Number of valid connections.
- * @dataProvider connectionprovider
- */
- public function test_redis_connection_parser($constring, $expected, $debug) {
- $handler = new \core\session\redis;
-
- $servers = $handler->connection_string_to_redis_servers($constring);
-
- if ($debug == true) {
- $this->assertDebuggingCalled();
- }
-
- $this->assertEquals($expected, $servers);
- }
-
- /**
- * Provides data for test_redis_connection_parser().
- * @return array array of connection results
- */
- public function connectionprovider() {
- return array(
- array(
- "tcp://127.0.0.1, unix:///var/run/redis/redis.sock",
- array(
- array(
- 'database' => 0,
- 'timeout' => 86400,
- 'port' => 6379,
- 'scheme' => 'tcp',
- 'prefix' => 'PHPREDIS_SESSION:',
- 'host' => '127.0.0.1'
- ),
- array(
- 'database' => 0,
- 'timeout' => 86400,
- 'scheme' => 'unix',
- 'prefix' => 'PHPREDIS_SESSION:',
- 'path' => '/var/run/redis/redis.sock'
- )
- ),
- false
- ),
- array(
- "tcp://127.0.0.1?database=2&timeout=2.5&port=54428",
- array(
- array(
- 'database' => '2',
- 'timeout' => '2.5',
- 'port' => '54428',
- 'scheme' => 'tcp',
- 'prefix' => 'PHPREDIS_SESSION:',
- 'host' => '127.0.0.1',
- 'query' => 'database=2&timeout=2.5&port=54428'
- ),
- ),
- false
- ),
- array(
- "127.0.0.1",
- array(),
- true
- ),
- array(
- "tcp:sdgf243@Q#t23",
- array(),
- true
- ),
- array(
- "/var/run/redis/redis.sock",
- array(),
- true
- )
- );
- }
-}
public function test_generate_pdf() {
global $CFG;
- if (empty($CFG->pathtounoconv) || !is_executable(trim($CFG->pathtounoconv))) {
+ if (empty($CFG->pathtounoconv) || !file_is_executable(trim($CFG->pathtounoconv))) {
// No conversions are possible, sorry.
return $this->markTestSkipped();
}
public function test_generate_markdown() {
global $CFG;
- if (empty($CFG->pathtounoconv) || !is_executable(trim($CFG->pathtounoconv))) {
+ if (empty($CFG->pathtounoconv) || !file_is_executable(trim($CFG->pathtounoconv))) {
// No conversions are possible, sorry.
return $this->markTestSkipped();
}
* table_sql download process is using the new data formats plugin which you can't use if you are buffering any output
* flexible_table::get_download_menu(), considered private, has been deleted. Use
$OUTPUT->download_dataformat_selector() instead.
+ when building Xpath, or pass the unescaped value when using the named selector.
+* Add new file_is_executable(), to consistently check for executables even in Windows (PHP bug #41062).
+* Introduced new hooks for plugin developers.
+ - <component>_pre_course_category_delete($category)
+ - <component>_pre_course_delete($course)
+ - <component>_pre_course_module_delete($cm)
+ - <component>_pre_block_delete($instance)
+ - <component>_pre_user_delete($user)
+ These hooks allow developers to use the item in question before it is deleted by core. For example, if your plugin is
+ a module (plugins located in the mod folder) called 'xxx' and you wish to interact with the user object before it is
+ deleted then the function to create would be mod_xxx_pre_user_delete($user) in mod/xxx/lib.php.
=== 3.0 ===
var UserInfo = function(selector) {
this._regionSelector = selector;
this._region = $(selector);
- this._userCache = [];
+ this._userCache = {};
$(document).on('user-changed', this._refreshUserInfo.bind(this));
};
/** @type {Integer} Remember the last user id to prevent unnessecary reloads. */
UserInfo.prototype._lastUserId = 0;
+ /**
+ * Get the assignment id
+ *
+ * @private
+ * @method _getAssignmentId
+ * @return int assignment id
+ */
+ UserInfo.prototype._getAssignmentId = function() {
+ return this._region.attr('data-assignmentid');
+ };
+
/**
* Get the user context - re-render the template in the page.
*
promise.resolve(this._userCache[userid]);
} else {
// Load context from ajax.
+ var assignmentId = this._getAssignmentId();
var requests = ajax.call([{
- methodname: 'core_user_get_users_by_field',
- args: { field: 'id', values: [ userid ] }
+ methodname: 'mod_assign_get_participant',
+ args: {
+ userid: userid,
+ assignid: assignmentId,
+ embeduser: true
+ }
}]);
- requests[0].done(function(result) {
- if (result.length < 1) {
+ requests[0].done(function(participant) {
+ if (!participant.hasOwnProperty('id')) {
promise.reject('No users');
} else {
- $.each(result, function(index, user) {
- this._userCache[user.id] = user;
- }.bind(this));
+ this._userCache[userid] = participant;
promise.resolve(this._userCache[userid]);
}
}.bind(this)).fail(notification.exception);
identity = [];
// Render the template.
context.courseid = $('[data-region="grading-navigation-panel"]').attr('data-courseid');
- // Build a string for the visible identity fields listed in showuseridentity config setting.
- $.each(identityfields, function(i, k) {
- if (typeof context[k] !== 'undefined' && context[k] !== '') {
- context.hasidentity = true;
- identity.push(context[k]);
+
+ if (context.user) {
+ // Build a string for the visible identity fields listed in showuseridentity config setting.
+ $.each(identityfields, function(i, k) {
+ if (typeof context.user[k] !== 'undefined' && context.user[k] !== '') {
+ context.hasidentity = true;
+ identity.push(context.user[k]);
+ }
+ });
+ context.identity = identity.join(', ');
+
+ // Add profile image url to context.
+ if (context.user.profileimageurl) {
+ context.profileimageurl = context.user.profileimageurl;
}
- });
- context.identity = identity.join(', ');
+ }
templates.render('mod_assign/grading_navigation_user_summary', context).done(function(html, js) {
// Update the page.
this._region.fadeIn("fast");
}.bind(this));
}.bind(this)).fail(notification.exception);
- });
+ }
+ .bind(this));
}.bind(this)).fail(notification.exception);
};
'ajax' => true,
'capabilities' => 'mod/assign:grade'
),
+ 'mod_assign_get_participant' => array(
+ 'classname' => 'mod_assign_external',
+ 'methodname' => 'get_participant',
+ 'classpath' => 'mod/assign/externallib.php',
+ 'description' => 'Get a participant for an assignment, with some summary info about their submissions.',
+ 'type' => 'read',
+ 'ajax' => true,
+ 'capabilities' => 'mod/assign:view, mod/assign:viewgrades'
+ ),
);
defined('MOODLE_INTERNAL') || die;
require_once("$CFG->libdir/externallib.php");
+require_once("$CFG->dirroot/user/externallib.php");
require_once("$CFG->dirroot/mod/assign/locallib.php");
/**
))
);
}
+
+ /**
+ * Returns description of method parameters
+ *
+ * @return external_function_parameters
+ * @since Moodle 3.1
+ */
+ public static function get_participant_parameters() {
+ return new external_function_parameters(
+ array(
+ 'assignid' => new external_value(PARAM_INT, 'assign instance id'),
+ 'userid' => new external_value(PARAM_INT, 'user id'),
+ 'embeduser' => new external_value(PARAM_BOOL, 'user id', VALUE_DEFAULT, false),
+ )
+ );
+ }
+
+ /**
+ * Get the user participating in the given assignment. An error with code 'usernotincourse'
+ * is thrown is the user isn't a participant of the given assignment.
+ *
+ * @param int $assignid the assign instance id
+ * @param int $userid the user id
+ * @param bool $embeduser return user details (only applicable if not blind marking)
+ * @return array of warnings and status result
+ * @since Moodle 3.1
+ * @throws moodle_exception
+ */
+ public static function get_participant($assignid, $userid, $embeduser) {
+ global $DB, $CFG;
+ require_once($CFG->dirroot . "/mod/assign/locallib.php");
+ require_once($CFG->dirroot . "/user/lib.php");
+
+ $params = self::validate_parameters(self::get_participant_parameters(), array(
+ 'assignid' => $assignid,
+ 'userid' => $userid,
+ 'embeduser' => $embeduser
+ ));
+
+ // Request and permission validation.
+ $assign = $DB->get_record('assign', array('id' => $params['assignid']), 'id', MUST_EXIST);
+ list($course, $cm) = get_course_and_cm_from_instance($assign, 'assign');
+
+ $context = context_module::instance($cm->id);
+ self::validate_context($context);
+
+ $assign = new assign($context, null, null);
+ $assign->require_view_grades();
+
+ $participant = $assign->get_participant($params['userid']);
+ if (!$participant) {
+ // No participant found so we can return early.
+ throw new moodle_exception('usernotincourse');
+ }
+
+ $return = array(
+ 'id' => $participant->id,
+ 'fullname' => $participant->fullname,
+ 'submitted' => $participant->submitted,
+ 'requiregrading' => $participant->requiregrading,
+ 'blindmarking' => $assign->is_blind_marking(),
+ );
+
+ if (!empty($participant->groupid)) {
+ $return['groupid'] = $participant->groupid;
+ }
+ if (!empty($participant->groupname)) {
+ $return['groupname'] = $participant->groupname;
+ }
+
+ // Skip the expensive lookup of user detail if we're blind marking or the caller
+ // hasn't asked for user details to be embedded.
+ if (!$assign->is_blind_marking() && $embeduser) {
+ $return['user'] = user_get_user_details($participant, $course);
+ }
+
+ return $return;
+ }
+
+ /**
+ * Returns description of method result value
+ *
+ * @return external_description
+ * @since Moodle 3.1
+ */
+ public static function get_participant_returns() {
+ $userdescription = core_user_external::user_description();
+ $userdescription->default = [];
+ $userdescription->required = VALUE_OPTIONAL;
+
+ return new external_single_structure(array(
+ 'id' => new external_value(PARAM_INT, 'ID of the user'),
+ 'fullname' => new external_value(PARAM_NOTAGS, 'The fullname of the user'),
+ 'submitted' => new external_value(PARAM_BOOL, 'have they submitted their assignment'),
+ 'requiregrading' => new external_value(PARAM_BOOL, 'is their submission waiting for grading'),
+ 'blindmarking' => new external_value(PARAM_BOOL, 'is blind marking enabled for this assignment'),
+ 'groupid' => new external_value(PARAM_INT, 'for group assignments this is the group id', VALUE_OPTIONAL),
+ 'groupname' => new external_value(PARAM_NOTAGS, 'for group assignments this is the group name', VALUE_OPTIONAL),
+ 'user' => $userdescription,
+ ));
+ }
}
if (!$this->is_downloading() && $this->hasgrade) {
$urlparams = array('id' => $this->assignment->get_course_module()->id,
'rownum' => 0,
- 'action' => 'grader',
- 'userid' => $row->userid);
+ 'action' => 'grader');
+
+ if ($this->assignment->is_blind_marking()) {
+ $urlparams['blindid'] = $this->assignment->get_uniqueid_for_user($row->userid);
+ } else {
+ $urlparams['userid'] = $row->userid;
+ }
+
$url = new moodle_url('/mod/assign/view.php', $urlparams);
$link = '<a href="' . $url . '" class="btn btn-primary">' . get_string('grade') . '</a>';
$grade .= $link . $separator;
}
/**
- * Get the submission status/grading status for all submissions in this assignment.
+ * Get the submission status/grading status for all submissions in this assignment for the
+ * given paticipants.
+ *
* These statuses match the available filters (requiregrading, submitted, notsubmitted).
* If this is a group assignment, group info is also returned.
*
- * @param int $currentgroup
- * @return array List of user records with extra fields 'submitted', 'notsubmitted', 'requiregrading', 'groupid', 'groupname'
+ * @param array $participants an associative array where the key is the participant id and
+ * the value is the participant record.
+ * @return array an associative array where the key is the participant id and the value is
+ * the participant record.
*/
- public function list_participants_with_filter_status_and_group($currentgroup) {
+ private function get_submission_info_for_participants($participants) {
global $DB;
- $participants = $this->list_participants($currentgroup, false);
-
if (empty($participants)) {
return $participants;
}
return $participants;
}
+ /**
+ * Get the submission status/grading status for all submissions in this assignment.
+ * These statuses match the available filters (requiregrading, submitted, notsubmitted).
+ * If this is a group assignment, group info is also returned.
+ *
+ * @param int $currentgroup
+ * @return array List of user records with extra fields 'submitted', 'notsubmitted', 'requiregrading', 'groupid', 'groupname'
+ */
+ public function list_participants_with_filter_status_and_group($currentgroup) {
+ $participants = $this->list_participants($currentgroup, false);
+
+ if (empty($participants)) {
+ return $participants;
+ } else {
+ return $this->get_submission_info_for_participants($participants);
+ }
+ }
+
/**
* Load a list of users enrolled in the current course with the specified permission and group.
* 0 for no group.
return $this->participants[$key];
}
+ /**
+ * Load a user if they are enrolled in the current course. Populated with submission
+ * status for this assignment.
+ *
+ * @param int $userid
+ * @return null|stdClass user record
+ */
+ public function get_participant($userid) {
+ global $DB;
+
+ $participant = $DB->get_record('user', array('id' => $userid));
+ if (!$participant) {
+ return null;
+ }
+
+ if (!is_enrolled($this->context, $participant, 'mod/assign:submit', $this->show_only_active_users())) {
+ return null;
+ }
+
+ $result = $this->get_submission_info_for_participants(array($participant->id => $participant));
+ return $result[$participant->id];
+ }
+
/**
* Load a count of valid teams for this assignment.
*
$o .= $this->get_renderer()->header();
$userid = optional_param('userid', 0, PARAM_INT);
+ $blindid = optional_param('blindid', 0, PARAM_INT);
+
+ if (!$userid && $blindid) {
+ $userid = $this->get_user_id_for_uniqueid($blindid);
+ }
$currentgroup = groups_get_activity_group($this->get_course_module(), true);
$framegrader = new grading_app($userid, $currentgroup, $this);
// Need submit permission to submit an assignment.
$userid = optional_param('userid', $USER->id, PARAM_INT);
$user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
+
+ // This variation on the url will link direct to this student.
+ // The benefit is the url will be the same every time for this student, so Atto autosave drafts can match up.
+ $returnparams = array('userid' => $userid, 'rownum' => 0, 'useridlistid' => 0);
+ $this->register_return_link('editsubmission', $returnparams);
+
if ($userid == $USER->id) {
if (!$this->can_edit_submission($userid, $USER->id)) {
print_error('nopermission');
.path-mod-assign [data-region="grade-panel"] #id_gradeheader {
display: table-cell;
+ min-width: 0;
}
.path-mod-assign [data-region="grade-panel"] #id_gradeheader > legend {
.path-mod-assign [data-region="grade-panel"] .gradingform_rubric {
padding-bottom: 0;
+ max-width: none;
}
.path-mod-assign [data-region="grade-panel"] .gradingform_rubric .criterion .description {
left: 20%;
right: 20%;
top: 20%;
+ bottom: 20%;
z-index: 1000;
border: 1px solid rgba(0, 0, 0, 0.3);
border-radius: 6px;
text-align: center;
padding-left: 15px;
padding-right: 15px;
+ height: 45px;
}
.path-mod-assign #page-content [data-region="grade-panel"] .mform:not(.unresponsive) .fcontainer .fitem.popout .fitemtitle label {
.path-mod-assign #page-content [data-region="grade-panel"] .mform:not(.unresponsive) .fcontainer .fitem.popout .felement {
padding: 10px 15px 15px;
+ height: calc(100% - 54px);
+ overflow: auto;
+}
+
+.path-mod-assign #page-content [data-region="grade-panel"] .mform:not(.unresponsive) .fcontainer .fitem.popout .felement .gradingform_rubric {
+ overflow: visible;
}
/***** End popout dialogue *****/
Then I should see "Submitted for grading"
And I should see "I'm the student second submission"
And I should not see "I'm the student first submission"
+
+ @javascript
+ Scenario: Auto-draft save online text submission
+ Given the following "courses" exist:
+ | fullname | shortname | category | groupmode |
+ | Course 1 | C1 | 0 | 1 |
+ And 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 config values are set as admin:
+ | autosavefrequency | 1 | editor_atto |
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ And I add a "Assignment" to section "1" and I fill the form with:
+ | Assignment name | Test assignment name |
+ | Description | Submit your online text |
+ | assignsubmission_onlinetext_enabled | 1 |
+ | assignsubmission_file_enabled | 0 |
+ And I log out
+ And I log in as "student1"
+ And I follow "Course 1"
+ And I follow "Test assignment name"
+ When I press "Add submission"
+ And I set the following fields to these values:
+ | Online text | text submission |
+ # Wait for the draft auto save.
+ And I wait "2" seconds
+ And I follow "Course 1"
+ And I follow "Test assignment name"
+ When I press "Add submission"
+ # Confirm draft was restored.
+ Then I should see "text submission" in the "#id_onlinetext_editoreditable" "css_element"
$this->assertEquals(0, $result['lastattempt']['submission']['groupid']);
$this->assertEquals($assign->get_instance()->id, $result['lastattempt']['submission']['assignment']);
$this->assertEquals(1, $result['lastattempt']['submission']['latest']);
- $this->assertEquals('Submission text', $result['lastattempt']['submission']['plugins'][0]['editorfields'][0]['text']);
- $this->assertEquals('/t.txt', $result['lastattempt']['submission']['plugins'][1]['fileareas'][0]['files'][0]['filepath']);
+
+ // Map plugins based on their type - we can't rely on them being in a
+ // particular order, especially if 3rd party plugins are installed.
+ $submissionplugins = array();
+ foreach ($result['lastattempt']['submission']['plugins'] as $plugin) {
+ $submissionplugins[$plugin['type']] = $plugin;
+ }
+ $this->assertEquals('Submission text', $submissionplugins['onlinetext']['editorfields'][0]['text']);
+ $this->assertEquals('/t.txt', $submissionplugins['file']['fileareas'][0]['files'][0]['filepath']);
}
/**
$this->assertEquals(50, $result['previousattempts'][0]['grade']['grade']);
$this->assertEquals($teacher->id, $result['previousattempts'][0]['grade']['grader']);
$this->assertEquals($student1->id, $result['previousattempts'][0]['grade']['userid']);
- $this->assertEquals('Yeeha!', $result['previousattempts'][0]['feedbackplugins'][0]['editorfields'][0]['text']);
- $submissionplugins = $result['previousattempts'][0]['submission']['plugins'];
- $this->assertEquals('Submission text', $submissionplugins[0]['editorfields'][0]['text']);
- $this->assertEquals('/t.txt', $submissionplugins[1]['fileareas'][0]['files'][0]['filepath']);
+
+ // Map plugins based on their type - we can't rely on them being in a
+ // particular order, especially if 3rd party plugins are installed.
+ $feedbackplugins = array();
+ foreach ($result['previousattempts'][0]['feedbackplugins'] as $plugin) {
+ $feedbackplugins[$plugin['type']] = $plugin;
+ }
+ $this->assertEquals('Yeeha!', $feedbackplugins['comments']['editorfields'][0]['text']);
+
+ $submissionplugins = array();
+ foreach ($result['previousattempts'][0]['submission']['plugins'] as $plugin) {
+ $submissionplugins[$plugin['type']] = $plugin;
+ }
+ $this->assertEquals('Submission text', $submissionplugins['onlinetext']['editorfields'][0]['text']);
+ $this->assertEquals('/t.txt', $submissionplugins['file']['fileareas'][0]['files'][0]['filepath']);
}
/**
mod_assign_external::get_submission_status($assign->get_instance()->id, $student1->id);
}
+
+ /**
+ * get_participant should throw an excaption if the requested assignment doesn't exist.
+ */
+ public function test_get_participant_no_assignment() {
+ $this->resetAfterTest(true);
+ $this->setExpectedException('moodle_exception');
+ mod_assign_external::get_participant('-1', '-1', false);
+ }
+
+ /**
+ * get_participant should throw a require_login_exception if the user doesn't have access
+ * to view assignments.
+ */
+ public function test_get_participant_no_view_capability() {
+ global $DB;
+ $this->resetAfterTest(true);
+
+ $result = $this->create_assign_with_student_and_teacher();
+ $assign = $result['assign'];
+ $student = $result['student'];
+ $course = $result['course'];
+ $context = context_course::instance($course->id);
+ $studentrole = $DB->get_record('role', array('shortname' => 'student'));
+
+ $this->setUser($student);
+ assign_capability('mod/assign:view', CAP_PROHIBIT, $studentrole->id, $context->id, true);
+
+ $this->setExpectedException('require_login_exception');
+ mod_assign_external::get_participant($assign->id, $student->id, false);
+ }
+
+ /**
+ * get_participant should throw a required_capability_exception if the user doesn't have access
+ * to view assignment grades.
+ */
+ public function test_get_participant_no_grade_capability() {
+ global $DB;
+ $this->resetAfterTest(true);
+
+ $result = $this->create_assign_with_student_and_teacher();
+ $assign = $result['assign'];
+ $student = $result['student'];
+ $teacher = $result['teacher'];
+ $course = $result['course'];
+ $context = context_course::instance($course->id);
+ $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
+
+ $this->setUser($teacher);
+ assign_capability('mod/assign:viewgrades', CAP_PROHIBIT, $teacherrole->id, $context->id, true);
+ assign_capability('mod/assign:grade', CAP_PROHIBIT, $teacherrole->id, $context->id, true);
+ accesslib_clear_all_caches_for_unit_testing();
+
+ $this->setExpectedException('required_capability_exception');
+ mod_assign_external::get_participant($assign->id, $student->id, false);
+ }
+
+ /**
+ * get_participant should throw an exception if the user isn't enrolled in the course.
+ */
+ public function test_get_participant_no_participant() {
+ global $DB;
+ $this->resetAfterTest(true);
+
+ $result = $this->create_assign_with_student_and_teacher(array('blindmarking' => true));
+ $student = $this->getDataGenerator()->create_user();
+ $assign = $result['assign'];
+ $teacher = $result['teacher'];
+
+ $this->setUser($teacher);
+
+ $this->setExpectedException('moodle_exception');
+ $result = mod_assign_external::get_participant($assign->id, $student->id, false);
+ }
+
+ /**
+ * get_participant should return a summarised list of details with a different fullname if blind
+ * marking is on for the requested assignment.
+ */
+ public function test_get_participant_blind_marking() {
+ global $DB;
+ $this->resetAfterTest(true);
+
+ $result = $this->create_assign_with_student_and_teacher(array('blindmarking' => true));
+ $assign = $result['assign'];
+ $student = $result['student'];
+ $teacher = $result['teacher'];
+ $course = $result['course'];
+ $context = context_course::instance($course->id);
+ $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
+
+ $this->setUser($teacher);
+
+ $result = mod_assign_external::get_participant($assign->id, $student->id, true);
+ $this->assertEquals($student->id, $result['id']);
+ $this->assertFalse(fullname($student) == $result['fullname']);
+ $this->assertFalse($result['submitted']);
+ $this->assertFalse($result['requiregrading']);
+ $this->assertTrue($result['blindmarking']);
+ // Make sure we don't get any additional info.
+ $this->assertTrue(empty($result['user']));
+ }
+
+ /**
+ * get_participant should return a summarised list of details if requested.
+ */
+ public function test_get_participant_no_user() {
+ global $DB;
+ $this->resetAfterTest(true);
+
+ $result = $this->create_assign_with_student_and_teacher();
+ $assignmodule = $result['assign'];
+ $student = $result['student'];
+ $teacher = $result['teacher'];
+ $course = $result['course'];
+ $context = context_course::instance($course->id);
+ $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
+
+ // Create an assign instance to save a submission.
+ set_config('submissionreceipts', 0, 'assign');
+
+ $cm = get_coursemodule_from_instance('assign', $assignmodule->id);
+ $context = context_module::instance($cm->id);
+
+ $assign = new assign($context, $cm, $course);
+
+ $this->setUser($student);
+
+ // Simulate a submission.
+ $data = new stdClass();
+ $data->onlinetext_editor = array(
+ 'itemid' => file_get_unused_draft_itemid(),
+ 'text' => 'Student submission text',
+ 'format' => FORMAT_MOODLE
+ );
+
+ $notices = array();
+ $assign->save_submission($data, $notices);
+
+ $data = new stdClass;
+ $data->userid = $student->id;
+ $assign->submit_for_grading($data, array());
+
+ $this->setUser($teacher);
+
+ $result = mod_assign_external::get_participant($assignmodule->id, $student->id, false);
+ $this->assertEquals($student->id, $result['id']);
+ $this->assertEquals(fullname($student), $result['fullname']);
+ $this->assertTrue($result['submitted']);
+ $this->assertTrue($result['requiregrading']);
+ $this->assertFalse($result['blindmarking']);
+ // Make sure we don't get any additional info.
+ $this->assertTrue(empty($result['user']));
+ }
+
+ /**
+ * get_participant should return user details if requested.
+ */
+ public function test_get_participant_full_details() {
+ global $DB;
+ $this->resetAfterTest(true);
+
+ $result = $this->create_assign_with_student_and_teacher();
+ $assign = $result['assign'];
+ $student = $result['student'];
+ $teacher = $result['teacher'];
+ $course = $result['course'];
+ $context = context_course::instance($course->id);
+ $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
+
+ $this->setUser($teacher);
+
+ $result = mod_assign_external::get_participant($assign->id, $student->id, true);
+ // Check some of the extended properties we get when requesting the user.
+ $this->assertEquals($student->id, $result['id']);
+ // We should get user infomation back.
+ $user = $result['user'];
+ $this->assertFalse(empty($user));
+ $this->assertEquals($student->firstname, $user['firstname']);
+ $this->assertEquals($student->lastname, $user['lastname']);
+ $this->assertEquals($student->email, $user['email']);
+ }
+
+ /**
+ * get_participant should return group details if a group submission was
+ * submitted.
+ */
+ public function test_get_participant_group_submission() {
+ global $DB, $CFG;
+ require_once($CFG->dirroot . '/mod/assign/tests/base_test.php');
+
+ $this->resetAfterTest(true);
+
+ $result = $this->create_assign_with_student_and_teacher(array(
+ 'assignsubmission_onlinetext_enabled' => 1,
+ 'teamsubmission' => 1
+ ));
+ $assignmodule = $result['assign'];
+ $student = $result['student'];
+ $teacher = $result['teacher'];
+ $course = $result['course'];
+ $context = context_course::instance($course->id);
+ $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
+ $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
+ $cm = get_coursemodule_from_instance('assign', $assignmodule->id);
+ $context = context_module::instance($cm->id);
+ $assign = new testable_assign($context, $cm, $course);
+
+ groups_add_member($group, $student);
+
+ $this->setUser($student);
+ $submission = $assign->get_group_submission($student->id, $group->id, true);
+ $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
+ $assign->testable_update_submission($submission, $student->id, true, false);
+ $data = new stdClass();
+ $data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(),
+ 'text' => 'Submission text',
+ 'format' => FORMAT_MOODLE);
+ $plugin = $assign->get_submission_plugin_by_type('onlinetext');
+ $plugin->save($submission, $data);
+
+ $this->setUser($teacher);
+
+ $result = mod_assign_external::get_participant($assignmodule->id, $student->id, false);
+ // Check some of the extended properties we get when not requesting a summary.
+ $this->assertEquals($student->id, $result['id']);
+ $this->assertEquals($group->id, $result['groupid']);
+ $this->assertEquals($group->name, $result['groupname']);
+ }
+
+ /**
+ * Create a a course, assignment module instance, student and teacher and enrol them in
+ * the course.
+ *
+ * @param array $params parameters to be provided to the assignment module creation
+ * @return array containing the course, assignment module, student and teacher
+ */
+ private function create_assign_with_student_and_teacher($params = array()) {
+ global $DB;
+
+ $course = $this->getDataGenerator()->create_course();
+ $params = array_merge(array(
+ 'course' => $course->id,
+ 'name' => 'assignment',
+ 'intro' => 'assignment intro text',
+ ), $params);
+
+ // Create a course and assignment and users.
+ $assign = $this->getDataGenerator()->create_module('assign', $params);
+
+ $cm = get_coursemodule_from_instance('assign', $assign->id);
+ $context = context_module::instance($cm->id);
+
+ $student = $this->getDataGenerator()->create_user();
+ $studentrole = $DB->get_record('role', array('shortname' => 'student'));
+ $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
+ $teacher = $this->getDataGenerator()->create_user();
+ $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
+ $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
+
+ assign_capability('mod/assign:view', CAP_ALLOW, $teacherrole->id, $context->id, true);
+ assign_capability('mod/assign:viewgrades', CAP_ALLOW, $teacherrole->id, $context->id, true);
+ assign_capability('mod/assign:grade', CAP_ALLOW, $teacherrole->id, $context->id, true);
+ accesslib_clear_all_caches_for_unit_testing();
+
+ return array(
+ 'course' => $course,
+ 'assign' => $assign,
+ 'student' => $student,
+ 'teacher' => $teacher
+ );
+ }
}
$this->assertEquals(2, count($assign->list_participants(null, true)));
}
+ public function test_get_participant_user_not_exist() {
+ $assign = $this->create_instance(array('grade' => 100));
+ $this->assertNull($assign->get_participant('-1'));
+ }
+
+ public function test_get_participant_not_enrolled() {
+ $assign = $this->create_instance(array('grade' => 100));
+ $user = $this->getDataGenerator()->create_user();
+ $this->assertNull($assign->get_participant($user->id));
+ }
+
+ public function test_get_participant_no_submission() {
+ $assign = $this->create_instance(array('grade' => 100));
+ $student = $this->students[0];
+ $participant = $assign->get_participant($student->id);
+
+ $this->assertEquals($student->id, $participant->id);
+ $this->assertFalse($participant->submitted);
+ $this->assertFalse($participant->requiregrading);
+ }
+
+ public function test_get_participant_with_ungraded_submission() {
+ $assign = $this->create_instance(array('grade' => 100));
+ $student = $this->students[0];
+ $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+
+ $this->setUser($student);
+
+ // Simulate a submission.
+ $data = new stdClass();
+ $data->onlinetext_editor = array(
+ 'itemid' => file_get_unused_draft_itemid(),
+ 'text' => 'Student submission text',
+ 'format' => FORMAT_MOODLE
+ );
+
+ $notices = array();
+ $assign->save_submission($data, $notices);
+
+ $data = new stdClass;
+ $data->userid = $student->id;
+ $assign->submit_for_grading($data, array());
+
+ $participant = $assign->get_participant($student->id);
+
+ $this->assertEquals($student->id, $participant->id);
+ $this->assertTrue($participant->submitted);
+ $this->assertTrue($participant->requiregrading);
+ }
+
+ public function test_get_participant_with_graded_submission() {
+ $assign = $this->create_instance(array('grade' => 100));
+ $student = $this->students[0];
+ $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+
+ $this->setUser($student);
+
+ // Simulate a submission.
+ $data = new stdClass();
+ $data->onlinetext_editor = array(
+ 'itemid' => file_get_unused_draft_itemid(),
+ 'text' => 'Student submission text',
+ 'format' => FORMAT_MOODLE
+ );
+
+ $notices = array();
+ $assign->save_submission($data, $notices);
+
+ $data = new stdClass;
+ $data->userid = $student->id;
+ $assign->submit_for_grading($data, array());
+
+ // This is to make sure the grade happens after the submission because
+ // we have no control over the timemodified values.
+ sleep(1);
+ // Grade the submission.
+ $this->setUser($this->teachers[0]);
+
+ $data = new stdClass();
+ $data->grade = '50.0';
+ $assign->testable_apply_grade_to_user($data, $student->id, 0);
+
+ $participant = $assign->get_participant($student->id);
+
+ $this->assertEquals($student->id, $participant->id);
+ $this->assertTrue($participant->submitted);
+ $this->assertFalse($participant->requiregrading);
+ }
+
public function test_count_teams() {
$this->create_extra_users();
$this->setUser($this->editingteachers[0]);
// Test that all the submission and feedback plugins are returning the expected file aras.
$usingfilearea = 0;
+ $coreplugins = core_plugin_manager::standard_plugins_list('assignsubmission');
foreach ($assign->get_submission_plugins() as $plugin) {
$type = $plugin->get_type();
+ if (!in_array($type, $coreplugins)) {
+ continue;
+ }
$fileareas = $plugin->get_file_areas();
if ($type == 'onlinetext') {
$this->assertEquals(2, $usingfilearea);
$usingfilearea = 0;
+ $coreplugins = core_plugin_manager::standard_plugins_list('assignfeedback');
foreach ($assign->get_feedback_plugins() as $plugin) {
$type = $plugin->get_type();
+ if (!in_array($type, $coreplugins)) {
+ continue;
+ }
$fileareas = $plugin->get_file_areas();
if ($type == 'editpdf') {
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'mod_assign'; // Full name of the plugin (used for diagnostics).
-$plugin->version = 2016041300; // The current module version (Date: YYYYMMDDXX).
+$plugin->version = 2016041301; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2015111000; // Requires this Moodle version.
$plugin->cron = 60;
$mform->addElement('hidden', 'id');
$mform->setType('id', PARAM_INT);
- $options = array('multiple' => true);
+ $options = array('multiple' => true, 'includefrontpage' => true);
$mform->addElement('course', 'mappedcourses', get_string('courses'), $options);
$this->add_action_buttons();
$common = $this->_customdata['common'];
$positionlist = $this->_customdata['positionlist'];
$position = $this->_customdata['position'];
+ $presentationoptions = $this->_customdata['presentationoptions'];
$mform =& $this->_form;
get_string('item_label', 'feedback'),
array('size'=>FEEDBACK_ITEM_LABEL_TEXTBOX_SIZE, 'maxlength'=>255));
- $options=array();
- $options[1] = get_string('responsetime', 'feedback');
- $options[2] = get_string('course');
- $options[3] = get_string('coursecategory');
$this->infotype = &$mform->addElement('select',
'presentation',
get_string('infotype', 'feedback'),
- $options);
+ $presentationoptions);
parent::definition();
$this->set_data($item);
//the elements for position dropdownlist
$positionlist = array_slice(range(0, $i_formselect_last), 1, $i_formselect_last, true);
- $item->presentation = empty($item->presentation) ? 1 : $item->presentation;
+ $item->presentation = empty($item->presentation) ? self::MODE_COURSE : $item->presentation;
$item->required = 0;
//all items for dependitem
'items'=>$feedbackitems,
'feedback'=>$feedback->id);
+ // Options for the 'presentation' select element.
+ $presentationoptions = array();
+ if ($feedback->anonymous == FEEDBACK_ANONYMOUS_NO || $item->presentation == self::MODE_RESPONSETIME) {
+ // "Response time" is hidden anyway in case of anonymous feedback, no reason to offer this option.
+ // However if it was already selected leave it in the dropdown.
+ $presentationoptions[self::MODE_RESPONSETIME] = get_string('responsetime', 'feedback');
+ }
+ $presentationoptions[self::MODE_COURSE] = get_string('course');
+ $presentationoptions[self::MODE_CATEGORY] = get_string('coursecategory');
+
//build the form
$this->item_form = new feedback_info_form('edit_item.php',
array('item'=>$item,
'common'=>$commonparams,
'positionlist'=>$positionlist,
- 'position' => $position));
+ 'position' => $position,
+ 'presentationoptions' => $presentationoptions));
}
public function save_item() {
switch($presentation) {
case self::MODE_RESPONSETIME:
$datavalue->value = $value->value;
- $datavalue->show = userdate($datavalue->value);
+ $datavalue->show = $value->value ? userdate($datavalue->value) : '';
break;
case self::MODE_COURSE:
$datavalue->value = $value->value;
echo '</th></tr>';
$sizeofdata = count($data);
for ($i = 0; $i < $sizeofdata; $i++) {
- echo '<tr><td colspan="2" class="singlevalue">';
+ $class = strlen(trim($data[$i]->show)) ? '' : ' class="isempty"';
+ echo '<tr'.$class.'><td colspan="2" class="singlevalue">';
echo str_replace("\n", '<br />', $data[$i]->show);
echo '</td></tr>';
}
} else if ($unread = forum_tp_count_forum_unread_posts($cm, $course)) {
$unreadlink = '<span class="unread"><a href="view.php?f='.$forum->id.'">'.$unread.'</a>';
$unreadlink .= '<a title="'.$strmarkallread.'" href="markposts.php?f='.
- $forum->id.'&mark=read"><img src="'.$OUTPUT->pix_url('t/markasread') . '" alt="'.$strmarkallread.'" class="iconsmall" /></a></span>';
+ $forum->id.'&mark=read&sesskey=' . sesskey() . '"><img src="'.$OUTPUT->pix_url('t/markasread') . '" alt="'.$strmarkallread.'" class="iconsmall" /></a></span>';
} else {
$unreadlink = '<span class="read">0</span>';
}
} else if ($unread = forum_tp_count_forum_unread_posts($cm, $course)) {
$unreadlink = '<span class="unread"><a href="view.php?f='.$forum->id.'">'.$unread.'</a>';
$unreadlink .= '<a title="'.$strmarkallread.'" href="markposts.php?f='.
- $forum->id.'&mark=read"><img src="'.$OUTPUT->pix_url('t/markasread') . '" alt="'.$strmarkallread.'" class="iconsmall" /></a></span>';
+ $forum->id.'&mark=read&sesskey=' . sesskey() . '"><img src="'.$OUTPUT->pix_url('t/markasread') . '" alt="'.$strmarkallread.'" class="iconsmall" /></a></span>';
} else {
$unreadlink = '<span class="read">0</span>';
}
echo $post->unread;
echo '</a>';
echo '<a title="'.$strmarkalldread.'" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
- $forum->id.'&d='.$post->discussion.'&mark=read&returnpage=view.php">' .
+ $forum->id.'&d='.$post->discussion.'&mark=read&returnpage=view.php&sesskey=' . sesskey() . '">' .
'<img src="'.$OUTPUT->pix_url('t/markasread') . '" class="iconsmall" alt="'.$strmarkalldread.'" /></a>';
echo '</span>';
} else {
if ($forumtracked) {
echo '<a title="'.get_string('markallread', 'forum').
'" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
- $forum->id.'&mark=read&returnpage=view.php">'.
+ $forum->id.'&mark=read&returnpage=view.php&sesskey=' . sesskey() . '">'.
'<img src="'.$OUTPUT->pix_url('t/markasread') . '" class="iconsmall" alt="'.get_string('markallread', 'forum').'" /></a>';
}
echo '</th>';
$user = $USER;
require_login($course, false, $cm);
+require_sesskey();
if ($returnpage == 'index.php') {
$returnto = new moodle_url("/mod/forum/$returnpage", array('id' => $course->id));
}
if (!is_null($discussionid)) {
$url->param('d', $discussionid);
- $discussion = $DB->get_record('forum_discussions', array('id' => $discussionid), '*', MUST_EXIST);
+ if (!$discussion = $DB->get_record('forum_discussions', array('id' => $discussionid, 'forum' => $id))) {
+ print_error('invaliddiscussionid', 'forum');
+ }
}
$PAGE->set_url($url);
$forum = $DB->get_record('forum', array('id' => $forumid), '*', MUST_EXIST);
$course = $DB->get_record('course', array('id' => $forum->course), '*', MUST_EXIST);
-$discussion = $DB->get_record('forum_discussions', array('id' => $discussionid), '*', MUST_EXIST);
+if (!$discussion = $DB->get_record('forum_discussions', array('id' => $discussionid, 'forum' => $forumid))) {
+ print_error('invaliddiscussionid', 'forum');
+}
$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id, false, MUST_EXIST);
$context = context_module::instance($cm->id);
options += ",width=" + width + ",height=" + height;
windowobj = window.open(url,name,options);
+ windowobj.opener = null;
if (!windowobj) {
return;
}
winobj = window.open(launch_url,'Popup', poptions);
this.target = 'Popup';
scormredirect(winobj);
+ winobj.opener = null;
}
// Listen for view form submit and generate popup on user interaction.
if (scormform) {
winobj = window.open(launch_url, 'Popup', poptions);
this.target = 'Popup';
scormredirect(winobj);
+ winobj.opener = null;
e.preventDefault();
}, scormform);
}
$content = wiki_refresh_cachedcontent($page);
$page = $content['page'];
}
- // Convert to HTML, then to text. Makes sure content is cleaned.
- $html = format_text($page->cachedcontent, FORMAT_MOODLE, array('overflowdiv' => true, 'allowid' => true));
- $content = content_to_text($page->cachedcontent, FORMAT_HTML);
+ // Convert to text.
+ $content = content_to_text($page->cachedcontent, FORMAT_MOODLE);
// Prepare associative array with data from DB.
$doc = \core_search\document_factory::instance($record->id, $this->componentname, $this->areaname);
+++ /dev/null
-// we might be able to just turn these off for 2.5 see bug MDL-????
-input[type=text],input[type=password],textarea {
- width:auto;
-}
-th,
-td {
- border: 0 solid #fff;
-}
$fields = get_user_fieldnames();
$authplugin = get_auth_plugin($user->auth);
$customfields = $authplugin->get_custom_user_profile_fields();
+ $customfieldsdata = profile_user_record($userid, false);
$fields = array_merge($fields, $customfields);
foreach ($fields as $field) {
if ($field === 'description') {
if (!$mform->elementExists($formfield)) {
continue;
}
- $value = $mform->getElement($formfield)->exportValue($mform->getElementValue($formfield)) ?: '';
+
+ // Get the original value for the field.
+ if (in_array($field, $customfields)) {
+ $key = str_replace('profile_field_', '', $field);
+ $value = isset($customfieldsdata->{$key}) ? $customfieldsdata->{$key} : '';
+ } else {
+ $value = $user->{$field};
+ }
+
$configvariable = 'field_lock_' . $field;
if (isset($authplugin->config->{$configvariable})) {
if ($authplugin->config->{$configvariable} === 'locked') {
/**
* Returns an object with the custom profile fields set for the given user
* @param integer $userid
+ * @param bool $onlyinuserobject True if you only want the ones in $USER.
* @return stdClass
*/
-function profile_user_record($userid) {
+function profile_user_record($userid, $onlyinuserobject = true) {
global $CFG, $DB;
$usercustomfields = new stdClass();
require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php');
$newfield = 'profile_field_'.$field->datatype;
$formfield = new $newfield($field->id, $userid);
- if ($formfield->is_user_object_data()) {
+ if (!$onlyinuserobject || $formfield->is_user_object_data()) {
$usercustomfields->{$field->shortname} = $formfield->data;
}
}
// Check that profile_user_record returns same (no) fields.
$this->assertObjectNotHasAttribute('frogdesc', profile_user_record($user->id));
+ // Check that profile_user_record returns all the fields when requested.
+ $this->assertObjectHasAttribute('frogdesc', profile_user_record($user->id, false));
+
// Add another custom field, this time of normal text type.
$id2 = $DB->insert_record('user_info_field', array(
'shortname' => 'frogname', 'name' => 'Name of frog', 'categoryid' => 1,
// Check profile_user_record returns same field.
$this->assertObjectHasAttribute('frogname', profile_user_record($user->id));
+
+ // Check that profile_user_record returns all the fields when requested.
+ $this->assertObjectHasAttribute('frogname', profile_user_record($user->id, false));
}
/**
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_userliblib_testcase extends advanced_testcase {
+ /**
+ * Test user_get_user_details_courses
+ */
+ public function test_user_get_user_details_courses() {
+ global $DB;
+
+ $this->resetAfterTest();
+
+ // Create user and modify user profile.
+ $user1 = $this->getDataGenerator()->create_user();
+ $user2 = $this->getDataGenerator()->create_user();
+
+ $course1 = $this->getDataGenerator()->create_course();
+ $coursecontext = context_course::instance($course1->id);
+ $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
+ $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
+ $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
+ role_assign($teacherrole->id, $user1->id, $coursecontext->id);
+ role_assign($teacherrole->id, $user2->id, $coursecontext->id);
+
+ accesslib_clear_all_caches_for_unit_testing();
+
+ // Get user2 details as a user with super system capabilities.
+ $result = user_get_user_details_courses($user2);
+ $this->assertEquals($user2->id, $result['id']);
+ $this->assertEquals(fullname($user2), $result['fullname']);
+ $this->assertEquals($course1->id, $result['enrolledcourses'][0]['id']);
+
+ $this->setUser($user1);
+ // Get user2 details as a user who can only see this user in a course.
+ $result = user_get_user_details_courses($user2);
+ $this->assertEquals($user2->id, $result['id']);
+ $this->assertEquals(fullname($user2), $result['fullname']);
+ $this->assertEquals($course1->id, $result['enrolledcourses'][0]['id']);
+
+ }
+
/**
* Test user_update_user.
*/
defined('MOODLE_INTERNAL') || die();
-$version = 2016050400.00; // YYYYMMDD = weekly release date of this DEV branch.
+$version = 2016051000.00; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
-$release = '3.1beta (Build: 20160504)'; // Human-friendly version name
+$release = '3.1beta+ (Build: 20160510)'; // Human-friendly version name
$branch = '31'; // This version's branch.
$maturity = MATURITY_BETA; // This version's maturity level.