MDL-51036 tool_lp: Implement a basic review process for competencies

This commit is contained in:
Frederic Massart
2016-04-18 10:58:49 +08:00
parent 23381ab803
commit c008e92d22
21 changed files with 1002 additions and 43 deletions
+1
View File
@@ -0,0 +1 @@
define(["jquery"],function(a){var b=function(){this._eventNode=a("<div></div>")};return b.prototype._eventNode=null,b.prototype.on=function(a,b){this._eventNode.on(a,b)},b.prototype._trigger=function(a,b){this._eventNode.trigger(a,[b])},b});
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
define(["jquery","core/templates","core/ajax","core/notification","core/str","tool_lp/menubar","tool_lp/base"],function(a,b,c,d,e,f,g){var h=function(){g.prototype.constructor.apply(this,[])};return h.prototype=Object.create(g.prototype),h.prototype._nodeSelector='[data-node="user-competency"]',h.prototype._cancelReviewRequest=function(a){var b={methodname:"tool_lp_user_competency_cancel_review_request",args:{userid:a.userid,competencyid:a.competencyid}};c.call([b])[0].then(function(){this._trigger("review-request-cancelled",a),this._trigger("status-changed",a)}.bind(this),function(){this._trigger("error-occured",a)}.bind(this))},h.prototype.cancelReviewRequest=function(a){this._cancelReviewRequest(a)},h.prototype._cancelReviewRequestHandler=function(b){b.preventDefault();var c=this._findUserCompetencyData(a(b.target));this.cancelReviewRequest(c)},h.prototype._requestReview=function(a){var b={methodname:"tool_lp_user_competency_request_review",args:{userid:a.userid,competencyid:a.competencyid}};c.call([b])[0].then(function(){this._trigger("review-requested",a),this._trigger("status-changed",a)}.bind(this),function(){this._trigger("error-occured",a)}.bind(this))},h.prototype.requestReview=function(a){this._requestReview(a)},h.prototype._requestReviewHandler=function(b){b.preventDefault();var c=this._findUserCompetencyData(a(b.target));this.requestReview(c)},h.prototype._startReview=function(a){var b={methodname:"tool_lp_user_competency_start_review",args:{userid:a.userid,competencyid:a.competencyid}};c.call([b])[0].then(function(){this._trigger("review-started",a),this._trigger("status-changed",a)}.bind(this),function(){this._trigger("error-occured",a)}.bind(this))},h.prototype.startReview=function(a){this._startReview(a)},h.prototype._startReviewHandler=function(b){b.preventDefault();var c=this._findUserCompetencyData(a(b.target));this.startReview(c)},h.prototype._stopReview=function(a){var b={methodname:"tool_lp_user_competency_stop_review",args:{userid:a.userid,competencyid:a.competencyid}};c.call([b])[0].then(function(){this._trigger("review-stopped",a),this._trigger("status-changed",a)}.bind(this),function(){this._trigger("error-occured",a)}.bind(this))},h.prototype.stopReview=function(a){this._stopReview(a)},h.prototype._stopReviewHandler=function(b){b.preventDefault();var c=this._findUserCompetencyData(a(b.target));this.stopReview(c)},h.prototype.enhanceMenubar=function(a){f.enhance(a,{'[data-action="request-review"]':this._requestReviewHandler.bind(this),'[data-action="cancel-review-request"]':this._cancelReviewRequestHandler.bind(this)})},h.prototype._findUserCompetencyData=function(a){var b,c=a.parents(this._nodeSelector);if(1!=c.length)throw new Error("The evidence node was not located.");if(b=c.data(),"undefined"==typeof b||"undefined"==typeof b.userid||"undefined"==typeof b.competencyid)throw new Error("User competency data could not be found.");return b},h.prototype.enhanceMenubar=function(a){f.enhance(a,{'[data-action="request-review"]':this._requestReviewHandler.bind(this),'[data-action="cancel-review-request"]':this._cancelReviewRequestHandler.bind(this),'[data-action="start-review"]':this._startReviewHandler.bind(this),'[data-action="stop-review"]':this._stopReviewHandler.bind(this)})},h.prototype.registerEvents=function(b){var c=a(b);c.find('[data-action="request-review"]').click(this._requestReviewHandler.bind(this)),c.find('[data-action="cancel-review-request"]').click(this._cancelReviewRequestHandler.bind(this)),c.find('[data-action="start-review"]').click(this._startReviewHandler.bind(this)),c.find('[data-action="stop-review"]').click(this._stopReviewHandler.bind(this))},h});
+59
View File
@@ -0,0 +1,59 @@
// 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/>.
/**
* Base javascript module.
*
* @module tool_lp/base
* @package tool_lp
* @copyright 2015 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery'], function($) {
/**
* Base class.
*/
var Base = function() {
this._eventNode = $('<div></div>');
};
/** @type {Node} The node we attach the events to. */
Base.prototype._eventNode = null;
/**
* Register an event listener.
*
* @param {String} type The event type.
* @param {Function} handler The event listener.
* @method on
*/
Base.prototype.on = function(type, handler) {
this._eventNode.on(type, handler);
};
/**
* Trigger an event.
*
* @param {String} type The type of event.
* @param {Object} The data to pass to the listeners.
* @method _trigger
*/
Base.prototype._trigger = function(type, data) {
this._eventNode.trigger(type, [data]);
};
return /** @alias module:tool_lp/base */ Base;
});
+12
View File
@@ -93,6 +93,18 @@ define(['jquery',
return args;
};
/**
* Refresh the plan view.
*
* This is useful when you only want to refresh the view.
*
* @param {String} selector The node to search the plan data from.
*/
PlanActions.prototype.refresh = function(selector) {
var planData = this._findPlanData($(selector));
this._callAndRefresh([], planData);
};
/**
* Callback to render the region template.
*
@@ -0,0 +1,301 @@
// 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/>.
/**
* User competency workflow.
*
* @module tool_lp/user_competency_workflow
* @package tool_lp
* @copyright 2015 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery',
'core/templates',
'core/ajax',
'core/notification',
'core/str',
'tool_lp/menubar',
'tool_lp/base'],
function($, Templates, Ajax, Notification, Str, Menubar, Base) {
/**
* UserCompetencyWorkflow class.
*
* @param {String} selector The node containing the buttons to switch mode.
*/
var UserCompetencyWorkflow = function() {
Base.prototype.constructor.apply(this, []);
};
UserCompetencyWorkflow.prototype = Object.create(Base.prototype);
/** @type {String} The selector to find the user competency data. */
UserCompetencyWorkflow.prototype._nodeSelector = '[data-node="user-competency"]';
/**
* Cancel a review request and refresh the view.
*
* @param {Object} data The user competency data.
* @return {Void}
* @method _cancelReviewRequest
*/
UserCompetencyWorkflow.prototype._cancelReviewRequest = function(data) {
var call = {
methodname: 'tool_lp_user_competency_cancel_review_request',
args: {
userid: data.userid,
competencyid: data.competencyid
}
};
Ajax.call([call])[0].then(function() {
this._trigger('review-request-cancelled', data);
this._trigger('status-changed', data);
}.bind(this), function() {
this._trigger('error-occured', data);
}.bind(this));
};
/**
* Cancel a review request an refresh the view.
*
* @param {Object} data The user competency data.
* @return {Void}
* @method cancelReviewRequest
*/
UserCompetencyWorkflow.prototype.cancelReviewRequest = function(data) {
this._cancelReviewRequest(data);
};
/**
* Cancel a review request handler.
*
* @param {Event} e The event.
* @return {Void}
* @method _cancelReviewRequestHandler
*/
UserCompetencyWorkflow.prototype._cancelReviewRequestHandler = function(e) {
e.preventDefault();
var data = this._findUserCompetencyData($(e.target));
this.cancelReviewRequest(data);
};
/**
* Request a review and refresh the view.
*
* @param {Object} data The user competency data.
* @return {Void}
* @method _requestReview
*/
UserCompetencyWorkflow.prototype._requestReview = function(data) {
var call = {
methodname: 'tool_lp_user_competency_request_review',
args: {
userid: data.userid,
competencyid: data.competencyid
}
};
Ajax.call([call])[0].then(function() {
this._trigger('review-requested', data);
this._trigger('status-changed', data);
}.bind(this), function() {
this._trigger('error-occured', data);
}.bind(this));
};
/**
* Request a review.
*
* @param {Object} data The user competency data.
* @return {Void}
* @method requestReview
*/
UserCompetencyWorkflow.prototype.requestReview = function(data) {
this._requestReview(data);
};
/**
* Request a review handler.
*
* @param {Event} e The event.
* @return {Void}
* @method _requestReviewHandler
*/
UserCompetencyWorkflow.prototype._requestReviewHandler = function(e) {
e.preventDefault();
var data = this._findUserCompetencyData($(e.target));
this.requestReview(data);
};
/**
* Start a review and refresh the view.
*
* @param {Object} data The user competency data.
* @return {Void}
* @method _startReview
*/
UserCompetencyWorkflow.prototype._startReview = function(data) {
var call = {
methodname: 'tool_lp_user_competency_start_review',
args: {
userid: data.userid,
competencyid: data.competencyid
}
};
Ajax.call([call])[0].then(function() {
this._trigger('review-started', data);
this._trigger('status-changed', data);
}.bind(this), function() {
this._trigger('error-occured', data);
}.bind(this));
};
/**
* Start a review.
*
* @param {Object} data The user competency data.
* @return {Void}
* @method startReview
*/
UserCompetencyWorkflow.prototype.startReview = function(data) {
this._startReview(data);
};
/**
* Start a review handler.
*
* @param {Event} e The event.
* @return {Void}
* @method _startReviewHandler
*/
UserCompetencyWorkflow.prototype._startReviewHandler = function(e) {
e.preventDefault();
var data = this._findUserCompetencyData($(e.target));
this.startReview(data);
};
/**
* Stop a review and refresh the view.
*
* @param {Object} data The user competency data.
* @return {Void}
* @method _stopReview
*/
UserCompetencyWorkflow.prototype._stopReview = function(data) {
var call = {
methodname: 'tool_lp_user_competency_stop_review',
args: {
userid: data.userid,
competencyid: data.competencyid
}
};
Ajax.call([call])[0].then(function() {
this._trigger('review-stopped', data);
this._trigger('status-changed', data);
}.bind(this), function() {
this._trigger('error-occured', data);
}.bind(this));
};
/**
* Stop a review.
*
* @param {Object} data The user competency data.
* @return {Void}
* @method stopReview
*/
UserCompetencyWorkflow.prototype.stopReview = function(data) {
this._stopReview(data);
};
/**
* Stop a review handler.
*
* @param {Event} e The event.
* @return {Void}
* @method _stopReviewHandler
*/
UserCompetencyWorkflow.prototype._stopReviewHandler = function(e) {
e.preventDefault();
var data = this._findUserCompetencyData($(e.target));
this.stopReview(data);
};
/**
* Enhance a menu bar.
*
* @param {String} selector Menubar selector.
*/
UserCompetencyWorkflow.prototype.enhanceMenubar = function(selector) {
Menubar.enhance(selector, {
'[data-action="request-review"]': this._requestReviewHandler.bind(this),
'[data-action="cancel-review-request"]': this._cancelReviewRequestHandler.bind(this),
});
};
/**
* Find the user competency data from a node.
*
* @param {Node} node The node to search from.
* @return {Object} User competency data.
*/
UserCompetencyWorkflow.prototype._findUserCompetencyData = function(node) {
var parent = node.parents(this._nodeSelector),
data;
if (parent.length != 1) {
throw new Error('The evidence node was not located.');
}
data = parent.data();
if (typeof data === 'undefined' || typeof data.userid === 'undefined' || typeof data.competencyid === 'undefined') {
throw new Error('User competency data could not be found.');
}
return data;
};
/**
* Enhance a menu bar.
*
* @param {String} selector Menubar selector.
*/
UserCompetencyWorkflow.prototype.enhanceMenubar = function(selector) {
Menubar.enhance(selector, {
'[data-action="request-review"]': this._requestReviewHandler.bind(this),
'[data-action="cancel-review-request"]': this._cancelReviewRequestHandler.bind(this),
'[data-action="start-review"]': this._startReviewHandler.bind(this),
'[data-action="stop-review"]': this._stopReviewHandler.bind(this),
});
};
/**
* Register the events in the region.
*
* @param {String} selector The base selector to search nodes in and attach events.
*/
UserCompetencyWorkflow.prototype.registerEvents = function(selector) {
var wrapper = $(selector);
wrapper.find('[data-action="request-review"]').click(this._requestReviewHandler.bind(this));
wrapper.find('[data-action="cancel-review-request"]').click(this._cancelReviewRequestHandler.bind(this));
wrapper.find('[data-action="start-review"]').click(this._startReviewHandler.bind(this));
wrapper.find('[data-action="stop-review"]').click(this._stopReviewHandler.bind(this));
};
return /** @alias module:tool_lp/user_competency_actions */ UserCompetencyWorkflow;
});
+105
View File
@@ -2044,6 +2044,7 @@ class api {
throw new coding_exception('A user competency plan is missing');
} else {
$uc = user_competency::create_relation($plan->get_userid(), $competency->get_id());
$uc->create();
}
}
@@ -2256,6 +2257,110 @@ class api {
return $competencyfrom->update();
}
/**
* Cancel a user competency review request.
*
* @param int $userid The user ID.
* @param int $competencyid The competency ID.
* @return bool
*/
public static function user_competency_cancel_review_request($userid, $competencyid) {
if (!plan::can_read_user($userid)) {
$context = context_user::instance($userid);
throw new required_capability_exception($context, 'tool/lp:planview', 'nopermissions', '');
}
$uc = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid));
if (!$uc || $uc->get_status() != user_competency::STATUS_WAITING_FOR_REVIEW) {
throw new coding_exception('The competency can not be cancel review request at this stage.');
} else if (!$uc->can_request_review()) {
throw new required_capability_exception($context, 'tool/lp:userevidencemanage', 'nopermissions', '');
}
$uc->set_status(user_competency::STATUS_IDLE);
return $uc->update();
}
/**
* Request a user competency review.
*
* @param int $userid The user ID.
* @param int $competencyid The competency ID.
* @return bool
*/
public static function user_competency_request_review($userid, $competencyid) {
if (!plan::can_read_user($userid)) {
$context = context_user::instance($userid);
throw new required_capability_exception($context, 'tool/lp:planview', 'nopermissions', '');
}
$uc = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid));
if (!$uc) {
$uc = user_competency::create_relation($userid, $competencyid);
$uc->create();
}
if ($uc->get_status() != user_competency::STATUS_IDLE) {
throw new coding_exception('The competency can not be sent for review at this stage.');
} else if (!$uc->can_request_review()) {
throw new required_capability_exception($context, 'tool/lp:userevidencemanage', 'nopermissions', '');
}
$uc->set_status(user_competency::STATUS_WAITING_FOR_REVIEW);
return $uc->update();
}
/**
* Start a user competency review.
*
* @param int $userid The user ID.
* @param int $competencyid The competency ID.
* @return bool
*/
public static function user_competency_start_review($userid, $competencyid) {
global $USER;
if (!plan::can_read_user($userid)) {
$context = context_user::instance($userid);
throw new required_capability_exception($context, 'tool/lp:planview', 'nopermissions', '');
}
$uc = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid));
if (!$uc || $uc->get_status() != user_competency::STATUS_WAITING_FOR_REVIEW) {
throw new coding_exception('The competency review can not be started at this stage.');
} else if (!$uc->can_review()) {
throw new required_capability_exception($context, 'tool/lp:competencygrade', 'nopermissions', '');
}
$uc->set_status(user_competency::STATUS_IN_REVIEW);
$uc->set_reviewerid($USER->id);
return $uc->update();
}
/**
* Stop a user competency review.
*
* @param int $userid The user ID.
* @param int $competencyid The competency ID.
* @return bool
*/
public static function user_competency_stop_review($userid, $competencyid) {
if (!plan::can_read_user($userid)) {
$context = context_user::instance($userid);
throw new required_capability_exception($context, 'tool/lp:planview', 'nopermissions', '');
}
$uc = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid));
if (!$uc || $uc->get_status() != user_competency::STATUS_IN_REVIEW) {
throw new coding_exception('The competency review can not be stopped at this stage.');
} else if (!$uc->can_review()) {
throw new required_capability_exception($context, 'tool/lp:competencygrade', 'nopermissions', '');
}
$uc->set_status(user_competency::STATUS_IDLE);
return $uc->update();
}
/**
* List all the related competencies.
*
+157
View File
@@ -2678,6 +2678,163 @@ class external extends external_api {
return new external_value(PARAM_BOOL, 'True if successful.');
}
/**
* Returns description of external function parameters.
*
* @return \external_function_parameters
*/
public static function user_competency_cancel_review_request_parameters() {
return new external_function_parameters(array(
'userid' => new external_value(PARAM_INT, 'The user ID'),
'competencyid' => new external_value(PARAM_INT, 'The competency ID'),
));
}
/**
* External function user_competency_cancel_review_request.
*
* @param int $id The ID description.
* @return boolean
*/
public static function user_competency_cancel_review_request($userid, $competencyid) {
$params = self::validate_parameters(self::user_competency_request_review_parameters(), array(
'userid' => $userid,
'competencyid' => $competencyid
));
$context = context_user::instance($params['userid']);
self::validate_context($context);
return api::user_competency_cancel_review_request($userid, $competencyid);
}
/**
* Returns description of external function result value.
*
* @return \external_function_parameters
*/
public static function user_competency_cancel_review_request_returns() {
new external_value(PARAM_BOOL, 'The success');
}
/**
* Returns description of external function parameters.
*
* @return \external_function_parameters
*/
public static function user_competency_request_review_parameters() {
return new external_function_parameters(array(
'userid' => new external_value(PARAM_INT, 'The user ID'),
'competencyid' => new external_value(PARAM_INT, 'The competency ID'),
));
}
/**
* External function user_competency_request_review.
*
* @param int $userid The user ID.
* @param int $competencyid The competency ID.
* @return boolean
*/
public static function user_competency_request_review($userid, $competencyid) {
$params = self::validate_parameters(self::user_competency_request_review_parameters(), array(
'userid' => $userid,
'competencyid' => $competencyid
));
$context = context_user::instance($params['userid']);
self::validate_context($context);
return api::user_competency_request_review($userid, $competencyid);
}
/**
* Returns description of external function result value.
*
* @return \external_function_parameters
*/
public static function user_competency_request_review_returns() {
new external_value(PARAM_BOOL, 'The success');
}
/**
* Returns description of external function parameters.
*
* @return \external_function_parameters
*/
public static function user_competency_start_review_parameters() {
return new external_function_parameters(array(
'userid' => new external_value(PARAM_INT, 'The user ID'),
'competencyid' => new external_value(PARAM_INT, 'The competency ID'),
));
}
/**
* External function user_competency_start_review.
*
* @param int $id The ID description.
* @return boolean
*/
public static function user_competency_start_review($userid, $competencyid) {
$params = self::validate_parameters(self::user_competency_request_review_parameters(), array(
'userid' => $userid,
'competencyid' => $competencyid
));
$context = context_user::instance($params['userid']);
self::validate_context($context);
return api::user_competency_start_review($userid, $competencyid);
}
/**
* Returns description of external function result value.
*
* @return \external_function_parameters
*/
public static function user_competency_start_review_returns() {
new external_value(PARAM_BOOL, 'The success');
}
/**
* Returns description of external function parameters.
*
* @return \external_function_parameters
*/
public static function user_competency_stop_review_parameters() {
return new external_function_parameters(array(
'userid' => new external_value(PARAM_INT, 'The user ID'),
'competencyid' => new external_value(PARAM_INT, 'The competency ID'),
));
}
/**
* External function user_competency_stop_review.
*
* @param int $id The ID description.
* @return boolean
*/
public static function user_competency_stop_review($userid, $competencyid) {
$params = self::validate_parameters(self::user_competency_request_review_parameters(), array(
'userid' => $userid,
'competencyid' => $competencyid
));
$context = context_user::instance($params['userid']);
self::validate_context($context);
return api::user_competency_stop_review($userid, $competencyid);
}
/**
* Returns description of external function result value.
*
* @return \external_function_parameters
*/
public static function user_competency_stop_review_returns() {
new external_value(PARAM_BOOL, 'The success');
}
/**
* Count the competencies (visible to this user) in this learning plan template.
*
@@ -24,6 +24,8 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_lp\external;
require_once($CFG->dirroot . '/comment/lib.php');
defined('MOODLE_INTERNAL') || die();
use comment;
@@ -51,6 +53,7 @@ class comment_area_exporter extends exporter {
$data->courseid = $comment->get_courseid();
$data->contextid = $comment->get_context()->id;
$data->cid = $comment->get_cid();
parent::__construct($data, $related);
}
@@ -24,6 +24,7 @@
namespace tool_lp\external;
defined('MOODLE_INTERNAL') || die();
use core_user;
use renderer_base;
use stdClass;
use tool_lp\user_competency;
@@ -68,17 +69,66 @@ class user_competency_exporter extends persistent_exporter {
}
$result->statusname = $statusname;
$result->canrequestreview = $this->persistent->can_request_review();
$result->canreview = $this->persistent->can_review();
$result->isstatusidle = $this->persistent->get_status() == user_competency::STATUS_IDLE;
$result->isstatusinreview = $this->persistent->get_status() == user_competency::STATUS_IN_REVIEW;
$result->isstatuswaitingforreview = $this->persistent->get_status() == user_competency::STATUS_WAITING_FOR_REVIEW;
$result->isrequestreviewallowed = $result->canrequestreview && $result->isstatusidle;
$result->iscancelreviewrequestallowed = $result->canrequestreview && $result->isstatuswaitingforreview;
$result->isstartreviewallowed = $result->canreview && $result->isstatuswaitingforreview;
$result->isstopreviewallowed = $result->canreview && $result->isstatusinreview;
if (!empty($result->isstatusinreview)) {
// TODO Make this more efficient.
$userexporter = new user_summary_exporter(core_user::get_user($this->persistent->get_reviewerid(), '*', MUST_EXIST));
$result->reviewer = $userexporter->export($output);
}
return (array) $result;
}
protected static function define_other_properties() {
return array(
'canrequestreview' => array(
'type' => PARAM_BOOL,
),
'canreview' => array(
'type' => PARAM_BOOL,
),
'gradename' => array(
'type' => PARAM_TEXT
),
'isrequestreviewallowed' => array(
'type' => PARAM_BOOL,
),
'iscancelreviewrequestallowed' => array(
'type' => PARAM_BOOL,
),
'isstartreviewallowed' => array(
'type' => PARAM_BOOL,
),
'isstopreviewallowed' => array(
'type' => PARAM_BOOL,
),
'isstatusidle' => array(
'type' => PARAM_BOOL,
),
'isstatusinreview' => array(
'type' => PARAM_BOOL,
),
'isstatuswaitingforreview' => array(
'type' => PARAM_BOOL,
),
'proficiencyname' => array(
'type' => PARAM_RAW
),
'reviewer' => array(
'type' => user_summary_exporter::read_properties_definition(),
'optional' => true
),
'statusname' => array(
'type' => PARAM_RAW
),
@@ -26,6 +26,7 @@ namespace tool_lp\external;
use context_user;
use renderer_base;
use stdClass;
use tool_lp\user_competency;
/**
* Class for exporting user competency data with additional related data.
@@ -76,7 +77,11 @@ class user_competency_summary_exporter extends exporter {
'evidence' => array(
'type' => evidence_exporter::read_properties_definition(),
'multiple' => true
)
),
'commentarea' => array(
'type' => comment_area_exporter::read_properties_definition(),
'optional' => true
),
);
}
@@ -143,6 +148,13 @@ class user_competency_summary_exporter extends exporter {
$result->evidence = $allevidence;
}
$usercompetency = !empty($this->related['usercompetency']) ? $this->related['usercompetency'] : null;
if (!empty($usercompetency)) {
$commentareaexporter = new comment_area_exporter($usercompetency->get_comment_object());
$result->commentarea = $commentareaexporter->export($output);
}
return (array) $result;
}
}
@@ -22,6 +22,7 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_lp\output;
defined('MOODLE_INTERNAL') || die();
use renderable;
use templatable;
@@ -64,7 +65,7 @@ class user_competency_summary_in_plan implements renderable, templatable {
global $DB;
$plan = api::read_plan($this->planid);
$pc = api::get_plan_competency($this->planid, $this->competencyid);
$pc = api::get_plan_competency($plan, $this->competencyid);
$competency = $pc->competency;
$usercompetency = $pc->usercompetency;
$usercompetencyplan = $pc->usercompetencyplan;
+76
View File
@@ -24,6 +24,9 @@
namespace tool_lp;
defined('MOODLE_INTERNAL') || die();
use coding_exception;
use context_user;
use comment;
use lang_string;
/**
@@ -86,6 +89,24 @@ class user_competency extends persistent {
);
}
/**
* Can the current user send the user competency for review?
*
* @return bool
*/
public function can_request_review() {
return static::can_request_review_user($this->get_userid());
}
/**
* Can the current user review the user competency?
*
* @return bool
*/
public function can_review() {
return static::can_review_user($this->get_userid());
}
/**
* Human readable status name.
*
@@ -131,6 +152,30 @@ class user_competency extends persistent {
return $list;
}
/**
* Get the comment object.
*
* @return comment
*/
public function get_comment_object() {
global $CFG;
require_once($CFG->dirroot . '/comment/lib.php');
if (!$this->get_id()) {
throw new coding_exception('The user competency record must exist.');
}
$comment = new comment((object) array(
'context' => $this->get_context(),
'component' => 'tool_lp',
'itemid' => $this->get_id(),
'area' => 'user_competency',
'showcount' => true,
));
$comment->set_fullwidth(true);
return $comment;
}
/**
* Return the competency Object.
*
@@ -140,6 +185,15 @@ class user_competency extends persistent {
return new competency($this->get_competencyid());
}
/**
* Get the context.
*
* @return context The context.
*/
public function get_context() {
return context_user::instance($this->get_userid());
}
/**
* Validate the user ID.
*
@@ -230,6 +284,28 @@ class user_competency extends persistent {
return true;
}
/**
* Can the current user send a user's competency for review?
*
* @param int $userid The user ID the competency belongs to.
* @return bool
*/
public static function can_request_review_user($userid) {
// We can request a review when we can attach evidence of prior learning.
return user_evidence::can_manage_user($userid);
}
/**
* Can the current user send review the user competency?
*
* @param int $userid The user ID the competency belongs to.
* @return bool
*/
public static function can_review_user($userid) {
// We can review a competency when we can grade them.
return has_capability('tool/lp:competencygrade', context_user::instance($userid));
}
/**
* Create a new user_competency object.
*
+19 -15
View File
@@ -28,6 +28,7 @@ defined('MOODLE_INTERNAL') || die();
$capabilities = array(
// Competencies.
'tool/lp:competencymanage' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_COURSECAT,
@@ -43,6 +44,22 @@ $capabilities = array(
),
'clonepermissionsfrom' => 'moodle/block:view'
),
'tool/lp:competencysuggestgrade' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_COURSE, // And CONTEXT_USER.
'archetypes' => array(
'teacher' => CAP_ALLOW
),
),
'tool/lp:competencygrade' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_COURSE, // And CONTEXT_USER.
'archetypes' => array(
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
),
),
// Course competencies.
'tool/lp:coursecompetencymanage' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_COURSE,
@@ -68,6 +85,7 @@ $capabilities = array(
),
'clonepermissionsfrom' => 'moodle/block:view'
),
// User plans.
'tool/lp:planmanage' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_USER,
@@ -125,6 +143,7 @@ $capabilities = array(
),
'clonepermissionsfrom' => 'moodle/site:config'
),
// Template.
'tool/lp:templatemanage' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_COURSECAT,
@@ -163,19 +182,4 @@ $capabilities = array(
),
'clonepermissionsfrom' => 'moodle/site:config'
),
'tool/lp:competencysuggestgrade' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_COURSE, // And CONTEXT_USER.
'archetypes' => array(
'teacher' => CAP_ALLOW
),
),
'tool/lp:competencygrade' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_COURSE, // And CONTEXT_USER.
'archetypes' => array(
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
),
),
);
+29
View File
@@ -0,0 +1,29 @@
<?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/>.
/**
* Message producers.
*
* @package tool_lp
* @copyright 2015 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$messageproviders = array (
'user_competency_comment' => array(),
);
+39
View File
@@ -662,6 +662,45 @@ $functions = array(
'capabilities' => 'tool/lp:userevidencemanageown',
'ajax' => true,
),
// User competency.
'tool_lp_user_competency_cancel_review_request' => array(
'classname' => 'tool_lp\external',
'methodname' => 'user_competency_cancel_review_request',
'classpath' => '',
'description' => 'Cancel a review request.',
'type' => 'write',
'capabilities' => 'tool/lp:userevidencemanageown',
'ajax' => true,
),
'tool_lp_user_competency_request_review' => array(
'classname' => 'tool_lp\external',
'methodname' => 'user_competency_request_review',
'classpath' => '',
'description' => 'Request a review.',
'type' => 'write',
'capabilities' => 'tool/lp:userevidencemanageown',
'ajax' => true,
),
'tool_lp_user_competency_start_review' => array(
'classname' => 'tool_lp\external',
'methodname' => 'user_competency_start_review',
'classpath' => '',
'description' => 'Start a review.',
'type' => 'write',
'capabilities' => 'tool/lp:competencygrade',
'ajax' => true,
),
'tool_lp_user_competency_stop_review' => array(
'classname' => 'tool_lp\external',
'methodname' => 'user_competency_stop_review',
'classpath' => '',
'description' => 'Stop a review.',
'type' => 'write',
'capabilities' => 'tool/lp:competencygrade',
'ajax' => true,
),
'tool_lp_grade_competency_in_plan' => array(
'classname' => 'tool_lp\external',
'methodname' => 'grade_competency_in_plan',
+6
View File
@@ -23,6 +23,7 @@
*/
$string['actions'] = 'Actions';
$string['acommentwaspostedonacompetency'] = 'A comment was posted on a competency.';
$string['addcohorts'] = 'Add cohorts';
$string['addcompetency'] = 'Add competency';
$string['addingcompetencywillresetparentrule'] = 'Adding a new competency will remove the rule set on \'{$a}\'. Do you want to continue?';
@@ -37,6 +38,7 @@ $string['allchildrenarecomplete'] = 'All children are complete';
$string['aplanswerecreated'] = '{$a} plans were created.';
$string['aplanswerecreatedmoremayrequiresync'] = '{$a} plans were created, more will be created during the next synchronization.';
$string['assigncohorts'] = 'Assign cohorts';
$string['cancelreviewrequest'] = 'Cancel review request';
$string['cannotaddrules'] = 'This competency cannot be configured.';
$string['cannotcreateuserplanswhentemplateduedateispassed'] = 'New user plans can not be created: this template\'s due date has, or is about to, expire.';
$string['cannotcreateuserplanswhentemplatehidden'] = 'New user plans can not be created while this template is hidden.';
@@ -156,6 +158,7 @@ $string['lp:userevidencemanage'] = 'Manage evidence of prior learning';
$string['lp:userevidencemanageown'] = 'Manage own evidence of prior learning';
$string['lp:userevidenceread'] = 'View evidence of prior learning of a user';
$string['managecompetenciesandframeworks'] = 'Manage competencies and frameworks';
$string['messageprovider:user_competency_comment'] = 'Comment posted on a competency.';
$string['move'] = 'Move';
$string['movecompetency'] = 'Move competency';
$string['movecompetencyafter'] = 'Move competency after \'{$a}\'';
@@ -201,6 +204,7 @@ $string['proficient'] = 'Proficient';
$string['rate'] = 'Rate';
$string['rating'] = 'Rating';
$string['relatedcompetencies'] = 'Related competencies';
$string['requestreview'] = 'Request review';
$string['reopenplan'] = 'Reopen this learning plan';
$string['reopenplanconfirm'] = 'Reopen the plan \'{$a}\'? The status of the users competencies that was recorded at the time the plan was previously completed will be deleted, and the plan will become active again.';
$string['savechanges'] = 'Save changes';
@@ -213,8 +217,10 @@ $string['selectedcompetency'] = 'Selected competency';
$string['selectcohortstosync'] = 'Select cohorts to sync';
$string['selectuserstocreateplansfor'] = 'Select users to create plans for';
$string['shortname'] = 'Name';
$string['startreview'] = 'Start review';
$string['state'] = 'State';
$string['status'] = 'Status';
$string['stopreview'] = 'Stop review';
$string['stopsyncingcohort'] = 'Stop syncing cohort';
$string['suggest'] = 'Suggest';
$string['syncplanscohorts'] = 'Sync plans from template cohorts';
+61
View File
@@ -169,3 +169,64 @@ function tool_lp_pluginfile($course, $cm, $context, $filearea, $args, $forcedown
send_stored_file($file, null, 0, $forcedownload);
}
/**
* Hook when a comment is added.
*
* @param stdClass $comment The comment.
* @param stdClass $params The parameters.
* @return array
*/
function tool_lp_comment_add($comment, $params) {
if ($params->commentarea == 'user_competency') {
$uc = new \tool_lp\user_competency($params->itemid);
// When the owner comments we message the reviewer, otherwise we message the current user.
$recipient = $comment->userid == $uc->get_userid() ? $uc->get_reviewerid() : $uc->get_userid();
if (!$recipient) {
return;
}
$message = new stdClass();
$message->component = 'tool_lp';
$message->name = 'user_competency_comment';
$message->notification = 1;
$message->userto = $recipient;
$message->userfrom = $comment->userid;
// TODO Make message useful.
$message->fullmessage = get_string('acommentwaspostedonacompetency', 'tool_lp');
$message->fullmessageformat = FORMAT_PLAIN;
$message->fullmessagehtml = '';
$message->smallmessage = get_string('acommentwaspostedonacompetency', 'tool_lp');
// TODO Make URL useful.
$message->contexturl = new moodle_url('/admin/tool/lp/plans.php', array('userid' => $uc->get_userid()));
$message->contexturlname = get_string('userplans', 'tool_lp');
message_send($message);
}
}
/**
* Return the permissions of for the comments.
*
* @param stdClass $params The parameters.
* @return array
*/
function tool_lp_comment_permissions($params) {
if ($params->commentarea == 'user_competency') {
$uc = new \tool_lp\user_competency($params->itemid);
$can = \tool_lp\plan::can_read_user($uc->get_userid());
return array('post' => $can, 'view' => $can);
}
return array('post' => false, 'view' => false);
}
/**
* Validates comments.
*
* @param stdClass $params The parameters.
* @return array
*/
function tool_lp_comment_validate($params) {
return true;
}
+39 -16
View File
@@ -66,15 +66,13 @@
<th scope="col">{{#str}}proficient, tool_lp{{/str}}</th>
{{^plan.iscompleted}}
<th scope="col">{{#str}}status, tool_lp{{/str}}</th>
{{/plan.iscompleted}}
{{#plan.canbeedited}}
<th scope="col">{{#str}}actions, tool_lp{{/str}}</th>
{{/plan.canbeedited}}
{{/plan.iscompleted}}
</tr>
</thead>
<tbody class="drag-parentnode">
<tbody class="drag-parentnode">
{{#competencies}}
<tr class="drag-samenode" data-id="{{competency.id}}">
<tr class="drag-samenode" data-node="user-competency" data-id="{{competency.id}}" data-competencyid="{{usercompetency.competencyid}}" data-userid="{{usercompetency.userid}}">
<td>
{{#plan.canbeedited}}
<span class="drag-handlecontainer pull-left"></span>
@@ -91,13 +89,32 @@
<td>{{usercompetency.proficiencyname}}</td>
<td>{{usercompetency.statusname}}</td>
{{/plan.iscompleted}}
{{#plan.canbeedited}}
{{^plan.iscompleted}}
<td>
<div class="pull-left">
<a href="#" data-action="delete-competency-link" data-id="{{competency.id}}">{{#pix}}t/delete, core, {{#str}}delete{{/str}}{{/pix}}</a>
</div>
<ul title="{{#str}}edit{{/str}}" class="user-competency-actions">
<li>
<a href="#">{{#str}}edit{{/str}}</a><b class="caret"></b>
<ul class="dropdown-menu">
{{#usercompetency.isrequestreviewallowed}}
<li>
<a href="#" data-action="request-review">{{#pix}}t/edit, core{{/pix}} {{#str}}requestreview, tool_lp{{/str}}</a>
</li>
{{/usercompetency.isrequestreviewallowed}}
{{#usercompetency.iscancelreviewrequestallowed}}
<li>
<a href="#" data-action="cancel-review-request">{{#pix}}t/edit, core{{/pix}} {{#str}}cancelreviewrequest, tool_lp{{/str}}</a>
</li>
{{/usercompetency.iscancelreviewrequestallowed}}
{{#plan.canbeedited}}
<li>
<a href="#" data-action="delete-competency-link" data-id="{{competency.id}}">{{#pix}}t/delete, core{{/pix}} {{#str}}delete{{/str}}</a>
</li>
{{/plan.canbeedited}}
</ul>
</li>
</ul>
</td>
{{/plan.canbeedited}}
{{/plan.iscompleted}}
</tr>
{{/competencies}}
</tbody>
@@ -114,19 +131,25 @@
</div>
</div>
{{#plan.canmanage}}
{{#js}}
require(['tool_lp/competencies', 'tool_lp/planactions'], function(mod, actionsMod) {
(new mod({{plan.id}}, 'plan', {{contextid}}));
require(['tool_lp/competencies', 'tool_lp/planactions', 'tool_lp/user_competency_workflow'], function(mod, actionsMod, UserCompWorkflow) {
var planActions = new actionsMod('plan');
planActions.registerEvents();
{{#plan.canmanage}}
(new mod({{plan.id}}, 'plan', {{contextid}}));
planActions.registerEvents();
{{/plan.canmanage}}
var ucw = new UserCompWorkflow();
ucw.enhanceMenubar('.user-competency-actions');
ucw.on('status-changed', planActions.refresh.bind(planActions, '[data-region="plan-summary"]'));
ucw.on('error-occured', planActions.refresh.bind(planActions, '[data-region="plan-summary"]'));
});
{{/js}}
{{/plan.canmanage}}
{{#js}}
require(['tool_lp/competencydialogue'], function(Compdialogue) {
var competencydialogue = new Compdialogue({includerelated : true});
competencydialogue.watch('[data-region="plan-page"]');
});
{{/js}}
@@ -40,14 +40,25 @@
This template does not have an example context because it includes ajax functionality.
}}
{{#usercompetencysummary}}
<div data-region="user-competency-full-info" >
<div data-region="user-competency-full-info" data-node="user-competency" data-competencyid="{{usercompetency.competencyid}}" data-userid="{{usercompetency.userid}}">
<div data-region="competency-summary">
{{#competency}}
{{> tool_lp/competency_summary }}
{{/competency}}
<dl>
<dt>{{#str}}rating, tool_lp{{/str}}</dt>
{{#usercompetency}}
<dt>{{#str}}status, tool_lp{{/str}}</dt>
<dd data-region="user-competency-status">{{statusname}}
{{#isstatusinreview}}
- {{reviewer.fullname}}
{{/isstatusinreview}}
{{#isrequestreviewallowed}}<button data-action="request-review">{{#str}}requestreview, tool_lp{{/str}}</button>{{/isrequestreviewallowed}}
{{#iscancelreviewrequestallowed}}<button data-action="cancel-review-request">{{#str}}cancelreviewrequest, tool_lp{{/str}}</button>{{/iscancelreviewrequestallowed}}
{{#isstartreviewallowed}}<button data-action="start-review">{{#str}}startreview, tool_lp{{/str}}</button>{{/isstartreviewallowed}}
{{#isstopreviewallowed}}<button data-action="stop-review">{{#str}}stopreview, tool_lp{{/str}}</button>{{/isstopreviewallowed}}
</dd>
<dt>{{#str}}proficient, tool_lp{{/str}}</dt>
<dd>
<span class="label{{^proficiency}} label-important{{/proficiency}} pull-left">
@@ -56,9 +67,9 @@
</dd>
<dt>{{#str}}rating, tool_lp{{/str}}</dt>
<dd>{{gradename}}
{{#cangradeorsuggest}}
<p>
<form class="form-inline pull-left" id="grade-competency-form-{{uniqid}}">
{{#cangradeorsuggest}}
<select></select>
<div class="btn-group">
{{#cangrade}}
@@ -68,25 +79,30 @@
<button class="btn btn-inverse" data-action="suggest">{{#str}}suggest, tool_lp{{/str}}</button>
{{/cansuggest}}
</div>
<br><br>
{{/cangradeorsuggest}}
</form>
<br><br>
</p>
</dd>
{{#js}}
require(['jquery', 'tool_lp/grade_user_competency_inline', 'tool_lp/user_competency_info'], function($, mod, info) {
require(['jquery', 'tool_lp/grade_user_competency_inline', 'tool_lp/user_competency_info', 'tool_lp/user_competency_workflow'], function($, mod, info, UserCompWorkflow) {
var scaleConfig = JSON.parse('{{{competency.scaleconfiguration}}}');
var inlineGrader = new mod('grade-competency-form-{{uniqid}}', scaleConfig, '{{competency.competency.id}}', '{{user.id}}', '{{plan.id}}', '', '{{#str}}chooserating, tool_lp{{/str}}');
var competencyElement = $(document.getElementById('grade-competency-form-{{uniqid}}')).closest('[data-region=user-competency-full-info]');
var infoReloader = new info(competencyElement, '{{competency.competency.id}}', '{{user.id}}', '{{plan.id}}');
var ucw = new UserCompWorkflow();
ucw.registerEvents('[data-region="user-competency-status"]');
ucw.on('status-changed', infoReloader.reload.bind(infoReloader));
ucw.on('error-occured', infoReloader.reload.bind(infoReloader));
var inlineGrader = new mod('grade-competency-form-{{uniqid}}', scaleConfig, '{{competency.competency.id}}', '{{user.id}}', '{{plan.id}}', '', '{{#str}}chooserating, tool_lp{{/str}}');
inlineGrader.on('competencyupdated', infoReloader.reload.bind(infoReloader));
});
{{/js}}
{{/cangradeorsuggest}}
{{/usercompetency}}
{{#usercompetencyplan}}
<dt>{{#str}}rating, tool_lp{{/str}}</dt>
<dd>{{gradename}} - {{#str}}plancompleted, tool_lp{{/str}}</dd>
{{#cangradeorsuggest}}
{{$gradeform}}
@@ -101,6 +117,9 @@
</dd>
{{/usercompetencyplan}}
</dl>
{{#commentarea}}
{{>tool_lp/comment_area}}
{{/commentarea}}
<dl data-region="evidence-listing">
<dt>{{#str}}evidence, tool_lp{{/str}}</dt>
<dd>
+1 -1
View File
@@ -1405,7 +1405,7 @@ class tool_lp_external_testcase extends externallib_advanced_testcase {
$plan = $lpg->create_plan(array('userid' => $this->user->id, 'templateid' => $tpl->get_id()));
$uc1a = $lpg->create_user_competency(array('userid' => $this->user->id, 'competencyid' => $c1a->get_id(),
'status' => user_competency::STATUS_IN_REVIEW));
'status' => user_competency::STATUS_IN_REVIEW, 'reviewerid' => $this->creator->id));
$uc1b = $lpg->create_user_competency(array('userid' => $this->user->id, 'competencyid' => $c1b->get_id()));
$uc2b = $lpg->create_user_competency(array('userid' => $this->user->id, 'competencyid' => $c2b->get_id(),
'grade' => 2, 'proficiency' => 1));