--- /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
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['core/str', 'core/yui'], function(str, Y) {
-
// Private variables and functions.
+ /**
+ * Store the current instance of the core drag drop.
+ *
+ * @property dragDropInstance M.tool_lp.dragdrop_reorder
+ */
+ var dragDropInstance = null;
+
/**
* Translate the drophit event from YUI
* into simple drag and drop nodes.
var context = {
callback: callback
};
- M.tool_lp.dragdrop_reorder({
+ if (dragDropInstance) {
+ dragDropInstance.destroy();
+ }
+ dragDropInstance = M.tool_lp.dragdrop_reorder({
group: group,
dragHandleText: dragHandleText,
sameNodeText: sameNodeText,
up: 38,
right: 39,
down: 40,
+ eight: 56,
asterisk: 106
};
Tree.prototype.handleKeyDown = function(item, e) {
var currentIndex = this.visibleItems.index(item);
var newItem = null;
+ var hasKeyModifier = e.shiftKey || e.ctrlKey || e.metaKey || e.altKey;
+ var thisObj = this;
switch (e.keyCode) {
case this.keys.home: {
newItem.focus();
if (e.shiftKey) {
this.multiSelectItem(newItem);
- } else if (!e.ctrlKey) {
+ } else if (!hasKeyModifier) {
this.selectItem(newItem);
}
newItem.focus();
if (e.shiftKey) {
this.multiSelectItem(newItem);
- } else if (!e.ctrlKey) {
+ } else if (!hasKeyModifier) {
this.selectItem(newItem);
}
if (e.shiftKey) {
this.multiSelectItem(item);
- } else if (e.ctrlKey) {
+ } else if (e.metaKey || e.ctrlKey) {
this.toggleItem(item);
} else {
this.selectItem(item);
itemParent.focus();
if (e.shiftKey) {
this.multiSelectItem(itemParent);
- } else if (!e.ctrlKey) {
+ } else if (!hasKeyModifier) {
this.selectItem(itemParent);
}
}
newItem.focus();
if (e.shiftKey) {
this.multiSelectItem(newItem);
- } else if (!e.ctrlKey) {
+ } else if (!hasKeyModifier) {
this.selectItem(newItem);
}
}
prev.focus();
if (e.shiftKey) {
this.multiSelectItem(prev);
- } else if (!e.ctrlKey) {
+ } else if (!hasKeyModifier) {
this.selectItem(prev);
}
}
next.focus();
if (e.shiftKey) {
this.multiSelectItem(next);
- } else if (!e.ctrlKey) {
+ } else if (!hasKeyModifier) {
this.selectItem(next);
}
}
}
case this.keys.asterisk: {
// Expand all groups.
-
- var thisObj = this;
-
this.parents.each(function() {
thisObj.expandGroup($(this));
});
e.stopPropagation();
return false;
}
+ case this.keys.eight: {
+ if (e.shiftKey) {
+ // Expand all groups.
+ this.parents.each(function() {
+ thisObj.expandGroup($(this));
+ });
+
+ e.stopPropagation();
+ }
+
+ return false;
+ }
}
return true;
* @param {Event} e The event.
*/
Tree.prototype.handleKeyPress = function(item, e) {
- if (e.altKey || e.ctrlKey || e.shiftKey) {
+ if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {
// Do nothing.
return true;
}
*/
Tree.prototype.handleDblClick = function(item, e) {
- if (e.altKey || e.ctrlKey || e.shiftKey) {
+ if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {
// Do nothing.
return true;
}
if (e.shiftKey) {
this.multiSelectItem(item);
- } else if (e.ctrlKey) {
+ } else if (e.metaKey || e.ctrlKey) {
this.toggleItem(item);
} else {
this.selectItem(item);
this._userId = userId + '';
this._competencyId = competencyId + '';
this._courseId = courseId;
- this._ignoreFirstUser = true;
- this._ignoreFirstCompetency = true;
$(userSelector).on('change', this._userChanged.bind(this));
$(competencySelector).on('change', this._competencyChanged.bind(this));
* @param {Event} e
*/
UserCompetencyCourseNavigation.prototype._userChanged = function(e) {
- if (this._ignoreFirstUser) {
- this._ignoreFirstUser = false;
- return;
- }
-
var newUserId = $(e.target).val();
var queryStr = '?userid=' + newUserId + '&courseid=' + this._courseId + '&competencyid=' + this._competencyId;
document.location = this._baseUrl + queryStr;
* @param {Event} e
*/
UserCompetencyCourseNavigation.prototype._competencyChanged = function(e) {
- if (this._ignoreFirstCompetency) {
- this._ignoreFirstCompetency = false;
- return;
- }
var newCompetencyId = $(e.target).val();
var queryStr = '?userid=' + this._userId + '&courseid=' + this._courseId + '&competencyid=' + newCompetencyId;
document.location = this._baseUrl + queryStr;
'type' => PARAM_TEXT,
),
'idnumber' => array(
- 'type' => PARAM_TEXT,
+ 'type' => PARAM_RAW,
)
);
}
namespace tool_lp\external;
use core_competency\api;
+use core_competency\user_competency;
use context_course;
use renderer_base;
use stdClass;
$related['usercompetency'] = null;
$exporter = new user_competency_summary_exporter(null, $related);
$result->usercompetencysummary = $exporter->export($output);
+ $result->usercompetencysummary->cangrade = user_competency::can_grade_user_in_course($this->related['user']->id,
+ $this->related['course']->id);
$context = context_course::instance($this->related['course']->id);
$exporter = new course_summary_exporter($this->related['course'], array('context' => $context));
$mform->setType('description', PARAM_RAW);
// ID number.
$mform->addElement('text', 'idnumber', get_string('idnumber', 'tool_lp'), 'maxlength="100"');
- $mform->setType('idnumber', PARAM_TEXT);
+ $mform->setType('idnumber', PARAM_RAW);
$mform->addRule('idnumber', null, 'required', null, 'client');
$mform->addRule('idnumber', get_string('maximumchars', '', 100), 'maxlength', 100, 'client');
$mform->setType('description', PARAM_RAW);
// ID number.
$mform->addElement('text', 'idnumber', get_string('idnumber', 'tool_lp'), 'maxlength="100"');
- $mform->setType('idnumber', PARAM_TEXT);
+ $mform->setType('idnumber', PARAM_RAW);
$mform->addRule('idnumber', null, 'required', null, 'client');
$mform->addRule('idnumber', get_string('maximumchars', '', 100), 'maxlength', 100, 'client');
$data->competencyid = $this->competencyid;
$data->planid = $this->planid;
$data->baseurl = $this->baseurl;
- $data->jumptocompetency = get_string('jumptocompetency', 'tool_lp');
$plancompetencies = \core_competency\api::list_plan_competencies($data->planid);
$data->competencies = array();
$data->courseid = $this->courseid;
$data->baseurl = $this->baseurl;
$data->groupselector = '';
- $data->jumptocompetency = get_string('jumptocompetency', 'tool_lp');
- $data->jumptouser = get_string('jumptouser', 'tool_lp');
if (has_any_capability(array('moodle/competency:usercompetencyview', 'moodle/competency:coursecompetencymanage'),
$context)) {
'type' => 'read',
'capabilities' => 'moodle/competency:coursecompetencyview',
'ajax' => true,
+ 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'tool_lp_data_for_template_competencies_page' => array(
'classname' => 'tool_lp\external',
'type' => 'read',
'capabilities' => 'moodle/competency:planviewown',
'ajax' => true,
+ 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'tool_lp_data_for_plan_page' => array(
'classname' => 'tool_lp\external',
'type' => 'read',
'capabilities' => 'moodle/competency:planview',
'ajax' => true,
+ 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'tool_lp_data_for_related_competencies_section' => array(
'classname' => 'tool_lp\external',
'type' => 'read',
'capabilities' => 'moodle/competency:userevidenceview',
'ajax' => true,
+ 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'tool_lp_data_for_user_evidence_page' => array(
'classname' => 'tool_lp\external',
'type' => 'read',
'capabilities' => 'moodle/competency:userevidenceview',
'ajax' => true,
+ 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
// User competency.
'type' => 'read',
'capabilities' => 'moodle/competency:planview',
'ajax' => true,
+ 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'tool_lp_data_for_user_competency_summary_in_plan' => array(
'classname' => 'tool_lp\external',
'type' => 'read',
'capabilities' => 'moodle/competency:planview',
'ajax' => true,
+ 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'tool_lp_data_for_user_competency_summary_in_course' => array(
'classname' => 'tool_lp\external',
'type' => 'read',
'capabilities' => 'moodle/competency:coursecompetencyview',
'ajax' => true,
+ 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
);
display: table-cell;
}
.path-admin-tool-lp [data-region="competencyruleconfig"] [data-region="rule-outcome"] select,
-.path-admin-tool-lp [data-region="competencyruleconfig"] [data-region="rule-type"] select {
+.path-admin-tool-lp [data-region="competencyruleconfig"] [data-region="rule-type"] select,
+.path-admin-tool-lp [data-region="competencylinktree"] select {
width: 100%;
}
<ul data-enhance="movetree" style="display: none;">
<li>
- <span>{{framework.shortname}}</span>
+ <span>{{{framework.shortname}}}</span>
<ul>
{{#competencies}}
{{> tool_lp/competencies_tree }}
<li data-id="{{id}}">
{{#canmanage}}
<span draggable="true">
- {{shortname}}
+ {{{shortname}}}
</span>
{{/canmanage}}
{{^canmanage}}
<span>
- {{shortname}}
+ {{{shortname}}}
</span>
{{/canmanage}}
{{#haschildren}}
<ul data-enhance="tree">
- <li><span>{{shortname}}</span>
+ <li><span>{{{shortname}}}</span>
<ul>
{{#competencies}}
{{> tool_lp/competencies_tree }}
}}
<nav id="competency-path-{{uniqid}}">
<small>
- <a href="{{pluginbaseurl}}/competencies.php?competencyframeworkid={{framework.id}}&pagecontextid={{pagecontextid}}" >{{framework.name}}</a>
+ <a href="{{pluginbaseurl}}/competencies.php?competencyframeworkid={{framework.id}}&pagecontextid={{pagecontextid}}" >{{{framework.name}}}</a>
/
{{#ancestors}}
- <a data-action="competency-dialogue" href="#" data-id="{{id}}">{{name}}</a>
+ <a data-action="competency-dialogue" href="#" data-id="{{id}}">{{{name}}}</a>
{{^last}}<span> / </span>{{/last}}
{{/ancestors}}
</small>
<h3>{{#str}}competencyframeworks, tool_lp{{/str}}</h3>
<select data-action="chooseframework">
{{#frameworks}}
-<option value="{{id}}" {{#selected}}selected="selected"{{/selected}}>{{shortname}} <em>{{idnumber}}</em></option>
+<option value="{{id}}" {{#selected}}selected="selected"{{/selected}}>{{{shortname}}} <em>{{idnumber}}</em></option>
{{/frameworks}}
</select>
{{/singleFramework}}
<button>{{#pix}}a/search, ,{{#str}}search{{/str}}{{/pix}}</button>
</form>
<ul data-enhance="linktree" style="display: none;">
- <li><span>{{framework.shortname}}</span>
+ <li><span>{{{framework.shortname}}}</span>
<ul>
{{#competencies}}
{{> tool_lp/competencies_tree }}
<button>{{#pix}}a/search, ,{{#str}}search{{/str}}{{/pix}}</button>
</form>
<ul data-enhance="linktree" style="display: none;">
- <li data-id="0"><span>{{framework.shortname}}</span>
+ <li data-id="0"><span>{{{framework.shortname}}}</span>
<ul>
{{#competencies}}
{{> tool_lp/competencies_tree }}
<h3>{{#str}}learningplans, tool_lp{{/str}}</h3>
<select data-action="chooseplan">
{{#plans}}
- <option value="{{id}}" {{#selected}}selected="selected"{{/selected}}>{{name}}</em></option>
+ <option value="{{id}}" {{#selected}}selected="selected"{{/selected}}>{{{name}}}</option>
{{/plans}}
</select>
{{/singlePlan}}
</form>
<ul data-enhance="linktree" style="display: none;">
- <li><span>{{plan.name}}</span>
+ <li><span>{{{plan.name}}}</span>
<ul>
{{#competencies}}
{{> tool_lp/competencies_tree }}
<div class="pull-right well">
{{#hascompetencies}}
<span>
-<label for="competency-nav-{{uniqid}}" class="accesshide">{{jumptocompetency}}</label>
+<label for="competency-nav-{{uniqid}}" class="accesshide">{{#str}}jumptocompetency, tool_lp{{/str}}</label>
<select id="competency-nav-{{uniqid}}">
{{#competencies}}
-<option value="{{id}}" {{#selected}}selected="selected"{{/selected}}>{{shortname}} {{idnumber}}</option>
+<option value="{{id}}" {{#selected}}selected="selected"{{/selected}}>{{{shortname}}} {{idnumber}}</option>
{{/competencies}}
</select>
</span>
require(['core/form-autocomplete', 'tool_lp/competency_plan_navigation'], function(autocomplete, nav) {
(new nav('#competency-nav-{{uniqid}}', '{{baseurl}}', {{userid}}, {{competencyid}}, {{planid}}));
{{#hascompetencies}}
- autocomplete.enhance('#competency-nav-{{uniqid}}', false, false, '{{jumptocompetency}}');
+ autocomplete.enhance('#competency-nav-{{uniqid}}', false, false, {{# quote }}{{# str }}jumptocompetency, tool_lp{{/ str }}{{/ quote }});
{{/hascompetencies}}
});
<tbody>
{{#children}}
<tr data-competency="{{id}}">
- <th scope="row">{{shortname}}</th>
- <td><input type="number" min="0" value="{{points}}" name="points" aria-label="{{#str}}pointsgivenfor, tool_lp, {{competency.shortname}}{{/str}}"></td>
- <td><input type="checkbox" value="1" name="required" {{#required}}checked{{/required}} aria-label="{{#str}}aisrequired, tool_lp, {{competency.shortname}}{{/str}}"></td>
+ <th scope="row">{{{shortname}}}</th>
+ <td>
+ <label class="accesshide" for="pointsforcompetency-{{id}}">{{#str}}pointsgivenfor, tool_lp, {{{competency.shortname}}}{{/str}}</label>
+ <input id="pointsforcompetency-{{id}}" type="number" min="0" value="{{points}}" name="points" />
+ </td>
+ <td>
+ <label class="accesshide" for="competency-{{id}}-isrequired">{{#str}}aisrequired, tool_lp, {{{competency.shortname}}}{{/str}}</label>
+ <input id="competency-{{id}}-isrequired" type="checkbox" value="1" name="required" {{#required}}checked{{/required}} />
+ </td>
</tr>
{{/children}}
</tbody>
<div class='competency-heading'>
- <h4 id="competency_link_{{competency.id}}">{{competency.shortname}}
+ <h4 id="competency_link_{{competency.id}}">{{{competency.shortname}}}
<small>{{competency.idnumber}}</small>
</h4>
{{#framework}}
<div class='competency-origin'>
- <p><small>{{framework.shortname}} - {{taxonomyterm}}</small>
+ <p><small>{{{framework.shortname}}} - {{taxonomyterm}}</small>
</div>
{{/framework}}
</div>
<a href="{{pluginbaseurl}}user_competency_in_course.php?courseid={{courseid}}&competencyid={{competency.id}}&userid={{gradableuserid}}"
id="competency-info-link-{{competency.id}}"
title="{{#str}}viewdetails, tool_lp{{/str}}">
- <p><strong>{{competency.shortname}} <em>{{competency.idnumber}}</em></strong></p>
+ <p><strong>{{{competency.shortname}}} <em>{{competency.idnumber}}</em></strong></p>
</a>
<p>{{{competency.description}}}</p>
{{/competency}}
<div>
{{#leastproficient}}
<a href="#competency-info-link-{{id}}">
- <div><p>{{shortname}} <em>{{idnumber}}</em></p></div>
+ <div><p>{{{shortname}}} <em>{{idnumber}}</em></p></div>
</a>
{{/leastproficient}}
</div>
}}
<div data-region="managecompetencies">
<h2>
- {{framework.shortname}}
+ {{{framework.shortname}}}
{{#canmanage}}
<a href="{{pluginbaseurl}}/editcompetencyframework.php?id={{framework.id}}&pagecontextid={{pagecontextid}}&return=competencies">{{#pix}}t/edit, core, {{#str}}editcompetencyframework, tool_lp{{/str}}{{/pix}}</a>
{{/canmanage}}
</h2>
+<div>{{{framework.description}}}</div>
<h3>{{#str}}competencies, core_competency{{/str}}</h3>
<div class="row-fluid">
<div class="span6">
function(ariatree, treeModel, actions, $) {
treeModel.init({{framework.id}},
- '{{framework.shortname}}',
+ {{#quote}} {{{framework.shortname}}} {{/quote}},
'{{search}}',
'[data-enhance=tree]',
{{canmanage}});
<tbody class="drag-parentnode">
{{#competencyframeworks}}
<tr class="drag-samenode" data-frameworkid="{{id}}">
- <td><span class="drag-handlecontainer"></span><span><a href="{{pluginbaseurl}}/competencies.php?competencyframeworkid={{id}}&pagecontextid={{pagecontextid}}">{{shortname}} ({{idnumber}})</a></span> {{^visible}}{{#str}}hiddenhint, tool_lp{{/str}}{{/visible}}</td>
+ <td><span class="drag-handlecontainer"></span><span><a href="{{pluginbaseurl}}/competencies.php?competencyframeworkid={{id}}&pagecontextid={{pagecontextid}}">{{{shortname}}} ({{idnumber}})</a></span> {{^visible}}{{#str}}hiddenhint, tool_lp{{/str}}{{/visible}}</td>
<td>{{competenciescount}}</td>
<td>{{contextnamenoprefix}}</td>
<td>
<tbody class="drag-parentnode">
{{#templates}}
<tr class="drag-samenode" data-templateid="{{id}}">
- <td><a href="{{pluginbaseurl}}/templatecompetencies.php?templateid={{id}}&pagecontextid={{pagecontextid}}">{{shortname}}</a></span> {{^visible}}{{#str}}hiddenhint, tool_lp{{/str}}{{/visible}}</td>
+ <td><a href="{{pluginbaseurl}}/templatecompetencies.php?templateid={{id}}&pagecontextid={{pagecontextid}}">{{{shortname}}}</a></span> {{^visible}}{{#str}}hiddenhint, tool_lp{{/str}}{{/visible}}</td>
<td>{{contextnamenoprefix}}</td>
<td><a class="template-cohorts" href="{{pluginbaseurl}}/template_cohorts.php?id={{id}}&pagecontextid={{pagecontextid}}">{{cohortscount}}</a></td>
<td><a class="template-userplans" href="{{pluginbaseurl}}/template_plans.php?id={{id}}&pagecontextid={{pagecontextid}}">{{planscount}}</a></td>
}}
<div data-region="plan-page" data-id="{{plan.id}}" data-userid="{{plan.userid}}">
<h2>
- {{plan.name}}
+ {{{plan.name}}}
{{#plan.canbeedited}}
<a href="{{pluginbaseurl}}/editplan.php?id={{plan.id}}&userid={{plan.userid}}">{{#pix}}t/edit, core, {{#str}}editplan, tool_lp{{/str}}{{/pix}}</a>
{{/plan.canbeedited}}
</div>
{{/plan.canbeedited}}
<div data-region="plan-summary">
+ {{{plan.description}}}
<dl>
<dt>{{#str}}status, tool_lp{{/str}}</dt>
<dd>
{{#canread}}
<a href="{{pluginbaseurl}}/templatecompetencies.php?templateid={{id}}&pagecontextid={{contextid}}">
{{/canread}}
- {{plan.template.shortname}}{{#canread}}</a>{{/canread}}
+ {{{plan.template.shortname}}}{{#canread}}</a>{{/canread}}
{{#plan.isunlinkallowed}}
(<a data-action="plan-unlink" href="#">{{#str}}unlinkplantemplate, tool_lp{{/str}}</a>)
{{/plan.isunlinkallowed}}
{{#plan.canbeedited}}
<span class="drag-handlecontainer pull-left"></span>
{{/plan.canbeedited}}
- <a data-usercompetency="true" href="#">{{competency.shortname}}</a>
+ <a data-usercompetency="true" href="#">{{{competency.shortname}}}</a>
<em>{{competency.idnumber}}</em>
{{#comppath}}
<br>
{{#plans}}
<tr data-region="plan-node" data-id="{{id}}" data-userid="{{userid}}">
<td>
- <span><a href="{{pluginbaseurl}}/plan.php?id={{id}}">{{name}}</a></span>
+ <span><a href="{{pluginbaseurl}}/plan.php?id={{id}}">{{{name}}}</a></span>
</td>
<td>
{{#isbasedontemplate}}
{{/showdeleterelatedaction}}
<p>
<a href="#" data-action="competency-dialogue" data-id="{{id}}">
- {{shortname}}{{#idnumber}} {{idnumber}}{{/idnumber}}
+ {{{shortname}}}{{#idnumber}} {{idnumber}}{{/idnumber}}
</a>
</p>
</li>
}}
<div data-region="templatecompetenciespage">
<h2>
- {{template.shortname}}
+ {{{template.shortname}}}
{{#template.canmanage}}
<a href="{{pluginbaseurl}}/edittemplate.php?id={{template.id}}&pagecontextid={{pagecontextid}}">{{#pix}}t/edit, core, {{#str}}edittemplate, tool_lp{{/str}}{{/pix}}</a>
{{/template.canmanage}}
</h2>
+ <div>{{{template.description}}}</div>
{{#canmanagetemplatecompetencies}}
<div data-region="actions" class="clearfix">
<div class="pull-left">
{{#hascourses}}
<ul class="inline">
{{#linkedcourses}}
- <li><a href="{{viewurl}}?id={{id}}">{{fullname}} ({{shortname}})</a></li>
+ <li><a href="{{viewurl}}?id={{id}}">{{{fullname}}} ({{{shortname}}})</a></li>
{{/linkedcourses}}
</ul>
{{/hascourses}}
{{#showcompetencylinks}}
<a href="#competency_link_{{id}}">
{{/showcompetencylinks}}
- <div><p>{{shortname}} <em>{{idnumber}}</em></p></div>
+ <div><p>{{{shortname}}} <em>{{idnumber}}</em></p></div>
{{#showcompetencylinks}}
</a>
{{/showcompetencylinks}}
<form class="user-competency-course-navigation">
{{#hasusers}}
<span>
-<label for="user-nav-{{uniqid}}" class="accesshide">{{jumptouser}}</label>
+<label for="user-nav-{{uniqid}}" class="accesshide">{{#str}}jumptouser, tool_lp{{/str}}</label>
<select id="user-nav-{{uniqid}}">
{{#users}}
<option value="{{id}}" {{#selected}}selected="selected"{{/selected}}>{{fullname}}</option>
<br>
{{#hascompetencies}}
<span>
-<label for="competency-nav-{{uniqid}}" class="accesshide">{{jumptocompetency}}</label>
+<label for="competency-nav-{{uniqid}}" class="accesshide">{{#str}}jumptocompetency, tool_lp{{/str}}</label>
<select id="competency-nav-{{uniqid}}">
{{#competencies}}
-<option value="{{id}}" {{#selected}}selected="selected"{{/selected}}>{{shortname}} {{idnumber}}</option>
+<option value="{{id}}" {{#selected}}selected="selected"{{/selected}}>{{{shortname}}} {{idnumber}}</option>
{{/competencies}}
</select>
</span>
require(['core/form-autocomplete', 'tool_lp/user_competency_course_navigation'], function(autocomplete, nav) {
(new nav('#user-nav-{{uniqid}}', '#competency-nav-{{uniqid}}', '{{baseurl}}', {{userid}}, {{competencyid}}, {{courseid}}));
{{#hasusers}}
- autocomplete.enhance('#user-nav-{{uniqid}}', false, false, '{{jumptouser}}');
+ autocomplete.enhance('#user-nav-{{uniqid}}', false, false, {{# quote }}{{# str }}jumptouser, tool_lp{{/ str }}{{/ quote }});
{{/hasusers}}
{{#hascompetencies}}
- autocomplete.enhance('#competency-nav-{{uniqid}}', false, false, '{{jumptocompetency}}');
+ autocomplete.enhance('#competency-nav-{{uniqid}}', false, false, {{# quote }}{{# str }}jumptocompetency, tool_lp{{/ str }}{{/ quote }});
{{/hascompetencies}}
});
}}
{{#usercompetencysummary}}
<div data-region="user-competency-full-info" data-node="user-competency" data-competencyid="{{usercompetency.competencyid}}" data-userid="{{usercompetency.userid}}" data-region-id="{{uniqid}}">
+<div>{{{plan.description}}}</div>
{{#plan.iscompleted}}
<div class="alert alert-info" role="alert">
{{#str}} usercompetencyfrozen, tool_lp {{/str}}
<tbody>
{{#evidence}}
<tr data-region='user-evidence-node' data-id="{{id}}" data-userid="{{userid}}">
- <td><a href="{{pluginbaseurl}}/user_evidence.php?id={{id}}">{{name}}</a></td>
+ <td><a href="{{pluginbaseurl}}/user_evidence.php?id={{id}}">{{{name}}}</a></td>
<td>
{{^hasurlorfiles}}
-
<ul class="user-evidence-competencies">
{{#usercompetencies}}
<li>
- {{competency.shortname}} <small><em>{{competency.idnumber}}</em></small> ({{usercompetency.statusname}}{{#usercompetency.reviewer.fullname}} / {{usercompetency.reviewer.fullname}}{{/usercompetency.reviewer.fullname}})
+ {{{competency.shortname}}} <small><em>{{competency.idnumber}}</em></small> ({{usercompetency.statusname}}{{#usercompetency.reviewer.fullname}} / {{usercompetency.reviewer.fullname}}{{/usercompetency.reviewer.fullname}})
</li>
{{/usercompetencies}}
</ul>
{{#userevidence}}
<div data-region="user-evidence-page" data-id="{{id}}" data-userid="{{userid}}">
<h2>
- {{name}}
+ {{{name}}}
{{#canmanage}}
<a href="{{pluginbaseurl}}/user_evidence_edit.php?id={{id}}&userid={{userid}}">{{#pix}}t/edit, core, {{#str}}editthisuserevidence, tool_lp{{/str}}{{/pix}}</a>
{{/canmanage}}
{{#usercompetencies}}
<tr data-id="{{competency.id}}">
<td>
- <a href="{{pluginbaseurl}}/user_competency.php?id={{usercompetency.id}}" data-id="{{usercompetency.id}}">{{competency.shortname}}</a>
+ <a href="{{pluginbaseurl}}/user_competency.php?id={{usercompetency.id}}" data-id="{{usercompetency.id}}">{{{competency.shortname}}}</a>
</td>
<td>
{{usercompetency.statusname}} {{#usercompetency.reviewer.fullname}} / {{usercompetency.reviewer.fullname}}{{/usercompetency.reviewer.fullname}}
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2016020925; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->version = 2016050400; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2014110400; // Requires this Moodle version.
$plugin->component = 'tool_lp'; // Full name of the plugin (used for diagnostics).
M.tool_lp = M.tool_lp || {};
M.tool_lp.dragdrop_reorder = function(params) {
- new DRAGREORDER(params);
+ return new DRAGREORDER(params);
};
<h4>{{#str}}unmappedin, tool_lpmigrate, {{frameworkfrom.shortname}}{{/str}}</h4>
<ul>
{{#unmappedfrom}}
- <li><a href="#" data-id="{{id}}" data-action="competency-dialogue" data-includecourses="true">{{shortname}}</a> <em>{{idnumber}}</em></li>
+ <li><a href="#" data-id="{{id}}" data-action="competency-dialogue" data-includecourses="true">{{{shortname}}}</a> <em>{{idnumber}}</em></li>
{{/unmappedfrom}}
</ul>
{{/hasunmappedfrom}}
<h4>{{#str}}unmappedin, tool_lpmigrate, {{frameworkto.shortname}}{{/str}}</h4>
<ul>
{{#unmappedto}}
- <li><a href="#" data-id="{{id}}" data-action="competency-dialogue" data-includecourses="true">{{shortname}}</a> <em>{{idnumber}}</em></li>
+ <li><a href="#" data-id="{{id}}" data-action="competency-dialogue" data-includecourses="true">{{{shortname}}}</a> <em>{{idnumber}}</em></li>
{{/unmappedto}}
</ul>
{{/hasunmappedto}}
throw new \moodle_exception('Failed to backup activity prior to deletion.');
}
+ // Have finished with the controller, let's destroy it, freeing mem and resources.
+ $controller->destroy();
+
// Grab the filename.
$file = $result['backup_destination'];
if (!$file->get_contenthash()) {
// Run the import.
$controller->execute_plan();
+ // Have finished with the controller, let's destroy it, freeing mem and resources.
+ $controller->destroy();
+
// Fire event.
$event = \tool_recyclebin\event\category_bin_item_restored::create(array(
'objectid' => $item->id,
throw new \moodle_exception('Failed to backup activity prior to deletion.');
}
+ // Have finished with the controller, let's destroy it, freeing mem and resources.
+ $controller->destroy();
+
// Grab the filename.
$file = $result['backup_destination'];
if (!$file->get_contenthash()) {
// Run the import.
$controller->execute_plan();
+ // Have finished with the controller, let's destroy it, freeing mem and resources.
+ $controller->destroy();
+
// Fire event.
$event = \tool_recyclebin\event\course_bin_item_restored::create(array(
'objectid' => $item->id,
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()));
}
$this->error('errorwhilerestoringcourse', new lang_string('errorwhilerestoringthecourse', 'tool_uploadcourse'));
}
$rc->destroy();
- unset($rc); // File logging is a mess, we can only try to rely on gc to close handles.
}
// Proceed with enrolment data.
*/
class tool_uploadcourse_course_testcase extends advanced_testcase {
- /**
- * Tidy up open files that may be left open.
- */
- protected function tearDown() {
- gc_collect_cycles();
- }
-
public function test_proceed_without_prepare() {
$this->resetAfterTest(true);
$mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
$this->assertTrue(isset($result['backup_destination']));
$c1backupfile = $result['backup_destination']->copy_content_to_temp();
$bc->destroy();
- unset($bc); // File logging is a mess, we can only try to rely on gc to close handles.
// Creating backup file.
$bc = new backup_controller(backup::TYPE_1COURSE, $c2->id, backup::FORMAT_MOODLE,
$this->assertTrue(isset($result['backup_destination']));
$c2backupfile = $result['backup_destination']->copy_content_to_temp();
$bc->destroy();
- unset($bc); // File logging is a mess, we can only try to rely on gc to close handles.
$oldcfg = isset($CFG->keeptempdirectoriesonbackup) ? $CFG->keeptempdirectoriesonbackup : false;
$CFG->keeptempdirectoriesonbackup = true;
*/
class tool_uploadcourse_processor_testcase extends advanced_testcase {
- /**
- * Tidy up open files that may be left open.
- */
- protected function tearDown() {
- gc_collect_cycles();
- }
-
public function test_basic() {
global $DB;
$this->resetAfterTest(true);
// It isn't possible to just rely on the configured suspension attribute since
// things like active directory use bit masks, other things using LDAP might
// do different stuff as well.
- $user->suspended = $this->is_user_suspended($user);
+ //
+ // The cast to int is a workaround for MDL-53959.
+ $user->suspended = (int)$this->is_user_suspended($user);
if (empty($user->lang)) {
$user->lang = $CFG->lang;
}
if (!empty($updatekeys)) {
$newuser = new stdClass();
$newuser->id = $userid;
- $newuser->suspended = $this->is_user_suspended((object) $newinfo);
+ // The cast to int is a workaround for MDL-53959.
+ $newuser->suspended = (int)$this->is_user_suspended((object) $newinfo);
foreach ($updatekeys as $key) {
if (isset($newinfo[$key])) {
}
// Front channel logout.
+$inputstream = file_get_contents("php://input");
if ($action == 'logout' && !empty($redirect)) {
if ($USER->auth == 'shibboleth') {
redirect($redirect);
}
-} else if (!file_get_contents("php://input")) {
+} else if (!empty($inputstream)) {
// Back channel logout.
// Set SOAP header.
public function destroy() {
// Only need to destroy circulars under the plan. Delegate to it.
$this->plan->destroy();
+ // Loggers may have also chained references, destroy them. Also closing resources when needed.
+ $this->logger->destroy();
}
public function finish_ui() {
$this->save_controller();
$tbc = self::load_controller($this->backupid);
$this->logger = $tbc->logger; // wakeup loggers
- $tbc->destroy(); // Clean temp controller structures
+ $tbc->plan->destroy(); // Clean plan controller structures, keeping logger alive.
} else if ($status == backup::STATUS_FINISHED_OK) {
// If the operation has ended without error (backup::STATUS_FINISHED_OK)
public function destroy() {
// Only need to destroy circulars under the plan. Delegate to it.
$this->plan->destroy();
+ // Loggers may have also chained references, destroy them. Also closing resources when needed.
+ $this->logger->destroy();
}
public function finish_ui() {
$this->save_controller();
$tbc = self::load_controller($this->restoreid);
$this->logger = $tbc->logger; // wakeup loggers
- $tbc->destroy(); // Clean temp controller structures
+ $tbc->plan->destroy(); // Clean plan controller structures, keeping logger alive.
} else if ($status == backup::STATUS_FINISHED_OK) {
// If the operation has ended without error (backup::STATUS_FINISHED_OK)
$groups = new backup_groups_setting('groups', base_setting::IS_BOOLEAN, true);
$groups->set_ui(new backup_setting_ui_checkbox($groups, get_string('rootsettinggroups', 'backup')));
$this->add_setting($groups);
+
+ // Define competencies inclusion setting if competencies are enabled.
+ $competencies = new backup_competencies_setting();
+ $competencies->set_ui(new backup_setting_ui_checkbox($competencies, get_string('rootsettingcompetencies', 'backup')));
+ $this->add_setting($competencies);
}
}
*/
class backup_userscompletion_setting extends backup_anonymize_setting {}
+/**
+ * root setting to control if backup will include competencies or not.
+ */
+class backup_competencies_setting extends backup_generic_setting {
+
+ /**
+ * backup_competencies_setting constructor.
+ */
+ public function __construct() {
+ $defaultvalue = false;
+ $visibility = base_setting::HIDDEN;
+ $status = base_setting::LOCKED_BY_CONFIG;
+ if (\core_competency\api::is_enabled()) {
+ $defaultvalue = true;
+ $visibility = base_setting::VISIBLE;
+ $status = base_setting::NOT_LOCKED;
+ }
+ parent::__construct('competencies', base_setting::IS_BOOLEAN, $defaultvalue, $visibility, $status);
+ }
+}
+
// Section backup settings
/**
return $wrapper;
}
+
+ /**
+ * Execute conditions.
+ *
+ * @return bool
+ */
+ protected function execute_condition() {
+
+ // Do not execute if competencies are not included.
+ if (!$this->get_setting_value('competencies')) {
+ return false;
+ }
+
+ return true;
+ }
}
/**
return $wrapper;
}
+
+ /**
+ * Execute conditions.
+ *
+ * @return bool
+ */
+ protected function execute_condition() {
+
+ // Do not execute if competencies are not included.
+ if (!$this->get_setting_value('competencies')) {
+ return false;
+ }
+
+ return true;
+ }
}
/**
$groups->set_ui(new backup_setting_ui_checkbox($groups, get_string('rootsettinggroups', 'backup')));
$groups->get_ui()->set_changeable($changeable);
$this->add_setting($groups);
+
+ // Competencies restore setting. Show when competencies is enabled and the setting is available.
+ $hascompetencies = !empty($rootsettings['competencies']);
+ $competencies = new restore_competencies_setting($hascompetencies);
+ $competencies->set_ui(new backup_setting_ui_checkbox($competencies, get_string('rootsettingcompetencies', 'backup')));
+ $this->add_setting($competencies);
}
}
*/
class restore_badges_setting extends restore_generic_setting {}
+/**
+ * root setting to control if competencies will also be restored.
+ */
+class restore_competencies_setting extends restore_generic_setting {
+
+ /**
+ * restore_competencies_setting constructor.
+ * @param bool $hascompetencies Flag whether to set the restore setting as checked and unlocked.
+ */
+ public function __construct($hascompetencies) {
+ $defaultvalue = false;
+ $visibility = base_setting::HIDDEN;
+ $status = base_setting::LOCKED_BY_CONFIG;
+ if (\core_competency\api::is_enabled()) {
+ $visibility = base_setting::VISIBLE;
+ if ($hascompetencies) {
+ $defaultvalue = true;
+ $status = base_setting::NOT_LOCKED;
+ }
+ }
+ parent::__construct('competencies', base_setting::IS_BOOLEAN, $defaultvalue, $visibility, $status);
+ }
+}
+
/**
* root setting to control if restore will create
* events or no, depends of @restore_users_setting
*/
public function process_course($data) {
global $CFG, $DB;
+ $context = context::instance_by_id($this->task->get_contextid());
+ $userid = $this->task->get_userid();
+ $target = $this->get_task()->get_target();
+ $isnewcourse = $target != backup::TARGET_CURRENT_ADDING && $target != backup::TARGET_EXISTING_ADDING;
+
+ // When restoring to a new course we can set all the things except for the ID number.
+ $canchangeidnumber = $isnewcourse || has_capability('moodle/course:changeidnumber', $context, $userid);
+ $canchangeshortname = $isnewcourse || has_capability('moodle/course:changeshortname', $context, $userid);
+ $canchangefullname = $isnewcourse || has_capability('moodle/course:changefullname', $context, $userid);
+ $canchangesummary = $isnewcourse || has_capability('moodle/course:changesummary', $context, $userid);
$data = (object)$data;
+ $data->id = $this->get_courseid();
$fullname = $this->get_setting_value('course_fullname');
$shortname = $this->get_setting_value('course_shortname');
$startdate = $this->get_setting_value('course_startdate');
- // Calculate final course names, to avoid dupes
+ // Calculate final course names, to avoid dupes.
list($fullname, $shortname) = restore_dbops::calculate_course_names($this->get_courseid(), $fullname, $shortname);
- // Need to change some fields before updating the course record
- $data->id = $this->get_courseid();
- $data->fullname = $fullname;
- $data->shortname= $shortname;
+ if ($canchangefullname) {
+ $data->fullname = $fullname;
+ } else {
+ unset($data->fullname);
+ }
+
+ if ($canchangeshortname) {
+ $data->shortname = $shortname;
+ } else {
+ unset($data->shortname);
+ }
+
+ if (!$canchangesummary) {
+ unset($data->summary);
+ unset($data->summaryformat);
+ }
// Only allow the idnumber to be set if the user has permission and the idnumber is not already in use by
// another course on this site.
- $context = context::instance_by_id($this->task->get_contextid());
- if (!empty($data->idnumber) && has_capability('moodle/course:changeidnumber', $context, $this->task->get_userid()) &&
- $this->task->is_samesite() && !$DB->record_exists('course', array('idnumber' => $data->idnumber))) {
+ if (!empty($data->idnumber) && $canchangeidnumber && $this->task->is_samesite()
+ && !$DB->record_exists('course', array('idnumber' => $data->idnumber))) {
// Do not reset idnumber.
+
+ } else if (!$isnewcourse) {
+ // Prevent override when restoring as merge.
+ unset($data->idnumber);
+
} else {
$data->idnumber = '';
}
*/
protected function execute_condition() {
- // Do not restore when competencies are disabled.
- if (!\core_competency\api::is_enabled()) {
+ // Do not execute if competencies are not included.
+ if (!$this->get_setting_value('competencies')) {
return false;
}
*/
protected function execute_condition() {
- // Do not restore when competencies are disabled.
- if (!\core_competency\api::is_enabled()) {
+ // Do not execute if competencies are not included.
+ if (!$this->get_setting_value('competencies')) {
return false;
}
*/
class core_backup_moodle2_course_format_testcase extends advanced_testcase {
- /**
- * Tidy up open files that may be left open.
- */
- protected function tearDown() {
- gc_collect_cycles();
- }
-
/**
* Tests a backup and restore adds the required section option data
* when the same course format is used.
),
) + parent::section_format_options($foreditform);
}
-}
\ No newline at end of file
+}
*/
class core_backup_moodle2_testcase extends advanced_testcase {
- /**
- * Tidy up open files that may be left open.
- */
- protected function tearDown() {
- gc_collect_cycles();
- }
-
/**
* Tests the availability field on modules and sections is correctly
* backed up and restored.
$thrown->getFile() . ':' . $thrown->getLine(). "]\n\n";
}
- // Must set restore_controller variable to null so that php
- // garbage-collects it; otherwise the file will be left open and
- // attempts to delete it will cause a permission error on Windows
- // systems, breaking unit tests.
- $rc = null;
$this->assertNull($thrown);
// Get information about the resulting course and check that it is set
This files describes API changes in /backup/*,
information provided here is intended especially for developers.
+=== 3.1 ===
+
+* New close() method added to loggers so they can close any open resource. Previously
+ any backup and restore operation using the file logger may be leaving unclosed files.
+* New destroy() method added to loggers, normally called from backup and restore controllers
+ own destroy() method to ensure that all references in the chained loggers are deleted
+ and any open resource within them is closed properly.
+
=== 3.0 ===
* The backup_auto_keep setting, in automated backups configuration, is now
backup::INTERACTIVE_NO, backup::MODE_GENERAL, $this->userid);
$this->assertTrue(backup_check::check_security($bc, true));
$this->assertTrue($bc instanceof backup_controller);
+ $bc->destroy();
}
}
// If included, add it
if ($included) {
- $includedtasks[] = $task;
+ $includedtasks[] = clone($task); // A clone is enough. In fact we only need the basepath.
}
}
+ $rc->destroy(); // Always need to destroy.
+
return $includedtasks;
}
// Calculate the context we are going to use for capability checking
$context = context_course::instance($courseid);
+ // TODO: Some day we must kill this dependency and change the process
+ // to pass info around without loading a controller copy.
// When conflicting users are detected we may need original site info.
- $restoreinfo = restore_controller_dbops::load_controller($restoreid)->get_info();
+ $rc = restore_controller_dbops::load_controller($restoreid);
+ $restoreinfo = $rc->get_info();
+ $rc->destroy(); // Always need to destroy.
// Calculate if we have perms to create users, by checking:
// to 'moodle/restore:createuser' and 'moodle/restore:userinfo'
$bc = backup_controller::load_controller($backupid);
$bc->log('Attempt to copy backup file to the specified directory using filesystem failed - ',
backup::LOG_WARNING, $dir);
+ $bc->destroy();
}
// bad luck, try to deal with the file the old way - keep backup in file area if we can not copy to ext system
}
return $this->level;
}
+ /**
+ * Destroy (nullify) the chain of loggers references, also closing resources when needed.
+ *
+ * @since Moodle 3.1
+ */
+ public final function destroy() {
+ // Recursively destroy the chain.
+ if ($this->next !== null) {
+ $this->next->destroy();
+ $this->next = null;
+ }
+ // And close every logger.
+ $this->close();
+ }
+
+ /**
+ * Close any resource the logger may have open.
+ *
+ * @since Moodle 3.1
+ */
+ public function close() {
+ // Nothing to do by default. Only loggers using resources (files, own connections...) need to override this.
+ }
+
// checksumable interface methods
public function calculate_checksum() {
}
}
+ /**
+ * Close the logger resources (file handle) if still open.
+ *
+ * @since Moodle 3.1
+ */
+ public function close() {
+ // Close the file handle if hasn't been closed already.
+ if (is_resource($this->fhandle)) {
+ fclose($this->fhandle);
+ $this->fhandle = null;
+ }
+ }
+
// Protected API starts here
protected function action($message, $level, $options = null) {
$this->assertEquals($lo1->get_levelstr(backup::LOG_WARNING), 'warn');
$this->assertEquals($lo1->get_levelstr(backup::LOG_INFO), 'info');
$this->assertEquals($lo1->get_levelstr(backup::LOG_DEBUG), 'debug');
+
+ // Test destroy.
+ $lo1 = new mock_base_logger1(backup::LOG_ERROR);
+ $lo2 = new mock_base_logger2(backup::LOG_ERROR);
+ $lo1->set_next($lo2);
+ $this->assertInstanceOf('base_logger', $lo1->get_next());
+ $this->assertNull($lo2->get_next());
+ $lo1->destroy();
+ $this->assertNull($lo1->get_next());
+ $this->assertNull($lo2->get_next());
}
/**
$result = $lo2->process($message2, backup::LOG_WARNING, $options);
$this->assertTrue($result);
- // Destruct loggers
- $lo1 = null;
- $lo2 = null;
+ // Destroy loggers.
+ $lo1->destroy();
+ $lo2->destroy();
// Load file results to analyze them
$fcontents = file_get_contents($file);
$this->assertTrue(file_exists($file));
$message = 'testing file_logger';
$result = $lo->process($message, backup::LOG_ERROR, $options);
+ $lo->close(); // Closes logger.
// Get file contents and inspect them
$fcontents = file_get_contents($file);
$this->assertTrue($result);
$this->assertTrue(strpos($fcontents, '[error]') !== false);
$this->assertTrue(strpos($fcontents, ' ') !== false);
$this->assertTrue(substr_count($fcontents , '] ') >= 2);
- $lo->__destruct(); // closes file handle
unlink($file); // delete file
// Instantiate, write something, force deletion, try to write again
$this->assertTrue(file_exists($file));
$message = 'testing file_logger';
$result = $lo->process($message, backup::LOG_ERROR);
- fclose($lo->get_fhandle()); // close file
+ $lo->close();
+ $this->assertNull($lo->get_fhandle());
try {
$result = @$lo->process($message, backup::LOG_ERROR); // Try to write again
$this->assertTrue(false, 'base_logger_exception expected');
$lo = new file_logger(backup::LOG_NONE, true, true, $file);
$this->assertTrue($lo instanceof file_logger);
$this->assertFalse(file_exists($file));
+ $lo->close();
// Remove the test dir and any content
@remove_dir(dirname($file));
// Calculate checksum and check it
$checksum = $bp->calculate_checksum();
$this->assertTrue($bp->is_checksum_correct($checksum));
+
+ $bc->destroy();
}
/**
$this->assertTrue($bs instanceof backup_step);
$this->assertEquals($bs->get_name(), 'stepname');
+ $bc->destroy();
}
/**
$this->assertTrue(strpos($contents, '<field2>value2</field2>') !== false);
$this->assertTrue(strpos($contents, '</test>') !== false);
+ $bc->destroy();
+
unlink($file); // delete file
// Remove the test dir and any content
$checksum = $bt->calculate_checksum();
$this->assertTrue($bt->is_checksum_correct($checksum));
+ $bc->destroy();
}
/**
// Add category. This node should appear after 'contact' so that administration block appears towards the end. Refer MDL-49928.
$category = new core_user\output\myprofile\category('badges', get_string('badges', 'badges'), 'contact');
$tree->add_category($category);
-
- // Determine context.
- if (isloggedin()) {
- $context = context_user::instance($USER->id);
- } else {
- $context = context_system::instance();
- }
+ $context = context_user::instance($user->id);
$courseid = empty($course) ? 0 : $course->id;
if ($USER->id == $user->id || has_capability('moodle/badges:viewotherbadges', $context)) {
}
}
}
-}
\ No newline at end of file
+}
--- /dev/null
+@block @block_badges
+Feature: Enable Block Badges in a course without badges
+ In order to view the badges block in a course
+ As a teacher
+ I can add badges block to a course and view the contents
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email | idnumber |
+ | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+ And the following "courses" exist:
+ | fullname | shortname | category |
+ | Course 1 | C1 | 0 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+
+ Scenario: Add the block to a the course when badges are disabled
+ Given I log in as "admin"
+ And the following config values are set as admin:
+ | enablebadges | 0 |
+ And I log out
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ When I add the "Latest badges" block
+ Then I should see "Badges are not enabled on this site." in the "Latest badges" "block"
+
+ Scenario: Add the block to a the course when badges are enabled
+ Given I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ When I add the "Latest badges" block
+ Then I should see "You have no badges to display" in the "Latest badges" "block"
--- /dev/null
+@block @block_badges @core_badges @_file_upload @javascript
+Feature: Enable Block Badges in a course
+ In order to enable the badges block in a course
+ As a teacher
+ I can add badges block to a course
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email | idnumber |
+ | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+ And the following "courses" exist:
+ | fullname | shortname | category |
+ | Course 1 | C1 | 0 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ # Issue badge 1 of 2
+ And I navigate to "Add a new badge" node in "Badges"
+ And I set the following fields to these values:
+ | id_name | Badge 1 |
+ | id_description | Badge 1 |
+ | id_issuername | Teacher 1 |
+ And I upload "blocks/badges/tests/fixtures/badge.png" file to "Image" filemanager
+ And I press "Create badge"
+ And I select "Manual issue by role" from the "Add badge criteria" singleselect
+ And I set the field "Teacher" to "1"
+ And I press "Save"
+ And I press "Enable access"
+ And I press "Continue"
+ And I follow "Recipients (0)"
+ And I press "Award badge"
+ And I set the field "potentialrecipients[]" to "Teacher 1 (teacher1@example.com)"
+ And I press "Award badge"
+ # Issue Badge 2 of 2
+ And I navigate to "Add a new badge" node in "Badges"
+ And I set the following fields to these values:
+ | id_name | Badge 2 |
+ | id_description | Badge 2 |
+ | id_issuername | Teacher 1 |
+ And I upload "blocks/badges/tests/fixtures/badge.png" file to "Image" filemanager
+ And I press "Create badge"
+ And I select "Manual issue by role" from the "Add badge criteria" singleselect
+ And I set the field "Teacher" to "1"
+ And I press "Save"
+ And I press "Enable access"
+ And I press "Continue"
+ And I follow "Recipients (0)"
+ And I press "Award badge"
+ And I set the field "potentialrecipients[]" to "Teacher 1 (teacher1@example.com)"
+ And I press "Award badge"
+ And I log out
+
+ Scenario: Add the recent badges block to a course.
+ Given I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ When I add the "Latest badges" block
+ Then I should see "Badge 1" in the "Latest badges" "block"
+ And I should see "Badge 2" in the "Latest badges" "block"
+
+ Scenario: Add the recent badges block to a course and limit it to only display 1 badge.
+ Given I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ When I add the "Latest badges" block
+ And I configure the "Latest badges" block
+ And I set the following fields to these values:
+ | id_config_numberofbadges | 1 |
+ And I press "Save changes"
+ Then I should see "Badge 2" in the "Latest badges" "block"
+ And I should not see "Badge 1" in the "Latest badges" "block"
--- /dev/null
+@block @block_badges @core_badges @_file_upload @javascript
+Feature: Enable Block Badges on the dashboard and view awarded badges
+ In order to view recent badges on the dashboard
+ As a teacher
+ I can add badges block to the dashboard
+
+ Scenario: Add the recent badges block to a course.
+ Given the following "users" exist:
+ | username | firstname | lastname | email | idnumber |
+ | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+ And the following "courses" exist:
+ | fullname | shortname | category |
+ | Course 1 | C1 | 0 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ # Issue badge 1 of 2
+ And I navigate to "Add a new badge" node in "Badges"
+ And I set the following fields to these values:
+ | id_name | Badge 1 |
+ | id_description | Badge 1 |
+ | id_issuername | Teacher 1 |
+ And I upload "blocks/badges/tests/fixtures/badge.png" file to "Image" filemanager
+ And I press "Create badge"
+ And I select "Manual issue by role" from the "Add badge criteria" singleselect
+ And I set the field "Teacher" to "1"
+ And I press "Save"
+ And I press "Enable access"
+ And I press "Continue"
+ And I follow "Recipients (0)"
+ And I press "Award badge"
+ And I set the field "potentialrecipients[]" to "Teacher 1 (teacher1@example.com)"
+ And I press "Award badge"
+ And I log out
+ When I log in as "teacher1"
+ And I click on "Dashboard" "link" in the "Navigation" "block"
+ Then I should see "Badge 1" in the "Latest badges" "block"
--- /dev/null
+@block @block_badges @core_badges @_file_upload @javascript
+Feature: Enable Block Badges on the frontpage and view awarded badges
+ In order to enable the badges block on the frontpage
+ As a admin
+ I can add badges block to the frontpage
+
+ Scenario: Add the recent badges block on the frontpage and view recent badges
+ Given the following "users" exist:
+ | username | firstname | lastname | email | idnumber |
+ | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+ And the following "courses" exist:
+ | fullname | shortname | category |
+ | Course 1 | C1 | 0 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ And I log in as "admin"
+ And I am on site homepage
+ And I navigate to "Turn editing on" node in "Front page settings"
+ And I add the "Latest badges" block
+ And I log out
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ # Issue badge 1 of 2
+ And I navigate to "Add a new badge" node in "Badges"
+ And I set the following fields to these values:
+ | id_name | Badge 1 |
+ | id_description | Badge 1 |
+ | id_issuername | Teacher 1 |
+ And I upload "blocks/badges/tests/fixtures/badge.png" file to "Image" filemanager
+ And I press "Create badge"
+ And I select "Manual issue by role" from the "Add badge criteria" singleselect
+ And I set the field "Teacher" to "1"
+ And I press "Save"
+ And I press "Enable access"
+ And I press "Continue"
+ And I follow "Recipients (0)"
+ And I press "Award badge"
+ And I set the field "potentialrecipients[]" to "Teacher 1 (teacher1@example.com)"
+ And I press "Award badge"
+ And I log out
+ When I log in as "teacher1"
+ And I am on site homepage
+ Then I should see "Badge 1" in the "Latest badges" "block"
--- /dev/null
+@block @block_calendar_month
+Feature: Enable the calendar block in a course and test it's functionality
+ In order to enable the calendar block in a course
+ As a teacher
+ I can add the calendar block to a course
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email | idnumber |
+ | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+ | student1 | Student | 1 | student1@example.com | S1 |
+ | student2 | Student | 2 | student2@example.com | S2 |
+ And the following "courses" exist:
+ | fullname | shortname | category |
+ | Course 1 | C1 | 0 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ | student1 | C1 | student |
+ | student2 | C1 | student |
+
+ Scenario: Add the block to a the course
+ Given I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ When I add the "Calendar" block
+ Then I should see "Events key" in the "Calendar" "block"
+
+ @javascript
+ Scenario: View a global event in the calendar block
+ Given I log in as "admin"
+ And I create a calendar event with form data:
+ | id_eventtype | Site |
+ | id_name | Site Event |
+ And I log out
+ When I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ And I add the "Calendar" block
+ And I hover over today in the calendar
+ Then I should see "Site Event"
+
+ @javascript
+ Scenario: Filter site events in the calendar block
+ Given I log in as "admin"
+ And I create a calendar event with form data:
+ | id_eventtype | Site |
+ | id_name | Site Event |
+ And I log out
+ When I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ And I add the "Calendar" block
+ And I create a calendar event with form data:
+ | id_eventtype | Course |
+ | id_name | Course Event |
+ And I follow "Course 1"
+ And I follow "Hide global events"
+ And I hover over today in the calendar
+ Then I should not see "Site Event"
+ And I should see "Course Event"
+
+ @javascript
+ Scenario: View a course event in the calendar block
+ Given I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ And I add the "Calendar" block
+ And I create a calendar event with form data:
+ | id_eventtype | Course |
+ | id_name | Course Event |
+ When I follow "Course 1"
+ And I hover over today in the calendar
+ Then I should see "Course Event"
+
+ @javascript
+ Scenario: Filter course events in the calendar block
+ Given I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ And I add the "Calendar" block
+ And I create a calendar event with form data:
+ | id_eventtype | Course |
+ | id_name | Course Event |
+ And I follow "Course 1"
+ And I create a calendar event with form data:
+ | id_eventtype | User |
+ | id_name | User Event |
+ When I click on "Dashboard" "link" in the "Navigation" "block"
+ And I follow "Course 1"
+ And I follow "Hide course events"
+ And I hover over today in the calendar
+ Then I should not see "Course Event"
+ And I should see "User Event"
+
+ @javascript
+ Scenario: View a user event in the calendar block
+ Given I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ And I add the "Calendar" block
+ And I create a calendar event with form data:
+ | id_eventtype | User |
+ | id_name | User Event |
+ When I click on "Dashboard" "link" in the "Navigation" "block"
+ And I follow "Course 1"
+ And I hover over today in the calendar
+ Then I should see "User Event"
+
+ @javascript
+ Scenario: Filter user events in the calendar block
+ Given I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ And I add the "Calendar" block
+ And I create a calendar event with form data:
+ | id_eventtype | Course |
+ | id_name | Course Event |
+ And I follow "Course 1"
+ And I create a calendar event with form data:
+ | id_eventtype | User |
+ | id_name | User Event |
+ When I click on "Dashboard" "link" in the "Navigation" "block"
+ And I follow "Course 1"
+ And I follow "Hide user events"
+ And I hover over today in the calendar
+ Then I should not see "User Event"
+ And I should see "Course Event"
+
+ @javascript
+ Scenario: View a group event in the calendar block
+ Given the following "groups" exist:
+ | name | course | idnumber |
+ | Group 1 | C1 | G1 |
+ | Group 2 | C1 | G2 |
+ And the following "group members" exist:
+ | user | group |
+ | student1 | G1 |
+ | student2 | G2 |
+ When I log in as "teacher1"
+ And I follow "Course 1"
+ And I navigate to "Edit settings" node in "Course administration"
+ And I set the following fields to these values:
+ | id_groupmode | Separate groups |
+ | id_groupmodeforce | Yes |
+ And I press "Save and display"
+ And I turn editing mode on
+ And I add the "Calendar" block
+ And I create a calendar event with form data:
+ | id_eventtype | Group |
+ | id_groupid | Group 1 |
+ | id_name | Group Event |
+ And I log out
+ Then I log in as "student1"
+ And I follow "Course 1"
+ And I hover over today in the calendar
+ And I should see "Group Event"
+ And I log out
+ And I log in as "student2"
+ And I follow "Course 1"
+ And I hover over today in the calendar
+ And I should not see "Group Event"
+
+ @javascript
+ Scenario: Filter group events in the calendar block
+ Given the following "groups" exist:
+ | name | course | idnumber |
+ | Group 1 | C1 | G1 |
+ | Group 2 | C1 | G2 |
+ And the following "group members" exist:
+ | user | group |
+ | student1 | G1 |
+ | student2 | G2 |
+ When I log in as "teacher1"
+ And I follow "Course 1"
+ And I navigate to "Edit settings" node in "Course administration"
+ And I set the following fields to these values:
+ | id_groupmode | Separate groups |
+ | id_groupmodeforce | Yes |
+ And I press "Save and display"
+ And I turn editing mode on
+ And I add the "Calendar" block
+ And I create a calendar event with form data:
+ | id_eventtype | Course |
+ | id_name | Course Event 1 |
+ And I follow "Course 1"
+ And I create a calendar event with form data:
+ | id_eventtype | Group |
+ | id_groupid | Group 1 |
+ | id_name | Group Event 1 |
+ And I log out
+ Then I log in as "student1"
+ And I follow "Course 1"
+ And I follow "Hide group events"
+ And I hover over today in the calendar
+ And I should not see "Group Event 1"
+ And I should see "Course Event 1"
--- /dev/null
+@block @block_calendar_month
+Feature: Enable the calendar block in a course
+ In order to enable the calendar block in a course
+ As a teacher
+ I can add the calendar block to a course
+
+ @javascript
+ Scenario: View a global event in the calendar block in a course
+ Given the following "users" exist:
+ | username | firstname | lastname | email | idnumber |
+ | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+ And the following "courses" exist:
+ | fullname | shortname | category |
+ | Course 1 | C1 | 0 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ When I log in as "admin"
+ And I create a calendar event with form data:
+ | id_eventtype | Site |
+ | id_name | Site Event |
+ And I log out
+ Then I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ And I add the "Calendar" block
+ And I hover over today in the calendar
+ And I should see "Site Event"
--- /dev/null
+@block @block_calendar_month
+Feature: View a site event on the dashboard
+ In order to view a site event
+ As a student
+ I can view the event in the calendar
+
+ @javascript
+ Scenario: View a global event in the calendar block on the dashboard
+ Given the following "users" exist:
+ | username | firstname | lastname | email | idnumber |
+ | student1 | Student | 1 | student1@example.com | S1 |
+ And I log in as "admin"
+ And I create a calendar event with form data:
+ | id_eventtype | Site |
+ | id_name | Site Event |
+ And I log out
+ When I log in as "student1"
+ And I hover over today in the calendar
+ Then I should see "Site Event"
--- /dev/null
+@block @block_calendar_month
+Feature: Enable the calendar block on the site front page
+ In order to enable the calendar block on the site front page
+ As an admin
+ I can add the calendar block on the site front page
+
+ @javascript
+ Scenario: View a global event in the calendar block on the front page
+ Given the following "users" exist:
+ | username | firstname | lastname | email | idnumber |
+ | student1 | Student | 1 | student1@example.com | S1 |
+ And I log in as "admin"
+ And I am on site homepage
+ And I create a calendar event with form data:
+ | id_eventtype | Site |
+ | id_name | Site Event |
+ And I log out
+ When I log in as "student1"
+ And I am on site homepage
+ And I hover over today in the calendar
+ Then I should see "Site Event"
--- /dev/null
+@block @block_calendar_upcoming
+Feature: Enable the upcoming events block in a course
+ In order to enable the calendar block in a course
+ As a teacher
+ I can view the event in the upcoming events block
+
+ Scenario: View a global event in the upcoming events block in a course
+ Given the following "users" exist:
+ | username | firstname | lastname | email | idnumber |
+ | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+ And the following "courses" exist:
+ | fullname | shortname | category |
+ | Course 1 | C1 | 0 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ When I log in as "admin"
+ And I create a calendar event with form data:
+ | id_eventtype | Site |
+ | id_name | My Site Event |
+ And I log out
+ When I log in as "teacher1"
+ Then I should see "My Site Event" in the "Upcoming events" "block"
--- /dev/null
+@block @block_calendar_upcoming
+Feature: View a site event on the dashboard
+ In order to view a site event
+ As a student
+ I can view the event in the upcoming events block
+
+ Scenario: View a global event in the upcoming events block on the dashboard
+ Given the following "users" exist:
+ | username | firstname | lastname | email | idnumber |
+ | student1 | Student | 1 | student1@example.com | S1 |
+ And I log in as "admin"
+ And I create a calendar event with form data:
+ | id_eventtype | Site |
+ | id_name | My Site Event |
+ And I log out
+ When I log in as "student1"
+ Then I should see "My Site Event"
--- /dev/null
+@block @block_calendar_upcoming
+Feature: View a site event on the frontpage
+ In order to view a site event
+ As a teacher
+ I can view the event in the upcoming events block
+
+ Scenario: View a global event in the upcoming events block on the frontpage
+ Given the following "users" exist:
+ | username | firstname | lastname | email | idnumber |
+ | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+ And I log in as "admin"
+ And I create a calendar event with form data:
+ | id_eventtype | Site |
+ | id_name | My Site Event |
+ And I am on site homepage
+ And I navigate to "Turn editing on" node in "Front page settings"
+ And I add the "Upcoming events" block
+ And I log out
+ When I log in as "teacher1"
+ And I am on site homepage
+ Then I should see "My Site Event" in the "Upcoming events" "block"
--- /dev/null
+@block @block_comments
+Feature: Enable Block comments on an activity page and view comments
+ In order to enable the comments block on an activity page
+ As a teacher
+ I can add the comments block to an activity page
+
+ Scenario: Add the comments block on an activity page and add comments
+ Given the following "courses" exist:
+ | fullname | shortname | category |
+ | Course 1 | C1 | 0 |
+ And the following "users" exist:
+ | username | firstname | lastname | email |
+ | teacher1 | Teacher | Frist | teacher1@example.com |
+ | student1 | Student | First | student1@example.com |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ | student1 | C1 | student |
+ And the following "activities" exist:
+ | activity | course | idnumber | name | intro |
+ | page | C1 | page1 | Test page name | Test page description |
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ And I follow "Test page name"
+ And I add the "Comments" block
+ And I follow "Show comments"
+ And I add "I'm a comment from the teacher" comment to comments block
+ And I log out
+ When I log in as "student1"
+ And I follow "Course 1"
+ And I follow "Test page name"
+ And I follow "Show comments"
+ Then I should see "I'm a comment from the teacher"
--- /dev/null
+@block @block_comments
+Feature: Enable Block comments on a course page and view comments
+ In order to enable the comments block on a course page
+ As a teacher
+ I can add the comments block to the course page
+
+ Scenario: Add the comments block on the course page and add comments
+ Given the following "courses" exist:
+ | fullname | shortname | category |
+ | Course 1 | C1 | 0 |
+ And the following "users" exist:
+ | username | firstname | lastname | email |
+ | teacher1 | Teacher | Frist | teacher1@example.com |
+ | student1 | Student | First | student1@example.com |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ | student1 | C1 | student |
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ And I add the "Comments" block
+ And I follow "Show comments"
+ And I add "I'm a comment from the teacher" comment to comments block
+ And I log out
+ When I log in as "student1"
+ And I follow "Course 1"
+ And I follow "Show comments"
+ Then I should see "I'm a comment from the teacher"
--- /dev/null
+@block @block_comments
+Feature: Enable Block comments on the frontpage and view comments
+ In order to enable the comments block on the frontpage
+ As a admin
+ I can add the comments block to the frontpage
+
+ Scenario: Add the comments block on the frontpage and add comments
+ Given the following "users" exist:
+ | username | firstname | lastname | email | idnumber |
+ | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+ And I log in as "admin"
+ And I am on site homepage
+ And I navigate to "Turn editing on" node in "Front page settings"
+ And I add the "Comments" block
+ And I follow "Show comments"
+ And I add "I'm a comment from admin" comment to comments block
+ And I log out
+ When I log in as "teacher1"
+ And I am on site homepage
+ And I follow "Show comments"
+ Then I should see "I'm a comment from admin"
--- /dev/null
+@block @block_completionstatus
+Feature: Enable Block Completion in a course
+ In order to view the completion block in a course
+ As a teacher
+ I can add completion 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 |
+ And the following "courses" exist:
+ | fullname | shortname | category | enablecompletion |
+ | Course 1 | C1 | 0 | 1 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ | student1 | C1 | student |
+
+ Scenario: Add the block to a the course where completion is disabled
+ Given I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ And I navigate to "Edit settings" node in "Course administration"
+ And I set the following fields to these values:
+ | Enable completion tracking | No |
+ And I press "Save and display"
+ When I add the "Course completion status" block
+ Then I should see "Completion is not enabled for this course" in the "Course completion status" "block"
+
+ Scenario: Add the block to a the course where completion is not set
+ Given I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ When I add the "Course completion status" block
+ Then I should see "No completion criteria set for this course" in the "Course completion status" "block"
+
+ Scenario: Add the block to a course with criteria and view as an untracked role.
+ Given 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 navigate to "Edit settings" node in "Page module administration"
+ And I set the following fields to these values:
+ | Completion tracking | Show activity as complete when conditions are met |
+ | Require view | 1 |
+ And I press "Save and return to course"
+ When I add the "Course completion status" block
+ And I navigate to "Course completion" node in "Course administration"
+ And I expand all fieldsets
+ And I set the following fields to these values:
+ | Test page name | 1 |
+ And I press "Save changes"
+ Then I should see "You are currently not being tracked by completion in this course" in the "Course completion status" "block"
--- /dev/null
+@block @block_completionstatus
+Feature: Enable Block Completion in a course using activity completion
+ In order to view the completion block in a course
+ As a teacher
+ I can add completion block to a course and set up activity completion
+
+ 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 |
+ And the following "courses" exist:
+ | fullname | shortname | category | enablecompletion |
+ | Course 1 | C1 | 0 | 1 |
+ 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 |
+
+ Scenario: Add the block to a the course and add course completion items
+ Given I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ And I follow "Test page name"
+ And I navigate to "Edit settings" node in "Page module administration"
+ And I set the following fields to these values:
+ | Completion tracking | Show activity as complete when conditions are met |
+ | Require view | 1 |
+ And I press "Save and return to course"
+ And I add the "Course completion status" block
+ And I navigate to "Course completion" node in "Course administration"
+ And I expand all fieldsets
+ And I set the following fields to these values:
+ | Test page name | 1 |
+ And I press "Save changes"
+ And I log out
+ When I log in as "student1"
+ And I follow "Course 1"
+ Then I should see "Status: Not yet started" in the "Course completion status" "block"
+ And I should see "0 of 1" in the "Activity completion" "table_row"
+
+ Scenario: Add the block to a the course and add course completion items
+ Given I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ And I follow "Test page name"
+ And I navigate to "Edit settings" node in "Page module administration"
+ And I set the following fields to these values:
+ | Completion tracking | Show activity as complete when conditions are met |
+ | Require view | 1 |
+ And I press "Save and return to course"
+ And I add the "Course completion status" block
+ And I navigate to "Course completion" node in "Course administration"
+ And I expand all fieldsets
+ And I set the following fields to these values:
+ | Test page name | 1 |
+ And I press "Save changes"
+ And I log out
+ When I log in as "student1"
+ And I follow "Course 1"
+ And I follow "Test page name"
+ And I follow "C1"
+ Then I should see "Status: Pending" in the "Course completion status" "block"
+ And I should see "0 of 1" in the "Activity completion" "table_row"
+ And I trigger cron
+ And I am on site homepage
+ And I follow "Course 1"
+ And I should see "1 of 1" in the "Activity completion" "table_row"
+ And I follow "More details"
+ And I should see "Yes" in the "Activity completion" "table_row"
--- /dev/null
+@block @block_completionstatus
+Feature: Enable Block Completion in a course using manual completion by others
+ In order to view the completion block in a course
+ As a teacher
+ I can add completion block to a course and set up manual completion by others
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email | idnumber |
+ | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+ | teacher2 | Teacher | 2 | teacher1@example.com | T2 |
+ | student1 | Student | 1 | student1@example.com | S1 |
+ And the following "courses" exist:
+ | fullname | shortname | category | enablecompletion |
+ | Course 1 | C1 | 0 | 1 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ | teacher2 | C1 | teacher |
+ | student1 | C1 | student |
+
+ Scenario: Add the block to a the course and mark a student complete.
+ Given I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ And I add the "Course completion status" block
+ And I navigate to "Course completion" node in "Course administration"
+ And I expand all fieldsets
+ And I set the following fields to these values:
+ | Teacher | 1 |
+ And I press "Save changes"
+ And I log out
+ When I log in as "student1"
+ And I follow "Course 1"
+ And I should see "Status: Not yet started" in the "Course completion status" "block"
+ And I should see "No" in the "Teacher" "table_row"
+ And I log out
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ And I navigate to "Course completion" node in "Reports"
+ And I follow "Click to mark user complete"
+ And I trigger cron
+ And I am on site homepage
+ And I log out
+ And I log in as "student1"
+ And I follow "Course 1"
+ Then I should see "Status: Complete" in the "Course completion status" "block"
+ And I should see "Yes" in the "Teacher" "table_row"
+ And I follow "More details"
+ And I should see "Yes" in the "Marked complete by Teacher" "table_row"
+
+
+ Scenario: Add the block to a the course and require multiple roles to mark a student complete.
+ Given I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ And I add the "Course completion status" block
+ And I navigate to "Course completion" node in "Course administration"
+ And I expand all fieldsets
+ And I set the following fields to these values:
+ | Teacher | 1 |
+ | Non-editing teacher | 1 |
+ | id_role_aggregation | ALL selected roles to mark when the condition is met |
+ And I press "Save changes"
+ And I log out
+ When I log in as "student1"
+ And I follow "Course 1"
+ And I should see "Status: Not yet started" in the "Course completion status" "block"
+ And I should see "No" in the "Teacher" "table_row"
+ And I should see "No" in the "Non-editing teacher" "table_row"
+ And I log out
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ And I navigate to "Course completion" node in "Reports"
+ And I follow "Click to mark user complete"
+ And I log out
+ And I log in as "student1"
+ And I follow "Course 1"
+ And I should see "Status: In progress" in the "Course completion status" "block"
+ And I should see "Yes" in the "Teacher" "table_row"
+ And I should see "No" in the "Non-editing teacher" "table_row"
+ And I follow "More details"
+ And I should see "Yes" in the "Marked complete by Teacher" "table_row"
+ And I should see "No" in the "Marked complete by Non-editing teacher" "table_row"
+ And I log out
+ And I log in as "teacher2"
+ And I follow "Course 1"
+ And I navigate to "Course completion" node in "Reports"
+ And I follow "Click to mark user complete"
+ And I trigger cron
+ And I am on site homepage
+ And I log out
+ And I log in as "student1"
+ And I follow "Course 1"
+ Then I should see "Status: Complete" in the "Course completion status" "block"
+ And I should see "Yes" in the "Teacher" "table_row"
+ And I should see "Yes" in the "Non-editing teacher" "table_row"
+ And I follow "More details"
+ And I should see "Yes" in the "Marked complete by Teacher" "table_row"
+ And I should see "Yes" in the "Marked complete by Non-editing teacher" "table_row"
--- /dev/null
+@block @block_completionstatus @block_selfcompletion
+Feature: Enable Block Completion in a course using manual self completion
+ In order to view the completion block in a course
+ As a teacher
+ I can add completion block to a course and set up manual self completion
+
+ Scenario: Add the block to a the course and manually complete the course
+ Given the following "users" exist:
+ | username | firstname | lastname | email | idnumber |
+ | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+ | student1 | Student | 1 | student1@example.com | S1 |
+ And the following "courses" exist:
+ | fullname | shortname | category | enablecompletion |
+ | Course 1 | C1 | 0 | 1 |
+ 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 "Course completion status" block
+ And I add the "Self completion" block
+ And I navigate to "Course completion" node in "Course administration"
+ And I expand all fieldsets
+ And I set the following fields to these values:
+ | id_criteria_self | 1 |
+ And I press "Save changes"
+ And I log out
+ When I log in as "student1"
+ And I follow "Course 1"
+ And I should see "Status: Not yet started" in the "Course completion status" "block"
+ And I should see "No" in the "Self completion" "table_row"
+ And I follow "Complete course"
+ And I should see "Confirm self completion"
+ And I press "Yes"
+ And I should see "Status: In progress" in the "Course completion status" "block"
+ And I trigger cron
+ And I am on site homepage
+ And I follow "Course 1"
+ Then I should see "Status: Complete" in the "Course completion status" "block"
+ And I should see "Yes" in the "Self completion" "table_row"
+ And I follow "More details"
+ And I should see "Yes" in the "Self completion" "table_row"
--- /dev/null
+.block_globalsearch .searchform {text-align: center;}
+.block_globalsearch .footer {text-align: center;}
<tbody>
{{#competencies}}
<tr>
- <td><a href="{{usercompetency.url}}">{{competency.shortname}}</a></td>
+ <td><a href="{{usercompetency.url}}">{{{competency.shortname}}}</a></td>
<td>{{user.fullname}}</td>
<td>{{usercompetency.statusname}}</td>
</tr>
<tbody>
{{#plans}}
<tr>
- <td><a href="{{plan.url}}">{{plan.name}}</a></td>
+ <td><a href="{{plan.url}}">{{{plan.name}}}</a></td>
<td>{{user.fullname}}</td>
<td>{{plan.statusname}}</td>
</tr>
{{#hasactiveplans}}
<ul>
{{#activeplans}}
- <li><a href="{{url}}">{{name}}</a></li>
+ <li><a href="{{url}}">{{{name}}}</a></li>
{{/activeplans}}
{{#hasmoreplans}}
<li class="more"><a href="{{plansurl}}">{{#str}}viewmore, block_lp{{/str}}</a></li>
<ul>
{{#compstoreview}}
<li>
- <a href="{{usercompetency.url}}">{{competency.shortname}}</a> ({{user.fullname}}) - {{usercompetency.statusname}}
+ <a href="{{usercompetency.url}}">{{{competency.shortname}}}</a> ({{user.fullname}}) - {{usercompetency.statusname}}
</li>
{{/compstoreview}}
{{#hasmorecompstoreview}}
<ul>
{{#planstoreview}}
<li>
- <a href="{{plan.url}}">{{plan.name}}</a> ({{user.fullname}}) - {{plan.statusname}}
+ <a href="{{plan.url}}">{{{plan.name}}}</a> ({{user.fullname}}) - {{plan.statusname}}
</li>
{{/planstoreview}}
{{#hasmoreplanstoreview}}
$content = $cm->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
}
- $this->content->items[] = $indent . $content;
+ $this->content->items[] = $indent . html_writer::div($content, 'main-menu-content');
}
}
return $this->content;
} else {
$content = html_writer::div($courserenderer->course_section_cm_name($mod), ' activity');
}
- $this->content->items[] = $indent. $content . $editbuttons;
+ $this->content->items[] = $indent . html_writer::div($content . $editbuttons, 'main-menu-content');
}
}
}
.block_site_main_menu li { clear: both; }
-.block_site_main_menu li .column { width: 100%; }
+.block_site_main_menu.block.list_block .unlist > li > .column {
+ /* Made specific to win over .block.list_block .unlist > li > .column. */
+ width: 100%;
+ display: table;
+}
.block_site_main_menu li .buttons { float: right; margin: 0; }
.dir-rtl .block_site_main_menu li .buttons { float: left; }
.block_site_main_menu li .buttons a img{ vertical-align: text-bottom;}
.block_site_main_menu .footer { margin-top: 1em; }
.block_site_main_menu .section_add_menus noscript div { display: inline;}
.block_site_main_menu .mod-indent,
-.block_site_main_menu .activity { display: table-cell; }
+.block_site_main_menu .main-menu-content { display: table-cell; }
'type' => PARAM_TEXT
),
'idnumber' => array(
- 'type' => PARAM_TEXT
+ 'type' => PARAM_RAW
),
'description' => array(
'default' => '',
'type' => PARAM_TEXT
),
'idnumber' => array(
- 'type' => PARAM_TEXT
+ 'type' => PARAM_RAW
),
'description' => array(
'type' => PARAM_RAW,
public static function define_properties() {
return array(
'id' => array(
- 'type' => PARAM_INT,
+ 'type' => \core_user::get_property_type('id'),
),
'email' => array(
- 'type' => PARAM_TEXT,
+ 'type' => \core_user::get_property_type('email'),
'default' => ''
),
'idnumber' => array(
- 'type' => PARAM_NOTAGS,
+ 'type' => \core_user::get_property_type('idnumber'),
'default' => ''
),
'phone1' => array(
- 'type' => PARAM_NOTAGS,
+ 'type' => \core_user::get_property_type('phone1'),
'default' => ''
),
'phone2' => array(
- 'type' => PARAM_NOTAGS,
+ 'type' => \core_user::get_property_type('phone2'),
'default' => ''
),
'department' => array(
- 'type' => PARAM_TEXT,
+ 'type' => \core_user::get_property_type('department'),
'default' => ''
),
'institution' => array(
- 'type' => PARAM_TEXT,
+ 'type' => \core_user::get_property_type('institution'),
'default' => ''
)
);
public static function define_other_properties() {
return array(
'fullname' => array(
- 'type' => PARAM_TEXT
+ 'type' => PARAM_RAW
),
'identity' => array(
- 'type' => PARAM_TEXT
+ 'type' => PARAM_RAW
),
'profileurl' => array(
'type' => PARAM_URL
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": [],
// $CFG->session_handler_class = '\core\session\file';
// $CFG->session_file_save_path = $CFG->dataroot.'/sessions';
//
-// Redis session handler (requires redis server and redis extension):
-// $CFG->session_handler_class = '\core\session\redis';
-// $CFG->session_redis_save_path = 'tcp://127.0.0.1'
-//
// Memcached session handler (requires memcached server and extension):
// $CFG->session_handler_class = '\core\session\memcached';
// $CFG->session_memcached_save_path = '127.0.0.1:11211';
--- /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 {
+
+ /**
+ * The context levels the search implementation is working on.
+ *
+ * @var array
+ */
+ protected static $levels = [CONTEXT_COURSE];
+
+ /**
+ * 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')));
+ }
+}
$courseinfo['id'] = $course->id;
$courseinfo['fullname'] = $course->fullname;
$courseinfo['shortname'] = $course->shortname;
+ $courseinfo['displayname'] = external_format_string(get_course_display_name_for_list($course), $context->id);
$courseinfo['categoryid'] = $course->category;
list($courseinfo['summary'], $courseinfo['summaryformat']) =
external_format_text($course->summary, $course->summaryformat, $context->id, 'course', 'summary', 0);
'categorysortorder' => new external_value(PARAM_INT,
'sort order into the category', VALUE_OPTIONAL),
'fullname' => new external_value(PARAM_TEXT, 'full name'),
+ 'displayname' => new external_value(PARAM_TEXT, 'course display name'),
'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
'summary' => new external_value(PARAM_RAW, 'summary'),
'summaryformat' => new external_format_value('summary'),
'requiredcapabilities' => $requiredcapabilities
);
$params = self::validate_parameters(self::search_courses_parameters(), $parameters);
+ self::validate_context(context_system::instance());
$allowedcriterianames = array('search', 'modulelist', 'blocklist', 'tagid');
if (!in_array($params['criterianame'], $allowedcriterianames)) {
class core_course_courselib_testcase extends advanced_testcase {
- /**
- * Tidy up open files that may be left open.
- */
- protected function tearDown() {
- gc_collect_cycles();
- }
-
/**
* Set forum specific test values for calling create_module().
*
$filepath = $CFG->dataroot . '/temp/backup/test-restore-course-event';
$file->extract_to_pathname($fp, $filepath);
$bc->destroy();
- unset($bc);
// Now we want to catch the restore course event.
$sink = $this->redirectEvents();
// Destroy the resource controller since we are done using it.
$rc->destroy();
- unset($rc);
}
/**
require_once($CFG->dirroot . '/course/externallib.php');
}
- /**
- * Tidy up open files that may be left open.
- */
- protected function tearDown() {
- gc_collect_cycles();
- }
-
/**
* Test create_categories
*/
$dbcourse = $generatedcourses[$course['id']];
$this->assertEquals($course['idnumber'], $dbcourse->idnumber);
$this->assertEquals($course['fullname'], $dbcourse->fullname);
+ $this->assertEquals($course['displayname'], get_course_display_name_for_list($dbcourse));
// Summary was converted to the HTML format.
$this->assertEquals($course['summary'], format_text($dbcourse->summary, FORMAT_MOODLE, array('para' => false)));
$this->assertEquals($course['summaryformat'], FORMAT_HTML);
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Course restore tests.
+ *
+ * @package core_course
+ * @copyright 2016 Frédéric Massart - FMCorz.net
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+global $CFG;
+
+require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
+require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
+
+/**
+ * Course restore testcase.
+ *
+ * @package core_course
+ * @copyright 2016 Frédéric Massart - FMCorz.net
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_restore_backup_testcase extends advanced_testcase {
+
+ /**
+ * Backup a course and return its backup ID.
+ *
+ * @param int $courseid The course ID.
+ * @param int $userid The user doing the backup.
+ * @return string
+ */
+ protected function backup_course($courseid, $userid = 2) {
+ globaL $CFG;
+ $packer = get_file_packer('application/vnd.moodle.backup');
+
+ $bc = new backup_controller(backup::TYPE_1COURSE, $courseid, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO,
+ backup::MODE_GENERAL, $userid);
+ $bc->execute_plan();
+
+ $results = $bc->get_results();
+ $results['backup_destination']->extract_to_pathname($packer, "$CFG->tempdir/backup/core_course_testcase");
+
+ $bc->destroy();
+ unset($bc);
+ return 'core_course_testcase';
+ }
+
+ /**
+ * Create a role with capabilities and permissions.
+ *
+ * @param string|array $caps Capability names.
+ * @param int $perm Constant CAP_* to apply to the capabilities.
+ * @return int The new role ID.
+ */
+ protected function create_role_with_caps($caps, $perm) {
+ $caps = (array) $caps;
+ $dg = $this->getDataGenerator();
+ $roleid = $dg->create_role();
+ foreach ($caps as $cap) {
+ assign_capability($cap, $perm, $roleid, context_system::instance()->id, true);
+ }
+ accesslib_clear_all_caches_for_unit_testing();
+ return $roleid;
+ }
+
+ /**
+ * Restore a course.
+ *
+ * @param int $backupid The backup ID.
+ * @param int $courseid The course ID to restore in, or 0.
+ * @param int $userid The ID of the user performing the restore.
+ * @return stdClass The updated course object.
+ */
+ protected function restore_course($backupid, $courseid, $userid) {
+ global $DB;
+
+ $target = backup::TARGET_CURRENT_ADDING;
+ if (!$courseid) {
+ $target = backup::TARGET_NEW_COURSE;
+ $categoryid = $DB->get_field_sql("SELECT MIN(id) FROM {course_categories}");
+ $courseid = restore_dbops::create_new_course('Tmp', 'tmp', $categoryid);
+ }
+
+ $rc = new restore_controller($backupid, $courseid, backup::INTERACTIVE_NO, backup::MODE_GENERAL, $userid, $target);
+ $target == backup::TARGET_NEW_COURSE ?: $rc->get_plan()->get_setting('overwrite_conf')->set_value(true);
+ $rc->execute_precheck();
+ $rc->execute_plan();
+
+ $course = $DB->get_record('course', array('id' => $rc->get_courseid()));
+
+ $rc->destroy();
+ unset($rc);
+ return $course;
+ }
+
+ /**
+ * Restore a course to an existing course.
+ *
+ * @param int $backupid The backup ID.
+ * @param int $courseid The course ID to restore in.
+ * @param int $userid The ID of the user performing the restore.
+ * @return stdClass The updated course object.
+ */
+ protected function restore_to_existing_course($backupid, $courseid, $userid = 2) {
+ return $this->restore_course($backupid, $courseid, $userid);
+ }
+
+ /**
+ * Restore a course to a new course.
+ *
+ * @param int $backupid The backup ID.
+ * @param int $userid The ID of the user performing the restore.
+ * @return stdClass The new course object.
+ */
+ protected function restore_to_new_course($backupid, $userid = 2) {
+ return $this->restore_course($backupid, 0, $userid);
+ }
+
+ public function test_restore_existing_idnumber_in_new_course() {
+ $this->resetAfterTest();
+
+ $dg = $this->getDataGenerator();
+ $c1 = $dg->create_course(['idnumber' => 'ABC']);
+ $backupid = $this->backup_course($c1->id);
+ $c2 = $this->restore_to_new_course($backupid);
+
+ // The ID number is set empty.
+ $this->assertEquals('', $c2->idnumber);
+ }
+
+ public function test_restore_non_existing_idnumber_in_new_course() {
+ global $DB;
+ $this->resetAfterTest();
+
+ $dg = $this->getDataGenerator();
+ $c1 = $dg->create_course(['idnumber' => 'ABC']);
+ $backupid = $this->backup_course($c1->id);
+
+ $c1->idnumber = 'BCD';
+ $DB->update_record('course', $c1);
+
+ // The ID number changed.
+ $c2 = $this->restore_to_new_course($backupid);
+ $this->assertEquals('ABC', $c2->idnumber);
+ }
+
+ public function test_restore_existing_idnumber_in_existing_course() {
+ global $DB;
+ $this->resetAfterTest();
+
+ $dg = $this->getDataGenerator();
+ $c1 = $dg->create_course(['idnumber' => 'ABC']);
+ $c2 = $dg->create_course(['idnumber' => 'DEF']);
+ $backupid = $this->backup_course($c1->id);
+
+ // The ID number does not change.
+ $c2 = $this->restore_to_existing_course($backupid, $c2->id);
+ $this->assertEquals('DEF', $c2->idnumber);
+
+ $c1 = $DB->get_record('course', array('id' => $c1->id));
+ $this->assertEquals('ABC', $c1->idnumber);
+ }
+
+ public function test_restore_non_existing_idnumber_in_existing_course() {
+ global $DB;
+ $this->resetAfterTest();
+
+ $dg = $this->getDataGenerator();
+ $c1 = $dg->create_course(['idnumber' => 'ABC']);
+ $c2 = $dg->create_course(['idnumber' => 'DEF']);
+ $backupid = $this->backup_course($c1->id);
+
+ $c1->idnumber = 'XXX';
+ $DB->update_record('course', $c1);
+
+ // The ID number has changed.
+ $c2 = $this->restore_to_existing_course($backupid, $c2->id);
+ $this->assertEquals('ABC', $c2->idnumber);
+ }
+
+ public function test_restore_idnumber_in_existing_course_without_permissions() {
+ global $DB;
+ $this->resetAfterTest();
+ $dg = $this->getDataGenerator();
+ $u1 = $dg->create_user();
+
+ $managers = get_archetype_roles('manager');
+ $manager = array_shift($managers);
+ $roleid = $this->create_role_with_caps('moodle/course:changeidnumber', CAP_PROHIBIT);
+ $dg->role_assign($manager->id, $u1->id);
+ $dg->role_assign($roleid, $u1->id);
+
+ $c1 = $dg->create_course(['idnumber' => 'ABC']);
+ $c2 = $dg->create_course(['idnumber' => 'DEF']);
+ $backupid = $this->backup_course($c1->id);
+
+ $c1->idnumber = 'XXX';
+ $DB->update_record('course', $c1);
+
+ // The ID number does not change.
+ $c2 = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
+ $this->assertEquals('DEF', $c2->idnumber);
+ }
+
+ public function test_restore_course_info_in_new_course() {
+ global $DB;
+ $this->resetAfterTest();
+ $dg = $this->getDataGenerator();
+
+ $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
+ $backupid = $this->backup_course($c1->id);
+
+ // The information is restored but adapted because names are already taken.
+ $c2 = $this->restore_to_new_course($backupid);
+ $this->assertEquals('SN_1', $c2->shortname);
+ $this->assertEquals('FN copy 1', $c2->fullname);
+ $this->assertEquals('DESC', $c2->summary);
+ $this->assertEquals(FORMAT_MOODLE, $c2->summaryformat);
+ }
+
+ public function test_restore_course_info_in_existing_course() {
+ global $DB;
+ $this->resetAfterTest();
+ $dg = $this->getDataGenerator();
+
+ $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
+ $c2 = $dg->create_course(['shortname' => 'A', 'fullname' => 'B', 'summary' => 'C', 'summaryformat' => FORMAT_PLAIN]);
+ $backupid = $this->backup_course($c1->id);
+
+ // The information is restored but adapted because names are already taken.
+ $c2 = $this->restore_to_existing_course($backupid, $c2->id);
+ $this->assertEquals('SN_1', $c2->shortname);
+ $this->assertEquals('FN copy 1', $c2->fullname);
+ $this->assertEquals('DESC', $c2->summary);
+ $this->assertEquals(FORMAT_MOODLE, $c2->summaryformat);
+ }
+
+ public function test_restore_course_shortname_in_existing_course_without_permissions() {
+ global $DB;
+ $this->resetAfterTest();
+ $dg = $this->getDataGenerator();
+ $u1 = $dg->create_user();
+
+ $managers = get_archetype_roles('manager');
+ $manager = array_shift($managers);
+ $roleid = $this->create_role_with_caps('moodle/course:changeshortname', CAP_PROHIBIT);
+ $dg->role_assign($manager->id, $u1->id);
+ $dg->role_assign($roleid, $u1->id);
+
+ $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
+ $c2 = $dg->create_course(['shortname' => 'A1', 'fullname' => 'B1', 'summary' => 'C1', 'summaryformat' => FORMAT_PLAIN]);
+
+ // The shortname does not change.
+ $backupid = $this->backup_course($c1->id);
+ $restored = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
+ $this->assertEquals($c2->shortname, $restored->shortname);
+ $this->assertEquals('FN copy 1', $restored->fullname);
+ $this->assertEquals('DESC', $restored->summary);
+ $this->assertEquals(FORMAT_MOODLE, $restored->summaryformat);
+ }
+
+ public function test_restore_course_fullname_in_existing_course_without_permissions() {
+ global $DB;
+ $this->resetAfterTest();
+ $dg = $this->getDataGenerator();
+ $u1 = $dg->create_user();
+
+ $managers = get_archetype_roles('manager');
+ $manager = array_shift($managers);
+ $roleid = $this->create_role_with_caps('moodle/course:changefullname', CAP_PROHIBIT);
+ $dg->role_assign($manager->id, $u1->id);
+ $dg->role_assign($roleid, $u1->id);
+
+ $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
+ $c2 = $dg->create_course(['shortname' => 'A1', 'fullname' => 'B1', 'summary' => 'C1', 'summaryformat' => FORMAT_PLAIN]);
+
+ // The fullname does not change.
+ $backupid = $this->backup_course($c1->id);
+ $restored = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
+ $this->assertEquals('SN_1', $restored->shortname);
+ $this->assertEquals($c2->fullname, $restored->fullname);
+ $this->assertEquals('DESC', $restored->summary);
+ $this->assertEquals(FORMAT_MOODLE, $restored->summaryformat);
+ }
+
+ public function test_restore_course_summary_in_existing_course_without_permissions() {
+ global $DB;
+ $this->resetAfterTest();
+ $dg = $this->getDataGenerator();
+ $u1 = $dg->create_user();
+
+ $managers = get_archetype_roles('manager');
+ $manager = array_shift($managers);
+ $roleid = $this->create_role_with_caps('moodle/course:changesummary', CAP_PROHIBIT);
+ $dg->role_assign($manager->id, $u1->id);
+ $dg->role_assign($roleid, $u1->id);
+
+ $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
+ $c2 = $dg->create_course(['shortname' => 'A1', 'fullname' => 'B1', 'summary' => 'C1', 'summaryformat' => FORMAT_PLAIN]);
+
+ // The summary and format do not change.
+ $backupid = $this->backup_course($c1->id);
+ $restored = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
+ $this->assertEquals('SN_1', $restored->shortname);
+ $this->assertEquals('FN copy 1', $restored->fullname);
+ $this->assertEquals($c2->summary, $restored->summary);
+ $this->assertEquals($c2->summaryformat, $restored->summaryformat);
+ }
+}
--- /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));
+ }
+}
$PAGE->set_url('/enrol/editinstance.php', array('courseid' => $course->id, 'id' => $instanceid, 'type' => $type));
$PAGE->set_pagelayout('admin');
+$PAGE->set_docs_path('enrol/' . $type . '/edit');
if (empty($return)) {
$return = new moodle_url('/enrol/instances.php', array('id' => $course->id));
global $DB;
$params = self::validate_parameters(self::get_course_enrolment_methods_parameters(), array('courseid' => $courseid));
-
- // Note that we can't use validate_context because the user is not enrolled in the course.
- require_login(null, false, null, false, true);
+ self::validate_context(context_system::instance());
$course = $DB->get_record('course', array('id' => $params['courseid']), '*', MUST_EXIST);
$context = context_course::instance($course->id);
throw new moodle_exception('invaliddata', 'error');
}
- require_login(null, false, null, false, true);
+ self::validate_context(context_system::instance());
$enrolinstance = $DB->get_record('enrol', array('id' => $params['instanceid']), '*', MUST_EXIST);
$course = $DB->get_record('course', array('id' => $enrolinstance->courseid), '*', MUST_EXIST);
'classname' => 'enrol_guest_external',
'methodname' => 'get_instance_info',
'description' => 'Return guest enrolment instance information.',
- 'type' => 'read'
+ 'type' => 'read',
+ 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
);
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2015111601; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version = 2015111602; // The current plugin version (Date: YYYYMMDDXX)
$plugin->requires = 2015111000; // Requires this Moodle version
$plugin->component = 'enrol_guest'; // Full name of the plugin (used for diagnostics)
$instance->enrol = 'lti';
$instance->status = $tool->status;
$ltienrol = enrol_get_plugin('lti');
- $ltienrol->enrol_user($instance, $userid, null, time(), $timeend);
+
+ // Hack - need to do this to workaround DB caching hack. See MDL-53977.
+ $timestart = intval(substr(time(), 0, 8) . '00') - 1;
+ $ltienrol->enrol_user($instance, $userid, null, $timestart, $timeend);
}
return self::ENROLMENT_SUCCESSFUL;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+$string['allowframembedding'] = 'In order to avoid problems embedding this site, please enable the \'Allow frame embedding\' setting in Admin > Security > HTTP security.';
$string['enrolenddate'] = 'End date';
$string['enrolenddate_help'] = 'If enabled, users can access until this date only.';
$string['enrolenddateerror'] = 'Enrolment end date cannot be earlier than start date';
$string['enrolmentnotstarted'] = 'Enrolment has not started.';
$string['enrolstartdate'] = 'Start date';
$string['enrolstartdate_help'] = 'If enabled, users can access from this date onward only.';
+$string['framembeddingnotenabled'] = 'The site you are connecting to does not have frame embedding enabled. To access the resource please click on the link below.';
$string['globalsharedsecret'] = 'Global shared secret';
$string['gradesync'] = 'Grade synchronisation';
$string['gradesync_help'] = 'This determines if we want grade synchronisation to occur.';
$string['notoolsprovided'] = 'No tools provided';
$string['lti:config'] = 'Configure LTI enrol instances';
$string['lti:unenrol'] = 'Unenrol users from the course';
+$string['opentool'] = 'Open tool';
$string['pluginname'] = 'Shared external tool';
$string['pluginname_desc'] = 'The shared external tool plugin allows externals users to access a course or an activity via a unique link - this requires the LTI authentication plugin to be enabled.';
$string['remotesystem'] = 'Remote system';
$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';
public function unenrol_user(stdClass $instance, $userid) {
global $DB;
- // Get the tool associated with this instance.
- $tool = $DB->get_record('enrol_lti_tools', array('enrolid' => $instance->id), 'id', MUST_EXIST);
-
- // Need to remove the user from the users table.
- $DB->delete_records('enrol_lti_users', array('userid' => $userid, 'toolid' => $tool->id));
+ // Get the tool associated with this instance. Note - it may not exist if we have deleted