require_sesskey();
// We must create them all or none.
$saved = 0;
- foreach ($data->userids as $userid) {
- if (empty($data->cohortids)) {
- $data->cohortids = array();
- }
- foreach ($data->cohortids as $cohortid) {
- $params = (object) array('userid' => $userid, 'cohortid' => $cohortid, 'roleid' => $data->roleid);
- $result = \tool_cohortroles\api::create_cohort_role_assignment($params);
- if ($result) {
- $saved++;
+ // Loop through userids and cohortids only if both of them are not empty.
+ if (!empty($data->userids) && !empty($data->cohortids)) {
+ foreach ($data->userids as $userid) {
+ foreach ($data->cohortids as $cohortid) {
+ $params = (object) array('userid' => $userid, 'cohortid' => $cohortid, 'roleid' => $data->roleid);
+ $result = \tool_cohortroles\api::create_cohort_role_assignment($params);
+ if ($result) {
+ $saved++;
+ }
}
}
}
*/
defined('MOODLE_INTERNAL') || die;
-$str = get_string('managecohortroles', 'tool_cohortroles');
-$ADMIN->add('roles', new admin_externalpage('toolcohortroles', $str, '/admin/tool/cohortroles/index.php', 'moodle/role:manage'));
+
+// This tool's required capabilities.
+$capabilities = [
+ 'moodle/cohort:view',
+ 'moodle/role:manage'
+];
+
+// Check if the user has all of the required capabilities.
+$context = context_system::instance();
+$hasaccess = has_all_capabilities($capabilities, $context);
+
+// Add this admin page only if the user has all of the required capabilities.
+if ($hasaccess) {
+ $str = get_string('managecohortroles', 'tool_cohortroles');
+ $url = new moodle_url('/admin/tool/cohortroles/index.php');
+ $ADMIN->add('roles', new admin_externalpage('toolcohortroles', $str, $url, $capabilities));
+}
--- /dev/null
+// 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/>.
+
+/**
+ * Event click on selecting competency in the competency autocomplete.
+ *
+ * @package tool_lp
+ * @copyright 2016 Issam Taboubi <issam.taboubi@umontreal.ca>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define(['jquery'], function($) {
+
+ /**
+ * CompetencyPlanNavigation
+ *
+ * @param {String} The selector of the competency element.
+ * @param {String} The base url for the page (no params).
+ * @param {Number} The user id
+ * @param {Number} The competency id
+ * @param {Number} The plan id
+ */
+ var CompetencyPlanNavigation = function(competencySelector, baseUrl, userId, competencyId, planId) {
+ this._baseUrl = baseUrl;
+ this._userId = userId + '';
+ this._competencyId = competencyId + '';
+ this._planId = planId;
+ this._ignoreFirstCompetency = true;
+
+ $(competencySelector).on('change', this._competencyChanged.bind(this));
+ };
+
+ /**
+ * The competency was changed in the select list.
+ *
+ * @method _competencyChanged
+ * @param {Event} e
+ */
+ CompetencyPlanNavigation.prototype._competencyChanged = function(e) {
+ if (this._ignoreFirstCompetency) {
+ this._ignoreFirstCompetency = false;
+ return;
+ }
+ var newCompetencyId = $(e.target).val();
+ var queryStr = '?userid=' + this._userId + '&planid=' + this._planId + '&competencyid=' + newCompetencyId;
+ document.location = this._baseUrl + queryStr;
+ };
+
+ /** @type {Number} The id of the competency. */
+ CompetencyPlanNavigation.prototype._competencyId = null;
+ /** @type {Number} The id of the user. */
+ CompetencyPlanNavigation.prototype._userId = null;
+ /** @type {Number} The id of the plan. */
+ CompetencyPlanNavigation.prototype._planId = null;
+ /** @type {String} Plugin base url. */
+ CompetencyPlanNavigation.prototype._baseUrl = null;
+ /** @type {Boolean} Ignore the first change event for competencies. */
+ CompetencyPlanNavigation.prototype._ignoreFirstCompetency = null;
+
+ return /** @alias module:tool_lp/competency_plan_navigation */ CompetencyPlanNavigation;
+
+});
}
};
- /**
- * Return if the level has a sub level.
- *
- * @param {Number} level The level.
- * @return {Boolean}
- * @function hasSubLevel
- */
- var hasSubLevel = function(level) {
- return typeof taxonomiesConstants[level + 1] !== 'undefined';
- };
-
/**
* Return the taxonomy constant for a level.
*
var competency = treeModel.getCompetency(id);
level = treeModel.getCompetencyLevel(id);
- if (!hasSubLevel(level)) {
- sublevel = false;
- } else {
- sublevel = level + 1;
- }
+ sublevel = level + 1;
actionMenu.show();
$('[data-region="competencyactions"]').data('competency', competency);
selectedTitle.text(str);
});
- if (!sublevel) {
- btn.hide();
- } else {
- strAddTaxonomy(sublevel).then(function(str) {
- btn.show()
- .find('[data-region="term"]')
- .text(str);
- });
- }
+ strAddTaxonomy(sublevel).then(function(str) {
+ btn.show()
+ .find('[data-region="term"]')
+ .text(str);
+ });
+
// We handled this event so consume it.
evt.preventDefault();
return false;
* @param {String} inputHiddenSelector The hidden input field selector.
* @param {String} staticElementSelector The static element displaying the parent competency.
* @param {Number} frameworkId The competency framework ID.
- * @param {Number} frameworkMaxLevel The framework max level.
* @param {Number} pageContextId The page context ID.
*/
var ParentCompetencyForm = function(buttonSelector,
inputHiddenSelector,
staticElementSelector,
frameworkId,
- frameworkMaxLevel,
pageContextId) {
this.buttonSelector = buttonSelector;
this.inputHiddenSelector = inputHiddenSelector;
this.staticElementSelector = staticElementSelector;
this.frameworkId = frameworkId;
- this.frameworkMaxLevel = frameworkMaxLevel;
this.pageContextId = pageContextId;
// Register the events.
ParentCompetencyForm.prototype.staticElementSelector = null;
/** @var {Number} The competency framework ID. */
ParentCompetencyForm.prototype.frameworkId = null;
- /** @var {Number} The framework max level. */
- ParentCompetencyForm.prototype.frameworkMaxLevel = null;
/** @var {Number} The page context ID. */
ParentCompetencyForm.prototype.pageContextId = null;
e.preventDefault();
var picker = new Picker(self.pageContextId, self.frameworkId, 'self', false);
- var maxlevel = self.frameworkMaxLevel;
- // Override the fetchcompetencies method to filter by max level.
- picker._fetchCompetencies = function(frameworkId, searchText) {
- var self = this;
-
- return ajax.call([
- { methodname: 'core_competency_search_competencies', args: {
- searchtext: searchText,
- competencyframeworkid: frameworkId
- }}
- ])[0].done(function(competencies) {
-
- var disabledcompetencies = [];
- function addCompetencyChildren(parent, competencies) {
- for (var i = 0; i < competencies.length; i++) {
- // Check if competency does not exceed the framework max level.
- var path = String(competencies[i].path),
- level = path.split('/').length - 2;
- if (level >= maxlevel && competencies[i].id !== "0") {
- disabledcompetencies.push(competencies[i].id);
- }
-
- if (competencies[i].parentid == parent.id) {
- parent.haschildren = true;
- competencies[i].children = [];
- competencies[i].haschildren = false;
- parent.children[parent.children.length] = competencies[i];
- addCompetencyChildren(competencies[i], competencies);
- }
- }
- }
- // Expand the list of competencies into a tree.
- var i, tree = [], comp;
- for (i = 0; i < competencies.length; i++) {
- comp = competencies[i];
- if (comp.parentid == "0") { // Loose check for now, because WS returns a string.
- comp.children = [];
- comp.haschildren = 0;
- tree[tree.length] = comp;
- addCompetencyChildren(comp, competencies);
- }
- }
-
- self._competencies = tree;
- self.setDisallowedCompetencyIDs(disabledcompetencies);
-
- }.bind(self)).fail(Notification.exception);
- };
// Override the render method to make framework selectable.
picker._render = function() {
var self = this;
* @param {String} inputHiddenSelector The hidden input field selector.
* @param {String} staticElementSelector The static element displaying the parent competency.
* @param {Number} frameworkId The competency framework ID.
- * @param {Number} frameworkMaxLevel The framework max level.
* @param {Number} pageContextId The page context ID.
* @method init
*/
inputSelector,
staticElementSelector,
frameworkId,
- frameworkMaxLevel,
pageContextId) {
// Create instance.
new ParentCompetencyForm(buttonSelector,
inputSelector,
staticElementSelector,
frameworkId,
- frameworkMaxLevel,
pageContextId);
}
};
--- /dev/null
+// 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/>.
+
+/**
+ * Module to open user competency plan in popup
+ *
+ * @package report_competency
+ * @copyright 2016 Issam Taboubi <issam.taboubi@umontreal.ca>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define(['jquery', 'core/notification', 'core/str', 'core/ajax', 'core/templates', 'tool_lp/dialogue'],
+ function($, notification, str, ajax, templates, Dialogue) {
+
+ /**
+ * UserCompetencyPopup
+ *
+ * @param {String} The regionSelector
+ * @param {String} The userCompetencySelector
+ * @param {Number} The plan ID
+ */
+ var UserCompetencyPopup = function(regionSelector, userCompetencySelector, planId) {
+ this._regionSelector = regionSelector;
+ this._userCompetencySelector = userCompetencySelector;
+ this._planId = planId;
+
+ $(this._regionSelector).on('click', this._userCompetencySelector, this._handleClick.bind(this));
+ };
+
+ /**
+ * Get the data from the closest TR and open the popup.
+ *
+ * @method _handleClick
+ * @param {Event} e
+ */
+ UserCompetencyPopup.prototype._handleClick = function(e) {
+ e.preventDefault();
+ var tr = $(e.target).closest('tr');
+ var competencyId = $(tr).data('competencyid');
+ var userId = $(tr).data('userid');
+ var planId = this._planId;
+
+ var requests = ajax.call([{
+ methodname : 'tool_lp_data_for_user_competency_summary_in_plan',
+ args: { competencyid: competencyId, planid: planId },
+ done: this._contextLoaded.bind(this),
+ fail: notification.exception
+ }]);
+
+ // Log the user competency viewed in plan event.
+ requests[0].then(function (result) {
+ var eventMethodName = 'core_competency_user_competency_viewed_in_plan';
+ // Trigger core_competency_user_competency_plan_viewed event instead if plan is already completed.
+ if (result.plan.iscompleted) {
+ eventMethodName = 'core_competency_user_competency_plan_viewed';
+ }
+ ajax.call([{
+ methodname: eventMethodName,
+ args: {competencyid: competencyId, userid: userId, planid: planId},
+ fail: notification.exception
+ }]);
+ });
+ };
+
+ /**
+ * We loaded the context, now render the template.
+ *
+ * @method _contextLoaded
+ * @param {Object} context
+ */
+ UserCompetencyPopup.prototype._contextLoaded = function(context) {
+ var self = this;
+ templates.render('tool_lp/user_competency_summary_in_plan', context).done(function(html, js) {
+ str.get_string('usercompetencysummary', 'report_competency').done(function(title) {
+ (new Dialogue(title, html, templates.runTemplateJS.bind(templates, js), self._refresh.bind(self), true));
+ }).fail(notification.exception);
+ }).fail(notification.exception);
+ };
+
+ /**
+ * Refresh the page.
+ *
+ * @method _refresh
+ */
+ UserCompetencyPopup.prototype._refresh = function() {
+ var planId = this._planId;
+
+ ajax.call([{
+ methodname : 'tool_lp_data_for_plan_page',
+ args: { planid: planId},
+ done: this._pageContextLoaded.bind(this),
+ fail: notification.exception
+ }]);
+ };
+
+ /**
+ * We loaded the context, now render the template.
+ *
+ * @method _pageContextLoaded
+ * @param {Object} context
+ */
+ UserCompetencyPopup.prototype._pageContextLoaded = function(context) {
+ var self = this;
+ templates.render('tool_lp/plan_page', context).done(function(html, js) {
+ templates.replaceNode(self._regionSelector, html, js);
+ }).fail(notification.exception);
+ };
+
+ /** @type {String} The selector for the region with the user competencies */
+ UserCompetencyPopup.prototype._regionSelector = null;
+ /** @type {String} The selector for the region with a single user competencies */
+ UserCompetencyPopup.prototype._userCompetencySelector = null;
+ /** @type {Number} The plan Id */
+ UserCompetencyPopup.prototype._planId = null;
+
+ return /** @alias module:tool_lp/user_competency_plan_popup */ UserCompetencyPopup;
+
+});
'canmanagecompetencyframeworks' => new external_value(PARAM_BOOL, 'User can manage competency frameworks'),
'canmanagecoursecompetencies' => new external_value(PARAM_BOOL, 'User can manage linked course competencies'),
'canconfigurecoursecompetencies' => new external_value(PARAM_BOOL, 'User can configure course competency settings'),
+ 'cangradecompetencies' => new external_value(PARAM_BOOL, 'User can grade competencies.'),
'settings' => course_competency_settings_exporter::get_read_structure(),
'statistics' => course_competency_statistics_exporter::get_read_structure(),
'competencies' => new external_multiple_structure(new external_single_structure(array(
'optional' => true
),
'usercompetencyplan' => array(
- 'type' => user_competency_exporter::read_properties_definition(),
+ 'type' => user_competency_plan_exporter::read_properties_definition(),
'optional' => true
),
'usercompetencycourse' => array(
'#tool_lp_parentcompetency',
'#id_parentdesc',
$framework->get_id(),
- \core_competency\competency_framework::get_taxonomies_max_level(),
$pagecontextid));
}
$mform->addElement('header', 'taxonomyhdr', get_string('taxonomies', 'tool_lp'));
$taxonomies = \core_competency\competency_framework::get_taxonomies_list();
$taxdefaults = array();
- for ($i = 1; $i <= \core_competency\competency_framework::get_taxonomies_max_level(); $i++) {
+ $taxcount = max($framework ? $framework->get_depth() : 4, 4);
+ for ($i = 1; $i <= $taxcount; $i++) {
$mform->addElement('select', "taxonomies[$i]", get_string('levela', 'tool_lp', $i), $taxonomies);
$taxdefaults[$i] = \core_competency\competency_framework::TAXONOMY_COMPETENCY;
}
$mform->addElement('url', 'url', get_string('userevidenceurl', 'tool_lp'), array(), array('usefilepicker' => false));
$mform->setType('url', PARAM_RAW_TRIMMED); // Can not use PARAM_URL, it silently converts bad URLs to ''.
+ $mform->addHelpButton('url', 'userevidenceurl', 'tool_lp');
$mform->addElement('filemanager', 'files', get_string('userevidencefiles', 'tool_lp'), array(),
$this->_customdata['fileareaoptions']);
--- /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/>.
+
+/**
+ * User competency plan page class.
+ *
+ * @package tool_lp
+ * @copyright 2016 Issam Taboubi <issam.taboubi@umontreal.ca>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace tool_lp\output;
+
+use renderable;
+use renderer_base;
+use templatable;
+use context_course;
+use \core_competency\external\competency_exporter;
+use stdClass;
+
+/**
+ * User competency plan navigation class.
+ *
+ * @package tool_lp
+ * @copyright 2016 Issam Taboubi <issam.taboubi@umontreal.ca>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class competency_plan_navigation implements renderable, templatable {
+
+ /** @var userid */
+ protected $userid;
+
+ /** @var competencyid */
+ protected $competencyid;
+
+ /** @var planid */
+ protected $planid;
+
+ /** @var baseurl */
+ protected $baseurl;
+
+ /**
+ * Construct.
+ *
+ * @param int $userid
+ * @param int $competencyid
+ * @param int $planid
+ * @param string $baseurl
+ */
+ public function __construct($userid, $competencyid, $planid, $baseurl) {
+ $this->userid = $userid;
+ $this->competencyid = $competencyid;
+ $this->planid = $planid;
+ $this->baseurl = $baseurl;
+ }
+
+ /**
+ * Export the data.
+ *
+ * @param renderer_base $output
+ * @return stdClass
+ */
+ public function export_for_template(renderer_base $output) {
+
+ $data = new stdClass();
+ $data->userid = $this->userid;
+ $data->competencyid = $this->competencyid;
+ $data->planid = $this->planid;
+ $data->baseurl = $this->baseurl;
+
+ $plancompetencies = \core_competency\api::list_plan_competencies($data->planid);
+ $data->competencies = array();
+ $contextcache = array();
+ foreach ($plancompetencies as $plancompetency) {
+ $frameworkid = $plancompetency->competency->get_competencyframeworkid();
+ if (!isset($contextcache[$frameworkid])) {
+ $contextcache[$frameworkid] = $plancompetency->competency->get_context();
+ }
+ $context = $contextcache[$frameworkid];
+ $exporter = new competency_exporter($plancompetency->competency, array('context' => $context));
+ $competency = $exporter->export($output);
+ if ($competency->id == $this->competencyid) {
+ $competency->selected = true;
+ }
+ $data->competencies[] = $competency;
+ }
+ $data->hascompetencies = count($data->competencies);
+ return $data;
+ }
+}
$this->coursecompetencylist = api::list_course_competencies($courseid);
$this->canmanagecoursecompetencies = has_capability('moodle/competency:coursecompetencymanage', $this->context);
$this->canconfigurecoursecompetencies = has_capability('moodle/competency:coursecompetencyconfigure', $this->context);
+ $this->cangradecompetencies = has_capability('moodle/competency:competencygrade', $this->context);
$this->coursecompetencysettings = api::read_course_competency_settings($courseid);
$this->coursecompetencystatistics = new course_competency_statistics($courseid);
$data->canmanagecompetencyframeworks = $this->canmanagecompetencyframeworks;
$data->canmanagecoursecompetencies = $this->canmanagecoursecompetencies;
$data->canconfigurecoursecompetencies = $this->canconfigurecoursecompetencies;
+ $data->cangradecompetencies = $this->cangradecompetencies;
$exporter = new course_competency_settings_exporter($this->coursecompetencysettings);
$data->settings = $exporter->export($output);
$related = array('context' => $this->context);
return parent::render_from_template('tool_lp/user_competency_course_navigation', $data);
}
+ /**
+ * Defer to template.
+ *
+ * @param competency_plan_navigation $nav
+ * @return string
+ */
+ public function render_competency_plan_navigation(competency_plan_navigation $nav) {
+ $data = $nav->export_for_template($this);
+ return parent::render_from_template('tool_lp/competency_plan_navigation', $data);
+ }
+
/**
* Defer to template.
*
$data->baseurl = $this->baseurl;
$data->groupselector = '';
- if (has_capability('moodle/competency:coursecompetencymanage', $context)) {
+ if (has_any_capability(array('moodle/competency:usercompetencyview', 'moodle/competency:coursecompetencymanage'),
+ $context)) {
$course = $DB->get_record('course', array('id' => $this->courseid));
$currentgroup = groups_get_course_group($course, true);
if ($currentgroup !== false) {
$title = get_string('competencies', 'core_competency');
$pagetitle = get_string('competencyframeworks', 'tool_lp');
-$pagesubtitle = get_string('listcompetencyframeworkscaption', 'tool_lp');
// Set up the page.
$PAGE->set_context($context);
$output = $PAGE->get_renderer('tool_lp');
echo $output->header();
echo $output->heading($pagetitle, 2);
-echo $output->heading($pagesubtitle, 3);
$page = new \tool_lp\output\manage_competency_frameworks_page($context);
echo $output->render($page);
$string['userevidencesummary'] = 'Summary';
$string['userevidenceupdated'] = 'Evidence of prior learning updated';
$string['userevidenceurl'] = 'URL';
+$string['userevidenceurl_help'] = 'The URL must start with \'http://\' or \'https://\'.';
$string['viewdetails'] = 'View details';
$string['visible'] = 'Visible';
$string['visible_help'] = 'A competency framework can be hidden whilst it is being set up or updated to a new version.';
vertical-align: top;
}
.path-admin-tool-lp .progress {
- width: 10em;
+ width: 100%;
display: inline-block;
- margin-left: 2em;
margin-right: 2em;
}
.dir-rtl.path-admin-tool-lp .progress .bar {
--- /dev/null
+<div class="pull-right well">
+{{#hascompetencies}}
+<span>
+<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>
+{{/competencies}}
+</select>
+</span>
+{{/hascompetencies}}
+</form>
+</div>
+{{#js}}
+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, '{{#str}}jumptocompetency, tool_lp{{/str}}');
+{{/hascompetencies}}
+
+});
+{{/js}}
}}
<div data-region="coursecompetenciespage">
<div data-region="actions" class="clearfix">
- <div class="pull-right">
+ <div class="pull-left">
{{#canmanagecoursecompetencies}}
<button disabled>{{#str}}addcoursecompetencies, tool_lp{{/str}}</button>
{{/canmanagecoursecompetencies}}
</div>
</div>
-<div data-region="configurecoursecompetencies">
-{{#settings.pushratingstouserplans}}
- <p class="alert">
- {{#str}}coursecompetencyratingsarepushedtouserplans, tool_lp{{/str}}
-{{/settings.pushratingstouserplans}}
-{{^settings.pushratingstouserplans}}
- <p class="alert alert-info">
- {{#str}}coursecompetencyratingsarenotpushedtouserplans, tool_lp{{/str}}
-{{/settings.pushratingstouserplans}}
-{{#canconfigurecoursecompetencies}}
- <a href="#"
- data-action="configure-course-competency-settings"
- data-courseid="{{courseid}}"
- data-pushratingstouserplans="{{settings.pushratingstouserplans}}"
- >{{#pix}}t/edit, core, {{#str}}edit{{/str}}{{/pix}}</a>
- </p>
-{{/canconfigurecoursecompetencies}}
-</div>
+ <div data-region="configurecoursecompetencies">
+ {{#cangradecompetencies}}
+ <p class="alert {{^settings.pushratingstouserplans}}alert-info{{/settings.pushratingstouserplans}}">
+ {{#settings.pushratingstouserplans}}
+ {{#str}}coursecompetencyratingsarepushedtouserplans, tool_lp{{/str}}
+ {{/settings.pushratingstouserplans}}
+ {{^settings.pushratingstouserplans}}
+ {{#str}}coursecompetencyratingsarenotpushedtouserplans, tool_lp{{/str}}
+ {{/settings.pushratingstouserplans}}
+ {{#canconfigurecoursecompetencies}}
+ <a href="#"
+ data-action="configure-course-competency-settings"
+ data-courseid="{{courseid}}"
+ data-pushratingstouserplans="{{settings.pushratingstouserplans}}">
+ {{#pix}}t/edit, core, {{#str}}edit{{/str}}{{/pix}}
+ </a>
+ {{/canconfigurecoursecompetencies}}
+ </p>
+ {{/cangradecompetencies}}
+ </div>
{{#statistics}}
{{> tool_lp/course_competency_statistics }}
{{/statistics}}
{{#competencycount}}
<div data-region="coursecompetencystatistics" class="well">
{{#canbegradedincourse}}
- <div>
- <div class="progresstext">
- {{#str}}xcompetenciesproficientoutofyincourse, tool_lp, { "x": "{{proficientcompetencycount}}", "y": "{{competencycount}}" } {{/str}}
+ <div class="clearfix">
+ <div class="span6">
+ <div class="progresstext">
+ {{#str}}xcompetenciesproficientoutofyincourse, tool_lp, { "x": "{{proficientcompetencycount}}", "y": "{{competencycount}}" } {{/str}}
+ </div>
</div>
- <div class="progress">
- <div class="bar" style="width: {{proficientcompetencypercentage}}%;">
- {{proficientcompetencypercentageformatted}} %
+ <div class="span6">
+ <span class="pull-right label label-info">{{proficientcompetencypercentageformatted}} %</span>
+ <div class="progress">
+ <div class="bar" style="width: {{proficientcompetencypercentage}}%;"></div>
</div>
</div>
</div>
* navigation - array of strings containing buttons for navigation
}}
<div data-region="managecompetencies">
-<div class="btn-group pull-right">
+<div class="btn-group pull-left">
{{#navigation}}
{{{.}}}
{{/navigation}}
</div>
<table class="generaltable fullwidth managecompetencies">
+ <caption>{{#str}}listcompetencyframeworkscaption, tool_lp{{/str}}</caption>
<thead>
<tr>
<th scope="col">{{#str}}competencyframeworkname, tool_lp{{/str}}</th>
* navigation - array of strings containing buttons for navigation
}}
<div data-region="managetemplates">
-<div class="btn-group pull-right">
+<div class="btn-group pull-left">
{{#navigation}}
{{{.}}}
{{/navigation}}
<a href="{{pluginbaseurl}}/editplan.php?id={{plan.id}}&userid={{plan.userid}}">{{#pix}}t/edit, core, {{#str}}editplan, tool_lp{{/str}}{{/pix}}</a>
{{/plan.canbeedited}}
</h2>
+ {{#plan.canbeedited}}
+ <div data-region="actions" class="clearfix">
+ <div class="pull-left">
+ <!-- Button to add competencies to the plan -->
+ <button class="btn" data-action="add">{{#pix}}t/add{{/pix}} {{#str}}addcompetency, tool_lp{{/str}}</button>
+ </div>
+ </div>
+ {{/plan.canbeedited}}
<div data-region="plan-summary">
<dl>
<dt>{{#str}}status, tool_lp{{/str}}</dt>
<dd>{{{plan.description}}}</dd>
{{/description}}
<dt>{{#str}}progress, tool_lp{{/str}}</dt>
- <dd><span class="progresstext">{{#str}}xcompetenciesproficientoutofy, tool_lp, { "x": "{{proficientcompetencycount}}", "y": "{{competencycount}}" }{{/str}}</span>
- <div class="progress">
- <div class="bar" style="width: {{proficientcompetencypercentage}}%;">
- {{proficientcompetencypercentageformatted}} %
+ <dd>
+ <div class="span4">
+ <div class="progresstext">
+ {{#str}}xcompetenciesproficientoutofy, tool_lp, { "x": "{{proficientcompetencycount}}", "y": "{{competencycount}}" }{{/str}}
+ </div>
+ </div>
+ <div class="span4">
+ <span class="pull-right label label-info">{{proficientcompetencypercentageformatted}} %</span>
+ <div class="progress">
+ <div class="bar" style="width: {{proficientcompetencypercentage}}%;"></div>
</div>
</div>
</dd>
{{/canpostorhascomments}}
{{/plan.commentarea}}
<div data-region="plan-competencies">
- <div data-region="actions">
- <div class="pull-right">
- <!-- Button to add competencies to the plan -->
- {{#plan.canbeedited}}
- <button class="btn" data-action="add">{{#pix}}t/add{{/pix}} {{#str}}addcompetency, tool_lp{{/str}}</button>
- {{/plan.canbeedited}}
- </div>
- </div>
<h3>{{#str}}learningplancompetencies, tool_lp{{/str}}</h3>
<table class="generaltable fullwidth managecompetencies">
<thead>
</thead>
<tbody class="drag-parentnode">
{{#competencies}}
- <tr class="drag-samenode" data-node="user-competency" data-id="{{competency.id}}" data-competencyid="{{usercompetency.competencyid}}" data-userid="{{usercompetency.userid}}">
+ <tr class="drag-samenode" data-node="user-competency" data-id="{{competency.id}}"
+ data-competencyid="{{competency.id}}"
+ data-userid="{{plan.userid}}">
<td>
{{#plan.canbeedited}}
<span class="drag-handlecontainer pull-left"></span>
{{/plan.canbeedited}}
- <a href="{{pluginbaseurl}}/user_competency_in_plan.php?competencyid={{competency.id}}&userid={{plan.userid}}&planid={{plan.id}}">{{competency.shortname}}</a>
+ <a data-usercompetency="true" href="#">{{competency.shortname}}</a>
<em>{{competency.idnumber}}</em>
{{#comppath}}
<br>
</div>
</div>
{{#js}}
-require(['tool_lp/competencies', 'tool_lp/planactions', 'tool_lp/user_competency_workflow'], function(mod, actionsMod, UserCompWorkflow) {
+require(['tool_lp/competencies', 'tool_lp/planactions', 'tool_lp/user_competency_workflow', 'tool_lp/user_competency_plan_popup'], function(mod, actionsMod, UserCompWorkflow, Popup) {
var planActions = new actionsMod('plan');
(new mod({{plan.id}}, 'plan', {{contextid}}));
+ (new Popup('[data-region=plan-page]', '[data-usercompetency=true]', {{plan.id}}));
planActions.registerEvents();
var ucw = new UserCompWorkflow();
}}
<div data-region="plans">
-<div class="btn-group pull-right">
+<div class="btn-group pull-left">
{{#navigation}}
{{{.}}}
{{/navigation}}
<a href="{{pluginbaseurl}}/edittemplate.php?id={{template.id}}&pagecontextid={{pagecontextid}}">{{#pix}}t/edit, core, {{#str}}edittemplate, tool_lp{{/str}}{{/pix}}</a>
{{/template.canmanage}}
</h2>
- <h3>{{#str}}templatecompetencies, tool_lp{{/str}}</h3>
- {{#statistics}}
- {{> tool_lp/template_statistics }}
- {{/statistics}}
{{#canmanagetemplatecompetencies}}
<div data-region="actions" class="clearfix">
- <div class="pull-right">
+ <div class="pull-left">
<button disabled>{{#str}}addtemplatecompetencies, tool_lp{{/str}}</button>
</div>
</div>
{{/canmanagetemplatecompetencies}}
+ <h3>{{#str}}templatecompetencies, tool_lp{{/str}}</h3>
+ {{#statistics}}
+ {{> tool_lp/template_statistics }}
+ {{/statistics}}
<div data-region="templatecompetencies">
<div class="managecompetencies">
<div class="drag-parentnode">
}}
{{#competencycount}}
<div data-region="templatestatistics" class="well">
- <div>
- <div class="progresstext">
- {{#str}}xcompetencieslinkedoutofy, tool_lp, { "x": "{{linkedcompetencycount}}", "y": "{{competencycount}}" } {{/str}}
+ <div class="clearfix">
+ <div class="span4">
+ <div class="progresstext">
+ {{#str}}xcompetencieslinkedoutofy, tool_lp, { "x": "{{linkedcompetencycount}}", "y": "{{competencycount}}" } {{/str}}
+ </div>
</div>
- <div class="progress">
- <div class="bar" style="width: {{linkedcompetencypercentage}}%;">
- {{linkedcompetencypercentageformatted}} %
+ <div class="span6">
+ <span class="pull-right label label-info">{{linkedcompetencypercentageformatted}} %</span>
+ <div class="progress">
+ <div class="bar" style="width: {{linkedcompetencypercentage}}%;"></div>
</div>
</div>
</div>
{{#plancount}}
- <div>
- <div class="progresstext">
- {{#str}}xplanscompletedoutofy, tool_lp, { "x": "{{completedplancount}}", "y": "{{plancount}}" } {{/str}}
+ <div class="clearfix">
+ <div class="span4">
+ <div class="progresstext">
+ {{#str}}xplanscompletedoutofy, tool_lp, { "x": "{{completedplancount}}", "y": "{{plancount}}" } {{/str}}
+ </div>
</div>
- <div class="progress">
- <div class="bar" style="width: {{completedplanpercentage}}%;">
- {{completedplanpercentageformatted}} %
+ <div class="span6">
+ <span class="pull-right label label-info">{{completedplanpercentageformatted}} %</span>
+ <div class="progress">
+ <div class="bar" style="width: {{completedplanpercentage}}%;">
+
+ </div>
</div>
</div>
</div>
{{/plancount}}
{{#usercompetencyplancount}}
- <div>
- <div class="progresstext">
- {{#str}}averageproficiencyrate, tool_lp, {{proficientusercompetencyplanpercentageformatted}} {{/str}}
+ <div class="clearfix">
+ <div class="span4">
+ <div class="progresstext">
+ {{#str}}averageproficiencyrate, tool_lp, {{proficientusercompetencyplanpercentageformatted}} {{/str}}
+ </div>
</div>
- <div class="progress">
- <div class="bar" style="width: {{proficientusercompetencyplanpercentage}}%;">
- {{proficientusercompetencyplanpercentageformatted}} %
+ <div class="span6">
+ <span class="pull-right label label-info">{{proficientusercompetencyplanpercentageformatted}} %</span>
+ <div class="progress">
+ <div class="bar" style="width: {{proficientusercompetencyplanpercentage}}%;"></div>
</div>
</div>
</div>
}}
<div data-region="user-evidence-list">
-<div class="btn-group pull-right">
+<div class="btn-group pull-left">
{{#navigation}}
{{{.}}}
{{/navigation}}
<a href="{{pluginbaseurl}}/user_evidence_edit.php?id={{id}}&userid={{userid}}">{{#pix}}t/edit, core, {{#str}}editthisuserevidence, tool_lp{{/str}}{{/pix}}</a>
{{/canmanage}}
</h2>
+ {{#canmanage}}
+ <div data-region="actions" class="clearfix">
+ <div class="pull-left">
+ {{#userhasplan}}
+ <button class="btn" data-action="link-competency">{{#pix}}t/add{{/pix}} {{#str}}linkcompetencies, tool_lp{{/str}}</button>
+ {{/userhasplan}}
+ </div>
+ </div>
+ {{/canmanage}}
<div data-region="user-evidence-summary">
{{#description}}
{{/usercompetencies}}
</tbody>
</table>
-
- <div data-region="actions">
- <div class="pull-right">
- {{#canmanage}}
- {{#userhasplan}}
- <button class="btn" data-action="link-competency">{{#pix}}t/add{{/pix}} {{#str}}linkcompetencies, tool_lp{{/str}}</button>
- {{/userhasplan}}
- {{/canmanage}}
- </div>
- </div>
</div>
</div>
require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php');
-use Behat\Behat\Context\Step\Given as Given;
-
/**
* Step definition for learning plan system.
*
*
* @param string $nodetext
* @param string $rowname
- * @return Given[] array of steps
*/
public function click_on_edit_menu_of_the_row($nodetext, $rowname) {
- $steps = array();
-
- $xpathtarget = "//ul//li//ul//li[@class='tool-lp-menu-item']//a[contains(.,'". $nodetext ."')]";
+ $xpathtarget = "//ul//li//ul//li[@class='tool-lp-menu-item']//a[contains(.,'" . $nodetext . "')]";
- $steps[] = new Given('I click on "Edit" "link" in the "' . $this->escape($rowname) . '" "table_row"');
- $steps[] = new Given('I click on "'.$xpathtarget.'" "xpath_element" in the "' . $this->escape($rowname) . '" "table_row"');
-
- return $steps;
+ $this->execute('behat_general::i_click_on_in_the', [get_string('edit'), 'link', $this->escape($rowname), 'table_row']);
+ $this->execute('behat_general::i_click_on_in_the', [$xpathtarget, 'xpath_element', $this->escape($rowname), 'table_row']);
}
/**
* @Given /^I select "([^"]*)" of the competency tree$/
*
* @param string $competencyname
- * @return Given[] array of steps
*/
public function select_of_the_competency_tree($competencyname) {
- $steps = array();
-
- $xpathtarget = "//li[@role='tree-item']//span[contains(.,'". $competencyname ."')]";
+ $xpathtarget = "//li[@role='tree-item']//span[contains(.,'" . $competencyname . "')]";
- $steps[] = new Given('I click on "' . $xpathtarget . '" "xpath_element"');
-
- return $steps;
+ $this->execute('behat_general::i_click_on', [$xpathtarget, 'xpath_element']);
}
/**
* @Given /^I click on "([^"]*)" item in the autocomplete list$/
*
* @param string $item
- * @return Given[] array of steps
*/
public function i_click_on_item_in_the_autocomplete_list($item) {
- $steps = array();
-
- $xpathtarget = "//ul[@class='form-autocomplete-suggestions']//li//span//span[contains(.,'". $item ."')]";
-
- $steps[] = new Given('I click on "' . $xpathtarget . '" "xpath_element"');
+ $xpathtarget = "//ul[@class='form-autocomplete-suggestions']//li//span//span[contains(.,'" . $item . "')]";
- return $steps;
+ $this->execute('behat_general::i_click_on', [$xpathtarget, 'xpath_element']);
}
}
$output = $PAGE->get_renderer('tool_lp');
echo $output->header();
echo $output->heading($title);
+// User competency plan navigation.
+$baseurl = new moodle_url('/admin/tool/lp/user_competency_in_plan.php');
+$nav = new \tool_lp\output\competency_plan_navigation($userid, $competencyid, $planid, $baseurl);
+echo $output->render($nav);
$page = new \tool_lp\output\user_competency_summary_in_plan($competencyid, $planid);
echo $output->render($page);
// Trigger the viewed event.
'archetypes' => array(
'manager' => CAP_ALLOW
),
- 'clonepermissionsfrom' => 'moodle/site:config'
),
);
// === Test client forms ===
-class moodle_user_create_users_form extends moodleform {
- public function definition() {
- global $CFG;
-
- $mform = $this->_form;
-
-
- $mform->addElement('header', 'wstestclienthdr', get_string('testclient', 'webservice'));
-
- //note: these values are intentionally PARAM_RAW - we want users to test any rubbish as parameters
- $data = $this->_customdata;
- if ($data['authmethod'] == 'simple') {
- $mform->addElement('text', 'wsusername', 'wsusername');
- $mform->setType('wsusername', core_user::get_property_type('username'));
- $mform->addElement('text', 'wspassword', 'wspassword');
- $mform->setType('wspassword', core_user::get_property_type('password'));
- } else if ($data['authmethod'] == 'token') {
- $mform->addElement('text', 'token', 'token');
- $mform->setType('token', PARAM_RAW_TRIMMED);
- }
-
- $mform->addElement('hidden', 'authmethod', $data['authmethod']);
- $mform->setType('authmethod', core_user::get_property_type('auth'));
-
- /// specific to the create users function
- $mform->addElement('text', 'username', 'username');
- $mform->setType('username', core_user::get_property_type('username'));
- $mform->addElement('text', 'password', 'password');
- $mform->setType('password', core_user::get_property_type('password'));
- $mform->addElement('text', 'firstname', 'firstname');
- $mform->setType('firstname', core_user::get_property_type('firstname'));
- $mform->addElement('text', 'lastname', 'lastname');
- $mform->setType('lastname', core_user::get_property_type('lastname'));
- $mform->addElement('text', 'email', 'email');
- $mform->setType('email', core_user::get_property_type('email'));
-
- $mform->addElement('text', 'customfieldtype', 'customfieldtype');
- $mform->setType('customfieldtype', PARAM_RAW);
- $mform->addElement('text', 'customfieldvalue', 'customfieldvalue');
- $mform->setType('customfieldvalue', PARAM_RAW);
-
- $mform->addElement('hidden', 'function');
- $mform->setType('function', PARAM_PLUGIN);
-
- $mform->addElement('hidden', 'protocol');
- $mform->setType('protocol', PARAM_ALPHA);
-
-
-
- $mform->addElement('static', 'warning', '', get_string('executewarnign', 'webservice'));
-
- $this->add_action_buttons(true, get_string('execute', 'webservice'));
- }
-
- public function get_params() {
- if (!$data = $this->get_data()) {
- return null;
- }
-
- //set customfields
- if (!empty($data->customfieldtype)) {
- $data->customfields = array(array('type' => $data->customfieldtype, 'value' => $data->customfieldvalue));
- }
-
- // remove unused from form data
- unset($data->submitbutton);
- unset($data->protocol);
- unset($data->function);
- unset($data->wsusername);
- unset($data->wspassword);
- unset($data->token);
- unset($data->authmethod);
- unset($data->customfieldtype);
- unset($data->customfieldvalue);
-
- $params = array();
- $params['users'] = array();
- $params['users'][] = (array)$data;
-
- return $params;
- }
-}
-
-
-class moodle_user_update_users_form extends moodleform {
- public function definition() {
- global $CFG;
-
- $mform = $this->_form;
-
-
- $mform->addElement('header', 'wstestclienthdr', get_string('testclient', 'webservice'));
-
- //note: these values are intentionally PARAM_RAW - we want users to test any rubbish as parameters
- $data = $this->_customdata;
- if ($data['authmethod'] == 'simple') {
- $mform->addElement('text', 'wsusername', 'wsusername');
- $mform->setType('wsusername', core_user::get_property_type('username'));
- $mform->addElement('text', 'wspassword', 'wspassword');
- $mform->setType('wspassword', core_user::get_property_type('password'));
- } else if ($data['authmethod'] == 'token') {
- $mform->addElement('text', 'token', 'token');
- $mform->setType('token', PARAM_RAW_TRIMMED);
- }
-
- $mform->addElement('hidden', 'authmethod', $data['authmethod']);
- $mform->setType('authmethod', core_user::get_property_type('auth'));
-
- /// specific to the create users function
- $mform->addElement('text', 'id', 'id');
- $mform->addRule('id', get_string('required'), 'required', null, 'client');
- $mform->setType('id', core_user::get_property_type('id'));
- $mform->addElement('text', 'username', 'username');
- $mform->setType('username', core_user::get_property_type('username'));
- $mform->addElement('text', 'password', 'password');
- $mform->setType('password', core_user::get_property_type('password'));
- $mform->addElement('text', 'firstname', 'firstname');
- $mform->setType('firstname', core_user::get_property_type('firstname'));
- $mform->addElement('text', 'lastname', 'lastname');
- $mform->setType('lastname', core_user::get_property_type('lastname'));
- $mform->addElement('text', 'email', 'email');
- $mform->setType('email', core_user::get_property_type('email'));
-
-
- $mform->addElement('text', 'customfieldtype', 'customfieldtype');
- $mform->setType('customfieldtype', PARAM_RAW);
- $mform->addElement('text', 'customfieldvalue', 'customfieldvalue');
- $mform->setType('customfieldvalue', PARAM_RAW);
-
- $mform->addElement('hidden', 'function');
- $mform->setType('function', PARAM_PLUGIN);
-
- $mform->addElement('hidden', 'protocol');
- $mform->setType('protocol', PARAM_ALPHA);
-
-
-
- $mform->addElement('static', 'warning', '', get_string('executewarnign', 'webservice'));
-
- $this->add_action_buttons(true, get_string('execute', 'webservice'));
- }
-
- public function get_params() {
- if (!$data = $this->get_data()) {
- return null;
- }
-
- //set customfields
- if (!empty($data->customfieldtype)) {
- $data->customfields = array(array('type' => $data->customfieldtype, 'value' => $data->customfieldvalue));
- }
-
- // remove unused from form data
- unset($data->submitbutton);
- unset($data->protocol);
- unset($data->function);
- unset($data->wsusername);
- unset($data->wspassword);
- unset($data->token);
- unset($data->authmethod);
- unset($data->customfieldtype);
- unset($data->customfieldvalue);
-
- foreach($data as $key => $value) {
- if (empty($value)) {
- unset($data->{$key});
- }
- }
-
- $params = array();
- $params['users'] = array();
- $params['users'][] = (array)$data;
-
- return $params;
- }
-}
-
-
-class moodle_user_delete_users_form extends moodleform {
- public function definition() {
- global $CFG;
-
- $mform = $this->_form;
-
-
- $mform->addElement('header', 'wstestclienthdr', get_string('testclient', 'webservice'));
-
- //note: these values are intentionally PARAM_RAW - we want users to test any rubbish as parameters
- $data = $this->_customdata;
- if ($data['authmethod'] == 'simple') {
- $mform->addElement('text', 'wsusername', 'wsusername');
- $mform->setType('wsusername', core_user::get_property_type('username'));
- $mform->addElement('text', 'wspassword', 'wspassword');
- $mform->setType('wspassword', core_user::get_property_type('password'));
- } else if ($data['authmethod'] == 'token') {
- $mform->addElement('text', 'token', 'token');
- $mform->setType('token', PARAM_RAW_TRIMMED);
- }
-
- $mform->addElement('hidden', 'authmethod', $data['authmethod']);
- $mform->setType('authmethod', core_user::get_property_type('auth'));
-
- /// beginning of specific code to the create users function
- $mform->addElement('text', 'userids[0]', 'userids[0]');
- $mform->addElement('text', 'userids[1]', 'userids[1]');
- $mform->addElement('text', 'userids[2]', 'userids[2]');
- $mform->addElement('text', 'userids[3]', 'userids[3]');
- $mform->setType('userids', core_user::get_property_type('id'));
- /// end of specific code to the create users function
-
- $mform->addElement('hidden', 'function');
- $mform->setType('function', PARAM_PLUGIN);
-
- $mform->addElement('hidden', 'protocol');
- $mform->setType('protocol', PARAM_ALPHA);
-
- $mform->addElement('static', 'warning', '', get_string('executewarnign', 'webservice'));
-
- $this->add_action_buttons(true, get_string('execute', 'webservice'));
- }
-
- public function get_params() {
- if (!$data = $this->get_data()) {
- return null;
- }
- // remove unused from form data
- unset($data->submitbutton);
- unset($data->protocol);
- unset($data->function);
- unset($data->wsusername);
- unset($data->wspassword);
- unset($data->token);
- unset($data->authmethod);
-
- /// beginning of specific code to the create users form
- $params = array();
- $params['userids'] = array();
- for ($i=0; $i<10; $i++) {
- if (empty($data->userids[$i])) {
- continue;
- }
- $params['userids'][] = $data->userids[$i];
- }
- /// end of specific code to the create users function
-
- return $params;
- }
-}
-
-
-class moodle_user_get_users_by_id_form extends moodleform {
- public function definition() {
- global $CFG;
-
- $mform = $this->_form;
-
-
- $mform->addElement('header', 'wstestclienthdr', get_string('testclient', 'webservice'));
-
- //note: these values are intentionally PARAM_RAW - we want users to test any rubbish as parameters
- $data = $this->_customdata;
- if ($data['authmethod'] == 'simple') {
- $mform->addElement('text', 'wsusername', 'wsusername');
- $mform->setType('wsusername', core_user::get_property_type('username'));
- $mform->addElement('text', 'wspassword', 'wspassword');
- $mform->setType('wspassword', core_user::get_property_type('password'));
- } else if ($data['authmethod'] == 'token') {
- $mform->addElement('text', 'token', 'token');
- $mform->setType('token', PARAM_RAW_TRIMMED);
- }
-
- $mform->addElement('hidden', 'authmethod', $data['authmethod']);
- $mform->setType('authmethod', core_user::get_property_type('auth'));
-
- /// beginning of specific code to the create users function
- $mform->addElement('text', 'userids[0]', 'userids[0]');
- $mform->addElement('text', 'userids[1]', 'userids[1]');
- $mform->addElement('text', 'userids[2]', 'userids[2]');
- $mform->addElement('text', 'userids[3]', 'userids[3]');
- $mform->setType('userids', core_user::get_property_type('id'));
- /// end of specific code to the create users function
-
- $mform->addElement('hidden', 'function');
- $mform->setType('function', PARAM_PLUGIN);
-
- $mform->addElement('hidden', 'protocol');
- $mform->setType('protocol', PARAM_ALPHA);
-
-
-
- $mform->addElement('static', 'warning', '', get_string('executewarnign', 'webservice'));
-
- $this->add_action_buttons(true, get_string('execute', 'webservice'));
- }
-
- public function get_params() {
- if (!$data = $this->get_data()) {
- return null;
- }
- // remove unused from form data
- unset($data->submitbutton);
- unset($data->protocol);
- unset($data->function);
- unset($data->wsusername);
- unset($data->wspassword);
- unset($data->token);
- unset($data->authmethod);
-
- /// beginning of specific code to the create users form
- $params = array();
- $params['userids'] = array();
- for ($i=0; $i<10; $i++) {
- if (empty($data->userids[$i])) {
- continue;
- }
- $params['userids'][] = $data->userids[$i];
- }
- /// end of specific code to the create users function
-
- return $params;
- }
-}
-
-class moodle_group_create_groups_form extends moodleform {
- public function definition() {
- global $CFG;
-
- $mform = $this->_form;
-
-
- $mform->addElement('header', 'wstestclienthdr', get_string('testclient', 'webservice'));
-
- //note: these values are intentionally PARAM_RAW - we want users to test any rubbish as parameters
- $data = $this->_customdata;
- if ($data['authmethod'] == 'simple') {
- $mform->addElement('text', 'wsusername', 'wsusername');
- $mform->setType('wsusername', core_user::get_property_type('username'));
- $mform->addElement('text', 'wspassword', 'wspassword');
- $mform->setType('wspassword', core_user::get_property_type('password'));
- } else if ($data['authmethod'] == 'token') {
- $mform->addElement('text', 'token', 'token');
- $mform->setType('token', PARAM_RAW_TRIMMED);
- }
-
- $mform->addElement('hidden', 'authmethod', $data['authmethod']);
- $mform->setType('authmethod', core_user::get_property_type('auth'));
-
- $mform->addElement('text', 'courseid', 'courseid');
- $mform->setType('courseid', PARAM_INT);
- $mform->addElement('text', 'name', 'name');
- $mform->setType('name', PARAM_TEXT);
- $mform->addElement('text', 'description', 'description');
- $mform->setType('description', PARAM_TEXT);
- $mform->addElement('text', 'enrolmentkey', 'enrolmentkey');
- $mform->setType('enrolmentkey', PARAM_RAW);
-
- $mform->addElement('hidden', 'function');
- $mform->setType('function', PARAM_PLUGIN);
-
- $mform->addElement('hidden', 'protocol');
- $mform->setType('protocol', PARAM_ALPHA);
-
-
-
- $mform->addElement('static', 'warning', '', get_string('executewarnign', 'webservice'));
-
- $this->add_action_buttons(true, get_string('execute', 'webservice'));
- }
-
- public function get_params() {
- if (!$data = $this->get_data()) {
- return null;
- }
- // remove unused from form data
- unset($data->submitbutton);
- unset($data->protocol);
- unset($data->function);
- unset($data->wsusername);
- unset($data->wspassword);
- unset($data->token);
- unset($data->authmethod);
-
- $params = array();
- $params['groups'] = array();
- $params['groups'][] = (array)$data;
-
- return $params;
- }
-}
-
-class moodle_group_get_groups_form extends moodleform {
- public function definition() {
- global $CFG;
-
- $mform = $this->_form;
-
- $mform->addElement('header', 'wstestclienthdr', get_string('testclient', 'webservice'));
-
- //note: these values are intentionally PARAM_RAW - we want users to test any rubbish as parameters
- $data = $this->_customdata;
- if ($data['authmethod'] == 'simple') {
- $mform->addElement('text', 'wsusername', 'wsusername');
- $mform->setType('wsusername', core_user::get_property_type('username'));
- $mform->addElement('text', 'wspassword', 'wspassword');
- $mform->setType('wspassword', core_user::get_property_type('password'));
- } else if ($data['authmethod'] == 'token') {
- $mform->addElement('text', 'token', 'token');
- $mform->setType('token', PARAM_RAW_TRIMMED);
- }
-
- $mform->addElement('hidden', 'authmethod', $data['authmethod']);
- $mform->setType('authmethod', core_user::get_property_type('auth'));
- $mform->addElement('text', 'groupids[0]', 'groupids[0]');
- $mform->addElement('text', 'groupids[1]', 'groupids[1]');
- $mform->addElement('text', 'groupids[2]', 'groupids[2]');
- $mform->addElement('text', 'groupids[3]', 'groupids[3]');
- $mform->setType('groupids', PARAM_INT);
-
- $mform->addElement('hidden', 'function');
- $mform->setType('function', PARAM_PLUGIN);
-
- $mform->addElement('hidden', 'protocol');
- $mform->setType('protocol', PARAM_ALPHA);
-
- $this->add_action_buttons(true, get_string('execute', 'webservice'));
- }
-
- public function get_params() {
- if (!$data = $this->get_data()) {
- return null;
- }
- // remove unused from form data
- unset($data->submitbutton);
- unset($data->protocol);
- unset($data->function);
- unset($data->wsusername);
- unset($data->wspassword);
- unset($data->token);
- unset($data->authmethod);
-
- $params = array();
- $params['groupids'] = array();
- for ($i=0; $i<10; $i++) {
- if (empty($data->groupids[$i])) {
- continue;
- }
- $params['groupids'][] = $data->groupids[$i];
- }
-
- return $params;
- }
-}
-
-class moodle_group_get_course_groups_form extends moodleform {
- public function definition() {
- global $CFG;
-
- $mform = $this->_form;
-
- $mform->addElement('header', 'wstestclienthdr', get_string('testclient', 'webservice'));
-
- //note: these values are intentionally PARAM_RAW - we want users to test any rubbish as parameters
- $data = $this->_customdata;
- if ($data['authmethod'] == 'simple') {
- $mform->addElement('text', 'wsusername', 'wsusername');
- $mform->setType('wsusername', core_user::get_property_type('username'));
- $mform->addElement('text', 'wspassword', 'wspassword');
- $mform->setType('wspassword', core_user::get_property_type('password'));
- } else if ($data['authmethod'] == 'token') {
- $mform->addElement('text', 'token', 'token');
- $mform->setType('token', PARAM_RAW_TRIMMED);
- }
-
- $mform->addElement('hidden', 'authmethod', $data['authmethod']);
- $mform->setType('authmethod', core_user::get_property_type('auth'));
- $mform->addElement('text', 'courseid', 'courseid');
-
- $mform->addElement('hidden', 'function');
- $mform->setType('function', PARAM_PLUGIN);
-
- $mform->addElement('hidden', 'protocol');
- $mform->setType('protocol', PARAM_ALPHA);
-
- $this->add_action_buttons(true, get_string('execute', 'webservice'));
- }
-
- public function get_params() {
- if (!$data = $this->get_data()) {
- return null;
- }
- // remove unused from form data
- unset($data->submitbutton);
- unset($data->protocol);
- unset($data->function);
- unset($data->wsusername);
- unset($data->wspassword);
- unset($data->token);
- unset($data->authmethod);
-
- $params = array();
- $params['courseid'] = $data->courseid;
-
- return $params;
- }
-}
-
-class moodle_group_delete_groups_form extends moodleform {
- public function definition() {
- global $CFG;
-
- $mform = $this->_form;
-
- $mform->addElement('header', 'wstestclienthdr', get_string('testclient', 'webservice'));
-
- //note: these values are intentionally PARAM_RAW - we want users to test any rubbish as parameters
- $data = $this->_customdata;
- if ($data['authmethod'] == 'simple') {
- $mform->addElement('text', 'wsusername', 'wsusername');
- $mform->setType('wsusername', core_user::get_property_type('username'));
- $mform->addElement('text', 'wspassword', 'wspassword');
- $mform->setType('wspassword', core_user::get_property_type('password'));
- } else if ($data['authmethod'] == 'token') {
- $mform->addElement('text', 'token', 'token');
- $mform->setType('token', PARAM_RAW_TRIMMED);
- }
-
- $mform->addElement('hidden', 'authmethod', $data['authmethod']);
- $mform->setType('authmethod', core_user::get_property_type('auth'));
- $mform->addElement('text', 'groupids[0]', 'groupids[0]');
- $mform->addElement('text', 'groupids[1]', 'groupids[1]');
- $mform->addElement('text', 'groupids[2]', 'groupids[2]');
- $mform->addElement('text', 'groupids[3]', 'groupids[3]');
- $mform->setType('groupids', PARAM_INT);
-
- $mform->addElement('hidden', 'function');
- $mform->setType('function', PARAM_PLUGIN);
-
- $mform->addElement('hidden', 'protocol');
- $mform->setType('protocol', PARAM_ALPHA);
-
- $mform->addElement('static', 'warning', '', get_string('executewarnign', 'webservice'));
-
- $this->add_action_buttons(true, get_string('execute', 'webservice'));
- }
-
- public function get_params() {
- if (!$data = $this->get_data()) {
- return null;
- }
- // remove unused from form data
- unset($data->submitbutton);
- unset($data->protocol);
- unset($data->function);
- unset($data->wsusername);
- unset($data->wspassword);
- unset($data->token);
- unset($data->authmethod);
-
- $params = array();
- $params['groupids'] = array();
- for ($i=0; $i<10; $i++) {
- if (empty($data->groupids[$i])) {
- continue;
- }
- $params['groupids'][] = $data->groupids[$i];
- }
-
- return $params;
- }
-}
-
-class moodle_group_get_groupmembers_form extends moodleform {
- public function definition() {
- global $CFG;
-
- $mform = $this->_form;
-
- $mform->addElement('header', 'wstestclienthdr', get_string('testclient', 'webservice'));
-
- //note: these values are intentionally PARAM_RAW - we want users to test any rubbish as parameters
- $data = $this->_customdata;
- if ($data['authmethod'] == 'simple') {
- $mform->addElement('text', 'wsusername', 'wsusername');
- $mform->setType('wsusername', core_user::get_property_type('username'));
- $mform->addElement('text', 'wspassword', 'wspassword');
- $mform->setType('wspassword', core_user::get_property_type('password'));
- } else if ($data['authmethod'] == 'token') {
- $mform->addElement('text', 'token', 'token');
- $mform->setType('token', PARAM_RAW_TRIMMED);
- }
-
- $mform->addElement('hidden', 'authmethod', $data['authmethod']);
- $mform->setType('authmethod', core_user::get_property_type('auth'));
- $mform->addElement('text', 'groupids[0]', 'groupids[0]');
- $mform->addElement('text', 'groupids[1]', 'groupids[1]');
- $mform->addElement('text', 'groupids[2]', 'groupids[2]');
- $mform->addElement('text', 'groupids[3]', 'groupids[3]');
- $mform->setType('groupids', PARAM_INT);
-
- $mform->addElement('hidden', 'function');
- $mform->setType('function', PARAM_PLUGIN);
-
- $mform->addElement('hidden', 'protocol');
- $mform->setType('protocol', PARAM_ALPHA);
-
- $this->add_action_buttons(true, get_string('execute', 'webservice'));
- }
-
- public function get_params() {
- if (!$data = $this->get_data()) {
- return null;
- }
- // remove unused from form data
- unset($data->submitbutton);
- unset($data->protocol);
- unset($data->function);
- unset($data->wsusername);
- unset($data->wspassword);
- unset($data->token);
- unset($data->authmethod);
-
- $params = array();
- $params['groupids'] = array();
- for ($i=0; $i<10; $i++) {
- if (empty($data->groupids[$i])) {
- continue;
- }
- $params['groupids'][] = $data->groupids[$i];
- }
-
- return $params;
- }
-}
-
-class moodle_group_add_groupmembers_form extends moodleform {
- public function definition() {
- global $CFG;
-
- $mform = $this->_form;
-
- $mform->addElement('header', 'wstestclienthdr', get_string('testclient', 'webservice'));
-
- //note: these values are intentionally PARAM_RAW - we want users to test any rubbish as parameters
- $data = $this->_customdata;
- if ($data['authmethod'] == 'simple') {
- $mform->addElement('text', 'wsusername', 'wsusername');
- $mform->setType('wsusername', core_user::get_property_type('username'));
- $mform->addElement('text', 'wspassword', 'wspassword');
- $mform->setType('wspassword', core_user::get_property_type('password'));
- } else if ($data['authmethod'] == 'token') {
- $mform->addElement('text', 'token', 'token');
- $mform->setType('token', PARAM_RAW_TRIMMED);
- }
-
- $mform->addElement('hidden', 'authmethod', $data['authmethod']);
- $mform->setType('authmethod', core_user::get_property_type('auth'));
- $mform->addElement('text', 'userid[0]', 'userid[0]');
- $mform->addElement('text', 'groupid[0]', 'groupid[0]');
- $mform->addElement('text', 'userid[1]', 'userid[1]');
- $mform->addElement('text', 'groupid[1]', 'groupid[1]');
- $mform->setType('userid', core_user::get_property_type('id'));
- $mform->setType('groupids', PARAM_INT);
-
- $mform->addElement('hidden', 'function');
- $mform->setType('function', PARAM_PLUGIN);
-
- $mform->addElement('hidden', 'protocol');
- $mform->setType('protocol', PARAM_ALPHA);
-
- $this->add_action_buttons(true, get_string('execute', 'webservice'));
- }
-
- public function get_params() {
- if (!$data = $this->get_data()) {
- return null;
- }
- // remove unused from form data
- unset($data->submitbutton);
- unset($data->protocol);
- unset($data->function);
- unset($data->wsusername);
- unset($data->wspassword);
- unset($data->token);
- unset($data->authmethod);
-
- $params = array();
- $params['members'] = array();
- for ($i=0; $i<10; $i++) {
- if (empty($data->groupid[$i]) or empty($data->userid[$i])) {
- continue;
- }
- $params['members'][] = array('userid'=>$data->userid[$i], 'groupid'=>$data->groupid[$i]);
- }
-
- return $params;
- }
-}
-
-class moodle_group_delete_groupmembers_form extends moodleform {
- public function definition() {
- global $CFG;
-
- $mform = $this->_form;
-
- $mform->addElement('header', 'wstestclienthdr', get_string('testclient', 'webservice'));
-
- //note: these values are intentionally PARAM_RAW - we want users to test any rubbish as parameters
- $data = $this->_customdata;
- if ($data['authmethod'] == 'simple') {
- $mform->addElement('text', 'wsusername', 'wsusername');
- $mform->setType('wsusername', core_user::get_property_type('username'));
- $mform->addElement('text', 'wspassword', 'wspassword');
- $mform->setType('wspassword', core_user::get_property_type('password'));
- } else if ($data['authmethod'] == 'token') {
- $mform->addElement('text', 'token', 'token');
- $mform->setType('token', PARAM_RAW_TRIMMED);
- }
-
- $mform->addElement('hidden', 'authmethod', $data['authmethod']);
- $mform->setType('authmethod', core_user::get_property_type('auth'));
- $mform->addElement('text', 'userid[0]', 'userid[0]');
- $mform->addElement('text', 'groupid[0]', 'groupid[0]');
- $mform->addElement('text', 'userid[1]', 'userid[1]');
- $mform->addElement('text', 'groupid[1]', 'groupid[1]');
- $mform->setType('userid', PARAM_INT);
- $mform->setType('groupids', PARAM_INT);
-
- $mform->addElement('hidden', 'function');
- $mform->setType('function', PARAM_PLUGIN);
-
- $mform->addElement('hidden', 'protocol');
- $mform->setType('protocol', PARAM_ALPHA);
-
- $this->add_action_buttons(true, get_string('execute', 'webservice'));
- }
-
- public function get_params() {
- if (!$data = $this->get_data()) {
- return null;
- }
- // remove unused from form data
- unset($data->submitbutton);
- unset($data->protocol);
- unset($data->function);
- unset($data->wsusername);
- unset($data->wspassword);
- unset($data->token);
- unset($data->authmethod);
-
- $params = array();
- $params['members'] = array();
- for ($i=0; $i<10; $i++) {
- if (empty($data->groupid[$i]) or empty($data->userid[$i])) {
- continue;
- }
- $params['members'][] = array('userid'=>$data->userid[$i], 'groupid'=>$data->groupid[$i]);
- }
-
- return $params;
- }
-}
-
/**
* Form class for create_categories() web service function test.
*
--- /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/>.
+
+/**
+ * LTI Authentication plugin.
+ *
+ * @package auth_lti
+ * @copyright 2016 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir.'/authlib.php');
+
+/**
+ * LTI Authentication plugin.
+ *
+ * @package auth_lti
+ * @copyright 2016 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class auth_plugin_lti extends auth_plugin_base {
+
+ /**
+ * Constructor.
+ */
+ public function __construct() {
+ $this->authtype = 'lti';
+ }
+
+ /**
+ * Users can not log in via the traditional login form.
+ *
+ * @param string $username The username
+ * @param string $password The password
+ * @return bool Authentication success or failure
+ */
+ public function user_login($username, $password) {
+ return false;
+ }
+}
--- /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/>.
+
+/**
+ * Strings for component 'auth_lti', language 'en'.
+ *
+ * @package auth_lti
+ * @copyright 2016 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$string['auth_ltidescription'] = 'The LTI authentication plugin enables your site to behave as an LTI provider - this plugin works in conjunction with the LTI enrolment plugin by allowing external users to access a course or individual activities.';
+$string['pluginname'] = 'LTI';
--- /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/>.
+
+/**
+ * LTI authentication plugin version information
+ *
+ * @package auth_lti
+ * @copyright 2016 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$plugin->version = 2016040800; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires = 2016040700; // Requires this Moodle version (3.1).
+$plugin->component = 'auth_lti'; // Full name of the plugin (used for diagnostics).
class backup_course_competencies_structure_step extends backup_structure_step {
protected function define_structure() {
+ $userinfo = $this->get_setting_value('users');
+
$wrapper = new backup_nested_element('course_competencies');
$settings = new backup_nested_element('settings', array('id'), array('pushratingstouserplans'));
$competencies = new backup_nested_element('competencies');
$wrapper->add_child($competencies);
- $competency = new backup_nested_element('competency', null, array('idnumber', 'ruleoutcome',
- 'sortorder', 'frameworkidnumber'));
+ $competency = new backup_nested_element('competency', null, array('id', 'idnumber', 'ruleoutcome',
+ 'sortorder', 'frameworkid', 'frameworkidnumber'));
$competencies->add_child($competency);
- $sql = 'SELECT c.idnumber, cc.ruleoutcome, cc.sortorder, f.idnumber AS frameworkidnumber
+ $sql = 'SELECT c.id, c.idnumber, cc.ruleoutcome, cc.sortorder, f.id AS frameworkid, f.idnumber AS frameworkidnumber
FROM {' . \core_competency\course_competency::TABLE . '} cc
JOIN {' . \core_competency\competency::TABLE . '} c ON c.id = cc.competencyid
JOIN {' . \core_competency\competency_framework::TABLE . '} f ON f.id = c.competencyframeworkid
ORDER BY cc.sortorder';
$competency->set_source_sql($sql, array('courseid' => backup::VAR_COURSEID));
+ $usercomps = new backup_nested_element('user_competencies');
+ $wrapper->add_child($usercomps);
+ if ($userinfo) {
+ $usercomp = new backup_nested_element('user_competency', null, array('userid', 'competencyid',
+ 'proficiency', 'grade'));
+ $usercomps->add_child($usercomp);
+
+ $sql = 'SELECT ucc.userid, ucc.competencyid, ucc.proficiency, ucc.grade
+ FROM {' . \core_competency\user_competency_course::TABLE . '} ucc
+ WHERE ucc.courseid = :courseid
+ AND ucc.grade IS NOT NULL';
+ $usercomp->set_source_sql($sql, array('courseid' => backup::VAR_COURSEID));
+ $usercomp->annotate_ids('user', 'userid');
+ }
+
return $wrapper;
}
}
* @return array
*/
protected function define_structure() {
+ $userinfo = $this->get_setting_value('users');
$paths = array(
new restore_path_element('course_competency', '/course_competencies/competencies/competency'),
- new restore_path_element('course_competency_settings', '/course_competencies/settings')
+ new restore_path_element('course_competency_settings', '/course_competencies/settings'),
);
+ if ($userinfo) {
+ $paths[] = new restore_path_element('user_competency_course',
+ '/course_competencies/user_competencies/user_competency');
+ }
return $paths;
}
*/
public function process_course_competency_settings($data) {
global $DB;
-
$data = (object) $data;
+
+ // We do not restore the course settings during merge.
+ $target = $this->get_task()->get_target();
+ if ($target == backup::TARGET_CURRENT_ADDING || $target == backup::TARGET_EXISTING_ADDING) {
+ return;
+ }
+
$courseid = $this->task->get_courseid();
- $exists = \core_competency\course_competency_settings::get_record(array('courseid' => $courseid));
+ $exists = \core_competency\course_competency_settings::record_exists_select('courseid = :courseid',
+ array('courseid' => $courseid));
- // Now update or insert.
+ // Strangely the course settings already exist, let's just leave them as is then.
if ($exists) {
- $settings = $exists;
- $settings->set_pushratingstouserplans($data->pushratingstouserplans);
- return $settings->update();
- } else {
- $data = (object) array('courseid' => $courseid, 'pushratingstouserplans' => $data->pushratingstouserplans);
- $settings = new \core_competency\course_competency_settings(0, $data);
- $result = $settings->create();
- return !empty($result);
+ $this->log('Course competency settings not restored, existing settings have been found.', backup::LOG_WARNING);
+ return;
}
+
+ $data = (object) array('courseid' => $courseid, 'pushratingstouserplans' => $data->pushratingstouserplans);
+ $settings = new \core_competency\course_competency_settings(0, $data);
+ $settings->create();
}
/**
if (!$competency) {
return;
}
+ $this->set_mapping(\core_competency\competency::TABLE, $data->id, $competency->get_id());
$params = array(
'competencyid' => $competency->get_id(),
}
}
+ /**
+ * Process the user competency course.
+ *
+ * @param array $data The data.
+ */
+ public function process_user_competency_course($data) {
+ global $USER, $DB;
+ $data = (object) $data;
+
+ $data->competencyid = $this->get_mappingid(\core_competency\competency::TABLE, $data->competencyid);
+ if (!$data->competencyid) {
+ // This is strange, the competency does not belong to the course.
+ return;
+ } else if ($data->grade === null) {
+ // We do not need to do anything when there is no grade.
+ return;
+ }
+
+ $data->userid = $this->get_mappingid('user', $data->userid);
+ $shortname = $DB->get_field('course', 'shortname', array('id' => $this->task->get_courseid()), MUST_EXIST);
+
+ // The method add_evidence also sets the course rating.
+ \core_competency\api::add_evidence($data->userid,
+ $data->competencyid,
+ $this->task->get_contextid(),
+ \core_competency\evidence::ACTION_OVERRIDE,
+ 'evidence_courserestored',
+ 'core_competency',
+ $shortname,
+ false,
+ null,
+ $data->grade,
+ $USER->id);
+ }
+
/**
* Execute conditions.
*
*/
protected function execute_condition() {
+ // Do not restore when competencies are disabled.
+ if (!\core_competency\api::is_enabled()) {
+ return false;
+ }
+
// Do not execute if the competencies XML file is not found.
$fullpath = $this->task->get_taskbasepath();
$fullpath = rtrim($fullpath, '/') . '/' . $this->filename;
*/
protected function execute_condition() {
+ // Do not restore when competencies are disabled.
+ if (!\core_competency\api::is_enabled()) {
+ return false;
+ }
+
// Do not execute if the competencies XML file is not found.
$fullpath = $this->task->get_taskbasepath();
$fullpath = rtrim($fullpath, '/') . '/' . $this->filename;
As a manager
I can add activities block in a course or on the frontpage
- Background:
- Given I log in as "admin"
- And I navigate to "Manage activities" node in "Site administration > Plugins > Activity modules"
- And I click on "//a[@title=\"Show\"]" "xpath_element" in the "Feedback" "table_row"
-
Scenario: Add activities block on the frontpage
Given the following "activities" exist:
| activity | name | intro | course | idnumber |
| wiki | Frontpage wiki name | Frontpage wiki description | Acceptance test site | wiki0 |
| workshop | Frontpage workshop name | Frontpage workshop description | Acceptance test site | workshop0 |
+ When I log in as "admin"
And I am on site homepage
- When I follow "Turn editing on"
+ And I follow "Turn editing on"
And I add the "Activities" block
And I click on "Assignments" "link" in the "Activities" "block"
Then I should see "Frontpage assignment name"
| wiki | Test wiki name | Test wiki description | C1 | wiki1 |
| workshop | Test workshop name | Test workshop description | C1 | workshop1 |
- When I follow "Courses"
+ When I log in as "admin"
+ And I follow "Courses"
And I follow "Course 1"
And I turn editing mode on
And I add the "Activities" block
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-if (is_file($CFG->dirroot.'/mod/feedback/lib.php')) {
- require_once($CFG->dirroot.'/mod/feedback/lib.php');
- define('FEEDBACK_BLOCK_LIB_IS_OK', true);
-}
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->dirroot . '/mod/feedback/lib.php');
class block_feedback extends block_list {
$this->content->icons = array();
$this->content->footer = '';
- if (!defined('FEEDBACK_BLOCK_LIB_IS_OK')) {
- $this->content->items = array(get_string('missing_feedback_module', 'block_feedback'));
- return $this->content;
- }
-
$courseid = $this->page->course->id;
if ($courseid <= 0) {
$courseid = SITEID;
function xmldb_block_feedback_install() {
global $DB;
-/// Disable this block by default (because Feedback is not technically part of 2.0)
- $DB->set_field('block', 'visible', 0, array('name'=>'feedback'));
-
}
$string['feedback'] = 'Feedback';
$string['feedback:addinstance'] = 'Add a new feedback block';
-$string['missing_feedback_module'] = 'This blocks relies on the Feedback activity module, but that module is not present!';
$string['pluginname'] = 'Feedback';
* @return array
*/
protected static function get_prefixed_keys(Memcached $connection, $prefix) {
+ $connkeys = $connection->getAllKeys();
+ if (empty($connkeys)) {
+ return array();
+ }
+
$keys = array();
$start = strlen($prefix);
- foreach ($connection->getAllKeys() as $key) {
+ foreach ($connkeys as $key) {
if (strpos($key, $prefix) === 0) {
$keys[] = substr($key, $start);
}
} else if (!preg_match('@/([0-9]+/)+@', $value)) {
// The format of the path is not correct.
return new lang_string('invaliddata', 'error');
-
- } else if ((substr_count($value, '/') - 1) > competency_framework::get_taxonomies_max_level()) {
- // Validate the depth of the path.
- return new lang_string('invaliddata', 'error');
}
return true;
return $rules;
}
+ /**
+ * Return the current depth of a competency framework.
+ *
+ * @param int $frameworkid The framework ID.
+ * @return int
+ */
+ public static function get_framework_depth($frameworkid) {
+ global $DB;
+ $totallength = $DB->sql_length('path');
+ $trimmedlength = $DB->sql_length("REPLACE(path, '/', '')");
+ $sql = "SELECT ($totallength - $trimmedlength - 1) AS depth
+ FROM {" . self::TABLE . "}
+ WHERE competencyframeworkid = :id
+ ORDER BY depth DESC";
+ $record = $DB->get_record_sql($sql, array('id' => $frameworkid), IGNORE_MULTIPLE);
+ if (!$record) {
+ $depth = 0;
+ } else {
+ $depth = $record->depth;
+ }
+ return $depth;
+ }
+
/**
* Build a framework tree with competency nodes.
*
}
+ /**
+ * Return the current depth of a competency framework.
+ *
+ * @see competency::get_framework_depth()
+ * @return int
+ */
+ public function get_depth() {
+ return competency::get_framework_depth($this->get_id());
+ }
+
/**
* Return the scale.
*
unset($taxonomies[0]);
// Ensure that we do not return empty levels.
- for ($i = 1; $i <= self::get_taxonomies_max_level(); $i++) {
- if (empty($taxonomies[$i])) {
+ foreach ($taxonomies as $i => $taxonomy) {
+ if (empty($taxonomy)) {
$taxonomies[$i] = self::TAXONOMY_COMPETENCY;
}
}
protected function validate_taxonomies($value) {
$terms = explode(',', $value);
- if (count($terms) > self::get_taxonomies_max_level()) {
- return new lang_string('invaliddata', 'error');
- }
-
foreach ($terms as $term) {
if (!empty($term) && !array_key_exists($term, self::get_taxonomies_list())) {
return new lang_string('invalidtaxonomy', 'core_competency', $term);
return self::get_taxonomies_list()[$constant];
}
- /**
- * Return the maximum number of taxonomy levels.
- *
- * This is a method and not a constant because we want to make it easy to adapt
- * to the number of levels desired in the future.
- *
- * @return int
- */
- public static function get_taxonomies_max_level() {
- return 6;
- }
-
/**
* Get the list of all taxonomies.
*
--- /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/>.
+
+/**
+ * Competency tests.
+ *
+ * @package core_competency
+ * @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;
+
+use core_competency\competency;
+
+/**
+ * Competency testcase.
+ *
+ * @package core_competency
+ * @copyright 2016 Frédéric Massart - FMCorz.net
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_competency_competency_testcase extends advanced_testcase {
+
+ public function test_get_framework_depth() {
+ $this->resetAfterTest();
+
+ $ccg = $this->getDataGenerator()->get_plugin_generator('core_competency');
+ $f1 = $ccg->create_framework();
+ $f2 = $ccg->create_framework();
+ $f3 = $ccg->create_framework();
+ $f4 = $ccg->create_framework();
+
+ $f1c1 = $ccg->create_competency(['competencyframeworkid' => $f1->get_id()]);
+ $f1c11 = $ccg->create_competency(['competencyframeworkid' => $f1->get_id(), 'parentid' => $f1c1->get_id()]);
+ $f1c111 = $ccg->create_competency(['competencyframeworkid' => $f1->get_id(), 'parentid' => $f1c11->get_id()]);
+ $f1c1111 = $ccg->create_competency(['competencyframeworkid' => $f1->get_id(), 'parentid' => $f1c111->get_id()]);
+
+ $f2c1 = $ccg->create_competency(['competencyframeworkid' => $f2->get_id()]);
+ $f2c2 = $ccg->create_competency(['competencyframeworkid' => $f2->get_id()]);
+ $f2c21 = $ccg->create_competency(['competencyframeworkid' => $f2->get_id(), 'parentid' => $f2c2->get_id()]);
+ $f2c22 = $ccg->create_competency(['competencyframeworkid' => $f2->get_id(), 'parentid' => $f2c2->get_id()]);
+ $f2c211 = $ccg->create_competency(['competencyframeworkid' => $f2->get_id(), 'parentid' => $f2c21->get_id()]);
+ $f2c221 = $ccg->create_competency(['competencyframeworkid' => $f2->get_id(), 'parentid' => $f2c22->get_id()]);
+ $f2c222 = $ccg->create_competency(['competencyframeworkid' => $f2->get_id(), 'parentid' => $f2c22->get_id()]);
+ $f2c223 = $ccg->create_competency(['competencyframeworkid' => $f2->get_id(), 'parentid' => $f2c22->get_id()]);
+ $f2c3 = $ccg->create_competency(['competencyframeworkid' => $f2->get_id()]);
+
+ $f3c1 = $ccg->create_competency(['competencyframeworkid' => $f3->get_id()]);
+
+ $this->assertEquals(4, competency::get_framework_depth($f1->get_id()));
+ $this->assertEquals(3, competency::get_framework_depth($f2->get_id()));
+ $this->assertEquals(1, competency::get_framework_depth($f3->get_id()));
+ $this->assertEquals(0, competency::get_framework_depth($f4->get_id()));
+ }
+
+}
// Reopening plan: with due date in the future => duedate unchanged.
$record = $plan->to_record();
$record->status = plan::STATUS_COMPLETE;
- $record->duedate = time() + plan::DUEDATE_THRESHOLD + 10;
+ $duedate = time() + plan::DUEDATE_THRESHOLD + 10;
+ $record->duedate = $duedate;
$DB->update_record(plan::TABLE, $record);
$success = core_competency\api::reopen_plan($plan->get_id());
$this->assertTrue($success);
$plan->read();
- // Check that the due date has not changed, but allow for PHP Unit latency.
- $this->assertTrue($plan->get_duedate() >= time() + plan::DUEDATE_THRESHOLD + 10);
- $this->assertTrue($plan->get_duedate() <= time() + plan::DUEDATE_THRESHOLD + 15);
+ // Check that the due date has not changed.
+ $this->assertNotEquals(0, $plan->get_duedate());
+ $this->assertEquals($duedate, $plan->get_duedate());
}
public function test_get_by_user_and_competency() {
}
}
-
-/**
- * Deprecated course external functions
- *
- * @package core_course
- * @copyright 2009 Petr Skodak
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- * @since Moodle 2.0
- * @deprecated Moodle 2.2 MDL-29106 - Please do not use this class any more.
- * @see core_course_external
- */
-class moodle_course_external extends external_api {
-
- /**
- * Returns description of method parameters
- *
- * @return external_function_parameters
- * @since Moodle 2.0
- * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
- * @see core_course_external::get_courses_parameters()
- */
- public static function get_courses_parameters() {
- return core_course_external::get_courses_parameters();
- }
-
- /**
- * Get courses
- *
- * @param array $options
- * @return array
- * @since Moodle 2.0
- * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
- * @see core_course_external::get_courses()
- */
- public static function get_courses($options) {
- return core_course_external::get_courses($options);
- }
-
- /**
- * Returns description of method result value
- *
- * @return external_description
- * @since Moodle 2.0
- * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
- * @see core_course_external::get_courses_returns()
- */
- public static function get_courses_returns() {
- return core_course_external::get_courses_returns();
- }
-
- /**
- * Marking the method as deprecated.
- *
- * @return bool
- */
- public static function get_courses_is_deprecated() {
- return true;
- }
-
- /**
- * Returns description of method parameters
- *
- * @return external_function_parameters
- * @since Moodle 2.0
- * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
- * @see core_course_external::create_courses_parameters()
- */
- public static function create_courses_parameters() {
- return core_course_external::create_courses_parameters();
- }
-
- /**
- * Create courses
- *
- * @param array $courses
- * @return array courses (id and shortname only)
- * @since Moodle 2.0
- * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
- * @see core_course_external::create_courses()
- */
- public static function create_courses($courses) {
- return core_course_external::create_courses($courses);
- }
-
- /**
- * Returns description of method result value
- *
- * @return external_description
- * @since Moodle 2.0
- * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
- * @see core_course_external::create_courses_returns()
- */
- public static function create_courses_returns() {
- return core_course_external::create_courses_returns();
- }
-
- /**
- * Marking the method as deprecated.
- *
- * @return bool
- */
- public static function create_courses_is_deprecated() {
- return true;
- }
-}
$courseid = required_param('courseid', PARAM_INT);
$type = required_param('type', PARAM_COMPONENT);
$instanceid = optional_param('id', 0, PARAM_INT);
-
+$return = optional_param('returnurl', 0, PARAM_LOCALURL);
$course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
$context = context_course::instance($course->id, MUST_EXIST);
$PAGE->set_url('/enrol/editinstance.php', array('courseid' => $course->id, 'id' => $instanceid, 'type' => $type));
$PAGE->set_pagelayout('admin');
-$return = new moodle_url('/enrol/instances.php', array('id' => $course->id));
+if (empty($return)) {
+ $return = new moodle_url('/enrol/instances.php', array('id' => $course->id));
+}
+
if (!enrol_is_enabled($type)) {
redirect($return);
}
$instance->status = ENROL_INSTANCE_ENABLED; // Do not use default for automatically created instances here.
}
-$mform = new enrol_instance_edit_form(null, array($instance, $plugin, $context, $type));
+$mform = new enrol_instance_edit_form(null, array($instance, $plugin, $context, $type, $return));
if ($mform->is_cancelled()) {
redirect($return);
$mform = $this->_form;
- list($instance, $plugin, $context, $type) = $this->_customdata;
+ list($instance, $plugin, $context, $type, $returnurl) = $this->_customdata;
$mform->addElement('header', 'header', get_string('pluginname', 'enrol_' . $type));
$mform->setType('type', PARAM_COMPONENT);
$instance->type = $type;
+ $mform->addElement('hidden', 'returnurl');
+ $mform->setType('returnurl', PARAM_LOCALURL);
+ $mform->setConstant('returnurl', $returnurl);
+
$this->add_action_buttons(true, ($instance->id ? null : get_string('addinstance', 'enrol')));
$this->set_data($instance);
return null;
}
}
-
-
-/**
- * Deprecated enrol and role external functions
- *
- * @package core_enrol
- * @copyright 2010 Jerome Mouneyrac
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- * @since Moodle 2.0
- * @deprecated Moodle 2.2 MDL-29106 - Please do not use this class any more.
- * @see core_enrol_external
- * @see core_role_external
- */
-class moodle_enrol_external extends external_api {
-
-
- /**
- * Returns description of method parameters
- *
- * @return external_function_parameters
- * @since Moodle 2.0
- * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
- * @see core_enrol_external::get_enrolled_users_parameters()
- */
- public static function get_enrolled_users_parameters() {
- return new external_function_parameters(
- array(
- 'courseid' => new external_value(PARAM_INT, 'Course id'),
- 'withcapability' => new external_value(PARAM_CAPABILITY, 'User should have this capability', VALUE_DEFAULT, null),
- 'groupid' => new external_value(PARAM_INT, 'Group id, null means all groups', VALUE_DEFAULT, null),
- 'onlyactive' => new external_value(PARAM_INT, 'True means only active, false means all participants', VALUE_DEFAULT, 0),
- )
- );
- }
-
- /**
- * Get list of course participants.
- *
- * @param int $courseid
- * @param text $withcapability
- * @param int $groupid
- * @param bool $onlyactive
- * @return array of course participants
- * @since Moodle 2.0
- * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
- * @see core_enrol_external::get_enrolled_users()
- */
- public static function get_enrolled_users($courseid, $withcapability = null, $groupid = null, $onlyactive = false) {
- global $DB, $CFG, $USER;
-
- // Do basic automatic PARAM checks on incoming data, using params description
- // If any problems are found then exceptions are thrown with helpful error messages
- $params = self::validate_parameters(self::get_enrolled_users_parameters(), array(
- 'courseid'=>$courseid,
- 'withcapability'=>$withcapability,
- 'groupid'=>$groupid,
- 'onlyactive'=>$onlyactive)
- );
-
- $coursecontext = context_course::instance($params['courseid'], IGNORE_MISSING);
- if ($courseid == SITEID) {
- $context = context_system::instance();
- } else {
- $context = $coursecontext;
- }
-
- try {
- self::validate_context($context);
- } catch (Exception $e) {
- $exceptionparam = new stdClass();
- $exceptionparam->message = $e->getMessage();
- $exceptionparam->courseid = $params['courseid'];
- throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
- }
-
- if ($courseid == SITEID) {
- require_capability('moodle/site:viewparticipants', $context);
- } else {
- require_capability('moodle/course:viewparticipants', $context);
- }
-
- if ($withcapability) {
- require_capability('moodle/role:review', $coursecontext);
- }
- if ($groupid && groups_is_member($groupid)) {
- require_capability('moodle/site:accessallgroups', $coursecontext);
- }
- if ($onlyactive) {
- require_capability('moodle/course:enrolreview', $coursecontext);
- }
-
- list($sqlparams, $params) = get_enrolled_sql($coursecontext, $withcapability, $groupid, $onlyactive);
- $sql = "SELECT ue.userid, e.courseid, u.firstname, u.lastname, u.username, c.id as usercontextid
- FROM {user_enrolments} ue
- JOIN {enrol} e ON (e.id = ue.enrolid)
- JOIN {user} u ON (ue.userid = u.id)
- JOIN {context} c ON (u.id = c.instanceid AND contextlevel = " . CONTEXT_USER . ")
- WHERE e.courseid = :courseid AND ue.userid IN ($sqlparams)
- GROUP BY ue.userid, e.courseid, u.firstname, u.lastname, u.username, c.id";
- $params['courseid'] = $courseid;
- $enrolledusers = $DB->get_records_sql($sql, $params);
- $result = array();
- $isadmin = is_siteadmin($USER);
- $canviewfullnames = has_capability('moodle/site:viewfullnames', $context);
- foreach ($enrolledusers as $enrolleduser) {
- $profilimgurl = moodle_url::make_pluginfile_url($enrolleduser->usercontextid, 'user', 'icon', NULL, '/', 'f1');
- $profilimgurlsmall = moodle_url::make_pluginfile_url($enrolleduser->usercontextid, 'user', 'icon', NULL, '/', 'f2');
- $resultuser = array(
- 'courseid' => $enrolleduser->courseid,
- 'userid' => $enrolleduser->userid,
- 'fullname' => fullname($enrolleduser),
- 'profileimgurl' => $profilimgurl->out(false),
- 'profileimgurlsmall' => $profilimgurlsmall->out(false)
- );
- // check if we can return username
- if ($isadmin) {
- $resultuser['username'] = $enrolleduser->username;
- }
- // check if we can return first and last name
- if ($isadmin or $canviewfullnames) {
- $resultuser['firstname'] = $enrolleduser->firstname;
- $resultuser['lastname'] = $enrolleduser->lastname;
- }
- $result[] = $resultuser;
- }
-
- return $result;
- }
-
- /**
- * Returns description of method result value
- *
- * @return external_description
- * @since Moodle 2.0
- * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
- * @see core_enrol_external::get_enrolled_users_returns()
- */
- public static function get_enrolled_users_returns() {
- return new external_multiple_structure(
- new external_single_structure(
- array(
- 'courseid' => new external_value(PARAM_INT, 'id of course'),
- 'userid' => new external_value(PARAM_INT, 'id of user'),
- 'firstname' => new external_value(PARAM_RAW, 'first name of user', VALUE_OPTIONAL),
- 'lastname' => new external_value(PARAM_RAW, 'last name of user', VALUE_OPTIONAL),
- 'fullname' => new external_value(PARAM_RAW, 'fullname of user'),
- 'username' => new external_value(PARAM_RAW, 'username of user', VALUE_OPTIONAL),
- 'profileimgurl' => new external_value(PARAM_URL, 'url of the profile image'),
- 'profileimgurlsmall' => new external_value(PARAM_URL, 'url of the profile image (small version)')
- )
- )
- );
- }
-
- /**
- * Marking the method as deprecated.
- *
- * @return bool
- */
- public static function get_enrolled_users_is_deprecated() {
- return true;
- }
-
- /**
- * Returns description of method parameters
- *
- * @return external_function_parameters
- * @since Moodle 2.1
- * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
- * @see core_enrol_external::get_users_courses_parameters()
- */
- public static function get_users_courses_parameters() {
- return core_enrol_external::get_users_courses_parameters();
- }
-
- /**
- * Get list of courses user is enrolled in (only active enrolments are returned).
- * Please note the current user must be able to access the course, otherwise the course is not included.
- *
- * @param int $userid
- * @return array of courses
- * @since Moodle 2.1
- * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
- * @see use core_enrol_external::get_users_courses()
- */
- public static function get_users_courses($userid) {
- return core_enrol_external::get_users_courses($userid);
- }
-
- /**
- * Returns description of method result value
- *
- * @return external_description
- * @since Moodle 2.1
- * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
- * @see core_enrol_external::get_users_courses_returns()
- */
- public static function get_users_courses_returns() {
- return core_enrol_external::get_users_courses_returns();
- }
-
- /**
- * Marking the method as deprecated.
- *
- * @return bool
- */
- public static function get_users_courses_is_deprecated() {
- return true;
- }
-
- /**
- * Returns description of method parameters
- *
- * @return external_function_parameters
- * @since Moodle 2.0
- * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
- * @see core_role_external::assign_roles_parameters()
- */
- public static function role_assign_parameters() {
- return core_role_external::assign_roles_parameters();
- }
-
- /**
- * Manual role assignments to users
- *
- * @param array $assignments An array of manual role assignment
- * @since Moodle 2.0
- * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
- * @see core_role_external::assign_roles()
- */
- public static function role_assign($assignments) {
- return core_role_external::assign_roles($assignments);
- }
-
- /**
- * Returns description of method result value
- *
- * @return null
- * @since Moodle 2.0
- * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
- * @see core_role_external::assign_roles_returns()
- */
- public static function role_assign_returns() {
- return core_role_external::assign_roles_returns();
- }
-
- /**
- * Marking the method as deprecated.
- *
- * @return bool
- */
- public static function role_assign_is_deprecated() {
- return true;
- }
-
- /**
- * Returns description of method parameters
- *
- * @return external_function_parameters
- * @since Moodle 2.0
- * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
- * @see core_role_external::unassign_roles_parameters()
- */
- public static function role_unassign_parameters() {
- return core_role_external::unassign_roles_parameters();
- }
-
- /**
- * Unassign roles from users
- *
- * @param array $unassignments An array of unassignment
- * @since Moodle 2.0
- * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
- * @see core_role_external::unassign_roles()
- */
- public static function role_unassign($unassignments) {
- return core_role_external::unassign_roles($unassignments);
- }
-
- /**
- * Returns description of method result value
- *
- * @return null
- * @since Moodle 2.0
- * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
- * @see core_role_external::unassign_roles_returns()
- */
- public static function role_unassign_returns() {
- return core_role_external::unassign_roles_returns();
- }
-
- /**
- * Marking the method as deprecated.
- *
- * @return bool
- */
- public static function role_unassign_is_deprecated() {
- return true;
- }
-}
--- /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/>.
+
+/**
+ * Defines the backup_enrol_lti_plugin class.
+ *
+ * @package enrol_lti
+ * @copyright 2016 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Define all the backup steps.
+ *
+ * @package enrol_lti
+ * @copyright 2016 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class backup_enrol_lti_plugin extends backup_enrol_plugin {
+
+ /**
+ * Defines the other LTI enrolment structures to append.
+ *
+ * @return backup_plugin_element
+ */
+ public function define_enrol_plugin_structure() {
+ // Get the parent we will be adding these elements to.
+ $plugin = $this->get_plugin_element();
+
+ // Define our elements.
+ $tool = new backup_nested_element('tool', array('id'), array(
+ 'enrolid', 'contextid', 'institution', 'lang', 'timezone', 'maxenrolled', 'maildisplay', 'city',
+ 'country', 'gradesync', 'gradesynccompletion', 'membersync', 'membersyncmode', 'roleinstructor',
+ 'rolelearner', 'secret', 'timecreated', 'timemodified'));
+
+ $users = new backup_nested_element('users');
+
+ $user = new backup_nested_element('user', array('id'), array(
+ 'userid', 'toolid', 'serviceurl', 'sourceid', 'consumerkey', 'consumersecret', 'membershipurl',
+ 'membershipsid'));
+
+ // Build elements hierarchy.
+ $plugin->add_child($tool);
+ $tool->add_child($users);
+ $users->add_child($user);
+
+ // Set sources to populate the data.
+ $tool->set_source_table('enrol_lti_tools',
+ array('enrolid' => backup::VAR_PARENTID));
+
+ // Users are only added only if users included.
+ if ($this->task->get_setting_value('users')) {
+ $user->set_source_table('enrol_lti_users', array('toolid' => backup::VAR_PARENTID));
+ }
+ }
+}
--- /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/>.
+
+/**
+ * Defines the restore_enrol_lti_plugin class.
+ *
+ * @package enrol_lti
+ * @copyright 2016 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Define all the restore steps.
+ *
+ * @package enrol_lti
+ * @copyright 2016 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class restore_enrol_lti_plugin extends restore_enrol_plugin {
+
+ /**
+ * @var array $tools Stores the IDs of the newly created tools.
+ */
+ protected $tools = array();
+
+ /**
+ * Declares the enrol LTI XML paths attached to the enrol element
+ *
+ * @return array of {@link restore_path_element}
+ */
+ protected function define_enrol_plugin_structure() {
+
+ $paths = array();
+ $paths[] = new restore_path_element('enrol_lti_tool', $this->connectionpoint->get_path() . '/tool');
+ $paths[] = new restore_path_element('enrol_lti_users', $this->connectionpoint->get_path() . '/tool/users/user');
+
+ return $paths;
+ }
+
+ /**
+ * Processes LTI tools element data
+ *
+ * @param array|stdClass $data
+ */
+ public function process_enrol_lti_tool($data) {
+ global $DB;
+
+ $data = (object) $data;
+
+ // Store the old id.
+ $oldid = $data->id;
+
+ // Change the values before we insert it.
+ $data->timecreated = time();
+ $data->timemodified = $data->timecreated;
+
+ // Now we can insert the new record.
+ $data->id = $DB->insert_record('enrol_lti_tools', $data);
+
+ // Add the array of tools we need to process later.
+ $this->tools[$data->id] = $data;
+
+ // Set up the mapping.
+ $this->set_mapping('enrol_lti_tool', $oldid, $data->id);
+ }
+
+ /**
+ * Processes LTI users element data
+ *
+ * @param array|stdClass $data The data to insert as a comment
+ */
+ public function process_enrol_lti_users($data) {
+ global $DB;
+
+ $data = (object) $data;
+
+ $data->userid = $this->get_mappingid('user', $data->userid);
+ $data->toolid = $this->get_mappingid('enrol_lti_tool', $data->toolid);
+ $data->timecreated = time();
+
+ $DB->insert_record('enrol_lti_users', $data);
+ }
+
+ /**
+ * This function is executed after all the tasks in the plan have been finished.
+ * This must be done here because the activities have not been restored yet.
+ */
+ public function after_restore_enrol() {
+ global $DB;
+
+ // Need to go through and change the values.
+ foreach ($this->tools as $tool) {
+ $updatetool = new stdClass();
+ $updatetool->id = $tool->id;
+ $updatetool->enrolid = $this->get_mappingid('enrol', $tool->enrolid);
+ $updatetool->contextid = $this->get_mappingid('context', $tool->contextid);
+ $DB->update_record('enrol_lti_tools', $updatetool);
+ }
+ }
+}
--- /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/>.
+
+/**
+ * LTI enrolment plugin helper.
+ *
+ * @package enrol_lti
+ * @copyright 2016 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace enrol_lti;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * LTI enrolment plugin helper class.
+ *
+ * @package enrol_lti
+ * @copyright 2016 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class helper {
+ /*
+ * The value used when we want to enrol new members and unenrol old ones.
+ */
+ const MEMBER_SYNC_ENROL_AND_UNENROL = 1;
+
+ /*
+ * The value used when we want to enrol new members only.
+ */
+ const MEMBER_SYNC_ENROL_NEW = 2;
+
+ /*
+ * The value used when we want to unenrol missing users.
+ */
+ const MEMBER_SYNC_UNENROL_MISSING = 3;
+
+ /**
+ * Code for when an enrolment was successful.
+ */
+ const ENROLMENT_SUCCESSFUL = true;
+
+ /**
+ * Error code for enrolment when max enrolled reached.
+ */
+ const ENROLMENT_MAX_ENROLLED = 'maxenrolledreached';
+
+ /**
+ * Error code for enrolment has not started.
+ */
+ const ENROLMENT_NOT_STARTED = 'enrolmentnotstarted';
+
+ /**
+ * Error code for enrolment when enrolment has finished.
+ */
+ const ENROLMENT_FINISHED = 'enrolmentfinished';
+
+ /**
+ * Error code for when an image file fails to upload.
+ */
+ const PROFILE_IMAGE_UPDATE_SUCCESSFUL = true;
+
+ /**
+ * Error code for when an image file fails to upload.
+ */
+ const PROFILE_IMAGE_UPDATE_FAILED = 'profileimagefailed';
+
+ /**
+ * Creates a unique username.
+ *
+ * @param string $consumerkey Consumer key
+ * @param string $ltiuserid External tool user id
+ * @return string The new username
+ */
+ public static function create_username($consumerkey, $ltiuserid) {
+ if (!empty($ltiuserid) && !empty($consumerkey)) {
+ $userkey = $consumerkey . ':' . $ltiuserid;
+ } else {
+ $userkey = false;
+ }
+
+ return 'enrol_lti' . sha1($consumerkey . '::' . $userkey);
+ }
+
+ /**
+ * Adds default values for the user object based on the tool provided.
+ *
+ * @param \stdClass $tool
+ * @param \stdClass $user
+ * @return \stdClass The $user class with added default values
+ */
+ public static function assign_user_tool_data($tool, $user) {
+ global $CFG;
+
+ $user->city = (!empty($tool->city)) ? $tool->city : "";
+ $user->country = (!empty($tool->country)) ? $tool->country : "";
+ $user->institution = (!empty($tool->institution)) ? $tool->institution : "";
+ $user->timezone = (!empty($tool->timezone)) ? $tool->timezone : "";
+ if (isset($tool->maildisplay)) {
+ $user->maildisplay = $tool->maildisplay;
+ } else if (isset($CFG->defaultpreference_maildisplay)) {
+ $user->maildisplay = $CFG->defaultpreference_maildisplay;
+ } else {
+ $user->maildisplay = 2;
+ }
+ $user->mnethostid = $CFG->mnet_localhost_id;
+ $user->confirmed = 1;
+ $user->lang = $tool->lang;
+
+ return $user;
+ }
+
+ /**
+ * Compares two users.
+ *
+ * @param \stdClass $newuser The new user
+ * @param \stdClass $olduser The old user
+ * @return bool True if both users are the same
+ */
+ public static function user_match($newuser, $olduser) {
+ if ($newuser->firstname != $olduser->firstname) {
+ return false;
+ }
+ if ($newuser->lastname != $olduser->lastname) {
+ return false;
+ }
+ if ($newuser->email != $olduser->email) {
+ return false;
+ }
+ if ($newuser->city != $olduser->city) {
+ return false;
+ }
+ if ($newuser->country != $olduser->country) {
+ return false;
+ }
+ if ($newuser->institution != $olduser->institution) {
+ return false;
+ }
+ if ($newuser->timezone != $olduser->timezone) {
+ return false;
+ }
+ if ($newuser->maildisplay != $olduser->maildisplay) {
+ return false;
+ }
+ if ($newuser->mnethostid != $olduser->mnethostid) {
+ return false;
+ }
+ if ($newuser->confirmed != $olduser->confirmed) {
+ return false;
+ }
+ if ($newuser->lang != $olduser->lang) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Updates the users profile image.
+ *
+ * @param int $userid the id of the user
+ * @param string $url the url of the image
+ * @return bool|string true if successful, else a string explaining why it failed
+ */
+ public static function update_user_profile_image($userid, $url) {
+ global $CFG, $DB;
+
+ require_once($CFG->libdir . '/filelib.php');
+ require_once($CFG->libdir . '/gdlib.php');
+
+ $fs = get_file_storage();
+
+ $context = \context_user::instance($userid, MUST_EXIST);
+ $fs->delete_area_files($context->id, 'user', 'newicon');
+
+ $filerecord = array(
+ 'contextid' => $context->id,
+ 'component' => 'user',
+ 'filearea' => 'newicon',
+ 'itemid' => 0,
+ 'filepath' => '/'
+ );
+
+ $urlparams = array(
+ 'calctimeout' => false,
+ 'timeout' => 5,
+ 'skipcertverify' => true,
+ 'connecttimeout' => 5
+ );
+
+ if (!$iconfiles = $fs->create_file_from_url($filerecord, $url, $urlparams)) {
+ return self::PROFILE_IMAGE_UPDATE_FAILED;
+ }
+
+ $iconfile = $fs->get_area_files($context->id, 'user', 'newicon', false, 'itemid', false);
+
+ // There should only be one.
+ $iconfile = reset($iconfile);
+
+ // Something went wrong while creating temp file - remove the uploaded file.
+ if (!$iconfile = $iconfile->copy_content_to_temp()) {
+ $fs->delete_area_files($context->id, 'user', 'newicon');
+ return self::PROFILE_IMAGE_UPDATE_FAILED;
+ }
+
+ // Copy file to temporary location and the send it for processing icon.
+ $newpicture = (int) process_new_icon($context, 'user', 'icon', 0, $iconfile);
+ // Delete temporary file.
+ @unlink($iconfile);
+ // Remove uploaded file.
+ $fs->delete_area_files($context->id, 'user', 'newicon');
+ // Set the user's picture.
+ $DB->set_field('user', 'picture', $newpicture, array('id' => $userid));
+ return self::PROFILE_IMAGE_UPDATE_SUCCESSFUL;
+ }
+
+ /**
+ * Enrol a user in a course.
+ *
+ * @param \stdclass $tool The tool object (retrieved using self::get_lti_tool() or self::get_lti_tools())
+ * @param int $userid The user id
+ * @return bool|string returns true if successful, else an error code
+ */
+ public static function enrol_user($tool, $userid) {
+ global $DB;
+
+ // Check if the user enrolment exists.
+ if (!$DB->record_exists('user_enrolments', array('enrolid' => $tool->enrolid, 'userid' => $userid))) {
+ // Check if the maximum enrolled limit has been met.
+ if ($tool->maxenrolled) {
+ if ($DB->count_records('user_enrolments', array('enrolid' => $tool->enrolid)) >= $tool->maxenrolled) {
+ return self::ENROLMENT_MAX_ENROLLED;
+ }
+ }
+ // Check if the enrolment has not started.
+ if ($tool->enrolstartdate && time() < $tool->enrolstartdate) {
+ return self::ENROLMENT_NOT_STARTED;
+ }
+ // Check if the enrolment has finished.
+ if ($tool->enrolenddate && time() > $tool->enrolenddate) {
+ return self::ENROLMENT_FINISHED;
+ }
+
+ $timeend = 0;
+ if ($tool->enrolperiod) {
+ $timeend = time() + $tool->enrolperiod;
+ }
+
+ // Finally, enrol the user.
+ $instance = new \stdClass();
+ $instance->id = $tool->enrolid;
+ $instance->courseid = $tool->courseid;
+ $instance->enrol = 'lti';
+ $instance->status = $tool->status;
+ $ltienrol = enrol_get_plugin('lti');
+ $ltienrol->enrol_user($instance, $userid, null, time(), $timeend);
+ }
+
+ return self::ENROLMENT_SUCCESSFUL;
+ }
+
+ /**
+ * Returns the LTI tool.
+ *
+ * @param int $toolid
+ * @return \stdClass the tool
+ */
+ public static function get_lti_tool($toolid) {
+ global $DB;
+
+ $sql = "SELECT elt.*, e.name, e.courseid, e.status, e.enrolstartdate, e.enrolenddate, e.enrolperiod
+ FROM {enrol_lti_tools} elt
+ JOIN {enrol} e
+ ON elt.enrolid = e.id
+ WHERE elt.id = :tid";
+
+ return $DB->get_record_sql($sql, array('tid' => $toolid), MUST_EXIST);
+ }
+
+ /**
+ * Returns the LTI tools requested.
+ *
+ * @param array $params The list of SQL params (eg. array('columnname' => value, 'columnname2' => value)).
+ * @param int $limitfrom return a subset of records, starting at this point (optional).
+ * @param int $limitnum return a subset comprising this many records in total
+ * @return array of tools
+ */
+ public static function get_lti_tools($params = array(), $limitfrom = 0, $limitnum = 0) {
+ global $DB;
+
+ $sql = "SELECT elt.*, e.name, e.courseid, e.status, e.enrolstartdate, e.enrolenddate, e.enrolperiod
+ FROM {enrol_lti_tools} elt
+ JOIN {enrol} e
+ ON elt.enrolid = e.id";
+ if ($params) {
+ $where = "WHERE";
+ foreach ($params as $colname => $value) {
+ $sql .= " $where $colname = :$colname";
+ $where = "AND";
+ }
+ }
+ $sql .= " ORDER BY elt.timecreated";
+
+ return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
+ }
+
+ /**
+ * Returns the number of LTI tools.
+ *
+ * @param array $params The list of SQL params (eg. array('columnname' => value, 'columnname2' => value)).
+ * @return int The number of tools
+ */
+ public static function count_lti_tools($params = array()) {
+ global $DB;
+
+ $sql = "SELECT COUNT(*)
+ FROM {enrol_lti_tools} elt
+ JOIN {enrol} e
+ ON elt.enrolid = e.id";
+ if ($params) {
+ $where = "WHERE";
+ foreach ($params as $colname => $value) {
+ $sql .= " $where $colname = :$colname";
+ $where = "AND";
+ }
+ }
+
+ return $DB->count_records_sql($sql, $params);
+ }
+
+ /**
+ * Create a IMS POX body request for sync grades.
+ *
+ * @param string $source Sourceid required for the request
+ * @param float $grade User final grade
+ * @return string
+ */
+ public static function create_service_body($source, $grade) {
+ return '<?xml version="1.0" encoding="UTF-8"?>
+ <imsx_POXEnvelopeRequest xmlns="http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0">
+ <imsx_POXHeader>
+ <imsx_POXRequestHeaderInfo>
+ <imsx_version>V1.0</imsx_version>
+ <imsx_messageIdentifier>' . (time()) . '</imsx_messageIdentifier>
+ </imsx_POXRequestHeaderInfo>
+ </imsx_POXHeader>
+ <imsx_POXBody>
+ <replaceResultRequest>
+ <resultRecord>
+ <sourcedGUID>
+ <sourcedId>' . $source . '</sourcedId>
+ </sourcedGUID>
+ <result>
+ <resultScore>
+ <language>en-us</language>
+ <textString>' . $grade . '</textString>
+ </resultScore>
+ </result>
+ </resultRecord>
+ </replaceResultRequest>
+ </imsx_POXBody>
+ </imsx_POXEnvelopeRequest>';
+ }
+}
--- /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/>.
+
+/**
+ * Displays enrolment LTI instances.
+ *
+ * @package enrol_lti
+ * @copyright 2016 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace enrol_lti;
+
+defined('MOODLE_INTERNAL') || die;
+
+global $CFG;
+
+require_once($CFG->libdir . '/tablelib.php');
+
+/**
+ * Handles displaying enrolment LTI instances.
+ *
+ * @package enrol_lti
+ * @copyright 2016 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class manage_table extends \table_sql {
+
+ /**
+ * @var \enrol_plugin $ltiplugin
+ */
+ protected $ltiplugin;
+
+ /**
+ * @var bool $ltienabled
+ */
+ protected $ltienabled;
+
+ /**
+ * @var bool $canconfig
+ */
+ protected $canconfig;
+
+ /**
+ * @var int $courseid The course id.
+ */
+ protected $courseid;
+
+ /**
+ * Sets up the table.
+ *
+ * @param string $courseid The id of the course.
+ */
+ public function __construct($courseid) {
+ parent::__construct('enrol_lti_manage_table');
+
+ $this->define_columns(array(
+ 'name',
+ 'url',
+ 'secret',
+ 'edit'
+ ));
+ $this->define_headers(array(
+ get_string('name'),
+ get_string('url'),
+ get_string('secret', 'enrol_lti'),
+ get_string('edit')
+ ));
+ $this->collapsible(false);
+ $this->sortable(false);
+
+ // Set the variables we need access to.
+ $this->ltiplugin = enrol_get_plugin('lti');
+ $this->ltienabled = enrol_is_enabled('lti');
+ $this->canconfig = has_capability('moodle/course:enrolconfig', \context_course::instance($courseid));
+ $this->courseid = $courseid;
+ }
+
+ /**
+ * Generate the name column.
+ *
+ * @param \stdClass $tool event data.
+ * @return string
+ */
+ public function col_name($tool) {
+ if (empty($tool->name)) {
+ $toolcontext = \context::instance_by_id($tool->contextid);
+ $name = $toolcontext->get_context_name();
+ } else {
+ $name = $tool->name;
+ };
+
+ return $this->get_display_text($tool, $name);
+ }
+
+ /**
+ * Generate the URL column.
+ *
+ * @param \stdClass $tool event data.
+ * @return string
+ */
+ public function col_url($tool) {
+ $url = new \moodle_url('/enrol/lti/tool.php', array('id' => $tool->id));
+ return $this->get_display_text($tool, $url);
+ }
+
+ /**
+ * Generate the secret column.
+ *
+ * @param \stdClass $tool event data.
+ * @return string
+ */
+ public function col_secret($tool) {
+ return $this->get_display_text($tool, $tool->secret);
+ }
+
+
+ /**
+ * Generate the edit column.
+ *
+ * @param \stdClass $tool event data.
+ * @return string
+ */
+ public function col_edit($tool) {
+ global $OUTPUT;
+
+ $buttons = array();
+
+ $instance = new \stdClass();
+ $instance->id = $tool->enrolid;
+ $instance->courseid = $tool->courseid;
+ $instance->enrol = 'lti';
+ $instance->status = $tool->status;
+
+ $strdelete = get_string('delete');
+ $strenable = get_string('enable');
+ $strdisable = get_string('disable');
+
+ $url = new \moodle_url('/enrol/lti/index.php', array('sesskey' => sesskey(), 'courseid' => $this->courseid));
+
+ if ($this->ltiplugin->can_delete_instance($instance)) {
+ $aurl = new \moodle_url($url, array('action' => 'delete', 'instanceid' => $instance->id));
+ $buttons[] = $OUTPUT->action_icon($aurl, new \pix_icon('t/delete', $strdelete, 'core',
+ array('class' => 'iconsmall')));
+ }
+
+ if ($this->ltienabled && $this->ltiplugin->can_hide_show_instance($instance)) {
+ if ($instance->status == ENROL_INSTANCE_ENABLED) {
+ $aurl = new \moodle_url($url, array('action' => 'disable', 'instanceid' => $instance->id));
+ $buttons[] = $OUTPUT->action_icon($aurl, new \pix_icon('t/hide', $strdisable, 'core',
+ array('class' => 'iconsmall')));
+ } else if ($instance->status == ENROL_INSTANCE_DISABLED) {
+ $aurl = new \moodle_url($url, array('action' => 'enable', 'instanceid' => $instance->id));
+ $buttons[] = $OUTPUT->action_icon($aurl, new \pix_icon('t/show', $strenable, 'core',
+ array('class' => 'iconsmall')));
+ }
+ }
+
+ if ($this->ltienabled && $this->canconfig) {
+ $linkparams = array(
+ 'courseid' => $instance->courseid,
+ 'id' => $instance->id, 'type' => $instance->enrol,
+ 'returnurl' => new \moodle_url('/enrol/lti/index.php', array('courseid' => $this->courseid))
+ );
+ $editlink = new \moodle_url("/enrol/editinstance.php", $linkparams);
+ $buttons[] = $OUTPUT->action_icon($editlink, new \pix_icon('t/edit', get_string('edit'), 'core',
+ array('class' => 'iconsmall')));
+ }
+
+ return implode(' ', $buttons);
+ }
+
+ /**
+ * Query the reader. Store results in the object for use by build_table.
+ *
+ * @param int $pagesize size of page for paginated displayed table.
+ * @param bool $useinitialsbar do you want to use the initials bar.
+ */
+ public function query_db($pagesize, $useinitialsbar = true) {
+ $total = \enrol_lti\helper::count_lti_tools(array('courseid' => $this->courseid));
+ $this->pagesize($pagesize, $total);
+ $tools = \enrol_lti\helper::get_lti_tools(array('courseid' => $this->courseid), $this->get_page_start(),
+ $this->get_page_size());
+ $this->rawdata = $tools;
+ // Set initial bars.
+ if ($useinitialsbar) {
+ $this->initialbars($total > $pagesize);
+ }
+ }
+
+ /**
+ * Returns text to display in the columns.
+ *
+ * @param \stdClass $tool the tool
+ * @param string $text the text to alter
+ * @return string
+ */
+ protected function get_display_text($tool, $text) {
+ if ($tool->status != ENROL_INSTANCE_ENABLED) {
+ return \html_writer::tag('span', $text, array('class' => 'dimmed_text'));
+ }
+
+ return $text;
+ }
+}
--- /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/>.
+
+/**
+ * Handles synchronising grades for the enrolment LTI.
+ *
+ * @package enrol_lti
+ * @copyright 2016 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace enrol_lti\task;
+
+/**
+ * Task for synchronising grades for the enrolment LTI.
+ *
+ * @package enrol_lti
+ * @copyright 2016 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class sync_grades extends \core\task\scheduled_task {
+
+ /**
+ * Get a descriptive name for this task.
+ *
+ * @return string
+ */
+ public function get_name() {
+ return get_string('tasksyncgrades', 'enrol_lti');
+ }
+
+ /**
+ * Performs the synchronisation of grades.
+ *
+ * @return bool|void
+ */
+ public function execute() {
+ global $DB, $CFG;
+
+ require_once($CFG->dirroot . '/enrol/lti/ims-blti/OAuth.php');
+ require_once($CFG->dirroot . '/enrol/lti/ims-blti/OAuthBody.php');
+ require_once($CFG->dirroot . '/lib/completionlib.php');
+ require_once($CFG->libdir . '/gradelib.php');
+ require_once($CFG->dirroot . '/grade/querylib.php');
+
+ // Check if the authentication plugin is disabled.
+ if (!is_enabled_auth('lti')) {
+ mtrace('Skipping task - ' . get_string('pluginnotenabled', 'auth', get_string('pluginname', 'auth_lti')));
+ return true;
+ }
+
+ // Check if the enrolment plugin is disabled - isn't really necessary as the task should not run if
+ // the plugin is disabled, but there is no harm in making sure core hasn't done something wrong.
+ if (!enrol_is_enabled('lti')) {
+ mtrace('Skipping task - ' . get_string('enrolisdisabled', 'enrol_lti'));
+ return true;
+ }
+
+ // Get all the enabled tools.
+ if ($tools = \enrol_lti\helper::get_lti_tools(array('status' => ENROL_INSTANCE_ENABLED, 'gradesync' => 1))) {
+ foreach ($tools as $tool) {
+ mtrace("Starting - Grade sync for shared tool '$tool->id' for the course '$tool->courseid'.");
+
+ // Variables to keep track of information to display later.
+ $usercount = 0;
+ $sendcount = 0;
+
+ // We check for all the users - users can access the same tool from different consumers.
+ if ($ltiusers = $DB->get_records('enrol_lti_users', array('toolid' => $tool->id), 'lastaccess DESC')) {
+ $completion = new \completion_info(get_course($tool->courseid));
+ foreach ($ltiusers as $ltiuser) {
+ $mtracecontent = "for the user '$ltiuser->userid' in the tool '$tool->id' for the course " .
+ "'$tool->courseid'";
+
+ $usercount = $usercount + 1;
+
+ // Check if we do not have a serviceurl - this can happen if the sync process has an unexpected error.
+ if (empty($ltiuser->serviceurl)) {
+ mtrace("Skipping - Empty serviceurl $mtracecontent.");
+ continue;
+ }
+
+ // Check if we do not have a sourceid - this can happen if the sync process has an unexpected error.
+ if (empty($ltiuser->sourceid)) {
+ mtrace("Skipping - Empty sourceid $mtracecontent.");
+ continue;
+ }
+
+ // Need a valid context to continue.
+ if (!$context = \context::instance_by_id($tool->contextid)) {
+ mtrace("Failed - Invalid contextid '$tool->contextid' for the tool '$tool->id'.");
+ continue;
+ }
+
+ // Ok, let's get the grade.
+ $grade = false;
+ if ($context->contextlevel == CONTEXT_COURSE) {
+ // Check if the user did not completed the course when it was required.
+ if ($tool->gradesynccompletion && !$completion->is_course_complete($ltiuser->userid)) {
+ mtrace("Skipping - Course not completed $mtracecontent.");
+ continue;
+ }
+
+ // Get the grade.
+ if ($grade = grade_get_course_grade($ltiuser->userid, $tool->courseid)) {
+ $grademax = floatval($grade->item->grademax);
+ $grade = $grade->grade;
+ }
+ } else if ($context->contextlevel == CONTEXT_MODULE) {
+ $cm = get_coursemodule_from_id(false, $context->instanceid, 0, false, MUST_EXIST);
+
+ if ($tool->gradesynccompletion) {
+ $data = $completion->get_data($cm, false, $ltiuser->userid);
+ if ($data->completionstate != COMPLETION_COMPLETE_PASS &&
+ $data->completionstate != COMPLETION_COMPLETE) {
+ mtrace("Skipping - Activity not completed $mtracecontent.");
+ continue;
+ }
+ }
+
+ $grades = grade_get_grades($cm->course, 'mod', $cm->modname, $cm->instance, $ltiuser->userid);
+ if (!empty($grades->items[0]->grades)) {
+ $grade = reset($grades->items[0]->grades);
+ if (!empty($grade->item)) {
+ $grademax = floatval($grade->item->grademax);
+ } else {
+ $grademax = floatval($grades->items[0]->grademax);
+ }
+ $grade = $grade->grade;
+ }
+ }
+
+ if ($grade === false || $grade === null || strlen($grade) < 1) {
+ mtrace("Skipping - Invalid grade $mtracecontent.");
+ continue;
+ }
+
+ // No need to be dividing by zero.
+ if (empty($grademax)) {
+ mtrace("Skipping - Invalid grade $mtracecontent.");
+ continue;
+ }
+
+ // This can happen if the sync process has an unexpected error.
+ if ($grade == $ltiuser->lastgrade) {
+ mtrace("Not sent - The grade $mtracecontent was not sent as the grades are the same.");
+ continue;
+ }
+
+ // Sync with the external system.
+ $floatgrade = $grade / $grademax;
+ $body = \enrol_lti\helper::create_service_body($ltiuser->sourceid, $floatgrade);
+
+ try {
+ $response = sendOAuthBodyPOST('POST', $ltiuser->serviceurl,
+ $ltiuser->consumerkey, $ltiuser->consumersecret, 'application/xml', $body);
+ } catch (\Exception $e) {
+ mtrace("Failed - The grade '$floatgrade' $mtracecontent failed to send.");
+ mtrace($e->getMessage());
+ continue;
+ }
+
+ if (strpos(strtolower($response), 'success') !== false) {
+ $DB->set_field('enrol_lti_users', 'lastgrade', intval($grade), array('id' => $ltiuser->id));
+ mtrace("Success - The grade '$floatgrade' $mtracecontent was sent.");
+ $sendcount = $sendcount + 1;
+ } else {
+ mtrace("Failed - The grade '$floatgrade' $mtracecontent failed to send.");
+ }
+
+ }
+ }
+ mtrace("Completed - Synced grades for tool '$tool->id' in the course '$tool->courseid'. " .
+ "Processed $usercount users; sent $sendcount grades.");
+ mtrace("");
+ }
+ }
+ }
+}
--- /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/>.
+
+/**
+ * Handles synchronising members using the enrolment LTI.
+ *
+ * @package enrol_lti
+ * @copyright 2016 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace enrol_lti\task;
+
+/**
+ * Task for synchronising members using the enrolment LTI.
+ *
+ * @package enrol_lti
+ * @copyright 2016 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class sync_members extends \core\task\scheduled_task {
+
+ /**
+ * The LTI message type.
+ */
+ const LTI_MESSAGE_TYPE = 'basic-lis-readmembershipsforcontext';
+
+ /**
+ * The LTI version.
+ */
+ const LTI_VERSION = 'LTI-1p0';
+
+ /**
+ * Get a descriptive name for this task.
+ *
+ * @return string
+ */
+ public function get_name() {
+ return get_string('tasksyncmembers', 'enrol_lti');
+ }
+
+ /**
+ * Performs the synchronisation of members.
+ *
+ * @return bool|void
+ */
+ public function execute() {
+ global $CFG, $DB;
+
+ require_once($CFG->dirroot . '/enrol/lti/ims-blti/OAuth.php');
+ require_once($CFG->dirroot . '/enrol/lti/ims-blti/OAuthBody.php');
+
+ // Check if the authentication plugin is disabled.
+ if (!is_enabled_auth('lti')) {
+ mtrace('Skipping task - ' . get_string('pluginnotenabled', 'auth', get_string('pluginname', 'auth_lti')));
+ return true;
+ }
+
+ // Check if the enrolment plugin is disabled - isn't really necessary as the task should not run if
+ // the plugin is disabled, but there is no harm in making sure core hasn't done something wrong.
+ if (!enrol_is_enabled('lti')) {
+ mtrace('Skipping task - ' . get_string('enrolisdisabled', 'enrol_lti'));
+ return true;
+ }
+
+ // Get all the enabled tools.
+ if ($tools = \enrol_lti\helper::get_lti_tools(array('status' => ENROL_INSTANCE_ENABLED, 'membersync' => 1))) {
+ $ltiplugin = enrol_get_plugin('lti');
+ $consumers = array();
+ $currentusers = array();
+ $userphotos = array();
+ foreach ($tools as $tool) {
+ mtrace("Starting - Member sync for shared tool '$tool->id' for the course '$tool->courseid'.");
+
+ // Variables to keep track of information to display later.
+ $usercount = 0;
+ $enrolcount = 0;
+ $unenrolcount = 0;
+
+ // We check for all the users - users can access the same tool from different consumers.
+ if ($ltiusers = $DB->get_records('enrol_lti_users', array('toolid' => $tool->id), 'lastaccess DESC')) {
+ foreach ($ltiusers as $ltiuser) {
+ $mtracecontent = "for the user '$ltiuser->userid' in the tool '$tool->id' for the course " .
+ "'$tool->courseid'";
+ $usercount++;
+
+ // Check if we do not have a membershipsurl - this can happen if the sync process has an unexpected error.
+ if (!$ltiuser->membershipsurl) {
+ mtrace("Skipping - Empty membershipsurl $mtracecontent.");
+ continue;
+ }
+
+ // Check if we do not have a membershipsid - this can happen if the sync process has an unexpected error.
+ if (!$ltiuser->membershipsid) {
+ mtrace("Skipping - Empty membershipsid $mtracecontent.");
+ continue;
+ }
+
+ $consumer = sha1($ltiuser->membershipsurl . ':' . $ltiuser->membershipsid . ':' .
+ $ltiuser->consumerkey . ':' . $ltiuser->consumersecret);
+ if (in_array($consumer, $consumers)) {
+ // We have already synchronised with this consumer.
+ continue;
+ }
+
+ $consumers[] = $consumer;
+
+ $params = array(
+ 'lti_message_type' => self::LTI_MESSAGE_TYPE,
+ 'id' => $ltiuser->membershipsid,
+ 'lti_version' => self::LTI_VERSION
+ );
+
+ mtrace("Calling memberships url '$ltiuser->membershipsurl' with body '" .
+ json_encode($params) . "'");
+
+ try {
+ $response = sendOAuthParamsPOST('POST', $ltiuser->membershipsurl, $ltiuser->consumerkey,
+ $ltiuser->consumersecret, 'application/x-www-form-urlencoded', $params);
+ } catch (\Exception $e) {
+ mtrace("Skipping - No response received $mtracecontent from '$ltiuser->membershipsurl'");
+ mtrace($e->getMessage());
+ continue;
+ }
+
+ // Check the response from the consumer.
+ $data = new \SimpleXMLElement($response);
+
+ // Check if we did not receive a valid response.
+ if (empty($data->statusinfo)) {
+ mtrace("Skipping - Bad response received $mtracecontent from '$ltiuser->membershipsurl'");
+ mtrace('Skipping - Error parsing the XML received \'' . substr($response, 0, 125) .
+ '\' ... (Displaying only 125 chars)');
+ continue;
+ }
+
+ // Check if we did not receive a valid response.
+ if (strpos(strtolower($data->statusinfo->codemajor), 'success') === false) {
+ mtrace('Skipping - Error received from the remote system: ' . $data->statusinfo->codemajor
+ . ' ' . $data->statusinfo->severity . ' ' . $data->statusinfo->codeminor);
+ continue;
+ }
+
+ $members = $data->memberships->member;
+ mtrace(count($members) . ' members received.');
+ foreach ($members as $member) {
+ // Set the user data.
+ $user = new \stdClass();
+ $user->username = \enrol_lti\helper::create_username($ltiuser->consumerkey, $member->user_id);
+ $user->firstname = \core_user::clean_field($member->person_name_given, 'firstname');
+ $user->lastname = \core_user::clean_field($member->person_name_family, 'lastname');
+ $user->email = \core_user::clean_field($member->person_contact_email_primary, 'email');
+
+ // Get the user data from the LTI consumer.
+ $user = \enrol_lti\helper::assign_user_tool_data($tool, $user);
+
+ if (!$dbuser = $DB->get_record('user', array('username' => $user->username, 'deleted' => 0))) {
+ if ($tool->membersyncmode == \enrol_lti\helper::MEMBER_SYNC_ENROL_AND_UNENROL ||
+ $tool->membersyncmode == \enrol_lti\helper::MEMBER_SYNC_ENROL_NEW) {
+ // If the email was stripped/not set then fill it with a default one. This
+ // stops the user from being redirected to edit their profile page.
+ if (empty($user->email)) {
+ $user->email = $user->username . "@example.com";
+ }
+
+ $user->auth = 'lti';
+ $user->id = user_create_user($user);
+
+ // Add the information to the necessary arrays.
+ $currentusers[] = $user->id;
+ $userphotos[$user->id] = $member->user_image;
+ }
+ } else {
+ // If email is empty remove it, so we don't update the user with an empty email.
+ if (empty($user->email)) {
+ unset($user->email);
+ }
+
+ $user->id = $dbuser->id;
+ user_update_user($user);
+
+ // Add the information to the necessary arrays.
+ $currentusers[] = $user->id;
+ $userphotos[$user->id] = $member->user_image;
+ }
+ if ($tool->membersyncmode == \enrol_lti\helper::MEMBER_SYNC_ENROL_AND_UNENROL ||
+ $tool->membersyncmode == \enrol_lti\helper::MEMBER_SYNC_ENROL_NEW) {
+ // Enrol the user in the course.
+ \enrol_lti\helper::enrol_user($tool, $user->id);
+ }
+ }
+ }
+ // Now we check if we have to unenrol users who were not listed.
+ if ($tool->membersyncmode == \enrol_lti\helper::MEMBER_SYNC_ENROL_AND_UNENROL ||
+ $tool->membersyncmode == \enrol_lti\helper::MEMBER_SYNC_UNENROL_MISSING) {
+ // Go through the users and check if any were never listed, if so, remove them.
+ foreach ($ltiusers as $ltiuser) {
+ if (!in_array($ltiuser->userid, $currentusers)) {
+ $instance = new \stdClass();
+ $instance->id = $tool->enrolid;
+ $instance->courseid = $tool->courseid;
+ $instance->enrol = 'lti';
+ $ltiplugin->unenrol_user($instance, $ltiuser->id);
+ }
+ }
+ }
+ }
+ mtrace("Completed - Synced members for tool '$tool->id' in the course '$tool->courseid'. " .
+ "Processed $usercount users; enrolled $enrolcount members; unenrolled $unenrolcount members.");
+ mtrace("");
+ }
+
+ // Sync the user profile photos.
+ mtrace("Started - Syncing user profile images.");
+ $counter = 0;
+ if (!empty($userphotos)) {
+ foreach ($userphotos as $userid => $url) {
+ if ($url) {
+ $result = \enrol_lti\helper::update_user_profile_image($userid, $url);
+ if ($result === \enrol_lti\helper::PROFILE_IMAGE_UPDATE_SUCCESSFUL) {
+ $counter++;
+ mtrace("Profile image succesfully downloaded and created for user '$userid' from $url.");
+ } else {
+ mtrace($result);
+ }
+ }
+ }
+ }
+ mtrace("Completed - Synced $counter profile images.");
+ }
+ }
+}
--- /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/>.
+
+/**
+ * Capabilities for LTI enrolment plugin.
+ *
+ * @package enrol_lti
+ * @copyright 2016 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$capabilities = array(
+
+ /* Add, edit or remove lti enrol instance. */
+ 'enrol/lti:config' => array(
+ 'captype' => 'write',
+ 'contextlevel' => CONTEXT_COURSE,
+ 'archetypes' => array(
+ 'manager' => CAP_ALLOW,
+ 'editingteacher' => CAP_ALLOW,
+ )
+ ),
+
+ 'enrol/lti:unenrol' => array(
+ 'captype' => 'write',
+ 'contextlevel' => CONTEXT_COURSE,
+ 'archetypes' => array(
+ 'manager' => CAP_ALLOW,
+ 'editingteacher' => CAP_ALLOW,
+ )
+ ),
+);
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<XMLDB PATH="enrol/lti/db" VERSION="20160322" COMMENT="XMLDB file for Moodle enrol/lti"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
+>
+ <TABLES>
+ <TABLE NAME="enrol_lti_tools" COMMENT="List of tools provided to the remote system">
+ <FIELDS>
+ <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
+ <FIELD NAME="enrolid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+ <FIELD NAME="contextid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+ <FIELD NAME="institution" TYPE="char" LENGTH="40" NOTNULL="true" SEQUENCE="false"/>
+ <FIELD NAME="lang" TYPE="char" LENGTH="30" NOTNULL="true" DEFAULT="en" SEQUENCE="false"/>
+ <FIELD NAME="timezone" TYPE="char" LENGTH="100" NOTNULL="true" DEFAULT="99" SEQUENCE="false"/>
+ <FIELD NAME="maxenrolled" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
+ <FIELD NAME="maildisplay" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="2" SEQUENCE="false"/>
+ <FIELD NAME="city" TYPE="char" LENGTH="120" NOTNULL="true" SEQUENCE="false"/>
+ <FIELD NAME="country" TYPE="char" LENGTH="2" NOTNULL="true" SEQUENCE="false"/>
+ <FIELD NAME="gradesync" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
+ <FIELD NAME="gradesynccompletion" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
+ <FIELD NAME="membersync" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
+ <FIELD NAME="membersyncmode" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
+ <FIELD NAME="roleinstructor" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+ <FIELD NAME="rolelearner" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+ <FIELD NAME="secret" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
+ <FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+ <FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+ </FIELDS>
+ <KEYS>
+ <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+ <KEY NAME="enrolid" TYPE="foreign" FIELDS="enrolid" REFTABLE="enrol" REFFIELDS="id"/>
+ <KEY NAME="contextid" TYPE="foreign" FIELDS="contextid" REFTABLE="context" REFFIELDS="id"/>
+ </KEYS>
+ </TABLE>
+ <TABLE NAME="enrol_lti_users" COMMENT="User access log and gradeback data">
+ <FIELDS>
+ <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
+ <FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+ <FIELD NAME="toolid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+ <FIELD NAME="serviceurl" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
+ <FIELD NAME="sourceid" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
+ <FIELD NAME="consumerkey" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
+ <FIELD NAME="consumersecret" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
+ <FIELD NAME="membershipsurl" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
+ <FIELD NAME="membershipsid" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
+ <FIELD NAME="lastgrade" TYPE="number" LENGTH="10" NOTNULL="false" SEQUENCE="false" DECIMALS="5" COMMENT="The last grade that was sent"/>
+ <FIELD NAME="lastaccess" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="The time the user last accessed"/>
+ <FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="The time the user was created"/>
+ </FIELDS>
+ <KEYS>
+ <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+ <KEY NAME="userid" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id"/>
+ <KEY NAME="toolid" TYPE="foreign" FIELDS="toolid" REFTABLE="enrol_lti_tools" REFFIELDS="id"/>
+ </KEYS>
+ </TABLE>
+ </TABLES>
+</XMLDB>
--- /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/>.
+
+/**
+ * Enrol LTI tasks.
+ *
+ * @package enrol_lti
+ * @copyright 2016 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$tasks = array(
+ array(
+ 'classname' => 'enrol_lti\task\sync_grades',
+ 'blocking' => 0,
+ 'minute' => '*/30',
+ 'hour' => '*',
+ 'day' => '*',
+ 'dayofweek' => '*',
+ 'month' => '*'
+ ),
+ array(
+ 'classname' => 'enrol_lti\task\sync_members',
+ 'blocking' => 0,
+ 'minute' => '*/30',
+ 'hour' => '*',
+ 'day' => '*',
+ 'dayofweek' => '*',
+ 'month' => '*'
+ ),
+);
--- /dev/null
+The MIT License
+
+Copyright (c) 2007 Andy Smith
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
--- /dev/null
+<?php
+// vim: foldmethod=marker
+
+$OAuth_last_computed_siguature = false;
+
+/* Generic exception class
+ */
+class OAuthException extends \Exception {
+ // pass
+}
+
+class OAuthConsumer {
+ public $key;
+ public $secret;
+
+ function __construct($key, $secret, $callback_url=NULL) {
+ $this->key = $key;
+ $this->secret = $secret;
+ $this->callback_url = $callback_url;
+ }
+
+ function __toString() {
+ return "OAuthConsumer[key=$this->key,secret=$this->secret]";
+ }
+}
+
+class OAuthToken {
+ // access tokens and request tokens
+ public $key;
+ public $secret;
+
+ /**
+ * key = the token
+ * secret = the token secret
+ */
+ function __construct($key, $secret) {
+ $this->key = $key;
+ $this->secret = $secret;
+ }
+
+ /**
+ * generates the basic string serialization of a token that a server
+ * would respond to request_token and access_token calls with
+ */
+ function to_string() {
+ return "oauth_token=" .
+ OAuthUtil::urlencode_rfc3986($this->key) .
+ "&oauth_token_secret=" .
+ OAuthUtil::urlencode_rfc3986($this->secret);
+ }
+
+ function __toString() {
+ return $this->to_string();
+ }
+}
+
+class OAuthSignatureMethod {
+ public function check_signature(&$request, $consumer, $token, $signature) {
+ $built = $this->build_signature($request, $consumer, $token);
+ return $built == $signature;
+ }
+}
+
+class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod {
+ function get_name() {
+ return "HMAC-SHA1";
+ }
+
+ public function build_signature($request, $consumer, $token) {
+ global $OAuth_last_computed_signature;
+ $OAuth_last_computed_signature = false;
+
+ $base_string = $request->get_signature_base_string();
+ $request->base_string = $base_string;
+
+ $key_parts = array(
+ $consumer->secret,
+ ($token) ? $token->secret : ""
+ );
+
+ $key_parts = OAuthUtil::urlencode_rfc3986($key_parts);
+ $key = implode('&', $key_parts);
+
+ $computed_signature = base64_encode(hash_hmac('sha1', $base_string, $key, true));
+ $OAuth_last_computed_signature = $computed_signature;
+ return $computed_signature;
+ }
+
+}
+
+class OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod {
+ public function get_name() {
+ return "PLAINTEXT";
+ }
+
+ public function build_signature($request, $consumer, $token) {
+ $sig = array(
+ OAuthUtil::urlencode_rfc3986($consumer->secret)
+ );
+
+ if ($token) {
+ array_push($sig, OAuthUtil::urlencode_rfc3986($token->secret));
+ } else {
+ array_push($sig, '');
+ }
+
+ $raw = implode("&", $sig);
+ // for debug purposes
+ $request->base_string = $raw;
+
+ return OAuthUtil::urlencode_rfc3986($raw);
+ }
+}
+
+class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod {
+ public function get_name() {
+ return "RSA-SHA1";
+ }
+
+ protected function fetch_public_cert(&$request) {
+ // not implemented yet, ideas are:
+ // (1) do a lookup in a table of trusted certs keyed off of consumer
+ // (2) fetch via http using a url provided by the requester
+ // (3) some sort of specific discovery code based on request
+ //
+ // either way should return a string representation of the certificate
+ throw Exception("fetch_public_cert not implemented");
+ }
+
+ protected function fetch_private_cert(&$request) {
+ // not implemented yet, ideas are:
+ // (1) do a lookup in a table of trusted certs keyed off of consumer
+ //
+ // either way should return a string representation of the certificate
+ throw Exception("fetch_private_cert not implemented");
+ }
+
+ public function build_signature(&$request, $consumer, $token) {
+ $base_string = $request->get_signature_base_string();
+ $request->base_string = $base_string;
+
+ // Fetch the private key cert based on the request
+ $cert = $this->fetch_private_cert($request);
+
+ // Pull the private key ID from the certificate
+ $privatekeyid = openssl_get_privatekey($cert);
+
+ // Sign using the key
+ $ok = openssl_sign($base_string, $signature, $privatekeyid);
+
+ // Release the key resource
+ openssl_free_key($privatekeyid);
+
+ return base64_encode($signature);
+ }
+
+ public function check_signature(&$request, $consumer, $token, $signature) {
+ $decoded_sig = base64_decode($signature);
+
+ $base_string = $request->get_signature_base_string();
+
+ // Fetch the public key cert based on the request
+ $cert = $this->fetch_public_cert($request);
+
+ // Pull the public key ID from the certificate
+ $publickeyid = openssl_get_publickey($cert);
+
+ // Check the computed signature against the one passed in the query
+ $ok = openssl_verify($base_string, $decoded_sig, $publickeyid);
+
+ // Release the key resource
+ openssl_free_key($publickeyid);
+
+ return $ok == 1;
+ }
+}
+
+class OAuthRequest {
+ private $parameters;
+ private $http_method;
+ private $http_url;
+ // for debug purposes
+ public $base_string;
+ public static $version = '1.0';
+ public static $POST_INPUT = 'php://input';
+
+ function __construct($http_method, $http_url, $parameters=NULL) {
+ @$parameters or $parameters = array();
+ $this->parameters = $parameters;
+ $this->http_method = $http_method;
+ $this->http_url = $http_url;
+ }
+
+
+ /**
+ * attempt to build up a request from what was passed to the server
+ */
+ public static function from_request($http_method=NULL, $http_url=NULL, $parameters=NULL) {
+ $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on")
+ ? 'http'
+ : 'https';
+ $port = "";
+ if ( $_SERVER['SERVER_PORT'] != "80" && $_SERVER['SERVER_PORT'] != "443" &&
+ strpos(':', $_SERVER['HTTP_HOST']) < 0 ) {
+ $port = ':' . $_SERVER['SERVER_PORT'] ;
+ }
+ @$http_url or $http_url = $scheme .
+ '://' . $_SERVER['HTTP_HOST'] .
+ $port .
+ $_SERVER['REQUEST_URI'];
+ @$http_method or $http_method = $_SERVER['REQUEST_METHOD'];
+
+ // We weren't handed any parameters, so let's find the ones relevant to
+ // this request.
+ // If you run XML-RPC or similar you should use this to provide your own
+ // parsed parameter-list
+ if (!$parameters) {
+ // Find request headers
+ $request_headers = OAuthUtil::get_headers();
+
+ // Parse the query-string to find GET parameters
+ $parameters = OAuthUtil::parse_parameters($_SERVER['QUERY_STRING']);
+
+ $ourpost = $_POST;
+ // Deal with magic_quotes
+ // http://www.php.net/manual/en/security.magicquotes.disabling.php
+ if ( get_magic_quotes_gpc() ) {
+ $outpost = array();
+ foreach ($_POST as $k => $v) {
+ $v = stripslashes($v);
+ $ourpost[$k] = $v;
+ }
+ }
+ // Add POST Parameters if they exist
+ $parameters = array_merge($parameters, $ourpost);
+
+ // We have a Authorization-header with OAuth data. Parse the header
+ // and add those overriding any duplicates from GET or POST
+ if (@substr($request_headers['Authorization'], 0, 6) == "OAuth ") {
+ $header_parameters = OAuthUtil::split_header(
+ $request_headers['Authorization']
+ );
+ $parameters = array_merge($parameters, $header_parameters);
+ }
+
+ }
+
+ return new OAuthRequest($http_method, $http_url, $parameters);
+ }
+
+ /**
+ * pretty much a helper function to set up the request
+ */
+ public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters=NULL) {
+ @$parameters or $parameters = array();
+ $defaults = array("oauth_version" => OAuthRequest::$version,
+ "oauth_nonce" => OAuthRequest::generate_nonce(),
+ "oauth_timestamp" => OAuthRequest::generate_timestamp(),
+ "oauth_consumer_key" => $consumer->key);
+ if ($token)
+ $defaults['oauth_token'] = $token->key;
+
+ $parameters = array_merge($defaults, $parameters);
+
+ // Parse the query-string to find and add GET parameters
+ $parts = parse_url($http_url);
+ if ( !empty($parts['query']) ) {
+ $qparms = OAuthUtil::parse_parameters($parts['query']);
+ $parameters = array_merge($qparms, $parameters);
+ }
+
+
+ return new OAuthRequest($http_method, $http_url, $parameters);
+ }
+
+ public function set_parameter($name, $value, $allow_duplicates = true) {
+ if ($allow_duplicates && isset($this->parameters[$name])) {
+ // We have already added parameter(s) with this name, so add to the list
+ if (is_scalar($this->parameters[$name])) {
+ // This is the first duplicate, so transform scalar (string)
+ // into an array so we can add the duplicates
+ $this->parameters[$name] = array($this->parameters[$name]);
+ }
+
+ $this->parameters[$name][] = $value;
+ } else {
+ $this->parameters[$name] = $value;
+ }
+ }
+
+ public function get_parameter($name) {
+ return isset($this->parameters[$name]) ? $this->parameters[$name] : null;
+ }
+
+ public function get_parameters() {
+ return $this->parameters;
+ }
+
+ public function unset_parameter($name) {
+ unset($this->parameters[$name]);
+ }
+
+ /**
+ * The request parameters, sorted and concatenated into a normalized string.
+ * @return string
+ */
+ public function get_signable_parameters() {
+ // Grab all parameters
+ $params = $this->parameters;
+
+ // Remove oauth_signature if present
+ // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.")
+ if (isset($params['oauth_signature'])) {
+ unset($params['oauth_signature']);
+ }
+
+ return OAuthUtil::build_http_query($params);
+ }
+
+ /**
+ * Returns the base string of this request
+ *
+ * The base string defined as the method, the url
+ * and the parameters (normalized), each urlencoded
+ * and the concated with &.
+ */
+ public function get_signature_base_string() {
+ $parts = array(
+ $this->get_normalized_http_method(),
+ $this->get_normalized_http_url(),
+ $this->get_signable_parameters()
+ );
+
+ $parts = OAuthUtil::urlencode_rfc3986($parts);
+
+ return implode('&', $parts);
+ }
+
+ /**
+ * just uppercases the http method
+ */
+ public function get_normalized_http_method() {
+ return strtoupper($this->http_method);
+ }
+
+ /**
+ * parses the url and rebuilds it to be
+ * scheme://host/path
+ */
+ public function get_normalized_http_url() {
+ $parts = parse_url($this->http_url);
+
+ $port = @$parts['port'];
+ $scheme = $parts['scheme'];
+ $host = $parts['host'];
+ $path = @$parts['path'];
+
+ $port or $port = ($scheme == 'https') ? '443' : '80';
+
+ if (($scheme == 'https' && $port != '443')
+ || ($scheme == 'http' && $port != '80')) {
+ $host = "$host:$port";
+ }
+ return "$scheme://$host$path";
+ }
+
+ /**
+ * builds a url usable for a GET request
+ */
+ public function to_url() {
+ $post_data = $this->to_postdata();
+ $out = $this->get_normalized_http_url();
+ if ($post_data) {
+ $out .= '?'.$post_data;
+ }
+ return $out;
+ }
+
+ /**
+ * builds the data one would send in a POST request
+ */
+ public function to_postdata() {
+ return OAuthUtil::build_http_query($this->parameters);
+ }
+
+ /**
+ * builds the Authorization: header
+ */
+ public function to_header() {
+ $out ='Authorization: OAuth realm=""';
+ $total = array();
+ foreach ($this->parameters as $k => $v) {
+ if (substr($k, 0, 5) != "oauth") continue;
+ if (is_array($v)) {
+ throw new OAuthException('Arrays not supported in headers');
+ }
+ $out .= ',' .
+ OAuthUtil::urlencode_rfc3986($k) .
+ '="' .
+ OAuthUtil::urlencode_rfc3986($v) .
+ '"';
+ }
+ return $out;
+ }
+
+ public function __toString() {
+ return $this->to_url();
+ }
+
+
+ public function sign_request($signature_method, $consumer, $token) {
+ $this->set_parameter(
+ "oauth_signature_method",
+ $signature_method->get_name(),
+ false
+ );
+ $signature = $this->build_signature($signature_method, $consumer, $token);
+ $this->set_parameter("oauth_signature", $signature, false);
+ }
+
+ public function build_signature($signature_method, $consumer, $token) {
+ $signature = $signature_method->build_signature($this, $consumer, $token);
+ return $signature;
+ }
+
+ /**
+ * util function: current timestamp
+ */
+ private static function generate_timestamp() {
+ return time();
+ }
+
+ /**
+ * util function: current nonce
+ */
+ private static function generate_nonce() {
+ $mt = microtime();
+ $rand = mt_rand();
+
+ return md5($mt . $rand); // md5s look nicer than numbers
+ }
+}
+
+class OAuthServer {
+ protected $timestamp_threshold = 300; // in seconds, five minutes
+ protected $version = 1.0; // hi blaine
+ protected $signature_methods = array();
+
+ protected $data_store;
+
+ function __construct($data_store) {
+ $this->data_store = $data_store;
+ }
+
+ public function add_signature_method($signature_method) {
+ $this->signature_methods[$signature_method->get_name()] =
+ $signature_method;
+ }
+
+ // high level functions
+
+ /**
+ * process a request_token request
+ * returns the request token on success
+ */
+ public function fetch_request_token(&$request) {
+ $this->get_version($request);
+
+ $consumer = $this->get_consumer($request);
+
+ // no token required for the initial token request
+ $token = NULL;
+
+ $this->check_signature($request, $consumer, $token);
+
+ $new_token = $this->data_store->new_request_token($consumer);
+
+ return $new_token;
+ }
+
+ /**
+ * process an access_token request
+ * returns the access token on success
+ */
+ public function fetch_access_token(&$request) {
+ $this->get_version($request);
+
+ $consumer = $this->get_consumer($request);
+
+ // requires authorized request token
+ $token = $this->get_token($request, $consumer, "request");
+
+
+ $this->check_signature($request, $consumer, $token);
+
+ $new_token = $this->data_store->new_access_token($token, $consumer);
+
+ return $new_token;
+ }
+
+ /**
+ * verify an api call, checks all the parameters
+ */
+ public function verify_request(&$request) {
+ global $OAuth_last_computed_signature;
+ $OAuth_last_computed_signature = false;
+ $this->get_version($request);
+ $consumer = $this->get_consumer($request);
+ $token = $this->get_token($request, $consumer, "access");
+ $this->check_signature($request, $consumer, $token);
+ return array($consumer, $token);
+ }
+
+ // Internals from here
+ /**
+ * version 1
+ */
+ private function get_version(&$request) {
+ $version = $request->get_parameter("oauth_version");
+ if (!$version) {
+ $version = 1.0;
+ }
+ if ($version && $version != $this->version) {
+ throw new OAuthException("OAuth version '$version' not supported");
+ }
+ return $version;
+ }
+
+ /**
+ * figure out the signature with some defaults
+ */
+ private function get_signature_method(&$request) {
+ $signature_method =
+ @$request->get_parameter("oauth_signature_method");
+ if (!$signature_method) {
+ $signature_method = "PLAINTEXT";
+ }
+ if (!in_array($signature_method,
+ array_keys($this->signature_methods))) {
+ throw new OAuthException(
+ "Signature method '$signature_method' not supported " .
+ "try one of the following: " .
+ implode(", ", array_keys($this->signature_methods))
+ );
+ }
+ return $this->signature_methods[$signature_method];
+ }
+
+ /**
+ * try to find the consumer for the provided request's consumer key
+ */
+ private function get_consumer(&$request) {
+ $consumer_key = @$request->get_parameter("oauth_consumer_key");
+ if (!$consumer_key) {
+ throw new OAuthException("Invalid consumer key");
+ }
+
+ $consumer = $this->data_store->lookup_consumer($consumer_key);
+ if (!$consumer) {
+ throw new OAuthException("Invalid consumer");
+ }
+
+ return $consumer;
+ }
+
+ /**
+ * try to find the token for the provided request's token key
+ */
+ private function get_token(&$request, $consumer, $token_type="access") {
+ $token_field = @$request->get_parameter('oauth_token');
+ if ( !$token_field) return false;
+ $token = $this->data_store->lookup_token(
+ $consumer, $token_type, $token_field
+ );
+ if (!$token) {
+ throw new OAuthException("Invalid $token_type token: $token_field");
+ }
+ return $token;
+ }
+
+ /**
+ * all-in-one function to check the signature on a request
+ * should guess the signature method appropriately
+ */
+ private function check_signature(&$request, $consumer, $token) {
+ // this should probably be in a different method
+ global $OAuth_last_computed_signature;
+ $OAuth_last_computed_signature = false;
+
+ $timestamp = @$request->get_parameter('oauth_timestamp');
+ $nonce = @$request->get_parameter('oauth_nonce');
+
+ $this->check_timestamp($timestamp);
+ $this->check_nonce($consumer, $token, $nonce, $timestamp);
+
+ $signature_method = $this->get_signature_method($request);
+
+ $signature = $request->get_parameter('oauth_signature');
+ $valid_sig = $signature_method->check_signature(
+ $request,
+ $consumer,
+ $token,
+ $signature
+ );
+
+ if (!$valid_sig) {
+ $ex_text = "Invalid signature";
+ if ( $OAuth_last_computed_signature ) {
+ $ex_text = $ex_text . " ours= $OAuth_last_computed_signature yours=$signature";
+ }
+ throw new OAuthException($ex_text);
+ }
+ }
+
+ /**
+ * check that the timestamp is new enough
+ */
+ private function check_timestamp($timestamp) {
+ // verify that timestamp is recentish
+ $now = time();
+ if ($now - $timestamp > $this->timestamp_threshold) {
+ throw new OAuthException(
+ "Expired timestamp, yours $timestamp, ours $now"
+ );
+ }
+ }
+
+ /**
+ * check that the nonce is not repeated
+ */
+ private function check_nonce($consumer, $token, $nonce, $timestamp) {
+ // verify that the nonce is uniqueish
+ $found = $this->data_store->lookup_nonce(
+ $consumer,
+ $token,
+ $nonce,
+ $timestamp
+ );
+ if ($found) {
+ throw new OAuthException("Nonce already used: $nonce");
+ }
+ }
+
+}
+
+class OAuthDataStore {
+ function lookup_consumer($consumer_key) {
+ // implement me
+ }
+
+ function lookup_token($consumer, $token_type, $token) {
+ // implement me
+ }
+
+ function lookup_nonce($consumer, $token, $nonce, $timestamp) {
+ // implement me
+ }
+
+ function new_request_token($consumer) {
+ // return a new token attached to this consumer
+ }
+
+ function new_access_token($token, $consumer) {
+ // return a new access token attached to this consumer
+ // for the user associated with this token if the request token
+ // is authorized
+ // should also invalidate the request token
+ }
+
+}
+
+class OAuthUtil {
+ public static function urlencode_rfc3986($input) {
+ if (is_array($input)) {
+ return array_map(array('OAuthUtil', 'urlencode_rfc3986'), $input);
+ } else if (is_scalar($input)) {
+ return str_replace(
+ '+',
+ ' ',
+ str_replace('%7E', '~', rawurlencode($input))
+ );
+ } else {
+ return '';
+ }
+}
+
+
+ // This decode function isn't taking into consideration the above
+ // modifications to the encoding process. However, this method doesn't
+ // seem to be used anywhere so leaving it as is.
+ public static function urldecode_rfc3986($string) {
+ return urldecode($string);
+ }
+
+ // Utility function for turning the Authorization: header into
+ // parameters, has to do some unescaping
+ // Can filter out any non-oauth parameters if needed (default behaviour)
+ public static function split_header($header, $only_allow_oauth_parameters = true) {
+ $pattern = '/(([-_a-z]*)=("([^"]*)"|([^,]*)),?)/';
+ $offset = 0;
+ $params = array();
+ while (preg_match($pattern, $header, $matches, PREG_OFFSET_CAPTURE, $offset) > 0) {
+ $match = $matches[0];
+ $header_name = $matches[2][0];
+ $header_content = (isset($matches[5])) ? $matches[5][0] : $matches[4][0];
+ if (preg_match('/^oauth_/', $header_name) || !$only_allow_oauth_parameters) {
+ $params[$header_name] = OAuthUtil::urldecode_rfc3986($header_content);
+ }
+ $offset = $match[1] + strlen($match[0]);
+ }
+
+ if (isset($params['realm'])) {
+ unset($params['realm']);
+ }
+
+ return $params;
+ }
+
+ // helper to try to sort out headers for people who aren't running apache
+ public static function get_headers() {
+ if (function_exists('apache_request_headers')) {
+ // we need this to get the actual Authorization: header
+ // because apache tends to tell us it doesn't exist
+ return apache_request_headers();
+ }
+ // otherwise we don't have apache and are just going to have to hope
+ // that $_SERVER actually contains what we need
+ $out = array();
+ foreach ($_SERVER as $key => $value) {
+ if (substr($key, 0, 5) == "HTTP_") {
+ // this is chaos, basically it is just there to capitalize the first
+ // letter of every word that is not an initial HTTP and strip HTTP
+ // code from przemek
+ $key = str_replace(
+ " ",
+ "-",
+ ucwords(strtolower(str_replace("_", " ", substr($key, 5))))
+ );
+ $out[$key] = $value;
+ }
+ }
+ return $out;
+ }
+
+ // This function takes a input like a=b&a=c&d=e and returns the parsed
+ // parameters like this
+ // array('a' => array('b','c'), 'd' => 'e')
+ public static function parse_parameters( $input ) {
+ if (!isset($input) || !$input) return array();
+
+ $pairs = explode('&', $input);
+
+ $parsed_parameters = array();
+ foreach ($pairs as $pair) {
+ $split = explode('=', $pair, 2);
+ $parameter = OAuthUtil::urldecode_rfc3986($split[0]);
+ $value = isset($split[1]) ? OAuthUtil::urldecode_rfc3986($split[1]) : '';
+
+ if (isset($parsed_parameters[$parameter])) {
+ // We have already recieved parameter(s) with this name, so add to the list
+ // of parameters with this name
+
+ if (is_scalar($parsed_parameters[$parameter])) {
+ // This is the first duplicate, so transform scalar (string) into an array
+ // so we can add the duplicates
+ $parsed_parameters[$parameter] = array($parsed_parameters[$parameter]);
+ }
+
+ $parsed_parameters[$parameter][] = $value;
+ } else {
+ $parsed_parameters[$parameter] = $value;
+ }
+ }
+ return $parsed_parameters;
+ }
+
+ public static function build_http_query($params) {
+ if (!$params) return '';
+
+ // Urlencode both keys and values
+ $keys = OAuthUtil::urlencode_rfc3986(array_keys($params));
+ $values = OAuthUtil::urlencode_rfc3986(array_values($params));
+ $params = array_combine($keys, $values);
+
+ // Parameters are sorted by name, using lexicographical byte value ordering.
+ // Ref: Spec: 9.1.1 (1)
+ uksort($params, 'strcmp');
+
+ $pairs = array();
+ foreach ($params as $parameter => $value) {
+ if (is_array($value)) {
+ // If two or more parameters share the same name, they are sorted by their value
+ // Ref: Spec: 9.1.1 (1)
+ natsort($value);
+ foreach ($value as $duplicate_value) {
+ $pairs[] = $parameter . '=' . $duplicate_value;
+ }
+ } else {
+ $pairs[] = $parameter . '=' . $value;
+ }
+ }
+ // For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61)
+ // Each name-value pair is separated by an '&' character (ASCII code 38)
+ return implode('&', $pairs);
+ }
+}
+
+?>
--- /dev/null
+<?php
+
+require_once("OAuth.php");
+require_once("TrivialOAuthDataStore.php");
+
+function getLastOAuthBodyBaseString() {
+ global $LastOAuthBodyBaseString;
+ return $LastOAuthBodyBaseString;
+}
+
+function handleOAuthBodyPOST($oauth_consumer_key, $oauth_consumer_secret)
+{
+ $request_headers = OAuthUtil::get_headers();
+ // print_r($request_headers);
+
+ // Must reject application/x-www-form-urlencoded
+ if ($request_headers['Content-type'] == 'application/x-www-form-urlencoded' ) {
+ throw new Exception("OAuth request body signing must not use application/x-www-form-urlencoded");
+ }
+
+ if (@substr($request_headers['Authorization'], 0, 6) == "OAuth ") {
+ $header_parameters = OAuthUtil::split_header($request_headers['Authorization']);
+
+ // echo("HEADER PARMS=\n");
+ // print_r($header_parameters);
+ $oauth_body_hash = $header_parameters['oauth_body_hash'];
+ // echo("OBH=".$oauth_body_hash."\n");
+ }
+
+ if ( ! isset($oauth_body_hash) ) {
+ throw new Exception("OAuth request body signing requires oauth_body_hash body");
+ }
+
+ // Verify the message signature
+ $store = new TrivialOAuthDataStore();
+ $store->add_consumer($oauth_consumer_key, $oauth_consumer_secret);
+
+ $server = new OAuthServer($store);
+
+ $method = new OAuthSignatureMethod_HMAC_SHA1();
+ $server->add_signature_method($method);
+ $request = OAuthRequest::from_request();
+
+ global $LastOAuthBodyBaseString;
+ $LastOAuthBodyBaseString = $request->get_signature_base_string();
+ // echo($LastOAuthBodyBaseString."\n");
+
+ try {
+ $server->verify_request($request);
+ } catch (Exception $e) {
+ $message = $e->getMessage();
+ throw new Exception("OAuth signature failed: " . $message);
+ }
+
+ $postdata = file_get_contents('php://input');
+ // echo($postdata);
+
+ $hash = base64_encode(sha1($postdata, TRUE));
+
+ if ( $hash != $oauth_body_hash ) {
+ throw new Exception("OAuth oauth_body_hash mismatch");
+ }
+
+ return $postdata;
+}
+
+function sendOAuthBodyPOST($method, $endpoint, $oauth_consumer_key, $oauth_consumer_secret, $content_type, $body)
+{
+ global $CFG;
+
+ require_once($CFG->dirroot . '/lib/filelib.php');
+
+ $hash = base64_encode(sha1($body, TRUE));
+
+ $parms = array('oauth_body_hash' => $hash);
+
+ $test_token = '';
+ $hmac_method = new OAuthSignatureMethod_HMAC_SHA1();
+ $test_consumer = new OAuthConsumer($oauth_consumer_key, $oauth_consumer_secret, NULL);
+
+ $acc_req = OAuthRequest::from_consumer_and_token($test_consumer, $test_token, $method, $endpoint, $parms);
+ $acc_req->sign_request($hmac_method, $test_consumer, $test_token);
+
+ // Pass this back up "out of band" for debugging
+ global $LastOAuthBodyBaseString;
+ $LastOAuthBodyBaseString = $acc_req->get_signature_base_string();
+ // echo($LastOAuthBodyBaseString."\m");
+
+ $headers = array();
+ $headers[] = $acc_req->to_header();
+ $headers[] = "Content-type: " . $content_type;
+
+ $curl = new curl();
+ $curl->setHeader($headers);
+ $response = $curl->post($endpoint, $body);
+
+ return $response;
+}
+
+function sendOAuthParamsPOST($method, $endpoint, $oauth_consumer_key, $oauth_consumer_secret, $content_type, $params)
+{
+
+ if (is_array($params)) {
+ $body = http_build_query($params, '', '&');
+ } else {
+ $body = $params;
+ }
+
+ $hash = base64_encode(sha1($body, TRUE));
+
+ $parms = $params;
+ $parms['oauth_body_hash'] = $hash;
+
+ $test_token = '';
+ $hmac_method = new OAuthSignatureMethod_HMAC_SHA1();
+ $test_consumer = new OAuthConsumer($oauth_consumer_key, $oauth_consumer_secret, NULL);
+
+ $acc_req = OAuthRequest::from_consumer_and_token($test_consumer, $test_token, $method, $endpoint, $parms);
+ $acc_req->sign_request($hmac_method, $test_consumer, $test_token);
+
+ // Pass this back up "out of band" for debugging
+ global $LastOAuthBodyBaseString;
+ $LastOAuthBodyBaseString = $acc_req->get_signature_base_string();
+ // echo($LastOAuthBodyBaseString."\m");
+
+ $header = $acc_req->to_header();
+ $header = $header . "\r\nContent-type: " . $content_type . "\r\n";
+
+ $params = array('http' => array(
+ 'method' => 'POST',
+ 'content' => $body,
+ 'header' => $header
+ ));
+ $ctx = stream_context_create($params);
+ $fp = @fopen($endpoint, 'rb', false, $ctx);
+ if (!$fp) {
+ throw new \Exception("Problem with $endpoint, $php_errormsg");
+ }
+ $response = @stream_get_contents($fp);
+ if ($response === false) {
+ throw new \Exception("Problem reading data from $endpoint, $php_errormsg");
+ }
+ return $response;
+}
+
+?>
--- /dev/null
+<?php
+
+require_once($CFG->dirroot . '/enrol/lti/ims-blti/OAuth.php');
+
+/**
+ * A Trivial memory-based store - no support for tokens
+ */
+class TrivialOAuthDataStore extends OAuthDataStore {
+ private $consumers = array();
+
+ function add_consumer($consumer_key, $consumer_secret) {
+ $this->consumers[$consumer_key] = $consumer_secret;
+ }
+
+ function lookup_consumer($consumer_key) {
+ if ( strpos($consumer_key, "http://" ) === 0 ) {
+ $consumer = new OAuthConsumer($consumer_key,"secret", NULL);
+ return $consumer;
+ }
+ if ( $this->consumers[$consumer_key] ) {
+ $consumer = new OAuthConsumer($consumer_key,$this->consumers[$consumer_key], NULL);
+ return $consumer;
+ }
+ return NULL;
+ }
+
+ function lookup_token($consumer, $token_type, $token) {
+ return new OAuthToken($consumer, "");
+ }
+
+ // Return NULL if the nonce has not been used
+ // Return $nonce if the nonce was previously used
+ function lookup_nonce($consumer, $token, $nonce, $timestamp) {
+ // Should add some clever logic to keep nonces from
+ // being reused - for no we are really trusting
+ // that the timestamp will save us
+ return NULL;
+ }
+
+ function new_request_token($consumer) {
+ return NULL;
+ }
+
+ function new_access_token($token, $consumer) {
+ return NULL;
+ }
+}
+?>
\ No newline at end of file
--- /dev/null
+<?php
+
+require_once($CFG->dirroot . '/enrol/lti/ims-blti/OAuth.php');
+require_once($CFG->dirroot . '/enrol/lti/ims-blti/TrivialOAuthDataStore.php');
+
+// Returns true if this is a Basic LTI message
+// with minimum values to meet the protocol
+function is_basic_lti_request() {
+ $good_message_type = $_REQUEST["lti_message_type"] == "basic-lti-launch-request";
+ $good_lti_version = ($_REQUEST["lti_version"] == "LTI-1p0" or $_REQUEST["lti_version"] == "LTI-1.0");
+ $resource_link_id = $_REQUEST["resource_link_id"];
+ if ($good_message_type and $good_lti_version and isset($resource_link_id) ) return(true);
+ return false;
+}
+
+// Basic LTI Class that does the setup and provides utility
+// functions
+class BLTI {
+
+ public $valid = false;
+ public $complete = false;
+ public $message = false;
+ public $basestring = false;
+ public $info = false;
+ public $row = false;
+ public $context_id = false; // Override context_id
+
+ function __construct($parm=false, $usesession=true, $doredirect=true) {
+
+
+ // If this request is not an LTI Launch, either
+ // give up or try to retrieve the context from session
+ if ( ! is_basic_lti_request() ) {
+
+ if ( $usesession === false ) return;
+
+ if ( strlen(session_id()) > 0 ) {
+ $row = $_SESSION['_basiclti_lti_row'];
+ if ( isset($row) ) $this->row = $row;
+ $context_id = $_SESSION['_basiclti_lti_context_id'];
+ if ( isset($context_id) ) $this->context_id = $context_id;
+ $info = $_SESSION['_basic_lti_context'];
+ if ( isset($info) ) {
+ $this->info = $info;
+ $this->valid = true;
+ return;
+ }
+ $this->message = "Could not find context in session";
+ return;
+ }
+ $this->message = "Session not available";
+ return;
+ }
+ // Insure we have a valid launch
+ if ( empty($_REQUEST["oauth_consumer_key"]) ) {
+ $this->message = "Missing oauth_consumer_key in request";
+ return;
+ }
+ $oauth_consumer_key = $_REQUEST["oauth_consumer_key"];
+
+ // Find the secret - either form the parameter as a string or
+ // look it up in a database from parameters we are given
+ $secret = false;
+ $row = false;
+ if ( is_string($parm) ) {
+ $secret = $parm;
+ } else if ( ! is_array($parm) ) {
+ $this->message = "Constructor requires a secret or database information.";
+ return;
+ }
+
+ // Verify the message signature
+ $store = new TrivialOAuthDataStore();
+ $store->add_consumer($oauth_consumer_key, $secret);
+
+ $server = new OAuthServer($store);
+
+ $method = new OAuthSignatureMethod_HMAC_SHA1();
+ $server->add_signature_method($method);
+ $request = OAuthRequest::from_request();
+
+ $this->basestring = $request->get_signature_base_string();
+ try {
+ $server->verify_request($request);
+ $this->valid = true;
+ } catch (Exception $e) {
+ $this->message = $e->getMessage();
+ return;
+ }
+ // Store the launch information in the session for later
+ $newinfo = array();
+ foreach($_POST as $key => $value ) {
+ if ( $key == "basiclti_submit" ) continue;
+ if ( strpos($key, "oauth_") === false ) {
+ $newinfo[$key] = $value;
+ continue;
+ }
+ if ( $key == "oauth_consumer_key" ) {
+ $newinfo[$key] = $value;
+ continue;
+ }
+ }
+ //Added abertranb to decode base 64 20120801
+ if (isset($newinfo['custom_lti_message_encoded_base64']) && $newinfo['custom_lti_message_encoded_base64']==1){
+ $newinfo = $this->decodeBase64($newinfo);
+ }
+
+ $this->info = $newinfo;
+
+ if ( $usesession == true and strlen(session_id()) > 0 ) {
+ $_SESSION['_basic_lti_context'] = $this->info;
+ unset($_SESSION['_basiclti_lti_row']);
+ unset($_SESSION['_basiclti_lti_context_id']);
+ if ( $this->row ) $_SESSION['_basiclti_lti_row'] = $this->row;
+ if ( $this->context_id ) $_SESSION['_basiclti_lti_context_id'] = $this->context_id;
+ }
+
+ if ( $this->valid && $doredirect ) {
+ $this->redirect();
+ $this->complete = true;
+ }
+ }
+
+ function addSession($location) {
+ if ( ini_get('session.use_cookies') == 0 ) {
+ if ( strpos($location,'?') > 0 ) {
+ $location = $location . '&';
+ } else {
+ $location = $location . '?';
+ }
+ $location = $location . session_name() . '=' . session_id();
+ }
+ return $location;
+ }
+
+ function isInstructor() {
+ $roles = $this->info['roles'];
+ $roles = strtolower($roles);
+ if ( ! ( strpos($roles,"instructor") === false ) ) return true;
+ if ( ! ( strpos($roles,"administrator") === false ) ) return true;
+ return false;
+ }
+
+ function getUserEmail() {
+ # set default email in the event privacy settings don't pass in email.
+ $email = $this->info['user_id'] . "@ltiuser.com";
+ if ( isset($this->info['lis_person_contact_email_primary']) ) $email = $this->info['lis_person_contact_email_primary'];
+ # Sakai Hack
+ if ( isset($this->info['lis_person_contact_emailprimary']) ) $email = $this->info['lis_person_contact_emailprimary'];
+ return $email;
+ }
+
+ function getUserShortName() {
+ $email = $this->getUserEmail();
+ $givenname = $this->info['lis_person_name_given'];
+ $familyname = $this->info['lis_person_name_family'];
+ $fullname = $this->info['lis_person_name_full'];
+ if ( strlen($email) > 0 ) return $email;
+ if ( strlen($givenname) > 0 ) return $givenname;
+ if ( strlen($familyname) > 0 ) return $familyname;
+ return $this->getUserName();
+ }
+
+ function getUserName() {
+ $givenname = $this->info['lis_person_name_given'];
+ $familyname = $this->info['lis_person_name_family'];
+ $fullname = $this->info['lis_person_name_full'];
+ if ( strlen($fullname) > 0 ) return $fullname;
+ if ( strlen($familyname) > 0 and strlen($givenname) > 0 ) return $givenname + $familyname;
+ if ( strlen($givenname) > 0 ) return $givenname;
+ if ( strlen($familyname) > 0 ) return $familyname;
+ return $this->getUserEmail();
+ }
+
+ function getUserKey() {
+ $oauth = $this->info['oauth_consumer_key'];
+ $id = $this->info['user_id'];
+ if ( strlen($id) > 0 and strlen($oauth) > 0 ) return $oauth . ':' . $id;
+ return false;
+ }
+
+ function getUserImage() {
+ $image = $this->info['user_image'];
+ if ( strlen($image) > 0 ) return $image;
+ $email = $this->getUserEmail();
+ if ( $email === false ) return false;
+ $size = 40;
+ $grav_url = $_SERVER['HTTPS'] ? 'https://' : 'http://';
+ $grav_url = $grav_url . "www.gravatar.com/avatar.php?gravatar_id=".md5( strtolower($email) )."&size=".$size;
+ return $grav_url;
+ }
+
+ function getResourceKey() {
+ $oauth = $this->info['oauth_consumer_key'];
+ $id = $this->info['resource_link_id'];
+ if ( strlen($id) > 0 and strlen($oauth) > 0 ) return $oauth . ':' . $id;
+ return false;
+ }
+
+ function getResourceTitle() {
+ $title = $this->info['resource_link_title'];
+ if ( strlen($title) > 0 ) return $title;
+ return false;
+ }
+
+ function getConsumerKey() {
+ $oauth = $this->info['oauth_consumer_key'];
+ return $oauth;
+ }
+
+ function getCourseKey() {
+ if ( $this->context_id ) return $this->context_id;
+ $oauth = $this->info['oauth_consumer_key'];
+ $id = $this->info['context_id'];
+ if ( strlen($id) > 0 and strlen($oauth) > 0 ) return $oauth . ':' . $id;
+ return false;
+ }
+
+ function getCourseName() {
+ $label = $this->info['context_label'];
+ $title = $this->info['context_title'];
+ $id = $this->info['context_id'];
+ if ( strlen($label) > 0 ) return $label;
+ if ( strlen($title) > 0 ) return $title;
+ if ( strlen($id) > 0 ) return $id;
+ return false;
+ }
+
+ // TODO: Add javasript version if headers are already sent
+ function redirect() {
+ $host = $_SERVER['HTTP_HOST'];
+ $uri = $_SERVER['PHP_SELF'];
+ $location = $_SERVER['HTTPS'] ? 'https://' : 'http://';
+ $location = $location . $host . $uri;
+ $location = $this->addSession($location);
+ header("Location: $location");
+ }
+
+ function dump() {
+ if ( ! $this->valid or $this->info == false ) return "Context not valid\n";
+ $ret = "";
+ if ( $this->isInstructor() ) {
+ $ret .= "isInstructor() = true\n";
+ } else {
+ $ret .= "isInstructor() = false\n";
+ }
+ $ret .= "getUserKey() = ".$this->getUserKey()."\n";
+ $ret .= "getUserEmail() = ".$this->getUserEmail()."\n";
+ $ret .= "getUserShortName() = ".$this->getUserShortName()."\n";
+ $ret .= "getUserName() = ".$this->getUserName()."\n";
+ $ret .= "getUserImage() = ".$this->getUserImage()."\n";
+ $ret .= "getResourceKey() = ".$this->getResourceKey()."\n";
+ $ret .= "getResourceTitle() = ".$this->getResourceTitle()."\n";
+ $ret .= "getCourseName() = ".$this->getCourseName()."\n";
+ $ret .= "getCourseKey() = ".$this->getCourseKey()."\n";
+ $ret .= "getConsumerKey() = ".$this->getConsumerKey()."\n";
+ return $ret;
+ }
+
+ /**
+ * Data submitter are in base64 then we have to decode
+ * @author Antoni Bertran (antoni@tresipunt.com)
+ * @param $info array
+ * @date 20120801
+ */
+ function decodeBase64($info) {
+ $keysNoEncode = array("lti_version", "lti_message_type", "tool_consumer_instance_description", "tool_consumer_instance_guid", "oauth_consumer_key", "custom_lti_message_encoded_base64", "oauth_nonce", "oauth_version", "oauth_callback", "oauth_timestamp", "basiclti_submit", "oauth_signature_method", "ext_ims_lis_memberships_id", "ext_ims_lis_memberships_url");
+ foreach ($info as $key => $item){
+ if (!in_array($key, $keysNoEncode))
+ $info[$key] = base64_decode($item);
+ }
+ return $info;
+ }
+
+}
+
+?>
--- /dev/null
+<?php
+
+require_once 'OAuth.php';
+
+ // Replace this with some real function that pulls from the LMS.
+ function getLMSDummyData() {
+ $parms = array(
+ "resource_link_id" => "120988f929-274612",
+ "resource_link_title" => "Weekly Blog",
+ "resource_link_description" => "Each student needs to reflect on the weekly reading. These should be one paragraph long.",
+ "user_id" => "292832126",
+ "roles" => "Instructor", // or Learner
+ "lis_person_name_full" => 'Jane Q. Public',
+ "lis_person_contact_email_primary" => "user@school.edu",
+ "lis_person_sourcedid" => "school.edu:user",
+ "context_id" => "456434513",
+ "context_title" => "Design of Personal Environments",
+ "context_label" => "SI182",
+ );
+
+ return $parms;
+ }
+
+ function validateDescriptor($descriptor)
+ {
+ $xml = new SimpleXMLElement($xmldata);
+ if ( ! $xml ) {
+ echo("Error parsing Descriptor XML\n");
+ return;
+ }
+ $launch_url = $xml->secure_launch_url[0];
+ if ( ! $launch_url ) $launch_url = $xml->launch_url[0];
+ if ( $launch_url ) $launch_url = (string) $launch_url;
+ return $launch_url;
+ }
+
+ // Parse a descriptor
+ function launchInfo($xmldata) {
+ $xml = new SimpleXMLElement($xmldata);
+ if ( ! $xml ) {
+ echo("Error parsing Descriptor XML\n");
+ return;
+ }
+ $launch_url = $xml->secure_launch_url[0];
+ if ( ! $launch_url ) $launch_url = $xml->launch_url[0];
+ if ( $launch_url ) $launch_url = (string) $launch_url;
+ $custom = array();
+ if ( $xml->custom[0]->parameter )
+ foreach ( $xml->custom[0]->parameter as $resource) {
+ $key = (string) $resource['key'];
+ $key = strtolower($key);
+ $nk = "";
+ for($i=0; $i < strlen($key); $i++) {
+ $ch = substr($key,$i,1);
+ if ( $ch >= "a" && $ch <= "z" ) $nk .= $ch;
+ else if ( $ch >= "0" && $ch <= "9" ) $nk .= $ch;
+ else $nk .= "_";
+ }
+ $value = (string) $resource;
+ $custom["custom_".$nk] = $value;
+ }
+ return array("launch_url" => $launch_url, "custom" => $custom ) ;
+ }
+
+function signParameters($oldparms, $endpoint, $method, $oauth_consumer_key, $oauth_consumer_secret,
+ $submit_text = false, $org_id = false, $org_desc = false)
+{
+ global $last_base_string;
+ $parms = $oldparms;
+ if ( ! isset($parms["lti_version"]) ) $parms["lti_version"] = "LTI-1p0";
+ if ( ! isset($parms["lti_message_type"]) ) $parms["lti_message_type"] = "basic-lti-launch-request";
+ if ( ! isset($parms["oauth_callback"]) ) $parms["oauth_callback"] = "about:blank";
+ if ( $org_id ) $parms["tool_consumer_instance_guid"] = $org_id;
+ if ( $org_desc ) $parms["tool_consumer_instance_description"] = $org_desc;
+ if ( $submit_text ) $parms["ext_submit"] = $submit_text;
+
+ $test_token = '';
+
+ $hmac_method = new OAuthSignatureMethod_HMAC_SHA1();
+ $test_consumer = new OAuthConsumer($oauth_consumer_key, $oauth_consumer_secret, NULL);
+
+ $acc_req = OAuthRequest::from_consumer_and_token($test_consumer, $test_token, $method, $endpoint, $parms);
+
+ $acc_req->sign_request($hmac_method, $test_consumer, $test_token);
+
+ // Pass this back up "out of band" for debugging
+ $last_base_string = $acc_req->get_signature_base_string();
+
+ $newparms = $acc_req->get_parameters();
+
+ return $newparms;
+}
+
+function signOnly($oldparms, $endpoint, $method, $oauth_consumer_key, $oauth_consumer_secret)
+{
+ global $last_base_string;
+ $parms = $oldparms;
+
+ $test_token = '';
+
+ $hmac_method = new OAuthSignatureMethod_HMAC_SHA1();
+ $test_consumer = new OAuthConsumer($oauth_consumer_key, $oauth_consumer_secret, NULL);
+
+ $acc_req = OAuthRequest::from_consumer_and_token($test_consumer, $test_token, $method, $endpoint, $parms);
+ $acc_req->sign_request($hmac_method, $test_consumer, $test_token);
+
+ // Pass this back up "out of band" for debugging
+ $last_base_string = $acc_req->get_signature_base_string();
+
+ $newparms = $acc_req->get_parameters();
+
+ return $newparms;
+}
+
+function postLaunchHTML($newparms, $endpoint, $debug=false, $iframeattr=false) {
+ global $last_base_string;
+ $r = "<div id=\"ltiLaunchFormSubmitArea\">\n";
+ if ( $iframeattr ) {
+ $r = "<form action=\"".$endpoint."\" name=\"ltiLaunchForm\" id=\"ltiLaunchForm\" method=\"post\" target=\"basicltiLaunchFrame\" encType=\"application/x-www-form-urlencoded\">\n" ;
+ } else {
+ $r = "<form action=\"".$endpoint."\" name=\"ltiLaunchForm\" id=\"ltiLaunchForm\" method=\"post\" encType=\"application/x-www-form-urlencoded\">\n" ;
+ }
+ $submit_text = $newparms['ext_submit'];
+ foreach($newparms as $key => $value ) {
+ $key = htmlspecialchars($key);
+ $value = htmlspecialchars($value);
+ if ( $key == "ext_submit" ) {
+ $r .= "<input type=\"submit\" name=\"";
+ } else {
+ $r .= "<input type=\"hidden\" name=\"";
+ }
+ $r .= $key;
+ $r .= "\" value=\"";
+ $r .= $value;
+ $r .= "\"/>\n";
+ }
+ if ( $debug ) {
+ $r .= "<script language=\"javascript\"> \n";
+ $r .= " //<![CDATA[ \n" ;
+ $r .= "function basicltiDebugToggle() {\n";
+ $r .= " var ele = document.getElementById(\"basicltiDebug\");\n";
+ $r .= " if(ele.style.display == \"block\") {\n";
+ $r .= " ele.style.display = \"none\";\n";
+ $r .= " }\n";
+ $r .= " else {\n";
+ $r .= " ele.style.display = \"block\";\n";
+ $r .= " }\n";
+ $r .= "} \n";
+ $r .= " //]]> \n" ;
+ $r .= "</script>\n";
+ $r .= "<a id=\"displayText\" href=\"javascript:basicltiDebugToggle();\">";
+ $r .= get_stringIMS("toggle_debug_data","basiclti")."</a>\n";
+ $r .= "<div id=\"basicltiDebug\" style=\"display:none\">\n";
+ $r .= "<b>".get_stringIMS("basiclti_endpoint","basiclti")."</b><br/>\n";
+ $r .= $endpoint . "<br/>\n <br/>\n";
+ $r .= "<b>".get_stringIMS("basiclti_parameters","basiclti")."</b><br/>\n";
+ foreach($newparms as $key => $value ) {
+ $key = htmlspecialchars($key);
+ $value = htmlspecialchars($value);
+ $r .= "$key = $value<br/>\n";
+ }
+ $r .= " <br/>\n";
+ $r .= "<p><b>".get_stringIMS("basiclti_base_string","basiclti")."</b><br/>\n".$last_base_string."</p>\n";
+ $r .= "</div>\n";
+ }
+ $r .= "</form>\n";
+ if ( $iframeattr ) {
+ $r .= "<iframe name=\"basicltiLaunchFrame\" id=\"basicltiLaunchFrame\" src=\"\"\n";
+ $r .= $iframeattr . ">\n<p>".get_stringIMS("frames_required","basiclti")."</p>\n</iframe>\n";
+ }
+ if ( ! $debug ) {
+ $ext_submit = "ext_submit";
+ $ext_submit_text = $submit_text;
+ $r .= " <script type=\"text/javascript\"> \n" .
+ " //<![CDATA[ \n" .
+ " document.getElementById(\"ltiLaunchForm\").style.display = \"none\";\n" .
+ " nei = document.createElement('input');\n" .
+ " nei.setAttribute('type', 'hidden');\n" .
+ " nei.setAttribute('name', '".$ext_submit."');\n" .
+ " nei.setAttribute('value', '".$ext_submit_text."');\n" .
+ " document.getElementById(\"ltiLaunchForm\").appendChild(nei);\n" .
+ " document.ltiLaunchForm.submit(); \n" .
+ " //]]> \n" .
+ " </script> \n";
+ }
+ $r .= "</div>\n";
+ return $r;
+}
+
+/* This is a bit of homage to Moodle's pattern of internationalisation */
+function get_stringIMS($key,$bundle) {
+ return $key;
+}
+
+function do_post_request($url, $data, $optional_headers = null)
+{
+ $params = array('http' => array(
+ 'method' => 'POST',
+ 'content' => $data
+ ));
+
+ if ($optional_headers !== null) {
+ $header = $optional_headers . "\r\n";
+ }
+ // $header = $header . "Content-type: application/x-www-form-urlencoded\r\n";
+ $params['http']['header'] = $header;
+ $ctx = stream_context_create($params);
+ $fp = @fopen($url, 'rb', false, $ctx);
+ if (!$fp) {
+ echo @stream_get_contents($fp);
+ throw new Exception("Problem with $url, $php_errormsg");
+ }
+ $response = @stream_get_contents($fp);
+ if ($response === false) {
+ throw new Exception("Problem reading data from $url, $php_errormsg");
+ }
+ return $response;
+}
+
--- /dev/null
+This library was originally published by the IMS at https://code.google.com/p/ims-dev/ which no longer exists. The
+current code was taken from https://github.com/jfederico/ims-dev/tree/master/basiclti/php-simple/ims-blti - with
+several changes to the code (including bug fixes). As the library is no longer supported upgrades are not possible.
+In future releases we should look into using a supported library.
--- /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/>.
+
+/**
+ * List the tool provided in a course
+ *
+ * @package enrol_lti
+ * @copyright 2016 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once(dirname(__FILE__) . '/../../config.php');
+require_once($CFG->dirroot.'/enrol/lti/lib.php');
+
+$courseid = required_param('courseid', PARAM_INT);
+$action = optional_param('action', '', PARAM_ALPHA);
+if ($action) {
+ require_sesskey();
+ $instanceid = required_param('instanceid', PARAM_INT);
+ $instance = $DB->get_record('enrol', array('id' => $instanceid), '*', MUST_EXIST);
+}
+$confirm = optional_param('confirm', 0, PARAM_INT);
+
+$course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
+
+$context = context_course::instance($course->id);
+
+require_login($course);
+require_capability('moodle/course:enrolreview', $context);
+
+$ltiplugin = enrol_get_plugin('lti');
+$canconfig = has_capability('moodle/course:enrolconfig', $context);
+$pageurl = new moodle_url('/enrol/lti/index.php', array('courseid' => $courseid));
+
+$PAGE->set_url($pageurl);
+$PAGE->set_title(get_string('course') . ': ' . $course->fullname);
+$PAGE->set_pagelayout('admin');
+
+// Check if we want to perform any actions.
+if ($action) {
+ if ($action === 'delete') {
+ if ($ltiplugin->can_delete_instance($instance)) {
+ if ($confirm) {
+ $ltiplugin->delete_instance($instance);
+ redirect($PAGE->url);
+ }
+
+ $yesurl = new moodle_url('/enrol/lti/index.php',
+ array('courseid' => $course->id,
+ 'action' => 'delete',
+ 'instanceid' => $instance->id,
+ 'confirm' => 1,
+ 'sesskey' => sesskey())
+ );
+ $displayname = $ltiplugin->get_instance_name($instance);
+ $users = $DB->count_records('user_enrolments', array('enrolid' => $instance->id));
+ if ($users) {
+ $message = markdown_to_html(get_string('deleteinstanceconfirm', 'enrol',
+ array('name' => $displayname,
+ 'users' => $users)));
+ } else {
+ $message = markdown_to_html(get_string('deleteinstancenousersconfirm', 'enrol',
+ array('name' => $displayname)));
+ }
+ echo $OUTPUT->header();
+ echo $OUTPUT->confirm($message, $yesurl, $PAGE->url);
+ echo $OUTPUT->footer();
+ die();
+ }
+ } else if ($action === 'disable') {
+ if ($ltiplugin->can_hide_show_instance($instance)) {
+ if ($instance->status != ENROL_INSTANCE_DISABLED) {
+ $ltiplugin->update_status($instance, ENROL_INSTANCE_DISABLED);
+ redirect($PAGE->url);
+ }
+ }
+ } else if ($action === 'enable') {
+ if ($ltiplugin->can_hide_show_instance($instance)) {
+ if ($instance->status != ENROL_INSTANCE_ENABLED) {
+ $ltiplugin->update_status($instance, ENROL_INSTANCE_ENABLED);
+ redirect($PAGE->url);
+ }
+ }
+ }
+}
+
+echo $OUTPUT->header();
+echo $OUTPUT->heading(get_string('toolsprovided', 'enrol_lti'));
+
+if (\enrol_lti\helper::count_lti_tools(array('courseid' => $courseid)) > 0) {
+ $table = new \enrol_lti\manage_table($courseid);
+ $table->define_baseurl($pageurl);
+ $table->out(50, false);
+} else {
+ $notify = new \core\output\notification(get_string('notoolsprovided', 'enrol_lti'),
+ \core\output\notification::NOTIFY_WARNING);
+ echo $OUTPUT->render($notify);
+}
+
+if ($ltiplugin->can_add_instance($course->id)) {
+ echo $OUTPUT->single_button(new moodle_url('/enrol/editinstance.php',
+ array(
+ 'type' => 'lti',
+ 'courseid' => $course->id,
+ 'returnurl' => new moodle_url('/enrol/lti/index.php', array('courseid' => $course->id)))
+ ),
+ get_string('add'));
+}
+
+echo $OUTPUT->footer();
--- /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/>.
+
+/**
+ * LTI enrolment plugin version information
+ *
+ * @package enrol_lti
+ * @copyright 2016 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$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['enrolisdisabled'] = 'The LTI enrolment plugin is disabled.';
+$string['enrolperiod'] = 'Enrolment duration';
+$string['enrolperiod_help'] = 'Length of time that the enrolment is valid, starting with the moment the user enrols themselves from the remote system. If disabled, the enrolment duration will be unlimited.';
+$string['enrolmentfinished'] = 'Enrolment finished.';
+$string['enrolmentnotstarted'] = 'Enrolment has not started.';
+$string['enrolstartdate'] = 'Start date';
+$string['enrolstartdate_help'] = 'If enabled, users can access from this date onward only.';
+$string['globalsharedsecret'] = 'Global shared secret';
+$string['gradesync'] = 'Grade synchronisation';
+$string['gradesync_help'] = 'This determines if we want grade synchronisation to occur.';
+$string['maxenrolled'] = 'Maximum enrolled';
+$string['maxenrolled_help'] = 'Specifies the maximum number of users that can access from the remote system. The value \'0\' means there is no limit.';
+$string['maxenrolledreached'] = 'Maximum number of users allowed to access was already reached.';
+$string['membersync'] = 'Member synchronisation';
+$string['membersync_help'] = 'This determines if we want member synchronisation to occur.';
+$string['membersyncmode'] = 'Members synchronisation mode';
+$string['membersyncmode_help'] = 'This setting determines what we should do when synchronising members.';
+$string['membersyncmodeenrolandunenrol'] = 'Enrol new and unenrol missing members';
+$string['membersyncmodeenrolnew'] = 'Enrol new members';
+$string['membersyncmodeunenrolmissing'] = 'Unenrol missing members';
+$string['notoolsprovided'] = 'No tools provided';
+$string['lti:config'] = 'Configure LTI enrol instances';
+$string['lti:unenrol'] = 'Unenrol users from the course';
+$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['requirecompletion'] = 'Require the course or activity to be completed before sending the grades';
+$string['roleinstructor'] = 'Role for instructor';
+$string['roleinstructor_help'] = 'This is the role that will be assigned at the context of the tool specificed to LTI consumer instructor.';
+$string['rolelearner'] = 'Role for learner';
+$string['rolelearner_help'] = 'This is the role that will be assigned at the context of the tool specificed to the LTI consumer student.';
+$string['secret'] = 'Secret';
+$string['secret_help'] = 'This is the secret that is shared with the LTI consumer in order for them to access this tool';
+$string['sharedexternaltools'] = 'Shared external tools';
+$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['toolsprovided'] = 'Tools provided';
+$string['tooltobeprovided'] = 'Tool to be provided';
+$string['userdefaultvalues'] = 'User default values';
--- /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/>.
+
+/**
+ * LTI enrolment plugin main library file.
+ *
+ * @package enrol_lti
+ * @copyright 2016 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * LTI enrolment plugin class.
+ *
+ * @package enrol_lti
+ * @copyright 2016 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class enrol_lti_plugin extends enrol_plugin {
+
+ /**
+ * Return true if we can add a new instance to this course.
+ *
+ * @param int $courseid
+ * @return boolean
+ */
+ public function can_add_instance($courseid) {
+ $context = context_course::instance($courseid, MUST_EXIST);
+ return has_capability('moodle/course:enrolconfig', $context) && has_capability('enrol/lti:config', $context);
+ }
+
+ /**
+ * Is it possible to delete