Merge branch 'MDL-60416_master' of git://github.com/markn86/moodle

This commit is contained in:
Andrew Nicols 2018-04-09 13:38:56 +08:00
commit 98645b5be4
29 changed files with 3260 additions and 230 deletions

View file

@ -87,6 +87,23 @@ abstract class restore_subplugin {
}
}
/**
* The after_restore dispatcher for any restore_subplugin class.
*
* This method will dispatch execution to the corresponding
* after_restore_xxx() method when available, with xxx
* being the connection point of the instance, so subplugin
* classes with multiple connection points will support
* multiple after_restore methods, one for each connection point.
*/
public function launch_after_restore_methods() {
// Check if the after_restore method exists and launch it.
$afterestore = 'after_restore_' . basename($this->connectionpoint->get_path());
if (method_exists($this, $afterestore)) {
$this->$afterestore();
}
}
// Protected API starts here
// restore_step/structure_step/task wrappers

View file

@ -1804,7 +1804,7 @@ class core_plugin_manager {
),
'ltiservice' => array(
'memberships', 'profile', 'toolproxy', 'toolsettings'
'gradebookservices', 'memberships', 'profile', 'toolproxy', 'toolsettings'
),
'mlbackend' => array(

View file

@ -28,6 +28,7 @@ namespace mod_lti\local\ltiservice;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/lti/locallib.php');
@ -41,7 +42,16 @@ require_once($CFG->dirroot . '/mod/lti/locallib.php');
*/
abstract class resource_base {
/** @var object Service associated with this resource. */
/** HTTP Post method */
const HTTP_POST = 'POST';
/** HTTP Get method */
const HTTP_GET = 'GET';
/** HTTP Put method */
const HTTP_PUT = 'PUT';
/** HTTP Delete method */
const HTTP_DELETE = 'DELETE';
/** @var service_base Service associated with this resource. */
private $service;
/** @var string Type for this resource. */
protected $type;
@ -62,7 +72,7 @@ abstract class resource_base {
/**
* Class constructor.
*
* @param mod_lti\local\ltiservice\service_base $service Service instance
* @param service_base $service Service instance
*/
public function __construct($service) {
@ -125,7 +135,7 @@ abstract class resource_base {
/**
* Get the resource's service.
*
* @return mod_lti\local\ltiservice\service_base
* @return mixed
*/
public function get_service() {
@ -190,7 +200,7 @@ abstract class resource_base {
/**
* Execute the request for this resource.
*
* @param mod_lti\local\ltiservice\response $response Response object for this request.
* @param response $response Response object for this request.
*/
public abstract function execute($response);
@ -224,7 +234,7 @@ abstract class resource_base {
}
}
if (!$ok) {
debugging('Requested service not included in tool proxy: ' . $this->get_id());
debugging('Requested service not included in tool proxy: ' . $this->get_id(), DEBUG_DEVELOPER);
}
}
}
@ -233,6 +243,45 @@ abstract class resource_base {
}
/**
* Check to make sure the request is valid.
*
* @param int $typeid The typeid we want to use
* @param int $contextid The course we are at
* @param string $permissionrequested The permission to be checked
* @param string $body Body of HTTP request message
*
* @return boolean
*/
public function check_type($typeid, $contextid, $permissionrequested, $body = null) {
$ok = false;
if ($this->get_service()->check_type($typeid, $contextid, $body)) {
$neededpermissions = $this->get_permissions($typeid);
foreach ($neededpermissions as $permission) {
if ($permission == $permissionrequested) {
$ok = true;
break;
}
}
if (!$ok) {
debugging('Requested service ' . $permissionrequested . ' not included in tool type: ' . $typeid,
DEBUG_DEVELOPER);
}
}
return $ok;
}
/**
* get permissions from the config of the tool for that resource
*
* @param int $ltitype Type of LTI
* @return array with the permissions related to this resource by the $ltitype or empty if none.
*/
public function get_permissions($ltitype) {
return array();
}
/**
* Parse a value for custom parameter substitution variables.
*

View file

@ -54,6 +54,8 @@ class response {
private $body;
/** @var array HTTP response codes. */
private $responsecodes;
/** @var array HTTP additional headers. */
private $additionalheaders;
/**
* Class constructor.
@ -83,6 +85,7 @@ class response {
500 => 'Internal Server Error',
501 => 'Not Implemented'
);
$this->additionalheaders = array();
}
@ -202,11 +205,23 @@ class response {
$this->body = $body;
}
/**
* Add an additional header.
*
* @param string $header The new header
*/
public function add_additional_header($header) {
array_push($this->additionalheaders, $header);
}
/**
* Send the response.
*/
public function send() {
header("HTTP/1.0 {$this->code} {$this->get_reason()}");
foreach ($this->additionalheaders as $header) {
header($header);
}
if (($this->code >= 200) && ($this->code < 300)) {
if (!empty($this->contenttype)) {
header("Content-Type: {$this->contenttype};charset=UTF-8");

View file

@ -28,11 +28,13 @@ namespace mod_lti\local\ltiservice;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/lti/locallib.php');
require_once($CFG->dirroot . '/mod/lti/OAuthBody.php');
// TODO: Switch to core oauthlib once implemented - MDL-30149.
use moodle\mod\lti as lti;
use stdClass;
/**
@ -133,10 +135,80 @@ abstract class service_base {
/**
* Get the resources for this service.
*
* @return array
* @return resource_base[]
*/
abstract public function get_resources();
/**
* Returns the configuration options for this service.
*
* @param \MoodleQuickForm $mform Moodle quickform object definition
*/
public function get_configuration_options(&$mform) {
}
/**
* Return an array with the names of the parameters that the service will be saving in the configuration
*
* @return array Names list of the parameters that the service will be saving in the configuration
*/
public function get_configuration_parameter_names() {
return array();
}
/**
* Default implementation will check for the existence of at least one mod_lti entry for that tool and context.
*
* It may be overridden if other inferences can be done.
*
* Ideally a Site Tool should be explicitly engaged with a course, the check on the presence of a link is a proxy
* to infer a Site Tool engagement until an explicit Site Tool - Course relationship exists.
*
* @param int $typeid The tool lti type id.
* @param int $courseid The course id.
* @return bool returns True if tool is used in context, false otherwise.
*/
public function is_used_in_context($typeid, $courseid) {
global $DB;
$ok = $DB->record_exists('lti', array('course' => $courseid, 'typeid' => $typeid));
return $ok || $DB->record_exists('lti_types', array('course' => $courseid, 'id' => $typeid));
}
/**
* Checks if there is a site tool or a course tool for this site.
*
* @param int $typeid The tool lti type id.
* @param int $courseid The course id.
* @return bool returns True if tool is allowed in context, false otherwise.
*/
public function is_allowed_in_context($typeid, $courseid) {
global $DB;
// Check if it is a Course tool for this course or a Site tool.
$type = $DB->get_record('lti_types', array('id' => $typeid));
return $type && ($type->course == $courseid || $type->course == SITEID);
}
/**
* Return an array of key/values to add to the launch parameters.
*
* @param string $messagetype 'basic-lti-launch-request' or 'ContentItemSelectionRequest'.
* @param string $courseid The course id.
* @param string $userid The user id.
* @param string $typeid The tool lti type id.
* @param string $modlti The id of the lti activity.
*
* The type is passed to check the configuration and not return parameters for services not used.
*
* @return array Key/value pairs to add as launch parameters.
*/
public function get_launch_parameters($messagetype, $courseid, $userid, $typeid, $modlti = null) {
return array();
}
/**
* Get the path for service requests.
*
@ -202,9 +274,35 @@ abstract class service_base {
if ($ok) {
$this->toolproxy = $toolproxy;
}
return $ok;
}
/**
* Check that the request has been properly signed.
*
* @param int $typeid The tool id
* @param int $courseid The course we are at
* @param string $body Request body (null if none)
*
* @return bool
*/
public function check_type($typeid, $courseid, $body = null) {
$ok = false;
$tool = null;
$consumerkey = lti\get_oauth_key_from_headers();
if (empty($typeid)) {
return $ok;
} else if ($this->is_allowed_in_context($typeid, $courseid)) {
$tool = lti_get_type_type_config($typeid);
if ($tool !== false) {
if (!$this->is_unsigned() && ($tool->lti_resourcekey == $consumerkey)) {
$ok = $this->check_signature($tool->lti_resourcekey, $tool->lti_password, $body);
} else {
$ok = $this->is_unsigned();
}
}
}
return $ok;
}
/**

View file

@ -49,10 +49,24 @@
defined('MOODLE_INTERNAL') || die;
global $CFG;
require_once($CFG->libdir.'/formslib.php');
require_once($CFG->dirroot.'/mod/lti/locallib.php');
class mod_lti_edit_types_form extends moodleform{
/**
* LTI Edit Form
*
* @package mod_lti
* @copyright 2009 Marc Alier, Jordi Piguillem, Nikolas Galanis
* marc.alier@upc.edu
* @copyright 2009 Universitat Politecnica de Catalunya http://www.upc.edu
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_lti_edit_types_form extends moodleform {
/**
* Define this form.
*/
public function definition() {
global $CFG;
@ -147,6 +161,16 @@ class mod_lti_edit_types_form extends moodleform{
$mform->disabledIf('lti_contentitem', null);
}
$mform->addElement('text', 'lti_toolurl_ContentItemSelectionRequest',
get_string('toolurl_contentitemselectionrequest', 'lti'), array('size' => '64'));
$mform->setType('lti_toolurl_ContentItemSelectionRequest', PARAM_URL);
$mform->setAdvanced('lti_toolurl_ContentItemSelectionRequest');
$mform->addHelpButton('lti_toolurl_ContentItemSelectionRequest', 'toolurl_contentitemselectionrequest', 'lti');
$mform->disabledIf('lti_toolurl_ContentItemSelectionRequest', 'lti_contentitem', 'notchecked');
if ($istool) {
$mform->disabledIf('lti_toolurl__ContentItemSelectionRequest', null);
}
$mform->addElement('hidden', 'oldicon');
$mform->setType('oldicon', PARAM_URL);
@ -160,6 +184,11 @@ class mod_lti_edit_types_form extends moodleform{
$mform->setAdvanced('lti_secureicon');
$mform->addHelpButton('lti_secureicon', 'secure_icon_url', 'lti');
if (!$istool) {
// Display the lti advantage services.
$this->get_lti_advantage_services($mform);
}
if (!$istool) {
// Add privacy preferences fieldset where users choose whether to send their data.
$mform->addElement('header', 'privacy', get_string('privacy', 'lti'));
@ -253,4 +282,19 @@ class mod_lti_edit_types_form extends moodleform{
}
return $data;
}
/**
* Generates the lti advantage extra configuration adding it to the mform
*
* @param MoodleQuickForm $mform
*/
public function get_lti_advantage_services(&$mform) {
// For each service add the label and get the array of configuration.
$services = lti_get_services();
$mform->addElement('header', 'services', get_string('services', 'lti'));
foreach ($services as $service) {
/** @var \mod_lti\local\ltiservice\service_base $service */
$service->get_configuration_options($mform);
}
}
}

View file

@ -510,9 +510,10 @@ A base URL of *quiz.tool.com* would match the following:
If two different tool configurations are for the same domain, the most specific match will be used.
You can also insert a cartridge URL if you have one and the details for the tool will be automatically filled.';
$string['toolurl_contentitemselectionrequest'] = 'Content Selection URL';
$string['toolurl_contentitemselectionrequest_help'] = 'The Content Selection URL will be used to launch the content selection page from the tool provider. If it is empty, the Tool URL will be used';
$string['typename'] = 'Tool name';
$string['typename_help'] = 'The tool name is used to identify the tool provider within Moodle. The name entered will be visible
to teachers when adding external tools within courses.';
$string['typename_help'] = 'The tool name is used to identify the tool provider within Moodle. The name entered will be visible to teachers when adding external tools within courses.';
$string['types'] = 'Types';
$string['unabletocreatetooltype'] = 'Unable to create tool';
$string['unabletofindtooltype'] = 'Unable to find tool for {$a->id}';

View file

@ -53,6 +53,7 @@ defined('MOODLE_INTERNAL') || die;
// TODO: Switch to core oauthlib once implemented - MDL-30149.
use moodle\mod\lti as lti;
global $CFG;
require_once($CFG->dirroot.'/mod/lti/OAuth.php');
require_once($CFG->libdir.'/weblib.php');
require_once($CFG->dirroot . '/course/modlib.php');
@ -96,7 +97,7 @@ define('LTI_VERSION_2', 'LTI-2p0');
* @since Moodle 3.0
*/
function lti_get_launch_data($instance) {
global $PAGE, $CFG;
global $PAGE, $CFG, $USER;
if (empty($instance->typeid)) {
$tool = lti_get_tool_by_url_match($instance->toolurl, $instance->course);
@ -230,6 +231,18 @@ function lti_get_launch_data($instance) {
$requestparams['launch_presentation_return_url'] = $returnurl;
// Add the parameters configured by the LTI advantage services.
if ($typeid && !$islti2) {
$services = lti_get_services();
foreach ($services as $service) {
$ltiadvantageparameters = $service->get_launch_parameters('basic-lti-launch-request',
$course->id, $USER->id , $typeid, $instance->id);
foreach ($ltiadvantageparameters as $ltiadvantagekey => $ltiadvantagevalue) {
$requestparams[$ltiadvantagekey] = $ltiadvantagevalue;
}
}
}
// Allow request params to be updated by sub-plugins.
$plugins = core_component::get_plugin_list('ltisource');
foreach (array_keys($plugins) as $plugin) {
@ -284,7 +297,7 @@ function lti_launch_tool($instance) {
/**
* Prepares an LTI registration request message
*
* $param object $instance Tool Proxy instance object
* @param object $toolproxy Tool Proxy instance object
*/
function lti_register($toolproxy) {
$endpoint = $toolproxy->regurl;
@ -617,6 +630,8 @@ function lti_build_custom_parameters($toolproxy, $tool, $instance, $params, $cus
function lti_build_content_item_selection_request($id, $course, moodle_url $returnurl, $title = '', $text = '', $mediatypes = [],
$presentationtargets = [], $autocreate = false, $multiple = false,
$unsigned = false, $canconfirm = false, $copyadvice = false) {
global $USER;
$tool = lti_get_type($id);
// Validate parameters.
if (!$tool) {
@ -693,6 +708,18 @@ function lti_build_content_item_selection_request($id, $course, moodle_url $retu
$requestparams = array_merge($requestparams, $lti2params);
}
// Add the parameters configured by the LTI advantage services.
if ($id && !$islti2) {
$services = lti_get_services();
foreach ($services as $service) {
$ltiadvantageparameters = $service->get_launch_parameters('ContentItemSelectionRequest',
$course->id, $USER->id , $id);
foreach ($ltiadvantageparameters as $ltiadvantagekey => $ltiadvantagevalue) {
$requestparams[$ltiadvantagekey] = $ltiadvantagevalue;
}
}
}
// Get standard request parameters and merge to the request parameters.
$orgid = !empty($typeconfig['organizationid']) ? $typeconfig['organizationid'] : '';
$standardparams = lti_build_standard_request(null, $orgid, $islti2, 'ContentItemSelectionRequest');
@ -856,9 +883,6 @@ function lti_tool_configuration_from_content_item($typeid, $messagetype, $ltiver
if (empty($items)) {
throw new moodle_exception('errorinvaliddata', 'mod_lti', '', $contentitemsjson);
}
if ($items->{'@context'} !== 'http://purl.imsglobal.org/ctx/lti/v1/ContentItem') {
throw new moodle_exception('errorinvalidmediatype', 'mod_lti', '', $items->{'@context'});
}
if (!isset($items->{'@graph'}) || !is_array($items->{'@graph'}) || (count($items->{'@graph'}) > 1)) {
throw new moodle_exception('errorinvalidresponseformat', 'mod_lti');
}
@ -922,7 +946,7 @@ function lti_tool_configuration_from_content_item($typeid, $messagetype, $ltiver
}
function lti_get_tool_table($tools, $id) {
global $CFG, $OUTPUT, $USER;
global $OUTPUT;
$html = '';
$typename = get_string('typename', 'lti');
@ -1126,7 +1150,7 @@ EOD;
*
* @param object $tool Tool instance object
*
* @return Array of enabled capabilities
* @return array List of enabled capabilities
*/
function lti_get_enabled_capabilities($tool) {
if (!isset($tool)) {
@ -1224,10 +1248,11 @@ function lti_get_custom_parameters($toolproxy, $tool, $params, $parameters) {
* @param string $value Custom parameter value
* @param boolean $islti2 True if an LTI 2 tool is being launched
*
* @return Parsed value of custom parameter
* @return string Parsed value of custom parameter
*/
function lti_parse_custom_parameter($toolproxy, $tool, $params, $value, $islti2) {
global $USER, $COURSE;
// This is required as {${$valarr[0]}->{$valarr[1]}}" may be using the USER var.
global $USER;
if ($value) {
if (substr($value, 0, 1) == '\\') {
@ -1403,8 +1428,6 @@ function lti_get_tools_by_url($url, $state, $courseid = null) {
function lti_get_tools_by_domain($domain, $state = null, $courseid = null) {
global $DB, $SITE;
$filters = array('tooldomain' => $domain);
$statefilter = '';
$coursefilter = '';
@ -1433,6 +1456,9 @@ function lti_get_tools_by_domain($domain, $state = null, $courseid = null) {
/**
* Returns all basicLTI tools configured by the administrator
*
* @param int $course
*
* @return array
*/
function lti_filter_get_types($course) {
global $DB;
@ -1698,7 +1724,7 @@ function lti_delete_type($id) {
function lti_set_state_for_type($id, $state) {
global $DB;
$DB->update_record('lti_types', array('id' => $id, 'state' => $state));
$DB->update_record('lti_types', (object)array('id' => $id, 'state' => $state));
}
/**
@ -1709,7 +1735,6 @@ function lti_set_state_for_type($id, $state) {
* @return array Basic LTI configuration details
*/
function lti_get_config($ltiobject) {
$typeconfig = array();
$typeconfig = (array)$ltiobject;
$additionalconfig = lti_get_type_config($ltiobject->typeid);
$typeconfig = array_merge($typeconfig, $additionalconfig);
@ -1722,7 +1747,7 @@ function lti_get_config($ltiobject) {
*
* @param int $id
*
* @return Instance configuration
* @return object configuration
*
*/
function lti_get_type_config_from_instance($id) {
@ -1760,7 +1785,7 @@ function lti_get_type_config_from_instance($id) {
*
* @param int $id
*
* @return Configuration details
* @return stdClass Configuration details
*/
function lti_get_type_type_config($id) {
global $DB;
@ -1847,6 +1872,10 @@ function lti_get_type_type_config($id) {
$type->lti_contentitem = $config['contentitem'];
}
if (isset($config['toolurl_ContentItemSelectionRequest'])) {
$type->lti_toolurl_ContentItemSelectionRequest = $config['toolurl_ContentItemSelectionRequest'];
}
if (isset($config['debuglaunch'])) {
$type->lti_debuglaunch = $config['debuglaunch'];
}
@ -1855,6 +1884,19 @@ function lti_get_type_type_config($id) {
$type->lti_module_class_type = $config['module_class_type'];
}
// Get the parameters from the LTI services.
$services = lti_get_services();
$ltiserviceprefixlength = 11;
foreach ($services as $service) {
$configurationparameters = $service->get_configuration_parameter_names();
foreach ($configurationparameters as $ltiserviceparameter) {
$shortltiserviceparameter = substr($ltiserviceparameter, $ltiserviceprefixlength);
if (isset($config[$shortltiserviceparameter])) {
$type->$ltiserviceparameter = $config[$shortltiserviceparameter];
}
}
}
return $type;
}
@ -1886,6 +1928,14 @@ function lti_prepare_type_for_save($type, $config) {
$type->contentitem = !empty($config->lti_contentitem) ? $config->lti_contentitem : 0;
$config->lti_contentitem = $type->contentitem;
}
if (isset($config->lti_toolurl_ContentItemSelectionRequest)) {
if (!empty($config->lti_toolurl_ContentItemSelectionRequest)) {
$type->toolurl_ContentItemSelectionRequest = $config->lti_toolurl_ContentItemSelectionRequest;
} else {
$type->toolurl_ContentItemSelectionRequest = '';
}
$config->lti_toolurl_ContentItemSelectionRequest = $type->toolurl_ContentItemSelectionRequest;
}
$type->timemodified = time();
@ -1901,7 +1951,6 @@ function lti_update_type($type, $config) {
lti_prepare_type_for_save($type, $config);
$clearcache = false;
if (lti_request_is_using_ssl() && !empty($type->secureicon)) {
$clearcache = !isset($config->oldicon) || ($config->oldicon !== $type->secureicon);
} else {
@ -1918,6 +1967,13 @@ function lti_update_type($type, $config) {
$record->value = $value;
lti_update_config($record);
}
if (substr($key, 0, 11) == 'ltiservice_' && !is_null($value)) {
$record = new \StdClass();
$record->typeid = $type->id;
$record->name = substr($key, 11);
$record->value = $value;
lti_update_config($record);
}
}
require_once($CFG->libdir.'/modinfolib.php');
if ($clearcache) {
@ -1964,10 +2020,17 @@ function lti_add_type($type, $config) {
if ($id) {
foreach ($config as $key => $value) {
if (substr($key, 0, 4) == 'lti_' && !is_null($value)) {
if (!is_null($value)) {
$fieldparts = preg_split("/(lti|ltiservice)_/i", $key);
// If array has only one element, it did not start with the pattern.
if (count($fieldparts) < 2) {
continue;
}
$fieldname = $fieldparts[1];
$record = new \StdClass();
$record->typeid = $id;
$record->name = substr($key, 4);
$record->name = $fieldname;
$record->value = $value;
lti_add_config($record);
@ -2033,7 +2096,7 @@ function lti_get_tool_proxies_from_registration_url($regurl) {
*
* @param int $id
*
* @return Tool Proxy details
* @return mixed Tool Proxy details
*/
function lti_get_tool_proxy($id) {
global $DB;
@ -2052,7 +2115,6 @@ function lti_get_tool_proxies($orphanedonly) {
global $DB;
if ($orphanedonly) {
$tools = $DB->get_records('lti_types');
$usedproxyids = array_values($DB->get_fieldset_select('lti_types', 'toolproxyid', 'toolproxyid IS NOT NULL'));
$proxies = $DB->get_records('lti_tool_proxies', null, 'state DESC, timemodified DESC');
foreach ($proxies as $key => $value) {
@ -2071,7 +2133,7 @@ function lti_get_tool_proxies($orphanedonly) {
*
* @param int $id
*
* @return Tool Proxy details
* @return mixed Tool Proxy details
*/
function lti_get_tool_proxy_config($id) {
$toolproxy = lti_get_tool_proxy($id);
@ -2190,12 +2252,11 @@ function lti_add_config($config) {
*
* @param object $config Tool configuration
*
* @return Record id number
* @return mixed Record id number
*/
function lti_update_config($config) {
global $DB;
$return = true;
$old = $DB->get_record('lti_types_config', array('typeid' => $config->typeid, 'name' => $config->name));
if ($old) {
@ -2243,7 +2304,7 @@ function lti_set_tool_settings($settings, $toolproxyid, $courseid = null, $insta
$record = $DB->get_record('lti_tool_settings', array('toolproxyid' => $toolproxyid,
'course' => $courseid, 'coursemoduleid' => $instanceid));
if ($record !== false) {
$DB->update_record('lti_tool_settings', array('id' => $record->id, 'settings' => $json, 'timemodified' => time()));
$DB->update_record('lti_tool_settings', (object)array('id' => $record->id, 'settings' => $json, 'timemodified' => time()));
} else {
$record = new \stdClass();
$record->toolproxyid = $toolproxyid;
@ -2259,11 +2320,12 @@ function lti_set_tool_settings($settings, $toolproxyid, $courseid = null, $insta
/**
* Signs the petition to launch the external tool using OAuth
*
* @param $oldparms Parameters to be passed for signing
* @param $endpoint url of the external tool
* @param $method Method for sending the parameters (e.g. POST)
* @param $oauth_consumoer_key Key
* @param $oauth_consumoer_secret Secret
* @param array $oldparms Parameters to be passed for signing
* @param string $endpoint url of the external tool
* @param string $method Method for sending the parameters (e.g. POST)
* @param string $oauthconsumerkey
* @param string $oauthconsumersecret
* @return array|null
*/
function lti_sign_parameters($oldparms, $endpoint, $method, $oauthconsumerkey, $oauthconsumersecret) {
@ -2285,9 +2347,10 @@ function lti_sign_parameters($oldparms, $endpoint, $method, $oauthconsumerkey, $
/**
* Posts the launch petition HTML
*
* @param $newparms Signed parameters
* @param $endpoint URL of the external tool
* @param $debug Debug (true/false)
* @param array $newparms Signed parameters
* @param string $endpoint URL of the external tool
* @param bool $debug Debug (true/false)
* @return string
*/
function lti_post_launch_html($newparms, $endpoint, $debug=false) {
$r = "<form action=\"" . $endpoint .
@ -2607,7 +2670,7 @@ function lti_get_services() {
*
* @param string $servicename Name of service
*
* @return mod_lti\local\ltiservice\service_base Service
* @return bool|\mod_lti\local\ltiservice\service_base Service
*/
function lti_get_service_by_name($servicename) {
@ -2624,7 +2687,7 @@ function lti_get_service_by_name($servicename) {
/**
* Finds a service by id
*
* @param array $services Array of services
* @param \mod_lti\local\ltiservice\service_base[] $services Array of services
* @param string $resourceid ID of resource
*
* @return mod_lti\local\ltiservice\service_base Service
@ -2742,15 +2805,14 @@ function get_tool_proxy_edit_url(stdClass $proxy) {
*
* @param stdClass $type The tool type
*
* @return string|void The url to the course of the tool type, void if it is a site wide type
* @return string The url to the course of the tool type, void if it is a site wide type
*/
function get_tool_type_course_url(stdClass $type) {
if ($type->course == 1) {
return;
} else {
if ($type->course != 1) {
$url = new moodle_url('/course/view.php', array('id' => $type->course));
return $url->out();
}
return null;
}
/**
@ -2758,7 +2820,7 @@ function get_tool_type_course_url(stdClass $type) {
*
* @param stdClass $type The tool type
*
* @return string The urls of the tool type
* @return array The urls of the tool type
*/
function get_tool_type_urls(stdClass $type) {
$courseurl = get_tool_type_course_url($type);
@ -2780,7 +2842,7 @@ function get_tool_type_urls(stdClass $type) {
*
* @param stdClass $proxy The tool proxy
*
* @return string The urls of the tool proxy
* @return array The urls of the tool proxy
*/
function get_tool_proxy_urls(stdClass $proxy) {
global $OUTPUT;
@ -2802,7 +2864,6 @@ function get_tool_proxy_urls(stdClass $proxy) {
* pending, configured, rejected, unknown
*/
function get_tool_type_state_info(stdClass $type) {
$state = '';
$isconfigured = false;
$ispending = false;
$isrejected = false;

View file

@ -0,0 +1,137 @@
<?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/>.
/**
* This file contains the class for restore of this gradebookservices plugin
*
* @package ltiservice_gradebookservices
* @copyright 2017 Cengage Learning http://www.cengage.com
* @author Dirk Singels, Diego del Blanco
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot.'/mod/lti/locallib.php');
/**
* Provides the information to backup gradebookservices lineitems
*
* @package ltiservice_gradebookservices
* @copyright 2017 Cengage Learning http://www.cengage.com
* @author Dirk Singels, Diego del Blanco, Claude Vervoort
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backup_ltiservice_gradebookservices_subplugin extends backup_subplugin {
/** TypeId contained in DB but is invalid */
const NONVALIDTYPEID = 0;
/**
* Returns the subplugin information to attach to submission element
* @return backup_subplugin_element
*/
protected function define_lti_subplugin_structure() {
global $DB;
// Create XML elements.
$subplugin = $this->get_subplugin_element();
$subpluginwrapper = new backup_nested_element($this->get_recommended_name());
// The gbs entries related with this element.
$lineitems = new backup_nested_element('lineitems');
$lineitem = new backup_nested_element('lineitem', array('id'), array(
'gradeitemid',
'courseid',
'toolproxyid',
'typeid',
'baseurl',
'ltilinkid',
'tag',
'vendorcode',
'guid'
)
);
// Build the tree.
$subplugin->add_child($subpluginwrapper);
$subpluginwrapper->add_child($lineitems);
$lineitems->add_child($lineitem);
// We need to know the actual activity tool or toolproxy.
// If and activity is assigned to a type that doesn't exists we don't want to backup any related lineitems.``
// Default to invalid condition.
$typeid = 0;
$toolproxyid = '0';
/* cache parent property to account for missing PHPDoc type specification */
/** @var backup_activity_task $activitytask */
$activitytask = $this->task;
$activityid = $activitytask->get_activityid();
$activitycourseid = $activitytask->get_courseid();
$lti = $DB->get_record('lti', ['id' => $activityid], 'typeid, toolurl, securetoolurl');
$ltitype = $DB->get_record('lti_types', ['id' => $lti->typeid], 'toolproxyid, baseurl');
if ($ltitype) {
$typeid = $lti->typeid;
$toolproxyid = $ltitype->toolproxyid;
} else if ($lti->typeid == self::NONVALIDTYPEID) { // This activity comes from an old backup.
// 1. Let's check if the activity is coupled. If so, find the values in the GBS element.
$gbsrecord = $DB->get_record('ltiservice_gradebookservices',
['ltilinkid' => $activityid], 'typeid,toolproxyid,baseurl');
if ($gbsrecord) {
$typeid = $gbsrecord->typeid;
$toolproxyid = $gbsrecord->toolproxyid;
} else { // 2. If it is uncoupled... we will need to guess the right activity typeid
// Guess the typeid for the activity.
$tool = lti_get_tool_by_url_match($lti->toolurl, $activitycourseid);
if (!$tool) {
$tool = lti_get_tool_by_url_match($lti->securetoolurl, $activitycourseid);
}
if ($tool) {
$alttypeid = $tool->id;
// If we have a valid typeid then get types again.
if ($alttypeid != self::NONVALIDTYPEID) {
$ltitype = $DB->get_record('lti_types', ['id' => $alttypeid], 'toolproxyid, baseurl');
$toolproxyid = $ltitype->toolproxyid;
}
}
}
}
// Define sources.
if ($toolproxyid != null) {
$lineitemssql = "SELECT l.*, t.vendorcode as vendorcode, t.guid as guid
FROM {ltiservice_gradebookservices} l
INNER JOIN {lti_tool_proxies} t ON (t.id = l.toolproxyid)
WHERE l.courseid = ?
AND l.toolproxyid = ?
AND l.typeid is null";
$lineitemsparams = ['courseid' => backup::VAR_COURSEID, backup_helper::is_sqlparam($toolproxyid)];
} else {
$lineitemssql = "SELECT l.*, null as vendorcode, null as guid
FROM {ltiservice_gradebookservices} l
WHERE l.courseid = ?
AND l.typeid = ?
AND l.toolproxyid is null";
$lineitemsparams = ['courseid' => backup::VAR_COURSEID, backup_helper::is_sqlparam($typeid)];
}
$lineitem->set_source_sql($lineitemssql, $lineitemsparams);
return $subplugin;
}
}

View file

@ -0,0 +1,209 @@
<?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/>.
/**
* This file contains the class for restore of this gradebookservices plugin
*
* @package ltiservice_gradebookservices
* @copyright 2017 Cengage Learning http://www.cengage.com
* @author Dirk Singels, Diego del Blanco, Claude Vervoort
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot.'/mod/lti/locallib.php');
/**
* Restore subplugin class.
*
* Provides the necessary information
* needed to restore the lineitems related with the lti activity (coupled),
* and all the uncoupled ones from the course.
*
* @package ltiservice_gradebookservices
* @copyright 2017 Cengage Learning http://www.cengage.com
* @author Dirk Singels, Diego del Blanco, Claude Vervoort
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class restore_ltiservice_gradebookservices_subplugin extends restore_subplugin {
/**
* Returns the subplugin structure to attach to the XML element.
*
* @return restore_path_element[] array of elements to be processed on restore.
*/
protected function define_lti_subplugin_structure() {
$paths = array();
$elename = $this->get_namefor('lineitem');
$elepath = $this->get_pathfor('/lineitems/lineitem');
$paths[] = new restore_path_element($elename, $elepath);
return $paths;
}
/**
* Processes one lineitem
*
* @param mixed $data
* @return void
*/
public function process_ltiservice_gradebookservices_lineitem($data) {
global $DB;
$data = (object)$data;
// The coupled lineitems are restored as any other grade item
// so we will only create the entry in the ltiservice_gradebookservices table.
// We will try to find a valid toolproxy in the system.
// If it has been found before... we use it.
/* cache parent property to account for missing PHPDoc type specification */
/** @var backup_activity_task $activitytask */
$activitytask = $this->task;
$courseid = $activitytask->get_courseid();
if ($data->typeid != null) {
if ($ltitypeid = $this->get_mappingid('ltitype', $data->typeid)) {
$newtypeid = $ltitypeid;
} else { // If not, then we will call our own function to find it.
$newtypeid = $this->find_typeid($data, $courseid);
}
} else {
$newtypeid = null;
}
if ($data->toolproxyid != null) {
$ltitoolproxy = $this->get_mappingid('ltitoolproxy', $data->toolproxyid);
if ($ltitoolproxy && $ltitoolproxy != 0) {
$newtoolproxyid = $ltitoolproxy;
} else { // If not, then we will call our own function to find it.
$newtoolproxyid = $this->find_proxy_id($data);
}
} else {
$newtoolproxyid = null;
}
if ($data->ltilinkid != null) {
$ltilinkid = $this->get_new_parentid('lti');
} else {
$ltilinkid = null;
}
// If this has not been restored before.
if ($this->get_mappingid('gbsgradeitemrestored', $data->id, 0) == 0) {
$newgbsid = $DB->insert_record('ltiservice_gradebookservices', (object) array(
'gradeitemid' => 0,
'courseid' => $courseid,
'toolproxyid' => $newtoolproxyid,
'ltilinkid' => $ltilinkid,
'typeid' => $newtypeid,
'baseurl' => $data->baseurl,
'tag' => $data->tag
));
$this->set_mapping('gbsgradeitemoldid', $newgbsid, $data->gradeitemid);
$this->set_mapping('gbsgradeitemrestored', $data->id, $data->id);
}
}
/**
* If the toolproxy is not in the mapping (or it is 0)
* we try to find the toolproxyid.
* If none is found, then we set it to 0.
*
* @param mixed $data
* @return integer $newtoolproxyid
*/
private function find_proxy_id($data) {
global $DB;
$newtoolproxyid = 0;
$oldtoolproxyguid = $data->guid;
$oldtoolproxyvendor = $data->vendorcode;
$dbtoolproxyjsonparams = array('guid' => $oldtoolproxyguid, 'vendorcode' => $oldtoolproxyvendor);
$dbtoolproxy = $DB->get_field('lti_tool_proxies', 'id', $dbtoolproxyjsonparams, IGNORE_MISSING);
if ($dbtoolproxy) {
$newtoolproxyid = $dbtoolproxy;
}
return $newtoolproxyid;
}
/**
* If the typeid is not in the mapping or it is 0, (it should be most of the times)
* we will try to find the better typeid that matches with the lineitem.
* If none is found, then we set it to 0.
*
* @param stdClass $data
* @param int $courseid
* @return int The item type id
*/
private function find_typeid($data, $courseid) {
global $DB;
$newtypeid = 0;
$oldtypeid = $data->typeid;
// 1. Find a type with the same id in the same course.
$dbtypeidparameter = array('id' => $oldtypeid, 'course' => $courseid, 'baseurl' => $data->baseurl);
$dbtype = $DB->get_field_select('lti_types', 'id', "id=:id
AND course=:course AND ".$DB->sql_compare_text('baseurl')."=:baseurl",
$dbtypeidparameter);
if ($dbtype) {
$newtypeid = $dbtype;
} else {
// 2. Find a site type for all the courses (course == 1), but with the same id.
$dbtypeidparameter = array('id' => $oldtypeid, 'baseurl' => $data->baseurl);
$dbtype = $DB->get_field_select('lti_types', 'id', "id=:id
AND course=1 AND ".$DB->sql_compare_text('baseurl')."=:baseurl",
$dbtypeidparameter);
if ($dbtype) {
$newtypeid = $dbtype;
} else {
// 3. Find a type with the same baseurl in the actual site.
$dbtypeidparameter = array('course' => $courseid, 'baseurl' => $data->baseurl);
$dbtype = $DB->get_field_select('lti_types', 'id', "course=:course
AND ".$DB->sql_compare_text('baseurl')."=:baseurl",
$dbtypeidparameter);
if ($dbtype) {
$newtypeid = $dbtype;
} else {
// 4. Find a site type for all the courses (course == 1) with the same baseurl.
$dbtypeidparameter = array('course' => 1, 'baseurl' => $data->baseurl);
$dbtype = $DB->get_field_select('lti_types', 'id', "course=1
AND ".$DB->sql_compare_text('baseurl')."=:baseurl",
$dbtypeidparameter);
if ($dbtype) {
$newtypeid = $dbtype;
}
}
}
}
return $newtypeid;
}
/**
* We call the after_restore_lti to update the grade_items id's that we didn't know in the moment of creating
* the gradebookservices rows.
*/
protected function after_restore_lti() {
global $DB;
$activitytask = $this->task;
$courseid = $activitytask->get_courseid();
$gbstoupdate = $DB->get_records('ltiservice_gradebookservices', array('gradeitemid' => 0, 'courseid' => $courseid));
foreach ($gbstoupdate as $gbs) {
$oldgradeitemid = $this->get_mappingid('gbsgradeitemoldid', $gbs->id, 0);
$newgradeitemid = $this->get_mappingid('grade_item', $oldgradeitemid, 0);
if ($newgradeitemid > 0) {
$gbs->gradeitemid = $newgradeitemid;
$DB->update_record('ltiservice_gradebookservices', $gbs);
}
}
}
}

View file

@ -0,0 +1,365 @@
<?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/>.
/**
* This file contains a class definition for the LineItem resource
*
* @package ltiservice_gradebookservices
* @copyright 2017 Cengage Learning http://www.cengage.com
* @author Dirk Singels, Diego del Blanco, Claude Vervoort
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace ltiservice_gradebookservices\local\resources;
use ltiservice_gradebookservices\local\service\gradebookservices;
use mod_lti\local\ltiservice\resource_base;
defined('MOODLE_INTERNAL') || die();
/**
* A resource implementing LineItem.
*
* @package ltiservice_gradebookservices
* @copyright 2017 Cengage Learning http://www.cengage.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class lineitem extends resource_base {
/**
* Class constructor.
*
* @param gradebookservices $service Service instance
*/
public function __construct($service) {
parent::__construct($service);
$this->id = 'LineItem.item';
$this->template = '/{context_id}/lineitems/{item_id}/lineitem';
$this->variables[] = 'LineItem.url';
$this->formats[] = 'application/vnd.ims.lis.v2.lineitem+json';
$this->methods[] = self::HTTP_GET;
$this->methods[] = self::HTTP_PUT;
$this->methods[] = self::HTTP_DELETE;
}
/**
* Execute the request for this resource.
*
* @param \mod_lti\local\ltiservice\response $response Response object for this request.
*/
public function execute($response) {
global $CFG, $DB;
$params = $this->parse_template();
$contextid = $params['context_id'];
$itemid = $params['item_id'];
if ($response->get_request_method() === 'GET') {
$contenttype = $response->get_accept();
} else {
$contenttype = $response->get_content_type();
}
// We will receive typeid when working with LTI 1.x, if not then we are in LTI 2.
$typeid = optional_param('type_id', null, PARAM_ALPHANUM);
if (is_null($typeid)) {
if (!$this->check_tool_proxy(null, $response->get_request_data())) {
$response->set_code(403);
$response->set_reason("Invalid tool proxy specified.");
return;
}
} else {
switch ($response->get_request_method()) {
case self::HTTP_GET:
if (!$this->check_type($typeid, $contextid, 'LineItem.item:get', $response->get_request_data())) {
$response->set_code(403);
$response->set_reason("This resource does not support GET requests.");
return;
}
break;
case self::HTTP_PUT:
if (!$this->check_type($typeid, $contextid, 'LineItem.item:put', $response->get_request_data())) {
$response->set_code(403);
$response->set_reason("This resource does not support PUT requests.");
return;
}
break;
case self::HTTP_DELETE:
if (!$this->check_type($typeid, $contextid, 'LineItem.item:delete', $response->get_request_data())) {
$response->set_code(403);
$response->set_reason("This resource does not support DELETE requests.");
return;
}
break;
default: // Should not be possible.
$response->set_code(405);
return;
}
}
if (empty($contextid) || (!empty($contenttype) && !in_array($contenttype, $this->formats))) {
$response->set_code(400);
$response->set_reason("Invalid request made.");
return;
}
if (!$DB->record_exists('course', array('id' => $contextid))) {
$response->set_code(404);
$response->set_reason("Not Found: Course $contextid doesn't exist.");
return;
}
if (!$DB->record_exists('grade_items', array('id' => $itemid))) {
$response->set_code(404);
$response->set_reason("Not Found: Grade item $itemid doesn't exist.");
return;
}
$item = $this->get_service()->get_lineitem($contextid, $itemid, $typeid);
if ($item === false) {
$response->set_code(403);
$response->set_reason("Line item does not exist.");
return;
}
require_once($CFG->libdir.'/gradelib.php');
switch ($response->get_request_method()) {
case 'GET':
$this->get_request($response, $item, $typeid);
break;
case 'PUT':
$json = $this->process_put_request($response->get_request_data(), $item, $typeid);
$response->set_body($json);
$response->set_code(200);
break;
case 'DELETE':
$this->process_delete_request($item);
$response->set_code(204);
break;
default: // Should not be possible.
$response->set_code(405);
$response->set_reason("Invalid request method specified.");
return;
}
}
/**
* Process a GET request.
*
* @param \mod_lti\local\ltiservice\response $response Response object for this request.
* @param object $item Grade item instance.
* @param string $typeid Tool Type Id
*/
private function get_request($response, $item, $typeid) {
$response->set_content_type($this->formats[0]);
$lineitem = gradebookservices::item_for_json($item, substr(parent::get_endpoint(),
0, strrpos(parent::get_endpoint(), "/", -10)), $typeid);
$response->set_body(json_encode($lineitem));
}
/**
* Process a PUT request.
*
* @param string $body PUT body
* @param \ltiservice_gradebookservices\local\resources\lineitem $olditem Grade item instance
* @param string $typeid Tool Type Id
*
* @return string
* @throws \Exception
*/
private function process_put_request($body, $olditem, $typeid) {
global $DB;
$json = json_decode($body);
if (empty($json) ||
!isset($json->scoreMaximum) ||
!isset($json->label)) {
throw new \Exception(null, 400);
}
$item = \grade_item::fetch(array('id' => $olditem->id, 'courseid' => $olditem->courseid));
$gbs = gradebookservices::find_ltiservice_gradebookservice_for_lineitem($olditem->id);
$updategradeitem = false;
$rescalegrades = false;
$oldgrademax = grade_floatval($item->grademax);
$upgradegradebookservices = false;
if ($item->itemname !== $json->label) {
$updategradeitem = true;
}
$item->itemname = $json->label;
if (!is_numeric($json->scoreMaximum)) {
throw new \Exception(null, 400);
} else {
if (grade_floats_different($oldgrademax,
grade_floatval($json->scoreMaximum))) {
$updategradeitem = true;
$rescalegrades = true;
}
$item->grademax = grade_floatval($json->scoreMaximum);
}
$resourceid = (isset($json->resourceId)) ? $json->resourceId : '';
if ($item->idnumber !== $resourceid) {
$updategradeitem = true;
}
$item->idnumber = $resourceid;
if ($gbs) {
$tag = (isset($json->tag)) ? $json->tag : null;
if ($gbs->tag !== $tag) {
$upgradegradebookservices = true;
}
$gbs->tag = $tag;
}
$ltilinkid = null;
if (isset($json->ltiLinkId)) {
if (is_numeric($json->ltiLinkId)) {
$ltilinkid = $json->ltiLinkId;
if ($gbs) {
if (intval($gbs->ltilinkid) !== intval($json->ltiLinkId)) {
$gbs->ltilinkid = $json->ltiLinkId;
$upgradegradebookservices = true;
}
} else {
if (intval($item->iteminstance) !== intval($json->ltiLinkId)) {
$item->iteminstance = intval($json->ltiLinkId);
$updategradeitem = true;
}
}
} else {
throw new \Exception(null, 400);
}
}
if ($ltilinkid != null) {
if (is_null($typeid)) {
if (!gradebookservices::check_lti_id($ltilinkid, $item->courseid,
$this->get_service()->get_tool_proxy()->id)) {
throw new \Exception(null, 403);
}
} else {
if (!gradebookservices::check_lti_1x_id($ltilinkid, $item->courseid,
$typeid)) {
throw new \Exception(null, 403);
}
}
}
if ($updategradeitem) {
if (!$item->update('mod/ltiservice_gradebookservices')) {
throw new \Exception(null, 500);
}
if ($rescalegrades) {
$item->rescale_grades_keep_percentage(0, $oldgrademax, 0, $item->grademax);
}
}
$lineitem = new lineitem($this->get_service());
$endpoint = $lineitem->get_endpoint();
if ($upgradegradebookservices) {
if (is_null($typeid)) {
$toolproxyid = $this->get_service()->get_tool_proxy()->id;
$baseurl = null;
} else {
$toolproxyid = null;
$baseurl = lti_get_type_type_config($typeid)->lti_toolurl;
}
$DB->update_record('ltiservice_gradebookservices', (object)array(
'id' => $gbs->id,
'gradeitemid' => $gbs->gradeitemid,
'courseid' => $gbs->courseid,
'toolproxyid' => $toolproxyid,
'typeid' => $typeid,
'baseurl' => $baseurl,
'ltilinkid' => $ltilinkid,
'tag' => $gbs->tag
));
}
if (is_null($typeid)) {
$id = "{$endpoint}";
$json->id = $id;
} else {
$id = "{$endpoint}?type_id={$typeid}";
$json->id = $id;
}
return json_encode($json, JSON_UNESCAPED_SLASHES);
}
/**
* Process a DELETE request.
*
* @param \ltiservice_gradebookservices\local\resources\lineitem $item Grade item instance
* @throws \Exception
*/
private function process_delete_request($item) {
global $DB;
$gradeitem = \grade_item::fetch(array('id' => $item->id));
if (($gbs = gradebookservices::find_ltiservice_gradebookservice_for_lineitem($item->id)) == false) {
throw new \Exception(null, 403);
}
if (!$gradeitem->delete('mod/ltiservice_gradebookservices')) {
throw new \Exception(null, 500);
} else {
$sqlparams = array();
$sqlparams['id'] = $gbs->id;
if (!$DB->delete_records('ltiservice_gradebookservices', $sqlparams)) {
throw new \Exception(null, 500);
}
}
}
/**
* Get permissions from the config of the tool for that resource
*
* @param int $typeid
*
* @return array with the permissions related to this resource by the $lti_type or null if none.
*/
public function get_permissions($typeid) {
$tool = lti_get_type_type_config($typeid);
if ($tool->ltiservice_gradesynchronization == '1') {
return array('LineItem.item:get');
} else if ($tool->ltiservice_gradesynchronization == '2') {
return array('LineItem.item:get', 'LineItem.item:put', 'LineItem.item:delete');
} else {
return array();
}
}
/**
* Parse a value for custom parameter substitution variables.
*
* @param string $value String to be parsed
*
* @return string
*/
public function parse_value($value) {
global $COURSE, $CFG;
if (strpos($value, '$LineItem.url') !== false) {
$resolved = '';
require_once($CFG->libdir . '/gradelib.php');
$this->params['context_id'] = $COURSE->id;
$id = optional_param('id', 0, PARAM_INT); // Course Module ID.
if (!empty($id)) {
$cm = get_coursemodule_from_id('lti', $id, 0, false, MUST_EXIST);
$id = $cm->instance;
$item = grade_get_grades($COURSE->id, 'mod', 'lti', $id);
if ($item && $item->items) {
$this->params['item_id'] = $item->items[0]->id;
$resolved = parent::get_endpoint();
}
}
$value = str_replace('$LineItem.url', $resolved, $value);
}
return $value;
}
}

View file

@ -0,0 +1,352 @@
<?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/>.
/**
* This file contains a class definition for the LineItem container resource
*
* @package ltiservice_gradebookservices
* @copyright 2017 Cengage Learning http://www.cengage.com
* @author Dirk Singels, Diego del Blanco, Claude Vervoort
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace ltiservice_gradebookservices\local\resources;
use ltiservice_gradebookservices\local\service\gradebookservices;
use mod_lti\local\ltiservice\resource_base;
defined('MOODLE_INTERNAL') || die();
/**
* A resource implementing LineItem container.
*
* @package ltiservice_gradebookservices
* @copyright 2017 Cengage Learning http://www.cengage.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class lineitems extends resource_base {
/**
* Class constructor.
*
* @param \ltiservice_gradebookservices\local\service\gradebookservices $service Service instance
*/
public function __construct($service) {
parent::__construct($service);
$this->id = 'LineItem.collection';
$this->template = '/{context_id}/lineitems';
$this->variables[] = 'LineItems.url';
$this->formats[] = 'application/vnd.ims.lis.v2.lineitemcontainer+json';
$this->formats[] = 'application/vnd.ims.lis.v2.lineitem+json';
$this->methods[] = self::HTTP_GET;
$this->methods[] = self::HTTP_POST;
}
/**
* Execute the request for this resource.
*
* @param \mod_lti\local\ltiservice\response $response Response object for this request.
*/
public function execute($response) {
global $DB;
$params = $this->parse_template();
$contextid = $params['context_id'];
$isget = $response->get_request_method() === self::HTTP_GET;
if ($isget) {
$contenttype = $response->get_accept();
} else {
$contenttype = $response->get_content_type();
}
$container = empty($contenttype) || ($contenttype === $this->formats[0]);
// We will receive typeid when working with LTI 1.x, if not the we are in LTI 2.
$typeid = optional_param('type_id', null, PARAM_ALPHANUM);
if (is_null($typeid)) {
if (!$this->check_tool_proxy(null, $response->get_request_data())) {
$response->set_code(403);
$response->set_reason("Invalid tool proxy specified.");
return;
}
} else {
switch ($response->get_request_method()) {
case self::HTTP_GET:
if (!$this->check_type($typeid, $contextid, 'LineItem.collection:get', $response->get_request_data())) {
$response->set_code(403);
$response->set_reason("This resource does not support GET requests.");
return;
}
break;
case self::HTTP_POST:
if (!$this->check_type($typeid, $contextid, 'LineItem.collection:post', $response->get_request_data())) {
$response->set_code(403);
$response->set_reason("This resource does not support POST requests.");
return;
}
break;
default: // Should not be possible.
$response->set_code(405);
$response->set_reason("Invalid request method specified.");
return;
}
}
if (empty($contextid) || !($container ^ ($response->get_request_method() === self::HTTP_POST)) ||
(!empty($contenttype) && !in_array($contenttype, $this->formats))) {
$response->set_code(400);
$response->set_reason("Invalid request made.");
return;
}
if (!$DB->record_exists('course', array('id' => $contextid))) {
$response->set_code(404);
$response->set_reason("Not Found: Course $contextid doesn't exist.");
return;
}
switch ($response->get_request_method()) {
case self::HTTP_GET:
$resourceid = optional_param('resource_id', null, PARAM_TEXT);
$ltilinkid = optional_param('lti_link_id', null, PARAM_TEXT);
$tag = optional_param('tag', null, PARAM_TEXT);
$limitnum = optional_param('limit', 0, PARAM_INT);
$limitfrom = optional_param('from', 0, PARAM_INT);
$itemsandcount = $this->get_service()->get_lineitems($contextid, $resourceid, $ltilinkid, $tag, $limitfrom,
$limitnum, $typeid);
$items = $itemsandcount[1];
$totalcount = $itemsandcount[0];
$json = $this->get_json_for_get_request($items, $resourceid, $ltilinkid, $tag, $limitfrom,
$limitnum, $totalcount, $typeid, $response);
$response->set_content_type($this->formats[0]);
break;
case self::HTTP_POST:
try {
$json = $this->get_json_for_post_request($response->get_request_data(), $contextid, $typeid);
$response->set_code(201);
$response->set_content_type($this->formats[1]);
} catch (\Exception $e) {
$response->set_code($e->getCode());
$response->set_reason($e->getMessage());
}
break;
default: // Should not be possible.
$response->set_code(405);
$response->set_reason("Invalid request method specified.");
return;
}
$response->set_body($json);
}
/**
* Generate the JSON for a GET request.
*
* @param array $items Array of lineitems
* @param string $resourceid Resource identifier used for filtering, may be null
* @param string $ltilinkid Resource Link identifier used for filtering, may be null
* @param string $tag Tag identifier used for filtering, may be null
* @param int $limitfrom Offset of the first line item to return
* @param int $limitnum Maximum number of line items to return, ignored if zero or less
* @param int $totalcount Number of total lineitems before filtering for paging
* @param int $typeid Maximum number of line items to return, ignored if zero or less
* @param \mod_lti\local\ltiservice\response $response
* @return string
*/
private function get_json_for_get_request($items, $resourceid, $ltilinkid,
$tag, $limitfrom, $limitnum, $totalcount, $typeid, $response) {
$firstpage = null;
$nextpage = null;
$prevpage = null;
$lastpage = null;
if (isset($limitnum) && $limitnum > 0) {
if ($limitfrom >= $totalcount || $limitfrom < 0) {
$outofrange = true;
} else {
$outofrange = false;
}
$limitprev = $limitfrom - $limitnum >= 0 ? $limitfrom - $limitnum : 0;
$limitcurrent = $limitfrom;
$limitlast = $totalcount - $limitnum + 1 >= 0 ? $totalcount - $limitnum + 1 : 0;
$limitfrom += $limitnum;
$baseurl = new \moodle_url($this->get_endpoint());
if (isset($resourceid)) {
$baseurl->param('resource_id', $resourceid);
}
if (isset($ltilinkid)) {
$baseurl->param('lti_link_id', $ltilinkid);
}
if (isset($tag)) {
$baseurl->param('tag', $tag);
}
if (is_null($typeid)) {
$baseurl->param('limit', $limitnum);
if (($limitfrom <= $totalcount - 1) && (!$outofrange)) {
$nextpage = new \moodle_url($baseurl, ['from' => $limitfrom]);
}
$firstpage = new \moodle_url($baseurl, ['from' => 0]);
$canonicalpage = new \moodle_url($baseurl, ['from' => $limitcurrent]);
$lastpage = new \moodle_url($baseurl, ['from' > $limitlast]);
if (($limitcurrent > 0) && (!$outofrange)) {
$prevpage = new \moodle_url($baseurl, ['from' => $limitprev]);
}
} else {
$baseurl->params(['type_id' => $typeid, 'limit' => $limitnum]);
if (($limitfrom <= $totalcount - 1) && (!$outofrange)) {
$nextpage = new \moodle_url($baseurl, ['from' => $limitfrom]);
}
$firstpage = new \moodle_url($baseurl, ['from' => 0]);
$canonicalpage = new \moodle_url($baseurl, ['from' => $limitcurrent]);
$lastpage = new \moodle_url($baseurl, ['from' => $limitlast]);
if (($limitcurrent > 0) && (!$outofrange)) {
$prevpage = new \moodle_url($baseurl, ['from' => $limitprev]);
}
}
}
$jsonitems=[];
$endpoint = parent::get_endpoint();
foreach ($items as $item) {
array_push($jsonitems, gradebookservices::item_for_json($item, $endpoint, $typeid));
}
if (isset($canonicalpage) && ($canonicalpage)) {
$links = 'Link: <' . $firstpage->out() . '>; rel=“first”';
if (!is_null($prevpage)) {
$links .= ', <' . $prevpage->out() . '>; rel=“prev”';
}
$links .= ', <' . $canonicalpage->out(). '>; rel=“canonical”';
if (!is_null($nextpage)) {
$links .= ', <' . $nextpage->out() . '>; rel=“next”';
}
$links .= ', <' . $lastpage->out() . '>; rel=“last”';
$response->add_additional_header($links);
}
return json_encode($jsonitems);
}
/**
* Generate the JSON for a POST request.
*
* @param string $body POST body
* @param string $contextid Course ID
* @param string $typeid
*
* @return string
* @throws \Exception
*/
private function get_json_for_post_request($body, $contextid, $typeid) {
global $CFG, $DB;
$json = json_decode($body);
if (empty($json) ||
!isset($json->scoreMaximum) ||
!isset($json->label)) {
throw new \Exception(null, 400);
}
if (is_numeric($json->scoreMaximum)) {
$max = $json->scoreMaximum;
} else {
throw new \Exception(null, 400);
}
require_once($CFG->libdir.'/gradelib.php');
$resourceid = (isset($json->resourceId)) ? $json->resourceId : '';
$ltilinkid = (isset($json->ltiLinkId)) ? $json->ltiLinkId : null;
if ($ltilinkid != null) {
if (is_null($typeid)) {
if (!gradebookservices::check_lti_id($ltilinkid, $contextid, $this->get_service()->get_tool_proxy()->id)) {
throw new \Exception(null, 403);
}
} else {
if (!gradebookservices::check_lti_1x_id($ltilinkid, $contextid, $typeid)) {
throw new \Exception(null, 403);
}
}
}
$tag = (isset($json->tag)) ? $json->tag : '';
if (is_null($typeid)) {
$toolproxyid = $this->get_service()->get_tool_proxy()->id;
$baseurl = null;
} else {
$toolproxyid = null;
$baseurl = lti_get_type_type_config($typeid)->lti_toolurl;
}
$params = array();
$params['itemname'] = $json->label;
$params['gradetype'] = GRADE_TYPE_VALUE;
$params['grademax'] = $max;
$params['grademin'] = 0;
$item = new \grade_item(array('id' => 0, 'courseid' => $contextid));
\grade_item::set_properties($item, $params);
$item->itemtype = 'manual';
$item->idnumber = $resourceid;
$item->grademax = $max;
$id = $item->insert('mod/ltiservice_gradebookservices');
$DB->insert_record('ltiservice_gradebookservices', (object)array(
'gradeitemid' => $id,
'courseid' => $contextid,
'toolproxyid' => $toolproxyid,
'typeid' => $typeid,
'baseurl' => $baseurl,
'ltilinkid' => $ltilinkid,
'tag' => $tag
));
if (is_null($typeid)) {
$json->id = parent::get_endpoint() . "/{$id}/lineitem";
} else {
$json->id = parent::get_endpoint() . "/{$id}/lineitem?type_id={$typeid}";
}
return json_encode($json, JSON_UNESCAPED_SLASHES);
}
/**
* get permissions from the config of the tool for that resource
*
* @param string $typeid
*
* @return array with the permissions related to this resource by the lti type or null if none.
*/
public function get_permissions($typeid) {
$tool = lti_get_type_type_config($typeid);
if ($tool->ltiservice_gradesynchronization == '1') {
return array('LineItem.collection:get');
} else if ($tool->ltiservice_gradesynchronization == '2') {
return array('LineItem.collection:get', 'LineItem.collection:post');
} else {
return array();
}
}
/**
* Parse a value for custom parameter substitution variables.
*
* @param string $value String to be parsed
*
* @return string
*/
public function parse_value($value) {
global $COURSE;
if (strpos($value, '$LineItems.url') !== false) {
$this->params['context_id'] = $COURSE->id;
$value = str_replace('$LineItems.url', parent::get_endpoint(), $value);
}
return $value;
}
}

View file

@ -0,0 +1,304 @@
<?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/>.
/**
* This file contains a class definition for the LISResults container resource
*
* @package ltiservice_gradebookservices
* @copyright 2017 Cengage Learning http://www.cengage.com
* @author Dirk Singels, Diego del Blanco, Claude Vervoort
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace ltiservice_gradebookservices\local\resources;
use ltiservice_gradebookservices\local\service\gradebookservices;
use mod_lti\local\ltiservice\resource_base;
defined('MOODLE_INTERNAL') || die();
/**
* A resource implementing LISResults container.
*
* @package ltiservice_gradebookservices
* @copyright 2017 Cengage Learning http://www.cengage.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class results extends resource_base {
/**
* Class constructor.
*
* @param \ltiservice_gradebookservices\local\service\gradebookservices $service Service instance
*/
public function __construct($service) {
parent::__construct($service);
$this->id = 'Result.collection';
$this->template = '/{context_id}/lineitems/{item_id}/lineitem/results';
$this->variables[] = 'Results.url';
$this->formats[] = 'application/vnd.ims.lis.v2.resultcontainer+json';
$this->methods[] = 'GET';
}
/**
* Execute the request for this resource.
*
* @param \mod_lti\local\ltiservice\response $response Response object for this request.
*/
public function execute($response) {
global $CFG, $DB;
$params = $this->parse_template();
$contextid = $params['context_id'];
$itemid = $params['item_id'];
$isget = $response->get_request_method() === 'GET';
if ($isget) {
$contenttype = $response->get_accept();
} else {
$contenttype = $response->get_content_type();
}
// We will receive typeid when working with LTI 1.x, if not the we are in LTI 2.
$typeid = optional_param('type_id', null, PARAM_ALPHANUM);
if (is_null($typeid)) {
if (!$this->check_tool_proxy(null, $response->get_request_data())) {
$response->set_code(403);
$response->set_reason("Invalid tool proxy specified.");
return;
}
} else {
if (!$this->check_type($typeid, $contextid, 'Result.collection:get', $response->get_request_data())) {
$response->set_code(403);
$response->set_reason("This resource does not support GET requests.");
return;
}
}
if (empty($contextid) || (!empty($contenttype) && !in_array($contenttype, $this->formats))) {
$response->set_code(400);
$response->set_reason("Invalid request made.");
return;
}
if (!$DB->record_exists('course', array('id' => $contextid))) {
$response->set_code(404);
$response->set_reason("Not Found: Course $contextid doesn't exist.");
return;
}
if (!$DB->record_exists('grade_items', array('id' => $itemid))) {
$response->set_code(404);
$response->set_reason("Not Found: Grade item $itemid doesn't exist.");
return;
}
$item = $this->get_service()->get_lineitem($contextid, $itemid, $typeid);
if ($item === false) {
$response->set_code(403);
$response->set_reason("Line item does not exist.");
return;
}
$gbs = gradebookservices::find_ltiservice_gradebookservice_for_lineitem($itemid);
$ltilinkid = null;
if (isset($item->iteminstance)) {
$ltilinkid = $item->iteminstance;
} else if ($gbs && isset($gbs->ltilinkid)) {
$ltilinkid = $gbs->ltilinkid;
}
if ($ltilinkid != null) {
if (is_null($typeid)) {
if (isset($item->iteminstance) && (!gradebookservices::check_lti_id($ltilinkid, $item->courseid,
$this->get_service()->get_tool_proxy()->id))) {
$response->set_code(403);
$response->set_reason("Invalid LTI id supplied.");
return;
}
} else {
if (isset($item->iteminstance) && (!gradebookservices::check_lti_1x_id($ltilinkid, $item->courseid,
$typeid))) {
$response->set_code(403);
$response->set_reason("Invalid LTI id supplied.");
return;
}
}
}
require_once($CFG->libdir.'/gradelib.php');
switch ($response->get_request_method()) {
case 'GET':
$useridfilter = optional_param('user_id', 0, PARAM_INT);
$limitnum = optional_param('limit', 0, PARAM_INT);
$limitfrom = optional_param('from', 0, PARAM_INT);
$typeid = optional_param('type_id', null, PARAM_TEXT);
$json = $this->get_json_for_get_request($item->id, $limitfrom, $limitnum,
$useridfilter, $typeid, $response);
$response->set_content_type($this->formats[0]);
$response->set_body($json);
break;
default: // Should not be possible.
$response->set_code(405);
$response->set_reason("Invalid request method specified.");
return;
}
$response->set_body($json);
}
/**
* Generate the JSON for a GET request.
*
* @param int $itemid Grade item instance ID
* @param int $limitfrom Offset for the first result to include in this paged set
* @param int $limitnum Maximum number of results to include in the response, ignored if zero
* @param int $useridfilter The user id to filter the results.
* @param int $typeid Lti tool typeid (or null)
* @param \mod_lti\local\ltiservice\response $response The response element needed to add a header.
*
* @return string
*/
private function get_json_for_get_request($itemid, $limitfrom, $limitnum, $useridfilter, $typeid, $response) {
if ($useridfilter > 0) {
$grades = \grade_grade::fetch_all(array('itemid' => $itemid, 'userid' => $useridfilter));
} else {
$grades = \grade_grade::fetch_all(array('itemid' => $itemid));
}
$firstpage = null;
$nextpage = null;
$prevpage = null;
$lastpage = null;
if ($grades && isset($limitnum) && $limitnum > 0) {
// Since we only display grades that have been modified, we need to filter first in order to support
// paging.
$resultgrades = array_filter($grades, function ($grade) {
return !empty($grade->timemodified);
});
// We save the total count to calculate the last page.
$totalcount = count($resultgrades);
// We slice to the requested item offset to insure proper item is always first, and we always return
// first pageset of any remaining items.
$grades = array_slice($resultgrades, $limitfrom);
if (count($grades) > 0) {
$pagedgrades = array_chunk($grades, $limitnum);
$pageset = 0;
$grades = $pagedgrades[$pageset];
}
if ($limitfrom >= $totalcount || $limitfrom < 0) {
$outofrange = true;
} else {
$outofrange = false;
}
$limitprev = $limitfrom - $limitnum >= 0 ? $limitfrom - $limitnum : 0;
$limitcurrent = $limitfrom;
$limitlast = $totalcount - $limitnum + 1 >= 0 ? $totalcount - $limitnum + 1 : 0;
$limitfrom += $limitnum;
$baseurl = new \moodle_url($this->get_endpoint());
if (is_null($typeid)) {
$baseurl->param('limit', $limitnum);
if (($limitfrom <= $totalcount - 1) && (!$outofrange)) {
$nextpage = new \moodle_url($baseurl, ['from' => $limitfrom]);
}
$firstpage = new \moodle_url($baseurl, ['from' => 0]);
$canonicalpage = new \moodle_url($baseurl, ['from' => $limitcurrent]);
$lastpage = new \moodle_url($baseurl, ['from' => $limitlast]);
if (($limitcurrent > 0) && (!$outofrange)) {
$prevpage = new \moodle_url($baseurl, ['from' => $limitprev]);
}
} else {
$baseurl->params(['type_id' => $typeid, 'limit' => $limitnum]);
if (($limitfrom <= $totalcount - 1) && (!$outofrange)) {
$nextpage = new \moodle_url($baseurl, ['from' => $limitfrom]);
}
$firstpage = new \moodle_url($baseurl, ['from' => 0]);
$canonicalpage = new \moodle_url($baseurl, ['from' => $limitcurrent]);
if (($limitcurrent > 0) && (!$outofrange)) {
$prevpage = new \moodle_url($baseurl, ['from' => $limitprev]);
}
}
}
$jsonresults = [];
$lineitem = new lineitem($this->get_service());
$endpoint = $lineitem->get_endpoint();
if ($grades) {
foreach ($grades as $grade) {
if (!empty($grade->timemodified)) {
array_push($jsonresults, gradebookservices::result_for_json($grade, $endpoint, $typeid));
}
}
}
if (isset($canonicalpage) && ($canonicalpage)) {
$links = 'Link: <' . $firstpage->out() . '>; rel=“first”';
if (!is_null($prevpage)) {
$links .= ', <' . $prevpage->out() . '>; rel=“prev”';
}
$links .= ', <' . $canonicalpage->out() . '>; rel=“canonical”';
if (!is_null($nextpage)) {
$links .= ', <' . $nextpage->out() . '>; rel=“next”';
}
$links .= ', <' . $lastpage->out() . '>; rel=“last”';
$response->add_additional_header($links);
}
return json_encode($jsonresults);
}
/**
* get permissions from the config of the tool for that resource
*
* @param int $typeid
*
* @return array with the permissions related to this resource by the $lti_type or null if none.
*/
public function get_permissions($typeid) {
$tool = lti_get_type_type_config($typeid);
if ($tool->ltiservice_gradesynchronization == '1') {
return array('Result.collection:get');
} else if ($tool->ltiservice_gradesynchronization == '2') {
return array('Result.collection:get');
} else {
return array();
}
}
/**
* Parse a value for custom parameter substitution variables.
*
* @param string $value String to be parsed
*
* @return string
*/
public function parse_value($value) {
global $COURSE, $CFG;
if (strpos($value, '$Results.url') !== false) {
require_once($CFG->libdir . '/gradelib.php');
$resolved = '';
$this->params['context_id'] = $COURSE->id;
$id = optional_param('id', 0, PARAM_INT); // Course Module ID.
if (!empty($id)) {
$cm = get_coursemodule_from_id('lti', $id, 0, false, MUST_EXIST);
$id = $cm->instance;
$item = grade_get_grades($COURSE->id, 'mod', 'lti', $id);
if ($item && $item->items) {
$this->params['item_id'] = $item->items[0]->id;
$resolved = parent::get_endpoint();
}
}
$value = str_replace('$Results.url', $resolved, $value);
}
return $value;
}
}

View file

@ -0,0 +1,270 @@
<?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/>.
/**
* This file contains a class definition for the LISResult container resource
*
* @package ltiservice_gradebookservices
* @copyright 2017 Cengage Learning http://www.cengage.com
* @author Dirk Singels, Diego del Blanco, Claude Vervoort
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace ltiservice_gradebookservices\local\resources;
use ltiservice_gradebookservices\local\service\gradebookservices;
use mod_lti\local\ltiservice\resource_base;
defined('MOODLE_INTERNAL') || die();
/**
* A resource implementing LISResult container.
*
* @package ltiservice_gradebookservices
* @copyright 2017 Cengage Learning http://www.cengage.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class scores extends resource_base {
/**
* Class constructor.
*
* @param \ltiservice_gradebookservices\local\service\gradebookservices $service Service instance
*/
public function __construct($service) {
parent::__construct($service);
$this->id = 'Score.collection';
$this->template = '/{context_id}/lineitems/{item_id}/lineitem/scores';
$this->variables[] = 'Scores.url';
$this->formats[] = 'application/vnd.ims.lis.v1.scorecontainer+json';
$this->formats[] = 'application/vnd.ims.lis.v1.score+json';
$this->methods[] = 'POST';
}
/**
* Execute the request for this resource.
*
* @param \mod_lti\local\ltiservice\response $response Response object for this request.
*/
public function execute($response) {
global $CFG, $DB;
$params = $this->parse_template();
$contextid = $params['context_id'];
$itemid = $params['item_id'];
// GET is disabled by the moment, but we have the code ready
// for a future implementation.
$isget = $response->get_request_method() === 'GET';
if ($isget) {
$contenttype = $response->get_accept();
} else {
$contenttype = $response->get_content_type();
}
$container = empty($contenttype) || ($contenttype === $this->formats[0]);
// We will receive typeid when working with LTI 1.x, if not the we are in LTI 2.
$typeid = optional_param('type_id', null, PARAM_ALPHANUM);
if (is_null($typeid)) {
if (!$this->check_tool_proxy(null, $response->get_request_data())) {
$response->set_code(403);
return;
}
} else {
switch ($response->get_request_method()) {
case 'GET':
$response->set_code(405);
$response->set_reason("GET requests are not allowed.");
return;
case 'POST':
if (!$this->check_type($typeid, $contextid, 'Score.collection:post', $response->get_request_data())) {
$response->set_code(401);
$response->set_reason("This resource does not support POST requests.");
return;
}
break;
default: // Should not be possible.
$response->set_code(405);
return;
}
}
if (empty($contextid) || !($container ^ ($response->get_request_method() === 'POST')) ||
(!empty($contenttype) && !in_array($contenttype, $this->formats))) {
$response->set_code(400);
return;
}
if (!$DB->record_exists('course', array('id' => $contextid))) {
$response->set_code(404);
$response->set_reason("Not Found: Course $contextid doesn't exist.");
return;
}
if (!$DB->record_exists('grade_items', array('id' => $itemid))) {
$response->set_code(404);
$response->set_reason("Not Found: Grade item $itemid doesn't exist.");
return;
}
$item = $this->get_service()->get_lineitem($contextid, $itemid, $typeid);
if ($item === false) {
$response->set_code(403);
$response->set_reason("Line item does not exist.");
return;
}
$gbs = gradebookservices::find_ltiservice_gradebookservice_for_lineitem($itemid);
$ltilinkid = null;
if (isset($item->iteminstance)) {
$ltilinkid = $item->iteminstance;
} else if ($gbs && isset($gbs->ltilinkid)) {
$ltilinkid = $gbs->ltilinkid;
}
if ($ltilinkid != null) {
if (is_null($typeid)) {
if (isset($item->iteminstance) && (!gradebookservices::check_lti_id($ltilinkid, $item->courseid,
$this->get_service()->get_tool_proxy()->id))) {
$response->set_code(403);
$response->set_reason("Invalid LTI id supplied.");
return;
}
} else {
if (isset($item->iteminstance) && (!gradebookservices::check_lti_1x_id($ltilinkid, $item->courseid,
$typeid))) {
$response->set_code(403);
$response->set_reason("Invalid LTI id supplied.");
return;
}
}
}
$json = '[]';
require_once($CFG->libdir.'/gradelib.php');
switch ($response->get_request_method()) {
case 'GET':
$response->set_code(405);
$response->set_reason("GET requests are not allowed.");
break;
case 'POST':
try {
$json = $this->get_json_for_post_request($response, $response->get_request_data(), $item, $contextid, $typeid);
$response->set_content_type($this->formats[1]);
} catch (\Exception $e) {
$response->set_code($e->getCode());
$response->set_reason($e->getMessage());
}
break;
default: // Should not be possible.
$response->set_code(405);
$response->set_reason("Invalid request method specified.");
return;
}
$response->set_body($json);
}
/**
* Generate the JSON for a POST request.
*
* @param \mod_lti\local\ltiservice\response $response Response object for this request.
* @param string $body POST body
* @param object $item Grade item instance
* @param string $contextid
* @param string $typeid
*
* @throws \Exception
*/
private function get_json_for_post_request($response, $body, $item, $contextid, $typeid) {
$score = json_decode($body);
if (empty($score) ||
!isset($score->userId) ||
!isset($score->timestamp) ||
!isset($score->gradingProgress) ||
!isset($score->activityProgress) ||
!isset($score->timestamp) ||
isset($score->timestamp) && !gradebookservices::validate_iso8601_date($score->timestamp) ||
(isset($score->scoreGiven) && !is_numeric($score->scoreGiven)) ||
(isset($score->scoreMaximum) && !is_numeric($score->scoreMaximum)) ||
(!gradebookservices::is_user_gradable_in_course($contextid, $score->userId))
) {
throw new \Exception('Incorrect score received' . $score, 400);
}
$score->timemodified = intval($score->timestamp);
if (!isset($score->scoreMaximum)) {
$score->scoreMaximum = 1;
}
$response->set_code(200);
$grade = \grade_grade::fetch(array('itemid' => $item->id, 'userid' => $score->userId));
if ($grade && !empty($grade->timemodified)) {
if ($grade->timemodified >= strtotime($score->timestamp)) {
$exmsg = "Refusing score with an earlier timestamp for item " . $item->id . " and user " . $score->userId;
throw new \Exception($exmsg, 409);
}
}
if (isset($score->scoreGiven)) {
if ($score->gradingProgress != 'FullyGraded') {
$score->scoreGiven = null;
}
}
gradebookservices::save_score($item, $score, $score->userId, $typeid);
}
/**
* get permissions from the config of the tool for that resource
*
* @param int $typeid
*
* @return array with the permissions related to this resource by the $lti_type or null if none.
*/
public function get_permissions($typeid) {
$tool = lti_get_type_type_config($typeid);
if ($tool->ltiservice_gradesynchronization == '1') {
return array('Score.collection:post');
} else if ($tool->ltiservice_gradesynchronization == '2') {
return array('Score.collection:post');
} else {
return array();
}
}
/**
* Parse a value for custom parameter substitution variables.
*
* @param string $value String to be parsed
*
* @return string
*/
public function parse_value($value) {
global $COURSE, $CFG;
if (strpos($value, '$Scores.url') !== false) {
require_once($CFG->libdir . '/gradelib.php');
$resolved = '';
$this->params['context_id'] = $COURSE->id;
$id = optional_param('id', 0, PARAM_INT); // Course Module ID.
if (!empty($id)) {
$cm = get_coursemodule_from_id('lti', $id, 0, false, MUST_EXIST);
$id = $cm->instance;
$item = grade_get_grades($COURSE->id, 'mod', 'lti', $id);
if ($item && $item->items) {
$this->params['item_id'] = $item->items[0]->id;
$resolved = parent::get_endpoint();
}
}
$value = str_replace('$Scores.url', $resolved, $value);
}
return $value;
}
}

View file

@ -0,0 +1,625 @@
<?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/>.
/**
* This file contains a class definition for the LTI Gradebook Services
*
* @package ltiservice_gradebookservices
* @copyright 2017 Cengage Learning http://www.cengage.com
* @author Dirk Singels, Diego del Blanco, Claude Vervoort
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace ltiservice_gradebookservices\local\service;
use ltiservice_gradebookservices\local\resources\lineitem;
use ltiservice_gradebookservices\local\resources\lineitems;
use ltiservice_gradebookservices\local\resources\results;
use ltiservice_gradebookservices\local\resources\scores;
use mod_lti\local\ltiservice\resource_base;
use mod_lti\local\ltiservice\service_base;
defined('MOODLE_INTERNAL') || die();
/**
* A service implementing LTI Gradebook Services.
*
* @package ltiservice_gradebookservices
* @copyright 2017 Cengage Learning http://www.cengage.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class gradebookservices extends service_base {
/** Internal service name */
const SERVICE_NAME = 'ltiservice_gradebookservices';
/**
* Class constructor.
*/
public function __construct() {
parent::__construct();
$this->id = 'gradebookservices';
$this->name = $this->get_string('servicename');
}
/**
* Get the resources for this service.
*
* @return resource_base[]
*/
public function get_resources() {
// The containers should be ordered in the array after their elements.
// Lineitems should be after lineitem.
if (empty($this->resources)) {
$this->resources = array();
$this->resources[] = new lineitem($this);
$this->resources[] = new lineitems($this);
$this->resources[] = new results($this);
$this->resources[] = new scores($this);
}
return $this->resources;
}
/**
* Adds form elements for gradebook sync add/edit page.
*
* @param \MoodleQuickForm $mform Moodle quickform object definition
*/
public function get_configuration_options(&$mform) {
$selectelementname = 'ltiservice_gradesynchronization';
$identifier = 'grade_synchronization';
$options = [
$this->get_string('nevergs'),
$this->get_string('partialgs'),
$this->get_string('alwaysgs')
];
$mform->addElement('select', $selectelementname, $this->get_string($identifier), $options);
$mform->setType($selectelementname, 'int');
$mform->setDefault($selectelementname, 0);
$mform->addHelpButton($selectelementname, $identifier, self::SERVICE_NAME);
}
/**
* Retrieves string from lang file
*
* @param string $identifier
* @return string
*/
private function get_string($identifier) {
return get_string($identifier, self::SERVICE_NAME);
}
/**
* Return an array with the names of the parameters that the service will be saving in the configuration
*
* @return array with the names of the parameters that the service will be saving in the configuration
*
*/
public function get_configuration_parameter_names() {
return array('ltiservice_gradesynchronization');
}
/**
* Return an array of key/values to add to the launch parameters.
*
* @param string $messagetype 'basic-lti-launch-request' or 'ContentItemSelectionRequest'.
* @param string $courseid the course id.
* @param object $user The user id.
* @param string $typeid The tool lti type id.
* @param string $modlti The id of the lti activity.
*
* The type is passed to check the configuration
* and not return parameters for services not used.
*
* @return array of key/value pairs to add as launch parameters.
*/
public function get_launch_parameters($messagetype, $courseid, $user, $typeid, $modlti = null) {
global $DB;
$launchparameters = array();
$tool = lti_get_type_type_config($typeid);
// Only inject parameters if the service is enabled for this tool.
if (isset($tool->ltiservice_gradesynchronization)) {
if ($tool->ltiservice_gradesynchronization == '1' || $tool->ltiservice_gradesynchronization == '2') {
// Check for used in context is only needed because there is no explicit site tool - course relation.
if ($this->is_allowed_in_context($typeid, $courseid)) {
$endpoint = $this->get_service_path() . "/{$courseid}/lineitems";
if (is_null($modlti)) {
$id = null;
} else {
$conditions = array('courseid' => $courseid, 'itemtype' => 'mod',
'itemmodule' => 'lti', 'iteminstance' => $modlti);
$lineitems = $DB->get_records('grade_items', $conditions);
$conditionsgbs = array('courseid' => $courseid, 'ltilinkid' => $modlti);
$lineitemsgbs = $DB->get_records('ltiservice_gradebookservices', $conditionsgbs);
if (count($lineitems) + count($lineitemsgbs) == 1) {
if ($lineitems) {
$lineitem = reset($lineitems);
$id = $lineitem->id;
} else {
$lineitemsgb = reset($lineitemsgbs);
$id = $lineitemsgb->gradeitemid;
}
} else {
$id = null;
}
}
$launchparameters['custom_lineitems_url'] = $endpoint . "?type_id={$typeid}";
if (!is_null($id)) {
$launchparameters['custom_lineitem_url'] = $endpoint . "/{$id}/lineitem?type_id={$typeid}";
}
}
}
}
return $launchparameters;
}
/**
* Fetch the lineitem instances.
*
* @param string $courseid ID of course
* @param string $resourceid Resource identifier used for filtering, may be null
* @param string $ltilinkid Resource Link identifier used for filtering, may be null
* @param string $tag
* @param int $limitfrom Offset for the first line item to include in a paged set
* @param int $limitnum Maximum number of line items to include in the paged set
* @param string $typeid
*
* @return array
* @throws \Exception
*/
public function get_lineitems($courseid, $resourceid, $ltilinkid, $tag, $limitfrom, $limitnum, $typeid) {
global $DB;
// Select all lti potential linetiems in site.
$params = array('courseid' => $courseid);
$optionalfilters = "";
if (isset($resourceid)) {
$optionalfilters .= " AND (i.idnumber = :resourceid)";
$params['resourceid'] = $resourceid;
}
$sql = "SELECT i.*
FROM {grade_items} i
WHERE (i.courseid = :courseid)
{$optionalfilters}
ORDER BY i.id";
$lineitems = $DB->get_records_sql($sql, $params);
// For each one, check the gbs id, and check that toolproxy matches. If so, add the
// tag to the result and add it to a final results array.
$lineitemstoreturn = array();
$lineitemsandtotalcount = array();
if ($lineitems) {
foreach ($lineitems as $lineitem) {
$gbs = $this->find_ltiservice_gradebookservice_for_lineitem($lineitem->id);
if ($gbs && (!isset($tag) || (isset($tag) && $gbs->tag == $tag))
&& (!isset($ltilinkid) || (isset($ltilinkid) && $gbs->ltilinkid == $ltilinkid))) {
if (is_null($typeid)) {
if ($this->get_tool_proxy()->id == $gbs->toolproxyid) {
array_push($lineitemstoreturn, $lineitem);
}
} else {
if ($typeid == $gbs->typeid) {
array_push($lineitemstoreturn, $lineitem);
}
}
} else if (($lineitem->itemtype == 'mod') && ($lineitem->itemmodule == 'lti') && (!isset($tag) &&
(!isset($ltilinkid) || (isset($ltilinkid) && $lineitem->iteminstance == $ltilinkid)))) {
// We will need to check if the activity related belongs to our tool proxy.
$ltiactivity = $DB->get_record('lti', array('id' => $lineitem->iteminstance));
if (($ltiactivity) && (isset($ltiactivity->typeid))) {
if ($ltiactivity->typeid != 0) {
$tool = $DB->get_record('lti_types', array('id' => $ltiactivity->typeid));
} else {
$tool = lti_get_tool_by_url_match($ltiactivity->toolurl, $courseid);
if (!$tool) {
$tool = lti_get_tool_by_url_match($ltiactivity->securetoolurl, $courseid);
}
}
if (is_null($typeid)) {
if (($tool) && ($this->get_tool_proxy()->id == $tool->toolproxyid)) {
array_push($lineitemstoreturn, $lineitem);
}
} else {
if (($tool) && ($tool->id == $typeid)) {
array_push($lineitemstoreturn, $lineitem);
}
}
}
}
}
$lineitemsandtotalcount = array();
array_push($lineitemsandtotalcount, count($lineitemstoreturn));
// Return the right array based in the paging parameters limit and from.
if (($limitnum) && ($limitnum > 0)) {
$lineitemstoreturn = array_slice($lineitemstoreturn, $limitfrom, $limitnum);
}
array_push($lineitemsandtotalcount, $lineitemstoreturn);
}
return $lineitemsandtotalcount;
}
/**
* Fetch a lineitem instance.
*
* Returns the lineitem instance if found, otherwise false.
*
* @param string $courseid ID of course
* @param string $itemid ID of lineitem
* @param string $typeid
*
* @return \ltiservice_gradebookservices\local\resources\lineitem|bool
*/
public function get_lineitem($courseid, $itemid, $typeid) {
global $DB, $CFG;
require_once($CFG->libdir . '/gradelib.php');
$lineitem = \grade_item::fetch(array('id' => $itemid));
if ($lineitem) {
$gbs = $this->find_ltiservice_gradebookservice_for_lineitem($itemid);
if (!$gbs) {
// We will need to check if the activity related belongs to our tool proxy.
$ltiactivity = $DB->get_record('lti', array('id' => $lineitem->iteminstance));
if (($ltiactivity) && (isset($ltiactivity->typeid))) {
if ($ltiactivity->typeid != 0) {
$tool = $DB->get_record('lti_types', array('id' => $ltiactivity->typeid));
} else {
$tool = lti_get_tool_by_url_match($ltiactivity->toolurl, $courseid);
if (!$tool) {
$tool = lti_get_tool_by_url_match($ltiactivity->securetoolurl, $courseid);
}
}
if (is_null($typeid)) {
if (!(($tool) && ($this->get_tool_proxy()->id == $tool->toolproxyid))) {
return false;
}
} else {
if (!(($tool) && ($tool->id == $typeid))) {
return false;
}
}
} else {
return false;
}
}
}
return $lineitem;
}
/**
* Set a grade item.
*
* @param object $gradeitem Grade Item record
* @param object $score Result object
* @param int $userid User ID
*
* @throws \Exception
*/
public static function save_score($gradeitem, $score, $userid) {
global $DB, $CFG;
$source = 'mod' . self::SERVICE_NAME;
if ($DB->get_record('user', array('id' => $userid)) === false) {
throw new \Exception(null, 400);
}
require_once($CFG->libdir . '/gradelib.php');
$finalgrade = null;
$timemodified = null;
if (isset($score->scoreGiven) && $score->scoreGiven) {
$finalgrade = grade_floatval($score->scoreGiven);
$max = 1;
if (isset($score->scoreMaximum)) {
$max = $score->scoreMaximum;
}
if (!is_null($max) && grade_floats_different($max, $gradeitem->grademax) && grade_floats_different($max, 0.0)) {
// Rescale to match the grade item maximum.
$finalgrade = grade_floatval($finalgrade * $gradeitem->grademax / $max);
}
if (isset($score->timestamp)) {
$timemodified = strtotime($score->timestamp);
} else {
$timemodified = time();
}
}
$feedbackformat = FORMAT_MOODLE;
$feedback = null;
if (isset($score->comment) && !empty($score->comment)) {
$feedback = $score->comment;
$feedbackformat = FORMAT_PLAIN;
}
if (!$grade = \grade_grade::fetch(array('itemid' => $gradeitem->id, 'userid' => $userid))) {
$grade = new \grade_grade();
$grade->userid = $userid;
$grade->itemid = $gradeitem->id;
}
$grade->rawgrademax = $score->scoreMaximum;
$grade->timemodified = $timemodified;
$grade->feedbackformat = $feedbackformat;
$grade->feedback = $feedback;
if ($gradeitem->is_manual_item()) {
$grade->finalgrade = $finalgrade;
if (empty($grade->id)) {
$result = (bool)$grade->insert($source);
} else {
$result = $grade->update($source);
}
} else {
$grade->rawgrade = $finalgrade;
$status = \grade_update($source, $gradeitem->courseid,
$gradeitem->itemtype, $gradeitem->itemmodule,
$gradeitem->iteminstance, $gradeitem->itemnumber,
$grade);
$result = ($status == GRADE_UPDATE_OK);
}
if (!$result) {
debugging("failed to save score for item ".$gradeitem->id." and user ".$grade->userid);
throw new \Exception(null, 500);
}
}
/**
* Get the json object representation of the grade item
*
* @param object $item Grade Item record
* @param string $endpoint Endpoint for lineitems container request
* @param string $typeid
*
* @return object
*/
public static function item_for_json($item, $endpoint, $typeid) {
$lineitem = new \stdClass();
if (is_null($typeid)) {
$typeidstring = "";
} else {
$typeidstring = "?type_id={$typeid}";
}
$lineitem->id = "{$endpoint}/{$item->id}/lineitem" . $typeidstring;
$lineitem->label = $item->itemname;
$lineitem->scoreMaximum = floatval($item->grademax);
$lineitem->resourceId = (!empty($item->idnumber)) ? $item->idnumber : '';
$gbs = self::find_ltiservice_gradebookservice_for_lineitem($item->id);
if ($gbs) {
$lineitem->tag = (!empty($gbs->tag)) ? $gbs->tag : '';
if (isset($gbs->ltilinkid)) {
$lineitem->ltiLinkId = strval($gbs->ltilinkid);
}
} else {
$lineitem->tag = '';
if (isset($item->iteminstance)) {
$lineitem->ltiLinkId = strval($item->iteminstance);
}
}
return $lineitem;
}
/**
* Get the object matching the JSON representation of the result.
*
* @param object $grade Grade record
* @param string $endpoint Endpoint for lineitem
* @param int $typeid The id of the type to include in the result url.
*
* @return object
*/
public static function result_for_json($grade, $endpoint, $typeid) {
if (is_null($typeid)) {
$id = "{$endpoint}/results?user_id={$grade->userid}";
} else {
$id = "{$endpoint}/results?type_id={$typeid}&user_id={$grade->userid}";
}
$result = new \stdClass();
$result->id = $id;
$result->userId = $grade->userid;
if (!empty($grade->finalgrade)) {
$result->resultScore = floatval($grade->finalgrade);
$result->resultMaximum = floatval($grade->rawgrademax);
if (!empty($grade->feedback)) {
$result->comment = $grade->feedback;
}
if (is_null($typeid)) {
$result->scoreOf = $endpoint;
} else {
$result->scoreOf = "{$endpoint}?type_id={$typeid}";
}
$result->timestamp = date('c', $grade->timemodified);
}
return $result;
}
/**
* Check if an LTI id is valid.
*
* @param string $linkid The lti id
* @param string $course The course
* @param string $toolproxy The tool proxy id
*
* @return boolean
*/
public static function check_lti_id($linkid, $course, $toolproxy) {
global $DB;
// Check if lti type is zero or not (comes from a backup).
$sqlparams1 = array();
$sqlparams1['linkid'] = $linkid;
$sqlparams1['course'] = $course;
$ltiactivity = $DB->get_record('lti', array('id' => $linkid, 'course' => $course));
if ($ltiactivity->typeid == 0) {
$tool = lti_get_tool_by_url_match($ltiactivity->toolurl, $course);
if (!$tool) {
$tool = lti_get_tool_by_url_match($ltiactivity->securetoolurl, $course);
}
return (($tool) && ($toolproxy == $tool->toolproxyid));
} else {
$sqlparams2 = array();
$sqlparams2['linkid'] = $linkid;
$sqlparams2['course'] = $course;
$sqlparams2['toolproxy'] = $toolproxy;
$sql = 'SELECT lti.*
FROM {lti} lti
INNER JOIN {lti_types} typ ON lti.typeid = typ.id
WHERE lti.id = ?
AND lti.course = ?
AND typ.toolproxyid = ?';
return $DB->record_exists_sql($sql, $sqlparams2);
}
}
/**
* Check if an LTI id is valid when we are in a LTI 1.x case
*
* @param string $linkid The lti id
* @param string $course The course
* @param string $typeid The lti type id
*
* @return boolean
*/
public static function check_lti_1x_id($linkid, $course, $typeid) {
global $DB;
// Check if lti type is zero or not (comes from a backup).
$sqlparams1 = array();
$sqlparams1['linkid'] = $linkid;
$sqlparams1['course'] = $course;
$ltiactivity = $DB->get_record('lti', array('id' => $linkid, 'course' => $course));
if ($ltiactivity) {
if ($ltiactivity->typeid == 0) {
$tool = lti_get_tool_by_url_match($ltiactivity->toolurl, $course);
if (!$tool) {
$tool = lti_get_tool_by_url_match($ltiactivity->securetoolurl, $course);
}
return (($tool) && ($typeid == $tool->id));
} else {
$sqlparams2 = array();
$sqlparams2['linkid'] = $linkid;
$sqlparams2['course'] = $course;
$sqlparams2['typeid'] = $typeid;
$sql = 'SELECT lti.*
FROM {lti} lti
INNER JOIN {lti_types} typ ON lti.typeid = typ.id
WHERE lti.id = ?
AND lti.course = ?
AND typ.id = ?';
return $DB->record_exists_sql($sql, $sqlparams2);
}
} else {
return false;
}
}
/**
* Deletes orphaned rows from the 'ltiservice_gradebookservices' table.
*
* Sometimes, if a gradebook entry is deleted and it was a lineitem
* the row in the table ltiservice_gradebookservices can become an orphan
* This method will clean these orphans. It will happens based on a task
* because it is not urgent and we don't want to slow the service
*/
public static function delete_orphans_ltiservice_gradebookservices_rows() {
global $DB;
$sql = "DELETE
FROM {ltiservice_gradebookservices}
WHERE gradeitemid NOT IN (SELECT id
FROM {grade_items} gi
WHERE gi.itemtype = 'mod'
AND gi.itemmodule = 'lti')";
$DB->execute($sql);
}
/**
* Check if a user can be graded in a course
*
* @param int $courseid The course
* @param int $userid The user
* @return bool
*/
public static function is_user_gradable_in_course($courseid, $userid) {
global $CFG;
$gradableuser = false;
$coursecontext = \context_course::instance($courseid);
if (is_enrolled($coursecontext, $userid, '', false)) {
$roles = get_user_roles($coursecontext, $userid);
$gradebookroles = explode(',', $CFG->gradebookroles);
foreach ($roles as $role) {
foreach ($gradebookroles as $gradebookrole) {
if ($role->roleid = $gradebookrole) {
$gradableuser = true;
}
}
}
}
return $gradableuser;
}
/**
* Find the right element in the ltiservice_gradebookservice table for a lineitem
*
* @param string $lineitemid The lineitem
* @return object|bool gradebookservice id or false if none
*/
public static function find_ltiservice_gradebookservice_for_lineitem($lineitemid) {
global $DB;
if (!$lineitemid) {
return false;
}
$gradeitem = $DB->get_record('grade_items', array('id' => $lineitemid));
if ($gradeitem) {
$gbs = $DB->get_record('ltiservice_gradebookservices',
array('gradeitemid' => $gradeitem->id, 'courseid' => $gradeitem->courseid));
if ($gbs) {
return $gbs;
} else {
return false;
}
} else {
return false;
}
}
/**
* Validates specific ISO 8601 format of the timestamps.
*
* @param string $date The timestamp to check.
* @return boolean true or false if the date matches the format.
*
*/
public static function validate_iso8601_date($date) {
if (preg_match('/^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])' .
'(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))' .
'([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)' .
'?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/', $date) > 0) {
return true;
} else {
return false;
}
}
}

View file

@ -0,0 +1,58 @@
<?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/>.
/**
* A scheduled task for gradebookservices.
*
* @package ltiservice_gradebookservices
* @copyright 2017 Cengage Learning http://www.cengage.com
* @author Dirk Singels, Diego del Blanco, Claude Vervoort
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace ltiservice_gradebookservices\task;
use core\task\scheduled_task;
use ltiservice_gradebookservices\local\service\gradebookservices;
defined('MOODLE_INTERNAL') || die();
/**
* Class containing the scheduled task for gradebookservices.
*
* @package ltiservice_gradebookservices
* @copyright 2017 Cengage Learning http://www.cengage.com
* @author Dirk Singels, Diego del Blanco, Claude Vervoort
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cleanup_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('taskcleanup', 'ltiservice_gradebookservices');
}
/**
* Run forum cron.
*/
public function execute() {
gradebookservices::delete_orphans_ltiservice_gradebookservices_rows();
}
}

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="mod/lti/service/gradebookservices/db" VERSION="20150915" COMMENT="XMLDB file for Moodle mod/lti/service/gradebookservices"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../../../lib/xmldb/xmldb.xsd"
>
<TABLES>
<TABLE NAME="ltiservice_gradebookservices" COMMENT="This file records the grade items created by the LTI Gradebook Services service">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="gradeitemid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="ID of the gradeItem related."/>
<FIELD NAME="courseid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="ID of the course related."/>
<FIELD NAME="toolproxyid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="ID of the Tool Proxy instance."/>
<FIELD NAME="typeid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="ID of the LTI Type if not Proxy."/>
<FIELD NAME="baseurl" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Lineitem URL that will be returned to the Tool provider"/>
<FIELD NAME="ltilinkid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="ID of the LTI element related with this lineitem."/>
<FIELD NAME="tag" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="Tag type specified for the line item"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="ltilinkid" TYPE="foreign" FIELDS="ltilinkid" REFTABLE="lti" REFFIELDS="id" COMMENT="The ID of the LTI element related with this lineitem."/>
<KEY NAME="itemnumbercourse" TYPE="foreign" FIELDS="gradeitemid,courseid" REFTABLE="grade_items" REFFIELDS="id,courseid" COMMENT="The row in gradeitems"/>
</KEYS>
</TABLE>
</TABLES>
</XMLDB>

View file

@ -0,0 +1,39 @@
<?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/>.
/**
* This file defines tasks performed by the plugin.
*
* @package ltiservice_gradebookservices
* @copyright 2017 Cengage Learning http://www.cengage.com
* @author Dirk Singels, Diego del Blanco, Claude Vervoort
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
// List of tasks.
$tasks = array(
array(
'classname' => 'ltiservice_gradebookservices\task\cleanup_task',
'blocking' => 0,
'minute' => 'R',
'hour' => 'R',
'day' => '*',
'dayofweek' => '*',
'month' => '*'
)
);

View file

@ -0,0 +1,38 @@
<?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 'ltiservice_gradebookservices', language 'en'
*
* @package ltiservice_gradebookservices
* @copyright 2017 Cengage Learning http://www.cengage.com
* @author Dirk Singels, Diego del Blanco, Claude Vervoort
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['alwaysgs'] = 'Use this service for grade sync and column management ';
$string['grade_synchronization'] = 'IMS LTI Assignment and Grade Services: ';
$string['grade_synchronization_help'] = 'Use the IMS LTI Assignment and Grade Service to synchronize the grades instead Basic Outcomes.
* **Do not use this service** - This will use the basic outcomes features and configuration
* **Use this service for grade sync only** - The service will populate the grades in an already existing gradebook column, but it will not be able to create new columns
* **Use this service for grade sync and column management** - The service will be able to create and update gradebook columns and manage the grades. ';
$string['modulename'] = 'LTI Grades';
$string['nevergs'] = 'Do not use this service';
$string['partialgs'] = 'Use this service for grade sync only';
$string['pluginname'] = 'LTI Assignment and Grade Services';
$string['servicename'] = 'LTI Assignment and Grade Services';
$string['taskcleanup'] = 'LTI Assignment and Grade Services table cleanup';

View file

@ -0,0 +1,103 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Tests cleaning up the gradebook services task.
*
* @package ltiservice_gradebookservices
* @category test
* @copyright 2018 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Tests cleaning up the gradebook services task.
*
* @package ltiservice_gradebookservices
* @category test
* @copyright 2018 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class ltiservice_gradebookservices_cleanup_task_testcase extends advanced_testcase {
/**
* Test set up.
*
* This is executed before running any test in this file.
*/
public function setUp() {
$this->resetAfterTest();
}
/**
* Test the cleanup task.
*/
public function test_cleanup_task() {
global $DB;
// Create a course.
$course = $this->getDataGenerator()->create_course();
// Create a few LTI items.
$lti = $this->getDataGenerator()->create_module('lti', ['course' => $course->id]);
$lti2 = $this->getDataGenerator()->create_module('lti', ['course' => $course->id]);
$conditions = [
'courseid' => $course->id,
'itemtype' => 'mod',
'itemmodule' => 'lti',
'iteminstance' => $lti->id
];
// Get the grade items.
$gradeitem = $DB->get_record('grade_items', $conditions);
$conditions['iteminstance'] = $lti2->id;
$gradeitem2 = $DB->get_record('grade_items', $conditions);
// Insert these into the 'ltiservice_gradebookservices' table.
$data = new stdClass();
$data->gradeitemid = $gradeitem->id;
$data->courseid = $course->id;
$DB->insert_record('ltiservice_gradebookservices', $data);
$data->gradeitemid = $gradeitem2->id;
$DB->insert_record('ltiservice_gradebookservices', $data);
$task = new \ltiservice_gradebookservices\task\cleanup_task();
$task->execute();
// Check they both still exist.
$this->assertEquals(2, $DB->count_records('ltiservice_gradebookservices'));
// Delete the first LTI activity.
course_delete_module($lti->cmid);
// Run the task again.
$task = new \ltiservice_gradebookservices\task\cleanup_task();
$task->execute();
// Check only the second grade item exists.
$gradebookserviceitems = $DB->get_records('ltiservice_gradebookservices');
$this->assertCount(1, $gradebookserviceitems);
$gradebookserviceitem = reset($gradebookserviceitems);
$this->assertEquals($gradeitem2->id, $gradebookserviceitem->gradeitemid);
}
}

View file

@ -0,0 +1,30 @@
<?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/>.
/**
* Version information for the ltiservice_gradebookservices service.
*
* @package ltiservice_gradebookservices
* @copyright 2017 Cengage Learning http://www.cengage.com
* @author Dirk Singels, Diego del Blanco, Claude Vervoort
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2018011100;
$plugin->requires = 2018010100;
$plugin->component = 'ltiservice_gradebookservices';

View file

@ -26,8 +26,9 @@
namespace ltiservice_memberships\local\resources;
use \mod_lti\local\ltiservice\service_base;
use mod_lti\local\ltiservice\resource_base;
use ltiservice_memberships\local\service\memberships;
use core_availability\info_module;
defined('MOODLE_INTERNAL') || die();
@ -39,18 +40,18 @@ defined('MOODLE_INTERNAL') || die();
* @copyright 2015 Vital Source Technologies http://vitalsource.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class contextmemberships extends \mod_lti\local\ltiservice\resource_base {
class contextmemberships extends resource_base {
/**
* Class constructor.
*
* @param ltiservice_memberships\local\service\memberships $service Service instance
* @param \ltiservice_memberships\local\service\memberships $service Service instance
*/
public function __construct($service) {
parent::__construct($service);
$this->id = 'ToolProxyBindingMemberships';
$this->template = '/{context_type}/{context_id}/bindings/{vendor_code}/{product_code}/{tool_code}/memberships';
$this->template = '/{context_type}/{context_id}/bindings/{tool_code}/memberships';
$this->variables[] = 'ToolProxyBinding.memberships.url';
$this->formats[] = 'application/vnd.ims.lis.v2.membershipcontainer+json';
$this->methods[] = 'GET';
@ -60,23 +61,24 @@ class contextmemberships extends \mod_lti\local\ltiservice\resource_base {
/**
* Execute the request for this resource.
*
* @param mod_lti\local\ltiservice\response $response Response object for this request.
* @param \mod_lti\local\ltiservice\response $response Response object for this request.
*/
public function execute($response) {
global $CFG, $DB;
global $DB;
$params = $this->parse_template();
$role = optional_param('role', '', PARAM_TEXT);
$limitnum = optional_param('limit', 0, PARAM_INT);
$limitfrom = optional_param('from', 0, PARAM_INT);
$linkid = optional_param('rlid', '', PARAM_TEXT);
$lti = null;
$modinfo = null;
if ($limitnum <= 0) {
$limitfrom = 0;
}
try {
if (!$this->get_service()->check_tool_proxy($params['product_code'])) {
throw new \Exception(null, 401);
}
if (!($course = $DB->get_record('course', array('id' => $params['context_id']), 'id', IGNORE_MISSING))) {
throw new \Exception(null, 404);
}
@ -84,14 +86,33 @@ class contextmemberships extends \mod_lti\local\ltiservice\resource_base {
throw new \Exception(null, 404);
}
if (!($tool = $DB->get_record('lti_types', array('id' => $params['tool_code']),
'toolproxyid,enabledcapability,parameter', IGNORE_MISSING))) {
'id,toolproxyid,enabledcapability,parameter', IGNORE_MISSING))) {
throw new \Exception(null, 404);
}
$toolproxy = $DB->get_record('lti_tool_proxies', array('id' => $tool->toolproxyid), 'guid', IGNORE_MISSING);
if (!$toolproxy || ($toolproxy->guid !== $this->get_service()->get_tool_proxy()->guid)) {
throw new \Exception(null, 400);
if (!empty($linkid)) {
if (!($lti = $DB->get_record('lti', array('id' => $linkid), 'id,course,typeid,servicesalt', IGNORE_MISSING))) {
throw new \Exception(null, 404);
}
$json = memberships::get_users_json($this, $context, $course->id, $tool, $role, $limitfrom, $limitnum, null, null);
$modinfo = get_fast_modinfo($course);
$cm = get_coursemodule_from_instance('lti', $linkid, $lti->course, false, MUST_EXIST);
$cm = $modinfo->get_cm($cm->id);
$modinfo = new info_module($cm);
if ($modinfo->is_available_for_all()) {
$modinfo = null;
}
}
if ($tool->toolproxyid == 0) {
if (!$this->check_type($params['tool_code'], $params['context_id'],
'ToolProxyBinding.memberships.url:get', null)) {
throw new \Exception(null, 403);
}
} else {
$toolproxy = $DB->get_record('lti_tool_proxies', array('id' => $tool->toolproxyid), 'guid', IGNORE_MISSING);
if (!$this->check_tool_proxy($toolproxy->guid, $response->get_request_data())) {
throw new \Exception(null, 403);
}
}
$json = memberships::get_users_json($this, $context, $course->id, $tool, $role, $limitfrom, $limitnum, $lti, $modinfo);
$response->set_content_type($this->formats[0]);
$response->set_body($json);
@ -101,6 +122,21 @@ class contextmemberships extends \mod_lti\local\ltiservice\resource_base {
}
}
/**
* get permissions from the config of the tool for that resource
*
* @param int $typeid
* @return array with the permissions related to this resource by the $lti_type or null if none.
*/
public function get_permissions($typeid) {
$tool = lti_get_type_type_config($typeid);
if ($tool->ltiservice_memberships == '1') {
return array('ToolProxyBinding.memberships.url:get');
} else {
return array();
}
}
/**
* Parse a value for custom parameter substitution variables.
*
@ -111,14 +147,13 @@ class contextmemberships extends \mod_lti\local\ltiservice\resource_base {
public function parse_value($value) {
global $COURSE, $DB;
if (strpos($value, '$ToolProxyBinding.memberships.url') !== false) {
if ($COURSE->id === SITEID) {
$this->params['context_type'] = 'Group';
} else {
$this->params['context_type'] = 'CourseSection';
}
$this->params['context_id'] = $COURSE->id;
$this->params['vendor_code'] = $this->get_service()->get_tool_proxy()->vendorcode;
$this->params['product_code'] = $this->get_service()->get_tool_proxy()->guid;
$id = optional_param('id', 0, PARAM_INT); // Course Module ID.
if (!empty($id)) {
@ -129,7 +164,7 @@ class contextmemberships extends \mod_lti\local\ltiservice\resource_base {
}
}
$value = str_replace('$ToolProxyBinding.memberships.url', parent::get_endpoint(), $value);
}
return $value;
}

View file

@ -26,27 +26,29 @@
namespace ltiservice_memberships\local\resources;
use \mod_lti\local\ltiservice\service_base;
use mod_lti\local\ltiservice\resource_base;
use ltiservice_memberships\local\service\memberships;
use core_availability\info;
use core_availability\info_module;
defined('MOODLE_INTERNAL') || die();
/**
* A resource implementing Link Memberships.
* The link membership is no longer defined in the published
* version of the LTI specification. It is replaced by the
* rlid parameter in the context membership URL.
*
* @package ltiservice_memberships
* @since Moodle 3.0
* @copyright 2015 Vital Source Technologies http://vitalsource.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class linkmemberships extends \mod_lti\local\ltiservice\resource_base {
class linkmemberships extends resource_base {
/**
* Class constructor.
*
* @param ltiservice_memberships\local\service\memberships $service Service instance
* @param \ltiservice_memberships\local\service\memberships $service Service instance
*/
public function __construct($service) {
@ -62,37 +64,49 @@ class linkmemberships extends \mod_lti\local\ltiservice\resource_base {
/**
* Execute the request for this resource.
*
* @param mod_lti\local\ltiservice\response $response Response object for this request.
* @param \mod_lti\local\ltiservice\response $response Response object for this request.
*/
public function execute($response) {
global $CFG, $DB;
global $DB;
$params = $this->parse_template();
$linkid = $params['link_id'];
$role = optional_param('role', '', PARAM_TEXT);
$limitnum = optional_param('limit', 0, PARAM_INT);
$limitfrom = optional_param('from', 0, PARAM_INT);
if ($limitnum <= 0) {
$limitfrom = 0;
}
try {
if (empty($linkid)) {
throw new \Exception(null, 404);
$response->set_code(404);
return;
}
if (!($lti = $DB->get_record('lti', array('id' => $linkid), 'id,course,typeid,servicesalt', IGNORE_MISSING))) {
throw new \Exception(null, 404);
$response->set_code(404);
return;
}
$tool = $DB->get_record('lti_types', array('id' => $lti->typeid));
if ($tool->toolproxyid == 0) { // We wil use the same permission for this and contextmembers.
if (!$this->check_type($lti->typeid, $lti->course, 'ToolProxyBinding.memberships.url:get', null)) {
$response->set_code(403);
return;
}
} else {
$toolproxy = $DB->get_record('lti_tool_proxies', array('id' => $tool->toolproxyid));
if (!$this->check_tool_proxy($toolproxy->guid, $response->get_request_data())) {
throw new \Exception(null, 401);
$response->set_code(403);
return;
}
}
if (!($course = $DB->get_record('course', array('id' => $lti->course), 'id', IGNORE_MISSING))) {
throw new \Exception(null, 404);
$response->set_code(404);
return;
}
if (!($context = \context_course::instance($lti->course))) {
throw new \Exception(null, 404);
$response->set_code(404);
return;
}
$modinfo = get_fast_modinfo($course);
$cm = get_coursemodule_from_instance('lti', $linkid, $lti->course, false, MUST_EXIST);
@ -101,16 +115,26 @@ class linkmemberships extends \mod_lti\local\ltiservice\resource_base {
if ($info->is_available_for_all()) {
$info = null;
}
$json = memberships::get_users_json($this, $context, $lti->course, $tool, $role, $limitfrom, $limitnum, $lti, $info);
$response->set_content_type($this->formats[0]);
$response->set_body($json);
} catch (\Exception $e) {
$response->set_code($e->getCode());
}
/**
* get permissions from the config of the tool for that resource
*
* @param string $typeid
*
* @return array with the permissions related to this resource by the $lti_type or null if none.
*/
public function get_permissions($typeid) {
$tool = lti_get_type_type_config($typeid);
if ($tool->memberships == '1') {
return array('ToolProxyBinding.memberships.url:get');
} else {
return array();
}
}
/**
@ -122,13 +146,14 @@ class linkmemberships extends \mod_lti\local\ltiservice\resource_base {
*/
public function parse_value($value) {
if (strpos($value, '$ToolProxyBinding.memberships.url') !== false) {
$id = optional_param('id', 0, PARAM_INT); // Course Module ID.
if (!empty($id)) {
$cm = get_coursemodule_from_id('lti', $id, 0, false, MUST_EXIST);
$this->params['link_id'] = $cm->instance;
}
$value = str_replace('$LtiLink.memberships.url', parent::get_endpoint(), $value);
}
return $value;
}

View file

@ -23,7 +23,6 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace ltiservice_memberships\local\service;
defined('MOODLE_INTERNAL') || die();
@ -46,6 +45,18 @@ class memberships extends \mod_lti\local\ltiservice\service_base {
const CONTEXT_ROLE_LEARNER = 'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner';
/** Capability used to identify Instructors */
const INSTRUCTOR_CAPABILITY = 'moodle/course:manageactivities';
/** Name of LTI service component */
const LTI_SERVICE_COMPONENT = 'ltiservice_memberships';
/** Membership services enabled */
const MEMBERSHIP_ENABLED = 1;
/** Always include field */
const ALWAYS_INCLUDE_FIELD = 1;
/** Allow the instructor to decide if included */
const DELEGATE_TO_INSTRUCTOR = 2;
/** Instructor chose to include field */
const INSTRUCTOR_INCLUDED = 1;
/** Instructor delegated and approved for include */
const INSTRUCTOR_DELEGATE_INCLUDED = array(self::DELEGATE_TO_INSTRUCTOR && self::INSTRUCTOR_INCLUDED);
/**
* Class constructor.
@ -54,7 +65,7 @@ class memberships extends \mod_lti\local\ltiservice\service_base {
parent::__construct();
$this->id = 'memberships';
$this->name = get_string('servicename', 'ltiservice_memberships');
$this->name = get_string('servicename', self::LTI_SERVICE_COMPONENT);
}
@ -80,17 +91,18 @@ class memberships extends \mod_lti\local\ltiservice\service_base {
*
* @param \mod_lti\local\ltiservice\resource_base $resource Resource handling the request
* @param \context_course $context Course context
* @param string $id Course ID
* @param string $contextid Course ID
* @param object $tool Tool instance object
* @param string $role User role requested (empty if none)
* @param int $limitfrom Position of first record to be returned
* @param int $limitnum Maximum number of records to be returned
* @param object $lti LTI instance record
* @param info_module $info Conditional availability information for LTI instance (null if context-level request)
* @param \core_availability\info_module $info Conditional availability information
* for LTI instance (null if context-level request)
*
* @return array
* @return string
*/
public static function get_users_json($resource, $context, $id, $tool, $role, $limitfrom, $limitnum, $lti, $info) {
public static function get_users_json($resource, $context, $contextid, $tool, $role, $limitfrom, $limitnum, $lti, $info) {
$withcapability = '';
$exclude = array();
@ -110,10 +122,9 @@ class memberships extends \mod_lti\local\ltiservice\service_base {
$limitfrom = 0;
$limitnum = 0;
}
$json = self::users_to_json($resource, $users, $id, $tool, $exclude, $limitfrom, $limitnum, $lti, $info);
$json = self::users_to_json($resource, $users, $contextid, $tool, $exclude, $limitfrom, $limitnum, $lti, $info);
return $json;
}
/**
@ -124,7 +135,7 @@ class memberships extends \mod_lti\local\ltiservice\service_base {
*
* @param \mod_lti\local\ltiservice\resource_base $resource Resource handling the request
* @param array $users Array of user records
* @param string $id Course ID
* @param string $contextid Course ID
* @param object $tool Tool instance object
* @param array $exclude Array of user records to be excluded from the response
* @param int $limitfrom Position of first record to be returned
@ -134,82 +145,197 @@ class memberships extends \mod_lti\local\ltiservice\service_base {
*
* @return string
*/
private static function users_to_json($resource, $users, $id, $tool, $exclude, $limitfrom, $limitnum,
private static function users_to_json($resource, $users, $contextid, $tool, $exclude, $limitfrom, $limitnum,
$lti, $info) {
global $DB;
$arrusers = [
'@context' => 'http://purl.imsglobal.org/ctx/lis/v2/MembershipContainer',
'@type' => 'Page',
'@id' => $resource->get_endpoint(),
];
$nextpage = 'null';
if ($limitnum > 0) {
$limitfrom += $limitnum;
$nextpage = "\"{$resource->get_endpoint()}?limit={$limitnum}&amp;from={$limitfrom}\"";
$nextpage = "{$resource->get_endpoint()}?limit={$limitnum}&from={$limitfrom}";
if (!is_null($lti)) {
$nextpage .= "&rlid={$lti->id}";
}
$arrusers['nextPage'] = $nextpage;
}
$json = <<< EOD
{
"@context" : "http://purl.imsglobal.org/ctx/lis/v2/MembershipContainer",
"@type" : "Page",
"@id" : "{$resource->get_endpoint()}",
"nextPage" : {$nextpage},
"pageOf" : {
"@type" : "LISMembershipContainer",
"membershipSubject" : {
"@type" : "Context",
"contextId" : "{$id}",
"membership" : [
EOD;
$arrusers['pageOf'] = [
'@type' => 'LISMembershipContainer',
'membershipSubject' => [
'@type' => 'Context',
'contextId' => $contextid,
'membership' => []
]
];
$enabledcapabilities = lti_get_enabled_capabilities($tool);
$sep = ' ';
$islti2 = $tool->toolproxyid > 0;
foreach ($users as $user) {
$include = !in_array($user->id, $exclude);
if ($include && !empty($info)) {
$include = $info->is_user_visible($info->get_course_module(), $user->id);
if (in_array($user->id, $exclude)) {
continue;
}
if ($include) {
$member = new \stdClass();
if (in_array('User.id', $enabledcapabilities)) {
$member->userId = $user->id;
if (!empty($info) && !$info->is_user_visible($info->get_course_module(), $user->id)) {
continue;
}
if (in_array('Person.sourcedId', $enabledcapabilities)) {
$member->sourcedId = format_string($user->idnumber);
}
if (in_array('Person.name.full', $enabledcapabilities)) {
$member->name = format_string("{$user->firstname} {$user->lastname}");
}
if (in_array('Person.name.given', $enabledcapabilities)) {
$member->givenName = format_string($user->firstname);
}
if (in_array('Person.name.family', $enabledcapabilities)) {
$member->familyName = format_string($user->lastname);
}
if (in_array('Person.email.primary', $enabledcapabilities)) {
$member->email = format_string($user->email);
}
if (in_array('Result.sourcedId', $enabledcapabilities) && !empty($lti) && !empty($lti->servicesalt)) {
$member->resultSourcedId = json_encode(lti_build_sourcedid($lti->id, $user->id, $lti->servicesalt,
$lti->typeid));
}
$roles = explode(',', lti_get_ims_role($user->id, null, $id, true));
$member = new \stdClass();
$member->{"@type" } = 'LISPerson';
$membership = new \stdClass();
$membership->status = 'Active';
$membership->role = explode(',', lti_get_ims_role($user->id, null, $contextid, true));
$toolconfig = lti_get_type_type_config($tool->id);
$instanceconfig = null;
if (!is_null($lti)) {
$instanceconfig = lti_get_type_config_from_instance($lti->id);
}
$isallowedlticonfig = self::is_allowed_field_set($toolconfig, $instanceconfig,
['name' => 'lti_sendname', 'email' => 'lti_sendemailaddr']);
$includedcapabilities = [
'User.id' => ['type' => 'id',
'member.field' => 'userId',
'source.value' => $user->id],
'Person.sourcedId' => ['type' => 'id',
'member.field' => 'sourcedId',
'source.value' => format_string($user->idnumber)],
'Person.name.full' => ['type' => 'name',
'member.field' => 'name',
'source.value' => format_string("{$user->firstname} {$user->lastname}")],
'Person.name.given' => ['type' => 'name',
'member.field' => 'givenName',
'source.value' => format_string($user->firstname)],
'Person.name.family' => ['type' => 'name',
'member.field' => 'familyName',
'source.value' => format_string($user->lastname)],
'Person.email.primary' => ['type' => 'email',
'member.field' => 'email',
'source.value' => format_string($user->email)]
];
if (!is_null($lti)) {
$message = new \stdClass();
$message->message_type = 'basic-lti-launch-request';
$conditions = array('courseid' => $contextid, 'itemtype' => 'mod',
'itemmodule' => 'lti', 'iteminstance' => $lti->id);
if (!empty($lti->servicesalt) && $DB->record_exists('grade_items', $conditions)) {
$message->lis_result_sourcedid = json_encode(lti_build_sourcedid($lti->id,
$user->id,
$lti->servicesalt,
$lti->typeid));
}
$membership->message = $message;
}
foreach ($includedcapabilities as $capabilityname => $capability) {
if ($islti2) {
if (!in_array($capabilityname, $enabledcapabilities)) {
continue;
}
} else {
if (($capability['type'] === 'id')
|| ($capability['type'] === 'name' && $isallowedlticonfig['name'])
|| ($capability['type'] === 'email' && $isallowedlticonfig['email'])) {
$member->{$capability['member.field']} = $capability['source.value'];
}
}
}
$membership->member = $member;
$membership->role = $roles;
$json .= $sep . json_encode($membership);
$sep = ",\n ";
$arrusers['pageOf']['membershipSubject']['membership'][] = $membership;
}
return json_encode($arrusers);
}
$json .= <<< EOD
]
/**
* Determines whether a user attribute may be used as part of LTI membership
* @param object $toolconfig Tool config
* @param object $instanceconfig Tool instance config
* @param array $fields Set of fields to return if allowed or not
* @return array Verification which associates an attribute with a boolean (allowed or not)
*/
private static function is_allowed_field_set($toolconfig, $instanceconfig, $fields) {
$isallowedstate = [];
foreach ($fields as $key => $field) {
$allowed = self::ALWAYS_INCLUDE_FIELD == $toolconfig->{$field};
if (!$allowed) {
if (self::DELEGATE_TO_INSTRUCTOR == $toolconfig->{$field} && !is_null($instanceconfig)) {
$allowed = $instanceconfig->{$field} == self::INSTRUCTOR_INCLUDED;
}
}
}
EOD;
$isallowedstate[$key] = $allowed;
}
return $isallowedstate;
}
return $json;
/**
* Adds form elements for membership add/edit page.
*
* @param \MoodleQuickForm $mform
*/
public function get_configuration_options(&$mform) {
$elementname = 'ltiservice_memberships';
$options = [
get_string('notallow', self::LTI_SERVICE_COMPONENT),
get_string('allow', self::LTI_SERVICE_COMPONENT)
];
$mform->addElement('select', $elementname, get_string($elementname, self::LTI_SERVICE_COMPONENT), $options);
$mform->setType($elementname, 'int');
$mform->setDefault($elementname, 0);
$mform->addHelpButton($elementname, $elementname, self::LTI_SERVICE_COMPONENT);
}
/**
* Return an array with the names of the parameters that the service will be saving in the configuration
*
* @return array with the names of the parameters that the service will be saving in the configuration
*
*/
public function get_configuration_parameter_names() {
return array(self::LTI_SERVICE_COMPONENT);
}
/**
* Return an array of key/values to add to the launch parameters.
*
* @param string $messagetype 'basic-lti-launch-request' or 'ContentItemSelectionRequest'.
* @param string $courseid The course id.
* @param string $user The user id.
* @param string $typeid The tool lti type id.
* @param string $modlti The id of the lti activity.
*
* The type is passed to check the configuration
* and not return parameters for services not used.
*
* @return array of key/value pairs to add as launch parameters.
*/
public function get_launch_parameters($messagetype, $courseid, $user, $typeid, $modlti = null) {
global $COURSE;
$launchparameters = array();
$tool = lti_get_type_type_config($typeid);
if (isset($tool->ltiservice_memberships)) {
if ($tool->ltiservice_memberships == '1' && $this->is_used_in_context($typeid, $courseid)) {
$endpoint = $this->get_service_path();
if ($COURSE->id === SITEID) {
$contexttype = 'Group';
} else {
$contexttype = 'CourseSection';
}
$launchparameters['custom_context_memberships_url'] = $endpoint .
"/{$contexttype}/{$courseid}/bindings/{$typeid}/memberships";
}
}
return $launchparameters;
}
}

View file

@ -23,5 +23,9 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['allow'] = 'Use this service to retrieve members\' information as per privacy settings';
$string['ltiservice_memberships'] = 'IMS LTI Membership: ';
$string['ltiservice_memberships_help'] = 'Allow the tool to retrieve member\'s info from the course using the IMS LTI Membership Service. The privacy settings will apply.';
$string['notallow'] = 'Do not use this service';
$string['pluginname'] = 'Memberships LTI Service';
$string['servicename'] = 'Memberships';

View file

@ -43,7 +43,7 @@ class profile extends \mod_lti\local\ltiservice\resource_base {
/**
* Class constructor.
*
* @param ltiservice_profile\local\resources\profile $service Service instance
* @param service_base $service Service instance
*/
public function __construct($service) {
@ -76,10 +76,9 @@ class profile extends \mod_lti\local\ltiservice\resource_base {
/**
* Execute the request for this resource.
*
* @param mod_lti\local\ltiservice\response $response Response object for this request.
* @param \mod_lti\local\ltiservice\response $response Response object for this request.
*/
public function execute($response) {
global $CFG;
$version = service_base::LTI_VERSION2P0;
@ -104,6 +103,7 @@ class profile extends \mod_lti\local\ltiservice\resource_base {
foreach ($services as $name => $location) {
if (in_array($name, $serviceofferedarr)) {
$classname = "\\ltiservice_{$name}\\local\\service\\{$name}";
/** @var service_base $service */
$service = new $classname();
$service->set_tool_proxy($toolproxy);
$resources = $service->get_resources();
@ -218,9 +218,9 @@ EOD;
* @return string
*/
public function parse_value($value) {
if (strpos($value, '$ToolConsumerProfile.url') !== false) {
$value = str_replace('$ToolConsumerProfile.url', $this->get_endpoint(), $value);
}
return $value;
}

View file

@ -26,7 +26,6 @@
namespace ltiservice_toolsettings\local\resources;
use ltiservice_toolsettings\local\resources\systemsettings;
use ltiservice_toolsettings\local\service\toolsettings;
defined('MOODLE_INTERNAL') || die();
@ -44,7 +43,7 @@ class contextsettings extends \mod_lti\local\ltiservice\resource_base {
/**
* Class constructor.
*
* @param ltiservice_toolsettings\local\resources\contextsettings $service Service instance
* @param \mod_lti\local\ltiservice\service_base $service Service instance
*/
public function __construct($service) {
@ -62,7 +61,7 @@ class contextsettings extends \mod_lti\local\ltiservice\resource_base {
/**
* Execute the request for this resource.
*
* @param mod_lti\local\ltiservice\response $response Response object for this request.
* @param \mod_lti\local\ltiservice\response $response Response object for this request.
*/
public function execute($response) {
@ -166,6 +165,7 @@ class contextsettings extends \mod_lti\local\ltiservice\resource_base {
public function parse_value($value) {
global $COURSE;
if (strpos($value, '$ToolProxyBinding.custom.url') !== false) {
if ($COURSE->format == 'site') {
$this->params['context_type'] = 'Group';
} else {
@ -175,7 +175,7 @@ class contextsettings extends \mod_lti\local\ltiservice\resource_base {
$this->params['vendor_code'] = $this->get_service()->get_tool_proxy()->vendorcode;
$this->params['product_code'] = $this->get_service()->get_tool_proxy()->guid;
$value = str_replace('$ToolProxyBinding.custom.url', parent::get_endpoint(), $value);
}
return $value;
}

View file

@ -23,11 +23,8 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace ltiservice_toolsettings\local\resources;
use ltiservice_toolsettings\local\resources\systemsettings;
use ltiservice_toolsettings\local\resources\contextsettings;
use ltiservice_toolsettings\local\service\toolsettings;
defined('MOODLE_INTERNAL') || die();
@ -45,7 +42,7 @@ class linksettings extends \mod_lti\local\ltiservice\resource_base {
/**
* Class constructor.
*
* @param ltiservice_toolsettings\local\resources\linksettings $service Service instance
* @param \mod_lti\local\ltiservice\service_base $service Service instance
*/
public function __construct($service) {
@ -63,7 +60,7 @@ class linksettings extends \mod_lti\local\ltiservice\resource_base {
/**
* Execute the request for this resource.
*
* @param mod_lti\local\ltiservice\response $response Response object for this request.
* @param \mod_lti\local\ltiservice\response $response Response object for this request.
*/
public function execute($response) {
global $DB, $COURSE;
@ -82,6 +79,7 @@ class linksettings extends \mod_lti\local\ltiservice\resource_base {
$systemsetting = null;
$contextsetting = null;
$lti = null;
if ($ok) {
$ok = !empty($linkid);
if ($ok) {
@ -195,13 +193,14 @@ class linksettings extends \mod_lti\local\ltiservice\resource_base {
*/
public function parse_value($value) {
if (strpos($value, '$LtiLink.custom.url') !== false) {
$id = optional_param('id', 0, PARAM_INT); // Course Module ID.
if (!empty($id)) {
$cm = get_coursemodule_from_id('lti', $id, 0, false, MUST_EXIST);
$this->params['link_id'] = $cm->instance;
}
$value = str_replace('$LtiLink.custom.url', parent::get_endpoint(), $value);
}
return $value;
}

View file

@ -27,6 +27,7 @@
namespace ltiservice_toolsettings\local\resources;
use ltiservice_toolsettings\local\service\toolsettings;
use mod_lti\local\ltiservice\resource_base;
defined('MOODLE_INTERNAL') || die();
@ -38,12 +39,12 @@ defined('MOODLE_INTERNAL') || die();
* @copyright 2014 Vital Source Technologies http://vitalsource.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class systemsettings extends \mod_lti\local\ltiservice\resource_base {
class systemsettings extends resource_base {
/**
* Class constructor.
*
* @param ltiservice_toolsettings\local\service\toolsettings $service Service instance
* @param \mod_lti\local\ltiservice\service_base $service Service instance
*/
public function __construct($service) {
@ -61,7 +62,7 @@ class systemsettings extends \mod_lti\local\ltiservice\resource_base {
/**
* Execute the request for this resource.
*
* @param mod_lti\local\ltiservice\response $response Response object for this request.
* @param \mod_lti\local\ltiservice\response $response Response object for this request.
*/
public function execute($response) {
@ -143,9 +144,9 @@ class systemsettings extends \mod_lti\local\ltiservice\resource_base {
* @return string
*/
public function parse_value($value) {
if (strpos($value, '$ToolProxy.custom.url') !== false) {
$value = str_replace('$ToolProxy.custom.url', parent::get_endpoint(), $value);
}
return $value;
}