--- /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);
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()));
}
--- /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;}
JOIN {' . self::TABLE . '} plancomp
ON plancomp.competencyid = comp.id
WHERE plancomp.planid = ?
- ORDER BY plancomp.sortorder ASC';
+ ORDER BY plancomp.sortorder ASC,
+ plancomp.id ASC';
$params = array($planid);
// TODO MDL-52229 Handle hidden competencies.
JOIN {' . self::TABLE . '} tplcomp
ON tplcomp.competencyid = comp.id
WHERE tplcomp.templateid = ?
- ORDER BY tplcomp.sortorder ASC';
+ ORDER BY tplcomp.sortorder ASC,
+ tplcomp.id ASC';
$params = array($templateid);
$results = $DB->get_records_sql($sql, $params);
$sql = "competencyid $insql";
}
- return self::get_records_select("userid = :userid AND $sql", $params);
+ // Order by ID to prevent random ordering.
+ return self::get_records_select("userid = :userid AND $sql", $params, 'id ASC');
}
/**
$sql = "competencyid $insql";
}
- return self::get_records_select("userid = :userid AND courseid = :courseid AND $sql", $params);
+ // Order by ID to prevent random ordering.
+ return self::get_records_select("userid = :userid AND courseid = :courseid AND $sql", $params, 'id ASC');
}
/**
ON ucp.competencyid = c.id
AND ucp.userid = :userid
WHERE ucp.planid = :planid
- ORDER BY ucp.sortorder ASC';
+ ORDER BY ucp.sortorder ASC,
+ ucp.id ASC';
$params = array('userid' => $userid, 'planid' => $planid);
$results = $DB->get_recordset_sql($sql, $params);
$sql = "competencyid $insql";
}
- return static::get_records_select("userid = :userid AND planid = :planid AND $sql", $params);
+ // Order by ID to prevent random ordering.
+ return static::get_records_select("userid = :userid AND planid = :planid AND $sql", $params, 'id ASC');
}
/**
$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
));
$dg = $this->getDataGenerator();
$lpg = $this->getDataGenerator()->get_plugin_generator('core_competency');
- $currenttime = time();
$syscontext = context_system::instance();
// Create users.
$pc1 = $lpg->create_plan_competency(array('planid' => $p1->get_id(), 'competencyid' => $c1->get_id()));
$pc2 = $lpg->create_plan_competency(array('planid' => $p2->get_id(), 'competencyid' => $c1->get_id()));
- // Create user competency. Add user_evidence and associate it to the user competency.
+ // Create user competency and add an evidence.
$uc = $lpg->create_user_competency(array('userid' => $user->id, 'competencyid' => $c1->get_id()));
- $ue = $lpg->create_user_evidence(array('userid' => $user->id));
- $uec = $lpg->create_user_evidence_competency(array('userevidenceid' => $ue->get_id(), 'competencyid' => $c1->get_id()));
$e1 = $lpg->create_evidence(array('usercompetencyid' => $uc->get_id()));
// Check both plans as one evidence.
$this->assertEquals(1, count(api::list_evidence($user->id, $c1->get_id(), $p2->get_id())));
// Complete second plan.
- $currenttime += 1;
$p2->set_status(plan::STATUS_COMPLETE);
$p2->update();
- $plansql = "UPDATE {" . plan::TABLE . "} SET timemodified = :currenttime WHERE id = :planid";
- $DB->execute($plansql, array('currenttime' => $currenttime, 'planid' => $p2->get_id()));
- // Add an other user evidence for the same competency.
- $currenttime += 1;
- $ue2 = $lpg->create_user_evidence(array('userid' => $user->id));
- $uec2 = $lpg->create_user_evidence_competency(array('userevidenceid' => $ue2->get_id(), 'competencyid' => $c1->get_id()));
+ // Add another evidence for the same competency, but in the future (time + 1).
$e2 = $lpg->create_evidence(array('usercompetencyid' => $uc->get_id()));
$evidencesql = "UPDATE {" . evidence::TABLE . "} SET timecreated = :currenttime WHERE id = :evidenceid";
- $DB->execute($evidencesql, array('currenttime' => $currenttime, 'evidenceid' => $e2->get_id()));
+ $DB->execute($evidencesql, array('currenttime' => time() + 1, 'evidenceid' => $e2->get_id()));
- // Check first plan which is not completed as all evidences.
+ // Check that the first plan, which is not completed, has all the evidence.
$this->assertEquals(2, count(api::list_evidence($user->id, $c1->get_id(), $p1->get_id())));
- // Check second plan completed before the new evidence as only the first evidence.
+ // Check that the second plan, completed before the new evidence, only has the first piece of evidence.
$listevidences = api::list_evidence($user->id, $c1->get_id(), $p2->get_id());
$this->assertEquals(1, count($listevidences));
$this->assertEquals($e1->get_id(), $listevidences[$e1->get_id()]->get_id());
/** @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": [],
--- /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/>.
+
+/**
+ * Search area for Moodle courses I can access.
+ *
+ * @package core_course
+ * @copyright 2016 Skylar Kelty <S.Kelty@kent.ac.uk>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace core_course\search;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Search area for Moodle courses I can access.
+ *
+ * @package core_course
+ * @copyright 2016 Skylar Kelty <S.Kelty@kent.ac.uk>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class mycourse extends \core_search\area\base {
+
+ /**
+ * Returns recordset containing required data for indexing courses.
+ *
+ * @param int $modifiedfrom timestamp
+ * @return \moodle_recordset
+ */
+ public function get_recordset_by_timestamp($modifiedfrom = 0) {
+ global $DB;
+ return $DB->get_recordset_select('course', 'timemodified >= ?', array($modifiedfrom));
+ }
+
+ /**
+ * Returns the document associated with this course.
+ *
+ * @param stdClass $record
+ * @param array $options
+ * @return \core_search\document
+ */
+ public function get_document($record, $options = array()) {
+ try {
+ $context = \context_course::instance($record->id);
+ } catch (\moodle_exception $ex) {
+ // Notify it as we run here as admin, we should see everything.
+ debugging('Error retrieving ' . $this->areaid . ' ' . $record->id . ' document, not all required data is available: ' .
+ $ex->getMessage(), DEBUG_DEVELOPER);
+ return false;
+ }
+ // Prepare associative array with data from DB.
+ $doc = \core_search\document_factory::instance($record->id, $this->componentname, $this->areaname);
+ $doc->set('title', $record->fullname);
+ $doc->set('content', content_to_text($record->summary, $record->summaryformat));
+ $doc->set('contextid', $context->id);
+ $doc->set('courseid', $record->id);
+ $doc->set('owneruserid', \core_search\manager::NO_OWNER_ID);
+ $doc->set('modified', $record->timemodified);
+ $doc->set('description1', $record->shortname);
+
+ // Check if this document should be considered new.
+ if (isset($options['lastindexedtime']) && $options['lastindexedtime'] < $record->timecreated) {
+ // If the document was created after the last index time, it must be new.
+ $doc->set_is_new(true);
+ }
+
+ return $doc;
+ }
+
+ /**
+ * Whether the user can access the document or not.
+ *
+ * @param int $id The course instance id.
+ * @return int
+ */
+ public function check_access($id) {
+ global $DB;
+ $course = $DB->get_record('course', array('id' => $id));
+ if (!$course) {
+ return \core_search\manager::ACCESS_DELETED;
+ }
+ if (can_access_course($course)) {
+ return \core_search\manager::ACCESS_GRANTED;
+ }
+ return \core_search\manager::ACCESS_DENIED;
+ }
+
+ /**
+ * Link to the course.
+ *
+ * @param \core_search\document $doc
+ * @return \moodle_url
+ */
+ public function get_doc_url(\core_search\document $doc) {
+ return $this->get_context_url($doc);
+ }
+
+ /**
+ * Link to the course.
+ *
+ * @param \core_search\document $doc
+ * @return \moodle_url
+ */
+ public function get_context_url(\core_search\document $doc) {
+ return new \moodle_url('/course/view.php', array('id' => $doc->get('courseid')));
+ }
+}
'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)) {
--- /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 global search unit tests.
+ *
+ * @package core
+ * @category phpunit
+ * @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/search/tests/fixtures/testable_core_search.php');
+
+/**
+ * Provides the unit tests for course global search.
+ *
+ * @package core
+ * @category phpunit
+ * @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class course_search_testcase extends advanced_testcase {
+
+ /**
+ * @var string Area id
+ */
+ protected $mycoursesareaid = null;
+
+ public function setUp() {
+ $this->resetAfterTest(true);
+ set_config('enableglobalsearch', true);
+
+ $this->mycoursesareaid = \core_search\manager::generate_areaid('core_course', 'mycourse');
+
+ // Set \core_search::instance to the mock_search_engine as we don't require the search engine to be working to test this.
+ $search = testable_core_search::instance();
+ }
+
+ /**
+ * Indexing my courses contents.
+ *
+ * @return void
+ */
+ public function test_mycourses_indexing() {
+
+ // Returns the instance as long as the area is supported.
+ $searcharea = \core_search\manager::get_search_area($this->mycoursesareaid);
+ $this->assertInstanceOf('\core_course\search\mycourse', $searcharea);
+
+ $user1 = self::getDataGenerator()->create_user();
+ $user2 = self::getDataGenerator()->create_user();
+
+ $course1 = self::getDataGenerator()->create_course();
+ $course2 = self::getDataGenerator()->create_course();
+
+ $this->getDataGenerator()->enrol_user($user1->id, $course1->id, 'student');
+ $this->getDataGenerator()->enrol_user($user2->id, $course1->id, 'student');
+
+ $record = new stdClass();
+ $record->course = $course1->id;
+
+ // All records.
+ $recordset = $searcharea->get_recordset_by_timestamp(0);
+ $this->assertTrue($recordset->valid());
+ $nrecords = 0;
+ foreach ($recordset as $record) {
+ $this->assertInstanceOf('stdClass', $record);
+ $doc = $searcharea->get_document($record);
+ $this->assertInstanceOf('\core_search\document', $doc);
+ $nrecords++;
+ }
+ // If there would be an error/failure in the foreach above the recordset would be closed on shutdown.
+ $recordset->close();
+ $this->assertEquals(3, $nrecords);
+
+ // The +2 is to prevent race conditions.
+ $recordset = $searcharea->get_recordset_by_timestamp(time() + 2);
+
+ // No new records.
+ $this->assertFalse($recordset->valid());
+ $recordset->close();
+ }
+
+ /**
+ * Document contents.
+ *
+ * @return void
+ */
+ public function test_mycourses_document() {
+
+ // Returns the instance as long as the area is supported.
+ $searcharea = \core_search\manager::get_search_area($this->mycoursesareaid);
+ $this->assertInstanceOf('\core_course\search\mycourse', $searcharea);
+
+ $user = self::getDataGenerator()->create_user();
+ $course = self::getDataGenerator()->create_course();
+ $this->getDataGenerator()->enrol_user($user->id, $course->id, 'teacher');
+
+ $doc = $searcharea->get_document($course);
+ $this->assertInstanceOf('\core_search\document', $doc);
+ $this->assertEquals($course->id, $doc->get('itemid'));
+ $this->assertEquals($this->mycoursesareaid . '-' . $course->id, $doc->get('id'));
+ $this->assertEquals($course->id, $doc->get('courseid'));
+ $this->assertFalse($doc->is_set('userid'));
+ $this->assertEquals(\core_search\manager::NO_OWNER_ID, $doc->get('owneruserid'));
+ $this->assertEquals($course->fullname, $doc->get('title'));
+
+ // Not nice. Applying \core_search\document::set line breaks clean up.
+ $summary = preg_replace("/\s+/", ' ', trim(content_to_text($course->summary, $course->summaryformat), "\r\n"));
+ $this->assertEquals($summary, $doc->get('content'));
+ $this->assertEquals($course->shortname, $doc->get('description1'));
+ }
+
+ /**
+ * Document accesses.
+ *
+ * @return void
+ */
+ public function test_mycourses_access() {
+
+ // Returns the instance as long as the area is supported.
+ $searcharea = \core_search\manager::get_search_area($this->mycoursesareaid);
+
+ $user1 = self::getDataGenerator()->create_user();
+ $user2 = self::getDataGenerator()->create_user();
+
+ $course1 = self::getDataGenerator()->create_course();
+ $course2 = self::getDataGenerator()->create_course(array('visible' => 0));
+ $course3 = self::getDataGenerator()->create_course();
+
+ $this->getDataGenerator()->enrol_user($user1->id, $course1->id, 'teacher');
+ $this->getDataGenerator()->enrol_user($user2->id, $course1->id, 'student');
+ $this->getDataGenerator()->enrol_user($user1->id, $course2->id, 'teacher');
+ $this->getDataGenerator()->enrol_user($user2->id, $course2->id, 'student');
+
+ $this->setUser($user1);
+ $this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($course1->id));
+ $this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($course2->id));
+ $this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($course3->id));
+ $this->assertEquals(\core_search\manager::ACCESS_DELETED, $searcharea->check_access(-123));
+
+ $this->setUser($user2);
+ $this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($course1->id));
+ $this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($course2->id));
+ $this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($course3->id));
+ }
+}
$string['syncsettings'] = 'Synchronisation settings';
$string['tooldoesnotexist'] = 'The requested tool does not exist.';
$string['tasksyncgrades'] = 'Handles syncing grades with the consumer';
-$string['tasksyncmembers'] = 'handles syncing members with the consumer';
+$string['tasksyncmembers'] = 'Handles syncing members with the consumer';
$string['toolsprovided'] = 'Tools provided';
$string['tooltobeprovided'] = 'Tool to be provided';
$string['userdefaultvalues'] = 'User default values';
// 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);
$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['runindexertest'] = 'Run indexer test';
$string['score'] = 'Score';
$string['search'] = 'Search';
+$string['search:mycourse'] = 'My courses';
$string['searcharea'] = 'Search area';
$string['searching'] = 'Searching in ...';
$string['searchnotpermitted'] = 'You are not allowed to do a search';
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);
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;
}
if (is_numeric($currentvalue)) {
// UTC offset.
- $modifier = ($currentvalue > 0) ? '+' : '';
- $a = 'UTC' . $modifier . number_format($currentvalue, 1);
+ if ($currentvalue == 0) {
+ $a = 'UTC';
+ } else {
+ $modifier = ($currentvalue > 0) ? '+' : '';
+ $a = 'UTC' . $modifier . number_format($currentvalue, 1);
+ }
$timezones[$currentvalue] = get_string('timezoneinvalid', 'core_admin', $a);
} else {
// Some string we don't recognise.
);
// Legacy GMT fallback.
- for ($i = -14; $i <= 13; $i++) {
+ for ($i = -12; $i <= 14; $i++) {
$off = abs($i);
if ($i < 0) {
$mapto = 'Etc/GMT+' . $off;
'choices' => array_merge(array('' => ''), \core_calendar\type_factory::get_list_of_calendar_types()));
$fields['theme'] = array('type' => PARAM_THEME, 'null' => NULL_NOT_ALLOWED,
'default' => theme_config::DEFAULT_THEME, 'choices' => array_merge(array('' => ''), get_list_of_themes()));
- $fields['timezone'] = array('type' => PARAM_TIMEZONE, 'null' => NULL_NOT_ALLOWED, 'default' => $CFG->timezone,
- 'choices' => core_date::get_list_of_timezones(null, true));
+ $fields['timezone'] = array('type' => PARAM_TIMEZONE, 'null' => NULL_NOT_ALLOWED,
+ 'default' => core_date::get_server_timezone()); // Must not use choices here: timezones can come and go.
$fields['firstaccess'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED);
$fields['lastaccess'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED);
$fields['lastlogin'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED);
* Get the choices of the property.
*
* This is a helper method to validate a value against a list of acceptable choices.
- * For instance: country, timezone, language, themes and etc.
+ * For instance: country, language, themes and etc.
*
* @param string $property property name to be retrieved.
* @throws coding_exception if the requested property name is invalid or if it does not has a list of choices.
$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;
}
$this->assertSame('Pacific/Auckland', core_date::normalise_timezone(-14));
$this->assertSame('Etc/GMT-12', core_date::normalise_timezone(12));
$this->assertSame('Etc/GMT-13', core_date::normalise_timezone(13));
- $this->assertSame('Pacific/Auckland', core_date::normalise_timezone(14));
+ $this->assertSame('Etc/GMT-14', core_date::normalise_timezone(14));
$this->assertSame('Asia/Kabul', core_date::normalise_timezone(4.5));
$this->assertSame('Asia/Kolkata', core_date::normalise_timezone(5.5));
$this->assertSame('Pacific/Auckland', core_date::normalise_timezone(-14));
$this->assertSame('Etc/GMT-12', core_date::normalise_timezone(12));
$this->assertSame('Etc/GMT-13', core_date::normalise_timezone(13));
- $this->assertSame('Pacific/Auckland', core_date::normalise_timezone(14));
+ $this->assertSame('Etc/GMT-14', core_date::normalise_timezone(14));
$this->setTimezone('Pacific/Auckland', 'Pacific/Auckland');
$tz = new DateTimeZone('Pacific/Auckland');
$this->assertSame('Etc/GMT-1', date_default_timezone_get());
}
+ public function legacyUserTimezoneProvider() {
+ return [
+ ['', 'Australia/Perth'], // Fallback on default timezone.
+ ['-13.0', 'Australia/Perth'], // Fallback on default timezone.
+ ['-12.5', 'Etc/GMT+12'],
+ ['-12.0', 'Etc/GMT+12'],
+ ['-11.5', 'Etc/GMT+11'],
+ ['-11.0', 'Etc/GMT+11'],
+ ['-10.5', 'Etc/GMT+10'],
+ ['-10.0', 'Etc/GMT+10'],
+ ['-9.5', 'Etc/GMT+9'],
+ ['-9.0', 'Etc/GMT+9'],
+ ['-8.5', 'Etc/GMT+8'],
+ ['-8.0', 'Etc/GMT+8'],
+ ['-7.5', 'Etc/GMT+7'],
+ ['-7.0', 'Etc/GMT+7'],
+ ['-6.5', 'Etc/GMT+6'],
+ ['-6.0', 'Etc/GMT+6'],
+ ['-5.5', 'Etc/GMT+5'],
+ ['-5.0', 'Etc/GMT+5'],
+ ['-4.5', 'Etc/GMT+4'],
+ ['-4.0', 'Etc/GMT+4'],
+ ['-3.5', 'Etc/GMT+3'],
+ ['-3.0', 'Etc/GMT+3'],
+ ['-2.5', 'Etc/GMT+2'],
+ ['-2.0', 'Etc/GMT+2'],
+ ['-1.5', 'Etc/GMT+1'],
+ ['-1.0', 'Etc/GMT+1'],
+ ['-0.5', 'Etc/GMT'],
+ ['0', 'Etc/GMT'],
+ ['0.0', 'Etc/GMT'],
+ ['0.5', 'Etc/GMT'],
+ ['1.0', 'Etc/GMT-1'],
+ ['1.5', 'Etc/GMT-1'],
+ ['2.0', 'Etc/GMT-2'],
+ ['2.5', 'Etc/GMT-2'],
+ ['3.0', 'Etc/GMT-3'],
+ ['3.5', 'Etc/GMT-3'],
+ ['4.0', 'Etc/GMT-4'],
+ ['4.5', 'Asia/Kabul'],
+ ['5.0', 'Etc/GMT-5'],
+ ['5.5', 'Asia/Kolkata'],
+ ['6.0', 'Etc/GMT-6'],
+ ['6.5', 'Asia/Rangoon'],
+ ['7.0', 'Etc/GMT-7'],
+ ['7.5', 'Etc/GMT-7'],
+ ['8.0', 'Etc/GMT-8'],
+ ['8.5', 'Etc/GMT-8'],
+ ['9.0', 'Etc/GMT-9'],
+ ['9.5', 'Australia/Darwin'],
+ ['10.0', 'Etc/GMT-10'],
+ ['10.5', 'Etc/GMT-10'],
+ ['11.0', 'Etc/GMT-11'],
+ ['11.5', 'Etc/GMT-11'],
+ ['12.0', 'Etc/GMT-12'],
+ ['12.5', 'Etc/GMT-12'],
+ ['13.0', 'Etc/GMT-13'],
+ ];
+ }
+
+ /**
+ * @dataProvider legacyUserTimezoneProvider
+ * @param string $tz The legacy timezone.
+ * @param string $expected The expected converted timezone.
+ */
+ public function test_get_legacy_user_timezone($tz, $expected) {
+ $this->setTimezone('Australia/Perth', 'Australia/Perth');
+ $this->assertEquals($expected, core_date::get_user_timezone($tz));
+ }
+
public function test_get_user_timezone() {
global $CFG, $USER;
$this->resetAfterTest();
</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);
'expectedoutput' => '1309485600'
),
array(
- 'usertimezone' => '14', // Server time.
+ 'usertimezone' => '-14', // Server time.
'year' => '2011',
'month' => '7',
'day' => '1',
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();
}
$this->assertArrayNotHasKey('unknowntheme', $choices);
$this->assertArrayNotHasKey('wrongtheme', $choices);
- // Test against timezone property choices.
- $choices = core_user::get_property_choices('timezone');
- $this->assertArrayHasKey('America/Sao_Paulo', $choices);
- $this->assertArrayHasKey('Australia/Perth', $choices);
- $this->assertArrayHasKey('99', $choices);
- $this->assertArrayHasKey('UTC', $choices);
- $this->assertArrayNotHasKey('North Korea', $choices);
- $this->assertArrayNotHasKey('New york', $choices);
-
// Try to fetch type of a non-existent properties.
$nonexistingproperty = 'language';
$this->setExpectedException('coding_exception', 'Invalid property requested: ' . $nonexistingproperty);
*/
public function test_get_property_default() {
global $CFG;
+ $this->resetAfterTest();
$country = core_user::get_property_default('country');
$this->assertEquals($CFG->country, $country);
$lang = core_user::get_property_default('lang');
$this->assertEquals($CFG->lang, $lang);
+ $this->setTimezone('Europe/London', 'Pacific/Auckland');
+ core_user::reset_caches();
$timezone = core_user::get_property_default('timezone');
- $this->assertEquals($CFG->timezone, $timezone);
- set_config('timezone', 99);
+ $this->assertEquals('Europe/London', $timezone);
+ $this->setTimezone('99', 'Pacific/Auckland');
core_user::reset_caches();
$timezone = core_user::get_property_default('timezone');
- $this->assertEquals(99, $timezone);
+ $this->assertEquals('Pacific/Auckland', $timezone);
$this->setExpectedException('coding_exception', 'Invalid property requested, or the property does not has a default value.');
core_user::get_property_default('firstname');
* 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,
+ ));
+ }
}
*/
protected function get_form_identifier() {
$params = $this->_customdata[2];
- return get_class($this) . '_' . $params['rownum'];
+ return get_class($this) . '_' . $params['userid'];
}
/**
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;
$actions = array();
- $urlparams = array('id'=>$this->assignment->get_course_module()->id,
- 'rownum'=>$this->rownum,
- 'action' => 'grade',
- 'useridlistid' => $this->assignment->get_useridlist_key_id());
+ $urlparams = array('id' => $this->assignment->get_course_module()->id,
+ 'rownum' => 0,
+ '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);
$noimage = null;
}
/**
- * 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()->render($header);
// If userid is passed - we are only grading a single student.
- $rownum = required_param('rownum', PARAM_INT);
+ $rownum = optional_param('rownum', 0, PARAM_INT);
$useridlistid = optional_param('useridlistid', $this->get_useridlist_key_id(), PARAM_ALPHANUM);
$userid = optional_param('userid', 0, PARAM_INT);
$attemptnumber = optional_param('attemptnumber', -1, PARAM_INT);
$useridlist = $SESSION->mod_assign_useridlist[$useridlistkey];
} else {
$rownum = 0;
+ $useridlistid = 0;
$useridlist = array($userid);
}
// Now show the grading form.
if (!$mform) {
- $pagination = array('rownum'=>$rownum,
- 'useridlistid'=>$useridlistid,
- 'last'=>$last,
- 'userid'=>optional_param('userid', 0, PARAM_INT),
- 'attemptnumber'=>$attemptnumber);
+ $pagination = array('rownum' => $rownum,
+ 'useridlistid' => $useridlistid,
+ 'last' => $last,
+ 'userid' => $userid,
+ 'attemptnumber' => $attemptnumber);
$formparams = array($this, $data, $pagination);
$mform = new mod_assign_grade_form(null,
$formparams,
$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');
$userid = isset($params['userid']) ? $params['userid'] : 0;
$attemptnumber = isset($params['attemptnumber']) ? $params['attemptnumber'] : 0;
$gradingpanel = !empty($params['gradingpanel']);
- if (!$userid) {
+ $bothids = ($userid && $useridlistid);
+
+ if (!$userid || $bothids) {
$useridlistkey = $this->get_useridlist_key($useridlistid);
if (empty($SESSION->mod_assign_useridlist[$useridlistkey])) {
$SESSION->mod_assign_useridlist[$useridlistkey] = $this->get_grading_userid_list();
$mform->setType('attemptnumber', PARAM_INT);
$mform->addElement('hidden', 'ajax', optional_param('ajax', 0, PARAM_INT));
$mform->setType('ajax', PARAM_INT);
+ $mform->addElement('hidden', 'userid', optional_param('userid', 0, PARAM_INT));
+ $mform->setType('userid', PARAM_INT);
if ($this->get_instance()->teamsubmission) {
$mform->addElement('header', 'groupsubmissionsettings', get_string('groupsubmissionsettings', 'assign'));
$data = new stdClass();
- $gradeformparams = array('rownum'=>$rownum,
- 'useridlistid'=>$useridlistid,
- 'last'=>false,
- 'attemptnumber'=>$attemptnumber,
- 'userid'=>optional_param('userid', 0, PARAM_INT));
+ $gradeformparams = array('rownum' => $rownum,
+ 'useridlistid' => $useridlistid,
+ 'last' => $last,
+ 'attemptnumber' => $attemptnumber,
+ 'userid' => $userid);
$mform = new mod_assign_grade_form(null,
array($this, $data, $gradeformparams),
'post',
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"
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]);
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;
display: inline-block;
vertical-align: middle;
}
+
+/* Styling for LTI view */
+#contentframe {
+ border: 1px solid #ddd;
+ border-radius: 4px;
+}
$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);
* @return string
*/
public function get_visible_name($lazyload = false) {
- return get_string('search:' . $this->areaname, $this->componentname, null, $lazyload);
+
+ $component = $this->componentname;
+
+ // Core subsystem strings go to lang/XX/search.php.
+ if ($this->componenttype === 'core') {
+ $component = 'search';
+ }
+ return get_string('search:' . $this->areaname, $component, null, $lazyload);
}
/**
public function get_config_var_name() {
if ($this->componenttype === 'core') {
- // Core subsystems config in search.
- return array('search', $this->areaid);
+ // Core subsystems config in core_search and setting name using only [a-zA-Z0-9_]+.
+ $parts = \core_search\manager::extract_areaid_parts($this->areaid);
+ return array('core_search', $parts[0] . '_' . $parts[1]);
}
// Plugins config in the plugin scope.
* @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.
*/
$user->country = 'WW';
$user->lang = 'xy';
$user->theme = 'somewrongthemename';
- $user->timezone = 'Paris';
+ $user->timezone = '30.5';
$user->url = 'wwww.somewrong@#$url.com.aus';
$debugmessages = $this->getDebuggingMessages();
user_update_user($user, true, false);
$user['country'] = 'WW';
$user['lang'] = 'xy';
$user['theme'] = 'somewrongthemename';
- $user['timezone'] = 'Paris';
+ $user['timezone'] = '-30.5';
$user['url'] = 'wwww.somewrong@#$url.com.aus';
$debugmessages = $this->getDebuggingMessages();
$user['id'] = user_create_user($user, true, false);
$dbuser = $DB->get_record('user', array('id' => $user['id']));
$this->assertEquals($dbuser->country, 0);
$this->assertEquals($dbuser->lang, 'en');
- $this->assertEquals($dbuser->timezone, 'Australia/Perth');
+ $this->assertEquals($dbuser->timezone, '');
// Now, with valid user data.
$user['username'] = 'johndoe321';
defined('MOODLE_INTERNAL') || die();
-$version = 2016050600.00; // YYYYMMDD = weekly release date of this DEV branch.
+$version = 2016051000.01; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
-$release = '3.1beta+ (Build: 20160506)'; // 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.