���ѧۧݧ�ӧ�� �ާ֧ߧ֧էا֧� - ���֧էѧܧ�ڧ��ӧѧ�� - /home3/cpr76684/public_html/service.tar
���ѧ٧ѧ�
profile/version.php 0000644 00000002134 15151264171 0010405 0 ustar 00 <?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_profile service. * * @package ltiservice_profile * @copyright 2014 Vital Source Technologies http://vitalsource.com * @author Stephen Vickers * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2022112800; $plugin->requires = 2022111800; $plugin->component = 'ltiservice_profile'; profile/classes/privacy/provider.php 0000644 00000003004 15151264171 0013661 0 ustar 00 <?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/>. /** * Privacy Subsystem implementation for ltiservice_profile. * * @package ltiservice_profile * @copyright 2018 Mark Nelson <markn@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace ltiservice_profile\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for ltiservice_profile implementing null_provider. * * @copyright 2018 Mark Nelson <markn@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason() : string { return 'privacy:metadata'; } } profile/classes/local/resources/profile.php 0000644 00000016315 15151264171 0015127 0 ustar 00 <?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 Tool Consumer Profile resource * * @package ltiservice_profile * @copyright 2014 Vital Source Technologies http://vitalsource.com * @author Stephen Vickers * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace ltiservice_profile\local\resources; use \mod_lti\local\ltiservice\service_base; defined('MOODLE_INTERNAL') || die(); /** * A resource implementing the Tool Consumer Profile. * * @package ltiservice_profile * @since Moodle 2.8 * @copyright 2014 Vital Source Technologies http://vitalsource.com * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class profile extends \mod_lti\local\ltiservice\resource_base { /** * Class constructor. * * @param service_base $service Service instance */ public function __construct($service) { parent::__construct($service); $this->id = 'ToolConsumerProfile'; $this->template = '/profile/{tool_proxy_id}'; $this->variables[] = 'ToolConsumerProfile.url'; $this->formats[] = 'application/vnd.ims.lti.v2.toolconsumerprofile+json'; $this->methods[] = 'GET'; } /** * Get the path for this resource. * * @return string */ public function get_path() { $path = $this->template; $toolproxy = $this->get_service()->get_tool_proxy(); if (!empty($toolproxy)) { $path = str_replace('{tool_proxy_id}', $toolproxy->guid, $path); } return $path; } /** * Execute the request for this resource. * * @param \mod_lti\local\ltiservice\response $response Response object for this request. */ public function execute($response) { global $CFG; $version = service_base::LTI_VERSION2P0; $params = $this->parse_template(); if (optional_param('lti_version', service_base::LTI_VERSION2P0, PARAM_ALPHANUMEXT) != $version) { $ok = false; $response->set_code(400); } else { $toolproxy = lti_get_tool_proxy_from_guid($params['tool_proxy_id']); $ok = $toolproxy !== false; } if ($ok) { $this->get_service()->set_tool_proxy($toolproxy); $response->set_content_type($this->formats[0]); $servicepath = $this->get_service()->get_service_path(); $id = $servicepath . $this->get_path(); $now = date('Y-m-d\TH:iO'); $capabilityofferedarr = explode("\n", $toolproxy->capabilityoffered); $serviceofferedarr = explode("\n", $toolproxy->serviceoffered); $serviceoffered = ''; $sep = ''; $services = \core_component::get_plugin_list('ltiservice'); 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(); foreach ($resources as $resource) { $formats = implode("\", \"", $resource->get_formats()); $methods = implode("\", \"", $resource->get_methods()); $capabilityofferedarr = array_merge($capabilityofferedarr, $resource->get_variables()); $template = $resource->get_path(); if (!empty($template)) { $path = $servicepath . preg_replace('/[\(\)]/', '', $template); } else { $path = $resource->get_endpoint(); } $serviceoffered .= <<< EOD {$sep} { "@type":"{$resource->get_type()}", "@id":"tcp:{$resource->get_id()}", "endpoint":"{$path}", "format":["{$formats}"], "action":["{$methods}"] } EOD; $sep = ','; } } } $capabilityoffered = implode("\",\n \"", $capabilityofferedarr); if (strlen($capabilityoffered) > 0) { $capabilityoffered = "\n \"{$capabilityoffered}\""; } $urlparts = parse_url($CFG->wwwroot); $orgid = $urlparts['host']; $name = 'Moodle'; $code = 'moodle'; $vendorname = 'Moodle.org'; $vendorcode = 'mdl'; $prodversion = strval($CFG->version); if (!empty($CFG->mod_lti_institution_name)) { $consumername = $CFG->mod_lti_institution_name; $consumerdesc = ''; } else { $consumername = get_site()->fullname; $consumerdesc = strip_tags(get_site()->summary); } $profile = <<< EOD { "@context":[ "http://purl.imsglobal.org/ctx/lti/v2/ToolConsumerProfile", { "tcp":"{$id}#" } ], "@type":"ToolConsumerProfile", "@id":"{$id}", "lti_version":"{$version}", "guid":"{$toolproxy->guid}", "product_instance":{ "guid":"{$orgid}", "product_info":{ "product_name":{ "default_value":"{$name}", "key":"product.name" }, "product_version":"{$prodversion}", "product_family":{ "code":"{$code}", "vendor":{ "code":"{$vendorcode}", "vendor_name":{ "default_value":"{$vendorname}", "key":"product.vendor.name" }, "timestamp":"{$now}" } } }, "service_owner":{ "@id":"ServiceOwner", "service_owner_name":{ "default_value":"{$consumername}", "key":"service_owner.name" }, "description":{ "default_value":"{$consumerdesc}", "key":"service_owner.description" } } }, "capability_offered":[{$capabilityoffered} ], "service_offered":[{$serviceoffered} ] } EOD; $response->set_body($profile); } } /** * Parse a value for custom parameter substitution variables. * * @param string $value String to be parsed * * @return string */ public function parse_value($value) { if (!empty($this->get_service()->get_tool_proxy()) && (strpos($value, '$ToolConsumerProfile.url') !== false)) { $value = str_replace('$ToolConsumerProfile.url', $this->get_endpoint(), $value); } return $value; } } profile/classes/local/service/profile.php 0000644 00000003670 15151264171 0014555 0 ustar 00 <?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 Tool Consumer Profile service * * @package ltiservice_profile * @copyright 2014 Vital Source Technologies http://vitalsource.com * @author Stephen Vickers * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace ltiservice_profile\local\service; defined('MOODLE_INTERNAL') || die(); /** * A service implementing the Tool Consumer Profile. * * @package ltiservice_profile * @since Moodle 2.8 * @copyright 2014 Vital Source Technologies http://vitalsource.com * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class profile extends \mod_lti\local\ltiservice\service_base { /** * Class constructor. */ public function __construct() { parent::__construct(); $this->id = 'profile'; $this->name = 'Tool Consumer Profile'; $this->unsigned = true; } /** * Get the resources for this service. * * @return array */ public function get_resources() { if (empty($this->resources)) { $this->resources = array(); $this->resources[] = new \ltiservice_profile\local\resources\profile($this); } return $this->resources; } } profile/lang/en/ltiservice_profile.php 0000644 00000002166 15151264171 0014141 0 ustar 00 <?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_profile', language 'en' * * @package ltiservice_profile * @copyright 2014 Vital Source Technologies http://vitalsource.com * @author Stephen Vickers * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['pluginname'] = 'Tool Consumer Profile LTI Service'; $string['privacy:metadata'] = 'The Tool Consumer Profile LTI Service plugin does not store any personal data.'; basicoutcomes/version.php 0000644 00000002056 15151264171 0011610 0 ustar 00 <?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_basicoutcomes service. * * @package ltiservice_basicoutcomes * @copyright 2019 Stephen Vickers * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2022112800; $plugin->requires = 2022111800; $plugin->component = 'ltiservice_basicoutcomes'; basicoutcomes/classes/privacy/provider.php 0000644 00000007514 15151264171 0015073 0 ustar 00 <?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/>. /** * Privacy Subsystem implementation for ltiservice_basicoutcomes. * * @package ltiservice_basicoutcomes * @copyright 2019 Stephen Vickers * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace ltiservice_basicoutcomes\privacy; use \core_privacy\local\metadata\collection; use \core_privacy\local\request\contextlist; use \core_privacy\local\request\approved_contextlist; use \core_privacy\local\request\userlist; use \core_privacy\local\request\approved_userlist; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for ltiservice_basicoutcomes. * * @copyright 2019 Stephen Vickers * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\provider, \core_privacy\local\request\core_userlist_provider, \core_privacy\local\request\plugin\provider { /** * Returns meta data about this system. * * @param collection $collection The initialised collection to add items to. * @return collection A listing of user data stored through this system. */ public static function get_metadata(collection $collection) : collection { $collection->link_external_location('External LTI provider.', [ 'userid' => 'privacy:metadata:userid', 'grade' => 'privacy:metadata:grade', ], 'privacy:metadata:externalpurpose'); return $collection; } /** * Get the list of contexts that contain user information for the specified user. * * @param int $userid The user to search. * @return contextlist The contextlist containing the list of contexts used in this plugin. */ public static function get_contexts_for_userid(int $userid) : contextlist { return new contextlist(); } /** * Get the list of users who have data within a context. * * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. */ public static function get_users_in_context(userlist $userlist) { } /** * Export all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts to export information for. */ public static function export_user_data(approved_contextlist $contextlist) { } /** * Delete all user data which matches the specified context. * * @param \context $context A user context. */ public static function delete_data_for_all_users_in_context(\context $context) { } /** * Delete multiple users within a single context. * * @param approved_userlist $userlist The approved context and user information to delete information for. */ public static function delete_data_for_users(approved_userlist $userlist) { } /** * Delete all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. */ public static function delete_data_for_user(approved_contextlist $contextlist) { } } basicoutcomes/classes/local/resources/basicoutcomes.php 0000644 00000004317 15151264171 0017526 0 ustar 00 <?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 Basic Outcomes resource * * @package ltiservice_basicoutcomes * @copyright 2019 Stephen Vickers * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace ltiservice_basicoutcomes\local\resources; defined('MOODLE_INTERNAL') || die(); /** * A resource implementing the Basic Outcomes service. * * @package ltiservice_basicoutcomes * @since Moodle 3.7 * @copyright 2019 Stephen Vickers * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class basicoutcomes extends \mod_lti\local\ltiservice\resource_base { /** * Class constructor. * * @param \mod_lti\local\ltiservice\service_base $service Service instance */ public function __construct($service) { parent::__construct($service); $this->id = 'Outcomes.LTI1'; $this->template = ''; $this->formats[] = 'application/vnd.ims.lti.v1.outcome+xml'; $this->methods[] = 'POST'; } /** * Get the resource fully qualified endpoint. * * @return string */ public function get_endpoint() { $url = new \moodle_url('/mod/lti/service.php'); return $url->out(false); } /** * Execute the request for this resource. * * @param \mod_lti\local\ltiservice\response $response Response object for this request. */ public function execute($response) { // Should never be called as the endpoint sends requests to the LTI 1 service endpoint. } } basicoutcomes/classes/local/service/basicoutcomes.php 0000644 00000006437 15151264171 0017161 0 ustar 00 <?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 Basic Outcomes service * * @package ltiservice_basicoutcomes * @copyright 2019 Stephen Vickers * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace ltiservice_basicoutcomes\local\service; defined('MOODLE_INTERNAL') || die(); /** * A service implementing Basic Outcomes. * * @package ltiservice_basicoutcomes * @since Moodle 3.7 * @copyright 2019 Stephen Vickers * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class basicoutcomes extends \mod_lti\local\ltiservice\service_base { /** Scope for accessing the service */ const SCOPE_BASIC_OUTCOMES = 'https://purl.imsglobal.org/spec/lti-bo/scope/basicoutcome'; /** * Class constructor. */ public function __construct() { parent::__construct(); $this->id = 'basicoutcomes'; $this->name = 'Basic Outcomes'; } /** * Get the resources for this service. * * @return array */ public function get_resources() { if (empty($this->resources)) { $this->resources = array(); $this->resources[] = new \ltiservice_basicoutcomes\local\resources\basicoutcomes($this); } return $this->resources; } /** * Get the scope(s) permitted for the tool relevant to this service. * * @return array */ public function get_permitted_scopes() { $scopes = array(); if (!isset($this->get_typeconfig()['acceptgrades']) || ($this->get_typeconfig()['acceptgrades'] != LTI_SETTING_NEVER)) { $scopes[] = self::SCOPE_BASIC_OUTCOMES; } return $scopes; } /** * Get the scope(s) permitted for the tool relevant to this service. * * @return array */ public function get_scopes() { return [self::SCOPE_BASIC_OUTCOMES]; } /** * Return an array of key/claim mapping allowing LTI 1.1 custom parameters * to be transformed to LTI 1.3 claims. * * @return array Key/value pairs of params to claim mapping. */ public function get_jwt_claim_mappings(): array { return [ 'lis_outcome_service_url' => [ 'suffix' => 'bo', 'group' => 'basicoutcome', 'claim' => 'lis_outcome_service_url', 'isarray' => false ], 'lis_result_sourcedid' => [ 'suffix' => 'bo', 'group' => 'basicoutcome', 'claim' => 'lis_result_sourcedid', 'isarray' => false ] ]; } } basicoutcomes/lang/en/ltiservice_basicoutcomes.php 0000644 00000003034 15151264171 0016534 0 ustar 00 <?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_basicoutcomes', language 'en' * * @package ltiservice_basicoutcomes * @copyright 2019 Stephen Vickers * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['allow'] = 'Use this service to accept grades from the tool'; $string['ltiservice_basicoutcomes'] = 'Basic Outcomes'; $string['ltiservice_basicoutcomes_help'] = 'Allow the tool to save and retrieve its grades to a gradebook column associated with each link.'; $string['notallow'] = 'Do not use this service'; $string['pluginname'] = 'Basic Outcomes Service'; $string['privacy:metadata:externalpurpose'] = 'This information is sent to an external LTI provider.'; $string['privacy:metadata:grade'] = 'The tool\'s grades of the user using the LTI consumer.'; $string['privacy:metadata:userid'] = 'The ID of the user using the LTI consumer.'; toolsettings/version.php 0000644 00000002336 15151264171 0011507 0 ustar 00 <?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_toolsettings service. * * @package ltiservice_toolsettings * @copyright 2014 Vital Source Technologies http://vitalsource.com * @author Stephen Vickers * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2022112800; $plugin->requires = 2022111800; $plugin->component = 'ltiservice_toolsettings'; $plugin->dependencies = array( 'ltiservice_profile' => 2022111800, 'ltiservice_toolproxy' => 2022111800 ); toolsettings/classes/privacy/provider.php 0000644 00000003030 15151264171 0014756 0 ustar 00 <?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/>. /** * Privacy Subsystem implementation for ltiservice_toolsettings. * * @package ltiservice_toolsettings * @copyright 2018 Mark Nelson <markn@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace ltiservice_toolsettings\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for ltiservice_toolsettings implementing null_provider. * * @copyright 2018 Mark Nelson <markn@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason() : string { return 'privacy:metadata'; } } toolsettings/classes/local/resources/systemsettings.php 0000644 00000014336 15151264171 0017673 0 ustar 00 <?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 System Settings resource * * @package ltiservice_toolsettings * @copyright 2014 Vital Source Technologies http://vitalsource.com * @author Stephen Vickers * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace ltiservice_toolsettings\local\resources; use ltiservice_toolsettings\local\service\toolsettings; use mod_lti\local\ltiservice\resource_base; defined('MOODLE_INTERNAL') || die(); /** * A resource implementing the System-level (ToolProxy) Settings. * * @package ltiservice_toolsettings * @since Moodle 2.8 * @copyright 2014 Vital Source Technologies http://vitalsource.com * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class systemsettings extends resource_base { /** * Class constructor. * * @param \mod_lti\local\ltiservice\service_base $service Service instance */ public function __construct($service) { parent::__construct($service); $this->id = 'ToolProxySettings'; $this->template = '/{config_type}/{tool_proxy_id}(/custom)'; $this->variables[] = 'ToolProxy.custom.url'; $this->formats[] = 'application/vnd.ims.lti.v2.toolsettings+json'; $this->formats[] = 'application/vnd.ims.lti.v2.toolsettings.simple+json'; $this->methods[] = self::HTTP_GET; $this->methods[] = self::HTTP_PUT; } /** * Execute the request for this resource. * * @param \mod_lti\local\ltiservice\response $response Response object for this request. */ public function execute($response) { $params = $this->parse_template(); $tpid = $params['tool_proxy_id']; $configtype = $params['config_type']; $ok = (in_array($configtype, array('toolproxy', 'tool'))); if ($ok) { $typeid = null; if (($configtype === 'tool') && is_numeric($tpid)) { $typeid = $tpid; } $bubble = optional_param('bubble', '', PARAM_ALPHA); $ok = !empty($tpid) && $this->check_tool($typeid, $response->get_request_data(), array(toolsettings::SCOPE_TOOL_SETTINGS)); } if (!$ok) { $response->set_code(401); } else if (!empty($this->get_service()->get_tool_proxy())) { $ok = ($this->get_service()->get_tool_proxy()->guid === $tpid); $id = $this->get_service()->get_tool_proxy()->id; } else if (!empty($typeid)) { $id = -$typeid; } else { $ok = false; $response->set_code(404); } $contenttype = $response->get_accept(); $simpleformat = !empty($contenttype) && ($contenttype == $this->formats[1]); if ($ok) { $ok = (empty($bubble) || ((($bubble == 'distinct') || ($bubble == 'all')))) && (!$simpleformat || empty($bubble) || ($bubble != 'all')) && (empty($bubble) || ($response->get_request_method() == 'GET')); if (!$ok) { $response->set_code(406); } } if ($ok) { $systemsettings = lti_get_tool_settings($id); if ($response->get_request_method() == 'GET') { $json = ''; if ($simpleformat) { $response->set_content_type($this->formats[1]); $json .= "{"; } else { $response->set_content_type($this->formats[0]); $json .= "{\n \"@context\":\"http://purl.imsglobal.org/ctx/lti/v2/ToolSettings\",\n \"@graph\":[\n"; } $json .= toolsettings::settings_to_json($systemsettings, $simpleformat, 'ToolProxy', $this); if ($simpleformat) { $json .= "\n}"; } else { $json .= "\n ]\n}"; } $response->set_body($json); } else { // PUT. $settings = null; if ($response->get_content_type() == $this->formats[0]) { $json = json_decode($response->get_request_data()); $ok = !empty($json); if ($ok) { $ok = isset($json->{"@graph"}) && is_array($json->{"@graph"}) && (count($json->{"@graph"}) == 1) && ($json->{"@graph"}[0]->{"@type"} == 'ToolProxy'); } if ($ok) { $settings = $json->{"@graph"}[0]->custom; unset($settings->{'@id'}); } } else { // Simple JSON. $json = json_decode($response->get_request_data(), true); $ok = !empty($json); if ($ok) { $ok = is_array($json); } if ($ok) { $settings = $json; } } if ($ok) { lti_set_tool_settings($settings, $id); } else { $response->set_code(406); } } } } /** * Parse a value for custom parameter substitution variables. * * @param string $value String to be parsed * * @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; } } toolsettings/classes/local/resources/contextsettings.php 0000644 00000020747 15151264171 0020036 0 ustar 00 <?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 Context Settings resource * * @package ltiservice_toolsettings * @copyright 2014 Vital Source Technologies http://vitalsource.com * @author Stephen Vickers * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace ltiservice_toolsettings\local\resources; use ltiservice_toolsettings\local\service\toolsettings; defined('MOODLE_INTERNAL') || die(); /** * A resource implementing the Context-level (ToolProxyBinding) Settings. * * @package ltiservice_toolsettings * @since Moodle 2.8 * @copyright 2014 Vital Source Technologies http://vitalsource.com * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class contextsettings extends \mod_lti\local\ltiservice\resource_base { /** * Class constructor. * * @param \mod_lti\local\ltiservice\service_base $service Service instance */ public function __construct($service) { parent::__construct($service); $this->id = 'ToolProxyBindingSettings'; $this->template = '/{context_type}/{context_id}/bindings/{vendor_code}/{product_code}(/custom)'; $this->variables[] = 'ToolProxyBinding.custom.url'; $this->formats[] = 'application/vnd.ims.lti.v2.toolsettings+json'; $this->formats[] = 'application/vnd.ims.lti.v2.toolsettings.simple+json'; $this->methods[] = 'GET'; $this->methods[] = 'PUT'; } /** * Execute the request for this resource. * * @param \mod_lti\local\ltiservice\response $response Response object for this request. */ public function execute($response) { $params = $this->parse_template(); $contexttype = $params['context_type']; $contextid = $params['context_id']; $vendorcode = $params['vendor_code']; $productcode = $params['product_code']; $bubble = optional_param('bubble', '', PARAM_ALPHA); $typeid = null; if (($vendorcode === 'tool') && is_numeric($productcode)) { $typeid = $productcode; } $ok = !empty($contexttype) && !empty($contextid) && !empty($vendorcode) && !empty($productcode) && $this->check_tool($typeid, $response->get_request_data(), array(toolsettings::SCOPE_TOOL_SETTINGS)); if (!$ok) { $response->set_code(401); } else { $toolproxy = $this->get_service()->get_tool_proxy(); if (!empty($toolproxy)) { $ok = $toolproxy->guid === $productcode; $typeid = null; $id = $toolproxy->id; } else { $ok = $vendorcode === 'tool'; $typeid = intval($productcode); $id = -$typeid; } $contenttype = $response->get_accept(); $simpleformat = !empty($contenttype) && ($contenttype == $this->formats[1]); if ($ok) { $ok = (empty($bubble) || ((($bubble == 'distinct') || ($bubble == 'all')))) && (!$simpleformat || empty($bubble) || ($bubble != 'all')) && (empty($bubble) || ($response->get_request_method() == 'GET')); } if (!$ok) { $response->set_code(404); } else { $systemsetting = null; $contextsettings = lti_get_tool_settings($id, $contextid); if (!empty($bubble)) { $systemsetting = new systemsettings($this->get_service()); $systemsetting->params['tool_proxy_id'] = $productcode; if ($id >= 0) { $systemsetting->params['config_type'] = 'toolproxy'; } else { $systemsetting->params['config_type'] = 'tool'; } $systemsettings = lti_get_tool_settings($id); if ($bubble == 'distinct') { toolsettings::distinct_settings($systemsettings, $contextsettings, null); } } else { $systemsettings = null; } if ($response->get_request_method() == 'GET') { $json = ''; if ($simpleformat) { $response->set_content_type($this->formats[1]); $json .= "{"; } else { $response->set_content_type($this->formats[0]); $json .= "{\n \"@context\":\"http://purl.imsglobal.org/ctx/lti/v2/ToolSettings\",\n \"@graph\":[\n"; } $settings = toolsettings::settings_to_json($systemsettings, $simpleformat, 'ToolProxy', $systemsetting); $json .= $settings; $isfirst = strlen($settings) <= 0; $settings = toolsettings::settings_to_json($contextsettings, $simpleformat, 'ToolProxyBinding', $this); if ((strlen($settings) > 0) && !$isfirst) { $json .= ","; } $json .= $settings; if ($simpleformat) { $json .= "\n}"; } else { $json .= "\n ]\n}"; } $response->set_body($json); } else { // PUT. $settings = null; if ($response->get_content_type() == $this->formats[0]) { $json = json_decode($response->get_request_data()); $ok = !empty($json); if ($ok) { $ok = isset($json->{"@graph"}) && is_array($json->{"@graph"}) && (count($json->{"@graph"}) == 1) && ($json->{"@graph"}[0]->{"@type"} == 'ToolProxyBinding'); } if ($ok) { $settings = $json->{"@graph"}[0]->custom; unset($settings->{'@id'}); } } else { // Simple JSON. $json = json_decode($response->get_request_data(), true); $ok = !empty($json); if ($ok) { $ok = is_array($json); } if ($ok) { $settings = $json; } } if ($ok) { lti_set_tool_settings($settings, $id, $contextid); } else { $response->set_code(406); } } } } } /** * 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, '$ToolProxyBinding.custom.url') !== false) { if ($COURSE->format == 'site') { $this->params['context_type'] = 'Group'; } else { $this->params['context_type'] = 'CourseSection'; } $this->params['context_id'] = $COURSE->id; if (!empty($this->get_service()->get_tool_proxy())) { $this->params['vendor_code'] = $this->get_service()->get_tool_proxy()->vendorcode; $this->params['product_code'] = $this->get_service()->get_tool_proxy()->guid; } else { $this->params['vendor_code'] = 'tool'; $this->params['product_code'] = $this->get_service()->get_type()->id; } $value = str_replace('$ToolProxyBinding.custom.url', parent::get_endpoint(), $value); } return $value; } } toolsettings/classes/local/resources/linksettings.php 0000644 00000021525 15151264171 0017302 0 ustar 00 <?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 Context Settings resource * * @package ltiservice_toolsettings * @copyright 2014 Vital Source Technologies http://vitalsource.com * @author Stephen Vickers * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace ltiservice_toolsettings\local\resources; use ltiservice_toolsettings\local\service\toolsettings; defined('MOODLE_INTERNAL') || die(); /** * A resource implementing the Context-level (ToolProxyBinding) Settings. * * @package ltiservice_toolsettings * @since Moodle 2.8 * @copyright 2014 Vital Source Technologies http://vitalsource.com * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class linksettings extends \mod_lti\local\ltiservice\resource_base { /** * Class constructor. * * @param \mod_lti\local\ltiservice\service_base $service Service instance */ public function __construct($service) { parent::__construct($service); $this->id = 'LtiLinkSettings'; $this->template = '/links/{link_id}(/custom)'; $this->variables[] = 'LtiLink.custom.url'; $this->formats[] = 'application/vnd.ims.lti.v2.toolsettings+json'; $this->formats[] = 'application/vnd.ims.lti.v2.toolsettings.simple+json'; $this->methods[] = 'GET'; $this->methods[] = 'PUT'; } /** * Execute the request for this resource. * * @param \mod_lti\local\ltiservice\response $response Response object for this request. */ public function execute($response) { global $DB, $COURSE; $params = $this->parse_template(); $linkid = $params['link_id']; $bubble = optional_param('bubble', '', PARAM_ALPHA); $contenttype = $response->get_accept(); $simpleformat = !empty($contenttype) && ($contenttype == $this->formats[1]); $ok = (empty($bubble) || ((($bubble == 'distinct') || ($bubble == 'all')))) && (!$simpleformat || empty($bubble) || ($bubble != 'all')) && (empty($bubble) || ($response->get_request_method() == self::HTTP_GET)); if (!$ok) { $response->set_code(406); } $systemsetting = null; $contextsetting = null; $lti = null; if ($ok) { $ok = !empty($linkid); if ($ok) { $lti = $DB->get_record('lti', array('id' => $linkid), 'course,typeid', MUST_EXIST); $ok = $this->check_tool($lti->typeid, $response->get_request_data(), array(toolsettings::SCOPE_TOOL_SETTINGS)); } if (!$ok) { $response->set_code(401); } } if ($ok) { if (!empty($this->get_service()->get_tool_proxy())) { $id = $this->get_service()->get_tool_proxy()->id; } else { $id = -$this->get_service()->get_type()->id; } if ($response->get_request_method() == 'GET') { $linksettings = lti_get_tool_settings($id, $lti->course, $linkid); if (!empty($bubble)) { $contextsetting = new contextsettings($this->get_service()); if ($COURSE == 'site') { $contextsetting->params['context_type'] = 'Group'; } else { $contextsetting->params['context_type'] = 'CourseSection'; } $contextsetting->params['context_id'] = $lti->course; if ($id >= 0) { $contextsetting->params['vendor_code'] = $this->get_service()->get_tool_proxy()->vendorcode; } else { $contextsetting->params['vendor_code'] = 'tool'; } $contextsetting->params['product_code'] = abs($id); $contextsettings = lti_get_tool_settings($id, $lti->course); $systemsetting = new systemsettings($this->get_service()); if ($id >= 0) { $systemsetting->params['config_type'] = 'toolproxy'; } else { $systemsetting->params['config_type'] = 'tool'; } $systemsetting->params['tool_proxy_id'] = abs($id); $systemsettings = lti_get_tool_settings($id); if ($bubble == 'distinct') { toolsettings::distinct_settings($systemsettings, $contextsettings, $linksettings); } } else { $contextsettings = null; $systemsettings = null; } $json = ''; if ($simpleformat) { $response->set_content_type($this->formats[1]); $json .= "{"; } else { $response->set_content_type($this->formats[0]); $json .= "{\n \"@context\":\"http://purl.imsglobal.org/ctx/lti/v2/ToolSettings\",\n \"@graph\":[\n"; } $settings = toolsettings::settings_to_json($systemsettings, $simpleformat, 'ToolProxy', $systemsetting); $json .= $settings; $isfirst = strlen($settings) <= 0; $settings = toolsettings::settings_to_json($contextsettings, $simpleformat, 'ToolProxyBinding', $contextsetting); if (strlen($settings) > 0) { if (!$isfirst) { $json .= ","; if (!$simpleformat) { $json .= "\n"; } } $isfirst = false; } $json .= $settings; $settings = toolsettings::settings_to_json($linksettings, $simpleformat, 'LtiLink', $this); if ((strlen($settings) > 0) && !$isfirst) { $json .= ","; if (!$simpleformat) { $json .= "\n"; } } $json .= $settings; if ($simpleformat) { $json .= "\n}"; } else { $json .= "\n ]\n}"; } $response->set_body($json); } else { // PUT. $settings = null; if ($response->get_content_type() == $this->formats[0]) { $json = json_decode($response->get_request_data()); $ok = !empty($json); if ($ok) { $ok = isset($json->{"@graph"}) && is_array($json->{"@graph"}) && (count($json->{"@graph"}) == 1) && ($json->{"@graph"}[0]->{"@type"} == 'LtiLink'); } if ($ok) { $settings = $json->{"@graph"}[0]->custom; unset($settings->{'@id'}); } } else { // Simple JSON. $json = json_decode($response->get_request_data(), true); $ok = !empty($json); if ($ok) { $ok = is_array($json); } if ($ok) { $settings = $json; } } if ($ok) { lti_set_tool_settings($settings, $id, $lti->course, $linkid); } else { $response->set_code(406); } } } } /** * Parse a value for custom parameter substitution variables. * * @param string $value String to be parsed * * @return string */ 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; } } toolsettings/classes/local/service/toolsettings.php 0000644 00000016420 15151264172 0016747 0 ustar 00 <?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 Tool Settings service * * @package ltiservice_toolsettings * @copyright 2014 Vital Source Technologies http://vitalsource.com * @author Stephen Vickers * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace ltiservice_toolsettings\local\service; defined('MOODLE_INTERNAL') || die(); /** * A service implementing Tool Settings. * * @package ltiservice_toolsettings * @since Moodle 2.8 * @copyright 2014 Vital Source Technologies http://vitalsource.com * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class toolsettings extends \mod_lti\local\ltiservice\service_base { /** Scope for managing tool settings */ const SCOPE_TOOL_SETTINGS = 'https://purl.imsglobal.org/spec/lti-ts/scope/toolsetting'; /** * Class constructor. */ public function __construct() { parent::__construct(); $this->id = 'toolsettings'; $this->name = 'Tool Settings'; } /** * Get the resources for this service. * * @return array */ public function get_resources() { if (empty($this->resources)) { $this->resources = array(); $this->resources[] = new \ltiservice_toolsettings\local\resources\systemsettings($this); $this->resources[] = new \ltiservice_toolsettings\local\resources\contextsettings($this); $this->resources[] = new \ltiservice_toolsettings\local\resources\linksettings($this); } return $this->resources; } /** * Get the scope(s) permitted for the tool relevant to this service. * * @return array */ public function get_permitted_scopes() { $scopes = array(); $ok = !empty($this->get_type()); if ($ok && isset($this->get_typeconfig()[$this->get_component_id()]) && ($this->get_typeconfig()[$this->get_component_id()] == parent::SERVICE_ENABLED)) { $scopes[] = self::SCOPE_TOOL_SETTINGS; } return $scopes; } /** * Get the scope(s) defined this service. * * @return array */ public function get_scopes() { return [self::SCOPE_TOOL_SETTINGS]; } /** * Get the distinct settings from each level by removing any duplicates from higher levels. * * @param array $systemsettings System level settings * @param array $contextsettings Context level settings * @param array $linksettings Link level settings */ public static function distinct_settings(&$systemsettings, &$contextsettings, $linksettings) { if (!empty($systemsettings)) { foreach ($systemsettings as $key => $value) { if ((!empty($contextsettings) && array_key_exists($key, $contextsettings)) || (!empty($linksettings) && array_key_exists($key, $linksettings))) { unset($systemsettings[$key]); } } } if (!empty($contextsettings)) { foreach ($contextsettings as $key => $value) { if (!empty($linksettings) && array_key_exists($key, $linksettings)) { unset($contextsettings[$key]); } } } } /** * Get the JSON representation of the settings. * * @param array $settings Settings * @param boolean $simpleformat <code>true</code> if simple JSON is to be returned * @param string $type JSON-LD type * @param \mod_lti\local\ltiservice\resource_base $resource Resource handling the request * * @return string */ public static function settings_to_json($settings, $simpleformat, $type, $resource) { $json = ''; if (!empty($resource)) { $indent = ''; if (!$simpleformat) { $json .= " {\n \"@type\":\"{$type}\",\n"; $json .= " \"@id\":\"{$resource->get_endpoint()}\",\n"; $json .= " \"custom\":{"; $indent = ' '; } $isfirst = true; if (!empty($settings)) { foreach ($settings as $key => $value) { if (!$isfirst) { $json .= ','; } else { $isfirst = false; } $json .= "\n{$indent} \"{$key}\":\"{$value}\""; } } if (!$simpleformat) { $json .= "\n{$indent}}\n }"; } } return $json; } /** * Adds form elements for membership add/edit page. * * @param \MoodleQuickForm $mform */ public function get_configuration_options(&$mform) { $elementname = $this->get_component_id(); $options = [ get_string('notallow', $this->get_component_id()), get_string('allow', $this->get_component_id()) ]; $mform->addElement('select', $elementname, get_string($elementname, $this->get_component_id()), $options); $mform->setType($elementname, 'int'); $mform->setDefault($elementname, 0); $mform->addHelpButton($elementname, $elementname, $this->get_component_id()); } /** * 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->{$this->get_component_id()})) { if ($tool->{$this->get_component_id()} == self::SERVICE_ENABLED && $this->is_used_in_context($typeid, $courseid)) { $launchparameters['system_setting_url'] = '$ToolProxy.custom.url'; $launchparameters['context_setting_url'] = '$ToolProxyBinding.custom.url'; if ($messagetype === 'basic-lti-launch-request') { $launchparameters['link_setting_url'] = '$LtiLink.custom.url'; } } } return $launchparameters; } } toolsettings/lang/en/ltiservice_toolsettings.php 0000644 00000002527 15151264172 0016337 0 ustar 00 <?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_toolsettings', language 'en' * * @package ltiservice_toolsettings * @copyright 2014 Vital Source Technologies http://vitalsource.com * @author Stephen Vickers * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['allow'] = 'Use this service'; $string['ltiservice_toolsettings'] = 'Tool Settings'; $string['ltiservice_toolsettings_help'] = 'Allow the tool to save and retrieve setting values.'; $string['notallow'] = 'Do not use this service'; $string['pluginname'] = 'Tool Settings Service'; $string['privacy:metadata'] = 'The Tool Settings Service plugin does not store any personal data.'; memberships/tests/privacy/provider_test.php 0000644 00000003350 15151264172 0015310 0 ustar 00 <?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/>. /** * Unit tests for ltiservice_memberships privacy provider. * * @package ltiservice_memberships * @copyright 2018 Mark Nelson <markn@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace ltiservice_memberships\privacy; defined('MOODLE_INTERNAL') || die(); use core_privacy\tests\provider_testcase; /** * Unit tests for ltiservice_memberships privacy provider. * * @copyright 2018 Mark Nelson <markn@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider_test extends provider_testcase { /** * Basic setup for these tests. */ public function setUp(): void { $this->resetAfterTest(true); } /** * Test getting the context for the user ID related to this plugin. */ public function test_get_contexts_for_userid() { $user = $this->getDataGenerator()->create_user(); $contextlist = \ltiservice_memberships\privacy\provider::get_contexts_for_userid($user->id); $this->assertEmpty($contextlist); } } memberships/version.php 0000644 00000002150 15151264172 0011262 0 ustar 00 <?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_memberships service. * * @package ltiservice_memberships * @copyright 2015 Vital Source Technologies http://vitalsource.com * @author Stephen Vickers * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2022112800; $plugin->requires = 2022111800; $plugin->component = 'ltiservice_memberships'; memberships/classes/privacy/provider.php 0000644 00000010107 15151264172 0014542 0 ustar 00 <?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/>. /** * Privacy Subsystem implementation for ltiservice_memberships. * * @package ltiservice_memberships * @copyright 2018 Mark Nelson <markn@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace ltiservice_memberships\privacy; use \core_privacy\local\metadata\collection; use \core_privacy\local\request\contextlist; use \core_privacy\local\request\approved_contextlist; use \core_privacy\local\request\userlist; use \core_privacy\local\request\approved_userlist; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for ltiservice_memberships. * * @copyright 2018 Mark Nelson <markn@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\provider, \core_privacy\local\request\core_userlist_provider, \core_privacy\local\request\plugin\provider { /** * Returns meta data about this system. * * @param collection $collection The initialised collection to add items to. * @return collection A listing of user data stored through this system. */ public static function get_metadata(collection $collection) : collection { $collection->link_external_location('External LTI provider.', [ 'userid' => 'privacy:metadata:userid', 'useridnumber' => 'privacy:metadata:useridnumber', 'fullname' => 'privacy:metadata:fullname', 'firstname' => 'privacy:metadata:firstname', 'lastname' => 'privacy:metadata:lastname', 'email' => 'privacy:metadata:email' ], 'privacy:metadata:externalpurpose'); return $collection; } /** * Get the list of contexts that contain user information for the specified user. * * @param int $userid The user to search. * @return contextlist The contextlist containing the list of contexts used in this plugin. */ public static function get_contexts_for_userid(int $userid) : contextlist { return new contextlist(); } /** * Get the list of users who have data within a context. * * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. */ public static function get_users_in_context(userlist $userlist) { } /** * Export all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts to export information for. */ public static function export_user_data(approved_contextlist $contextlist) { } /** * Delete all user data which matches the specified context. * * @param \context $context A user context. */ public static function delete_data_for_all_users_in_context(\context $context) { } /** * Delete multiple users within a single context. * * @param approved_userlist $userlist The approved context and user information to delete information for. */ public static function delete_data_for_users(approved_userlist $userlist) { } /** * Delete all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. */ public static function delete_data_for_user(approved_contextlist $contextlist) { } } memberships/classes/local/resources/linkmemberships.php 0000644 00000012252 15151264172 0017536 0 ustar 00 <?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 Link Memberships resource * * @package ltiservice_memberships * @copyright 2015 Vital Source Technologies http://vitalsource.com * @author Stephen Vickers * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace ltiservice_memberships\local\resources; use mod_lti\local\ltiservice\resource_base; use ltiservice_memberships\local\service\memberships; 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 resource_base { /** * Class constructor. * * @param \ltiservice_memberships\local\service\memberships $service Service instance */ public function __construct($service) { parent::__construct($service); $this->id = 'LtiLinkMemberships'; $this->template = '/links/{link_id}/memberships'; $this->variables[] = 'LtiLink.memberships.url'; $this->formats[] = 'application/vnd.ims.lis.v2.membershipcontainer+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 $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; } if (empty($linkid)) { $response->set_code(404); return; } if (!($lti = $DB->get_record('lti', array('id' => $linkid), 'id,course,typeid,servicesalt', IGNORE_MISSING))) { $response->set_code(404); return; } if (!$this->check_tool($lti->typeid, $response->get_request_data(), array(memberships::SCOPE_MEMBERSHIPS_READ))) { $response->set_code(403); return; } if (!($course = $DB->get_record('course', array('id' => $lti->course), 'id', IGNORE_MISSING))) { $response->set_code(404); return; } if (!($context = \context_course::instance($lti->course))) { $response->set_code(404); return; } $modinfo = get_fast_modinfo($course); $cm = get_coursemodule_from_instance('lti', $linkid, $lti->course, false, MUST_EXIST); $cm = $modinfo->get_cm($cm->id); $info = new info_module($cm); if ($info->is_available_for_all()) { $info = null; } $json = $this->get_service()->get_members_json($this, $context, $course, $role, $limitfrom, $limitnum, $lti, $info, $response); $response->set_content_type($this->formats[0]); $response->set_body($json); } /** * 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(); } } /** * Parse a value for custom parameter substitution variables. * * @param string $value String to be parsed * * @return string */ public function parse_value($value) { if (strpos($value, '$LtiLink.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; } } memberships/classes/local/resources/contextmemberships.php 0000644 00000012566 15151264172 0020275 0 ustar 00 <?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 Context Memberships resource * * @package ltiservice_memberships * @copyright 2015 Vital Source Technologies http://vitalsource.com * @author Stephen Vickers * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace ltiservice_memberships\local\resources; use mod_lti\local\ltiservice\resource_base; use ltiservice_memberships\local\service\memberships; use core_availability\info_module; defined('MOODLE_INTERNAL') || die(); /** * A resource implementing Context Memberships. * * @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 contextmemberships extends resource_base { /** * Class constructor. * * @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/{tool_code}/memberships'; $this->variables[] = 'ToolProxyBinding.memberships.url'; $this->formats[] = 'application/vnd.ims.lis.v2.membershipcontainer+json'; $this->formats[] = 'application/vnd.ims.lti-nrps.v2.membershipcontainer+json'; $this->methods[] = self::HTTP_GET; } /** * 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(); $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->check_tool($params['tool_code'], $response->get_request_data(), array(memberships::SCOPE_MEMBERSHIPS_READ))) { throw new \Exception(null, 401); } if (!($course = $DB->get_record('course', array('id' => $params['context_id']), 'id,shortname,fullname', IGNORE_MISSING))) { throw new \Exception("Not Found: Course {$params['context_id']} doesn't exist", 404); } if (!$this->get_service()->is_allowed_in_context($params['tool_code'], $course->id)) { throw new \Exception(null, 404); } if (!($context = \context_course::instance($course->id))) { throw new \Exception("Not Found: Course instance {$course->id} doesn't exist", 404); } if (!empty($linkid)) { if (!($lti = $DB->get_record('lti', array('id' => $linkid), 'id,course,typeid,servicesalt', IGNORE_MISSING))) { throw new \Exception("Not Found: LTI link {$linkid} doesn't exist", 404); } $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; } } $json = $this->get_service()->get_members_json($this, $context, $course, $role, $limitfrom, $limitnum, $lti, $modinfo, $response); $response->set_body($json); } catch (\Exception $e) { $response->set_code($e->getCode()); $response->set_reason($e->getMessage()); } } /** * Parse a value for custom parameter substitution variables. * * @param string $value String to be parsed * * @return string */ 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; if ($tool = $this->get_service()->get_type()) { $this->params['tool_code'] = $tool->id; } $value = str_replace('$ToolProxyBinding.memberships.url', parent::get_endpoint(), $value); } return $value; } } memberships/classes/local/service/memberships.php 0000644 00000063655 15151264172 0016323 0 ustar 00 <?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 Memberships service * * @package ltiservice_memberships * @copyright 2015 Vital Source Technologies http://vitalsource.com * @author Stephen Vickers * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace ltiservice_memberships\local\service; defined('MOODLE_INTERNAL') || die(); /** * A service implementing Memberships. * * @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 memberships extends \mod_lti\local\ltiservice\service_base { /** Default prefix for context-level roles */ const CONTEXT_ROLE_PREFIX = 'http://purl.imsglobal.org/vocab/lis/v2/membership#'; /** Context-level role for Instructor */ const CONTEXT_ROLE_INSTRUCTOR = 'http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor'; /** Context-level role for Learner */ const CONTEXT_ROLE_LEARNER = 'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'; /** Capability used to identify Instructors */ const INSTRUCTOR_CAPABILITY = 'moodle/course:manageactivities'; /** 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); /** Scope for reading membership data */ const SCOPE_MEMBERSHIPS_READ = 'https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly'; /** * Class constructor. */ public function __construct() { parent::__construct(); $this->id = 'memberships'; $this->name = get_string($this->get_component_id(), $this->get_component_id()); } /** * Get the resources for this service. * * @return array */ public function get_resources() { if (empty($this->resources)) { $this->resources = array(); $this->resources[] = new \ltiservice_memberships\local\resources\contextmemberships($this); $this->resources[] = new \ltiservice_memberships\local\resources\linkmemberships($this); } return $this->resources; } /** * Get the scope(s) permitted for the tool relevant to this service. * * @return array */ public function get_permitted_scopes() { $scopes = array(); $ok = !empty($this->get_type()); if ($ok && isset($this->get_typeconfig()[$this->get_component_id()]) && ($this->get_typeconfig()[$this->get_component_id()] == parent::SERVICE_ENABLED)) { $scopes[] = self::SCOPE_MEMBERSHIPS_READ; } return $scopes; } /** * Get the scope(s) defined by this service. * * @return array */ public function get_scopes() { return [self::SCOPE_MEMBERSHIPS_READ]; } /** * Get the JSON for members. * * @param \mod_lti\local\ltiservice\resource_base $resource Resource handling the request * @param \context_course $context Course context * @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 \core_availability\info_module $info Conditional availability information * for LTI instance (null if context-level request) * * @return string * @deprecated since Moodle 3.7 MDL-62599 - please do not use this function any more. * @see memberships::get_members_json($resource, $context, $course, $role, $limitfrom, $limitnum, $lti, $info, $response) */ public static function get_users_json($resource, $context, $contextid, $tool, $role, $limitfrom, $limitnum, $lti, $info) { global $DB; debugging('get_users_json() has been deprecated, ' . 'please use memberships::get_members_json() instead.', DEBUG_DEVELOPER); $course = $DB->get_record('course', array('id' => $contextid), 'id,shortname,fullname', IGNORE_MISSING); $memberships = new memberships(); $memberships->check_tool($tool->id, null, array(self::SCOPE_MEMBERSHIPS_READ)); $response = new \mod_lti\local\ltiservice\response(); $json = $memberships->get_members_json($resource, $context, $course, $role, $limitfrom, $limitnum, $lti, $info, $response); return $json; } /** * Get the JSON for members. * * @param \mod_lti\local\ltiservice\resource_base $resource Resource handling the request * @param \context_course $context Course context * @param \course $course Course * @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 \core_availability\info_module $info Conditional availability information * for LTI instance (null if context-level request) * @param \mod_lti\local\ltiservice\response $response Response object for the request * * @return string */ public function get_members_json($resource, $context, $course, $role, $limitfrom, $limitnum, $lti, $info, $response) { $withcapability = ''; $exclude = array(); if (!empty($role)) { if ((strpos($role, 'http://') !== 0) && (strpos($role, 'https://') !== 0)) { $role = self::CONTEXT_ROLE_PREFIX . $role; } if ($role === self::CONTEXT_ROLE_INSTRUCTOR) { $withcapability = self::INSTRUCTOR_CAPABILITY; } else if ($role === self::CONTEXT_ROLE_LEARNER) { $exclude = array_keys(get_enrolled_users($context, self::INSTRUCTOR_CAPABILITY, 0, 'u.id', null, null, null, true)); } } $users = get_enrolled_users($context, $withcapability, 0, 'u.*', null, 0, 0, true); if (($response->get_accept() === 'application/vnd.ims.lti-nrps.v2.membershipcontainer+json') || (($response->get_accept() !== 'application/vnd.ims.lis.v2.membershipcontainer+json') && ($this->get_type()->ltiversion === LTI_VERSION_1P3))) { $json = $this->users_to_json($resource, $users, $course, $exclude, $limitfrom, $limitnum, $lti, $info, $response); } else { $json = $this->users_to_jsonld($resource, $users, $course->id, $exclude, $limitfrom, $limitnum, $lti, $info, $response); } return $json; } /** * Get the JSON-LD representation of the users. * * Note that when a limit is set and the exclude array is not empty, then the number of memberships * returned may be less than the limit. * * @param \mod_lti\local\ltiservice\resource_base $resource Resource handling the request * @param array $users Array of user records * @param string $contextid Course ID * @param array $exclude Array of user records to be excluded from the response * @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 \core_availability\info_module $info Conditional availability information * for LTI instance (null if context-level request) * @param \mod_lti\local\ltiservice\response $response Response object for the request * * @return string */ private function users_to_jsonld($resource, $users, $contextid, $exclude, $limitfrom, $limitnum, $lti, $info, $response) { global $DB; $tool = $this->get_type(); $toolconfig = $this->get_typeconfig(); $arrusers = [ '@context' => 'http://purl.imsglobal.org/ctx/lis/v2/MembershipContainer', '@type' => 'Page', '@id' => $resource->get_endpoint(), ]; $arrusers['pageOf'] = [ '@type' => 'LISMembershipContainer', 'membershipSubject' => [ '@type' => 'Context', 'contextId' => $contextid, 'membership' => [] ] ]; $enabledcapabilities = lti_get_enabled_capabilities($tool); $islti2 = $tool->toolproxyid > 0; $n = 0; $more = false; foreach ($users as $user) { if (in_array($user->id, $exclude)) { continue; } if (!empty($info) && !$info->is_user_visible($info->get_course_module(), $user->id)) { continue; } $n++; if ($limitnum > 0) { if ($n <= $limitfrom) { continue; } if (count($arrusers['pageOf']['membershipSubject']['membership']) >= $limitnum) { $more = true; break; } } $member = new \stdClass(); $member->{"@type" } = 'LISPerson'; $membership = new \stdClass(); $membership->status = 'Active'; $membership->role = explode(',', lti_get_ims_role($user->id, null, $contextid, true)); $instanceconfig = null; if (!is_null($lti)) { $instanceconfig = lti_get_type_config_from_instance($lti->id); } $isallowedlticonfig = self::is_allowed_field_set($toolconfig, $instanceconfig, ['name' => 'sendname', 'email' => '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)], 'User.username' => ['type' => 'name', 'member.field' => 'ext_user_username', 'source.value' => format_string($user->username)] ]; 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)); // Not per specification but added to comply with earlier version of the service. $member->resultSourcedId = $message->lis_result_sourcedid; } $membership->message = [$message]; } foreach ($includedcapabilities as $capabilityname => $capability) { if ($islti2) { if (in_array($capabilityname, $enabledcapabilities)) { $member->{$capability['member.field']} = $capability['source.value']; } } 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; $arrusers['pageOf']['membershipSubject']['membership'][] = $membership; } if ($more) { $nextlimitfrom = $limitfrom + $limitnum; $nextpage = "{$resource->get_endpoint()}?limit={$limitnum}&from={$nextlimitfrom}"; if (!is_null($lti)) { $nextpage .= "&rlid={$lti->id}"; } $arrusers['nextPage'] = $nextpage; } $response->set_content_type('application/vnd.ims.lis.v2.membershipcontainer+json'); return json_encode($arrusers); } /** * Get the NRP service JSON representation of the users. * * Note that when a limit is set and the exclude array is not empty, then the number of memberships * returned may be less than the limit. * * @param \mod_lti\local\ltiservice\resource_base $resource Resource handling the request * @param array $users Array of user records * @param \course $course Course * @param array $exclude Array of user records to be excluded from the response * @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 \core_availability\info_module $info Conditional availability information for LTI instance * @param \mod_lti\local\ltiservice\response $response Response object for the request * * @return string */ private function users_to_json($resource, $users, $course, $exclude, $limitfrom, $limitnum, $lti, $info, $response) { global $DB, $CFG; $tool = $this->get_type(); $toolconfig = $this->get_typeconfig(); $context = new \stdClass(); $context->id = $course->id; $context->label = trim(html_to_text($course->shortname, 0)); $context->title = trim(html_to_text($course->fullname, 0)); $arrusers = [ 'id' => $resource->get_endpoint(), 'context' => $context, 'members' => [] ]; $islti2 = $tool->toolproxyid > 0; $n = 0; $more = false; foreach ($users as $user) { if (in_array($user->id, $exclude)) { continue; } if (!empty($info) && !$info->is_user_visible($info->get_course_module(), $user->id)) { continue; } $n++; if ($limitnum > 0) { if ($n <= $limitfrom) { continue; } if (count($arrusers['members']) >= $limitnum) { $more = true; break; } } $member = new \stdClass(); $member->status = 'Active'; $member->roles = explode(',', lti_get_ims_role($user->id, null, $course->id, true)); $instanceconfig = null; if (!is_null($lti)) { $instanceconfig = lti_get_type_config_from_instance($lti->id); } if (!$islti2) { $isallowedlticonfig = self::is_allowed_field_set($toolconfig, $instanceconfig, ['name' => 'sendname', 'givenname' => 'sendname', 'familyname' => 'sendname', 'email' => 'sendemailaddr']); } else { $isallowedlticonfig = self::is_allowed_capability_set($tool, ['name' => 'Person.name.full', 'givenname' => 'Person.name.given', 'familyname' => 'Person.name.family', 'email' => 'Person.email.primary']); } $includedcapabilities = [ 'User.id' => ['type' => 'id', 'member.field' => 'user_id', 'source.value' => $user->id], 'Person.sourcedId' => ['type' => 'id', 'member.field' => 'lis_person_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' => 'givenname', 'member.field' => 'given_name', 'source.value' => format_string($user->firstname)], 'Person.name.family' => ['type' => 'familyname', 'member.field' => 'family_name', 'source.value' => format_string($user->lastname)], 'Person.email.primary' => ['type' => 'email', 'member.field' => 'email', 'source.value' => format_string($user->email)], 'User.username' => ['type' => 'name', 'member.field' => 'ext_user_username', 'source.value' => format_string($user->username)], ]; if (!is_null($lti)) { $message = new \stdClass(); $message->{'https://purl.imsglobal.org/spec/lti/claim/message_type'} = 'LtiResourceLinkRequest'; $conditions = array('courseid' => $course->id, 'itemtype' => 'mod', 'itemmodule' => 'lti', 'iteminstance' => $lti->id); if (!empty($lti->servicesalt) && $DB->record_exists('grade_items', $conditions)) { $basicoutcome = new \stdClass(); $basicoutcome->lis_result_sourcedid = json_encode(lti_build_sourcedid($lti->id, $user->id, $lti->servicesalt, $lti->typeid)); // Add outcome service URL. $serviceurl = new \moodle_url('/mod/lti/service.php'); $serviceurl = $serviceurl->out(); $forcessl = false; if (!empty($CFG->mod_lti_forcessl)) { $forcessl = true; } if ((isset($toolconfig['forcessl']) && ($toolconfig['forcessl'] == '1')) or $forcessl) { $serviceurl = lti_ensure_url_is_https($serviceurl); } $basicoutcome->lis_outcome_service_url = $serviceurl; $message->{'https://purl.imsglobal.org/spec/lti-bo/claim/basicoutcome'} = $basicoutcome; } $member->message = [$message]; } foreach ($includedcapabilities as $capabilityname => $capability) { if (($capability['type'] === 'id') || $isallowedlticonfig[$capability['type']]) { $member->{$capability['member.field']} = $capability['source.value']; } } $arrusers['members'][] = $member; } if ($more) { $nextlimitfrom = $limitfrom + $limitnum; $nextpage = "{$resource->get_endpoint()}?limit={$limitnum}&from={$nextlimitfrom}"; if (!is_null($lti)) { $nextpage .= "&rlid={$lti->id}"; } $response->add_additional_header("Link: <{$nextpage}>; rel=\"next\""); } $response->set_content_type('application/vnd.ims.lti-nrps.v2.membershipcontainer+json'); return json_encode($arrusers); } /** * Determines whether a user attribute may be used as part of LTI membership * @param array $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 = isset($toolconfig[$field]) && (self::ALWAYS_INCLUDE_FIELD == $toolconfig[$field]); if (!$allowed && isset($toolconfig[$field]) && (self::DELEGATE_TO_INSTRUCTOR == $toolconfig[$field]) && !is_null($instanceconfig)) { $allowed = isset($instanceconfig->{"lti_{$field}"}) && ($instanceconfig->{"lti_{$field}"} == self::INSTRUCTOR_INCLUDED); } $isallowedstate[$key] = $allowed; } return $isallowedstate; } /** * Adds form elements for membership add/edit page. * * @param \MoodleQuickForm $mform */ public function get_configuration_options(&$mform) { $elementname = $this->get_component_id(); $options = [ get_string('notallow', $this->get_component_id()), get_string('allow', $this->get_component_id()) ]; $mform->addElement('select', $elementname, get_string($elementname, $this->get_component_id()), $options); $mform->setType($elementname, 'int'); $mform->setDefault($elementname, 0); $mform->addHelpButton($elementname, $elementname, $this->get_component_id()); } /** * 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->{$this->get_component_id()})) { if ($tool->{$this->get_component_id()} == parent::SERVICE_ENABLED && $this->is_used_in_context($typeid, $courseid)) { $launchparameters['context_memberships_url'] = '$ToolProxyBinding.memberships.url'; $launchparameters['context_memberships_v2_url'] = '$ToolProxyBinding.memberships.url'; $launchparameters['context_memberships_versions'] = '1.0,2.0'; } } return $launchparameters; } /** * Return an array of key/claim mapping allowing LTI 1.1 custom parameters * to be transformed to LTI 1.3 claims. * * @return array Key/value pairs of params to claim mapping. */ public function get_jwt_claim_mappings(): array { return [ 'custom_context_memberships_v2_url' => [ 'suffix' => 'nrps', 'group' => 'namesroleservice', 'claim' => 'context_memberships_url', 'isarray' => false ], 'custom_context_memberships_versions' => [ 'suffix' => 'nrps', 'group' => 'namesroleservice', 'claim' => 'service_versions', 'isarray' => true ] ]; } } memberships/lang/en/ltiservice_memberships.php 0000644 00000004445 15151264172 0015700 0 ustar 00 <?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_memberships', language 'en' * * @package ltiservice_memberships * @copyright 2015 Vital Source Technologies http://vitalsource.com * @author Stephen Vickers * @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 Names and Role Provisioning'; $string['ltiservice_memberships_help'] = 'Allow the tool to retrieve members\' info from the course using the IMS LTI Names and Role Provisioning Service. The privacy settings will apply. For course-level requests these will be based on the tool configuration settings. If you wish to always send such details, do not delegate the choice to teachers. Link-level requests will always use the privacy settings which apply to the link.'; $string['notallow'] = 'Do not use this service'; $string['pluginname'] = 'Names and Role Provisioning LTI Service'; $string['privacy:metadata:email'] = 'The email of the user using the LTI consumer.'; $string['privacy:metadata:externalpurpose'] = 'This information is sent to an external LTI provider.'; $string['privacy:metadata:firstname'] = 'The firstname of the user using the LTI consumer.'; $string['privacy:metadata:fullname'] = 'The fullname of the user using the LTI consumer.'; $string['privacy:metadata:lastname'] = 'The lastname of the user using the LTI consumer.'; $string['privacy:metadata:userid'] = 'The ID of the user using the LTI consumer.'; $string['privacy:metadata:useridnumber'] = 'The ID number of the user using the LTI consumer'; toolproxy/version.php 0000644 00000002253 15151264172 0011027 0 ustar 00 <?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_toolproxy service. * * @package ltiservice_toolproxy * @copyright 2014 Vital Source Technologies http://vitalsource.com * @author Stephen Vickers * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2022112800; $plugin->requires = 2022111800; $plugin->component = 'ltiservice_toolproxy'; $plugin->dependencies = array( 'ltiservice_profile' => 2022111800 ); toolproxy/classes/privacy/provider.php 0000644 00000003014 15151264172 0014302 0 ustar 00 <?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/>. /** * Privacy Subsystem implementation for ltiservice_toolproxy. * * @package ltiservice_toolproxy * @copyright 2018 Mark Nelson <markn@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace ltiservice_toolproxy\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for ltiservice_toolproxy implementing null_provider. * * @copyright 2018 Mark Nelson <markn@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason() : string { return 'privacy:metadata'; } } toolproxy/classes/local/resources/toolproxy.php 0000644 00000032017 15151264172 0016163 0 ustar 00 <?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 Tool Proxy resource * * @package ltiservice_toolproxy * @copyright 2014 Vital Source Technologies http://vitalsource.com * @author Stephen Vickers * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace ltiservice_toolproxy\local\resources; defined('MOODLE_INTERNAL') || die(); require_once($CFG->dirroot . '/mod/lti/OAuth.php'); require_once($CFG->dirroot . '/mod/lti/TrivialStore.php'); // TODO: Switch to core oauthlib once implemented - MDL-30149. use moodle\mod\lti as lti; /** * A resource implementing the Tool Proxy. * * @package ltiservice_toolproxy * @since Moodle 2.8 * @copyright 2014 Vital Source Technologies http://vitalsource.com * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class toolproxy extends \mod_lti\local\ltiservice\resource_base { /** * Class constructor. * * @param ltiservice_toolproxy\local\resources\toolproxy $service Service instance */ public function __construct($service) { parent::__construct($service); $this->id = 'ToolProxy.collection'; $this->template = '/toolproxy'; $this->formats[] = 'application/vnd.ims.lti.v2.toolproxy+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) { $ok = $this->check_tool(null, $response->get_request_data()); $ok = $ok && ($this->get_service()->get_tool_proxy()); if ($ok) { $toolproxy = $this->get_service()->get_tool_proxy(); } if (!$ok) { $toolproxy = null; $response->set_code(401); } $tools = array(); // Ensure all required elements are present in the Tool Proxy. if ($ok) { $toolproxyjson = json_decode($response->get_request_data()); $ok = !empty($toolproxyjson); if (!$ok) { debugging('Tool proxy is not properly formed JSON'); } else { $ok = isset($toolproxyjson->tool_profile->product_instance->product_info->product_family->vendor->code); $ok = $ok && isset($toolproxyjson->security_contract->shared_secret); $ok = $ok && isset($toolproxyjson->tool_profile->resource_handler); if (!$ok) { debugging('One or more missing elements from tool proxy: vendor code, shared secret or resource handlers'); } } } // Check all capabilities requested were offered. if ($ok) { $offeredcapabilities = explode("\n", $toolproxy->capabilityoffered); $resources = $toolproxyjson->tool_profile->resource_handler; $errors = array(); foreach ($resources as $resource) { if (isset($resource->message)) { foreach ($resource->message as $message) { if (!in_array($message->message_type, $offeredcapabilities)) { $errors[] = $message->message_type; } else if (isset($resource->parameter)) { foreach ($message->parameter as $parameter) { if (isset($parameter->variable) && !in_array($parameter->variable, $offeredcapabilities)) { $errors[] = $parameter->variable; } } } } } } if (count($errors) > 0) { $ok = false; debugging('Tool proxy contains capabilities which were not offered: ' . implode(', ', $errors)); } } // Check all services requested were offered (only tool services currently supported). $requestsbasicoutcomes = false; if ($ok && isset($toolproxyjson->security_contract->tool_service)) { $contexts = lti_get_contexts($toolproxyjson); $profileservice = lti_get_service_by_name('profile'); $profileservice->set_tool_proxy($toolproxy); $context = $profileservice->get_service_path() . $profileservice->get_resources()[0]->get_path() . '#'; $offeredservices = explode("\n", $toolproxy->serviceoffered); $services = lti_get_services(); $tpservices = $toolproxyjson->security_contract->tool_service; $errors = array(); foreach ($tpservices as $service) { $fqid = lti_get_fqid($contexts, $service->service); $requestsbasicoutcomes = $requestsbasicoutcomes || (substr($fqid, -13) === 'Outcomes.LTI1'); if (substr($fqid, 0, strlen($context)) !== $context) { $errors[] = $service->service; } else { $id = explode('#', $fqid, 2); $aservice = lti_get_service_by_resource_id($services, $id[1]); $classname = explode('\\', get_class($aservice)); if (empty($aservice) || !in_array($classname[count($classname) - 1], $offeredservices)) { $errors[] = $service->service; } } } if (count($errors) > 0) { $ok = false; debugging('Tool proxy contains services which were not offered: ' . implode(', ', $errors)); } } // Extract all launchable tools from the resource handlers. if ($ok) { $resources = $toolproxyjson->tool_profile->resource_handler; $messagetypes = [ 'basic-lti-launch-request', 'ContentItemSelectionRequest', ]; foreach ($resources as $resource) { $launchable = false; $messages = array(); $tool = new \stdClass(); $iconinfo = null; if (is_array($resource->icon_info)) { $iconinfo = $resource->icon_info[0]; } else { $iconinfo = $resource->icon_info; } if (isset($iconinfo) && isset($iconinfo->default_location) && isset($iconinfo->default_location->path)) { $tool->iconpath = $iconinfo->default_location->path; } foreach ($resource->message as $message) { if (in_array($message->message_type, $messagetypes)) { $launchable = $launchable || ($message->message_type === 'basic-lti-launch-request'); $messages[$message->message_type] = $message; } } if (!$launchable) { continue; } $tool->name = $resource->resource_name->default_value; $tool->messages = $messages; $tools[] = $tool; } $ok = count($tools) > 0; if (!$ok) { debugging('No launchable messages found in tool proxy'); } } // Add tools and custom parameters. if ($ok) { $baseurl = ''; if (isset($toolproxyjson->tool_profile->base_url_choice[0]->default_base_url)) { $baseurl = $toolproxyjson->tool_profile->base_url_choice[0]->default_base_url; } $securebaseurl = ''; if (isset($toolproxyjson->tool_profile->base_url_choice[0]->secure_base_url)) { $securebaseurl = $toolproxyjson->tool_profile->base_url_choice[0]->secure_base_url; } foreach ($tools as $tool) { $messages = $tool->messages; $launchrequest = $messages['basic-lti-launch-request']; $config = new \stdClass(); $config->lti_toolurl = "{$baseurl}{$launchrequest->path}"; $config->lti_typename = $tool->name; $config->lti_coursevisible = 1; $config->lti_forcessl = 0; if (isset($messages['ContentItemSelectionRequest'])) { $contentitemrequest = $messages['ContentItemSelectionRequest']; $config->lti_contentitem = 1; if ($launchrequest->path !== $contentitemrequest->path) { $config->lti_toolurl_ContentItemSelectionRequest = $baseurl . $contentitemrequest->path; } $contentitemcapabilities = implode("\n", $contentitemrequest->enabled_capability); $config->lti_enabledcapability_ContentItemSelectionRequest = $contentitemcapabilities; $contentitemparams = self::lti_extract_parameters($contentitemrequest->parameter); $config->lti_parameter_ContentItemSelectionRequest = $contentitemparams; } $type = new \stdClass(); $type->state = LTI_TOOL_STATE_PENDING; $type->ltiversion = LTI_VERSION_2; $type->toolproxyid = $toolproxy->id; // Ensure gradebook column is created. if ($requestsbasicoutcomes && !in_array('BasicOutcome.url', $launchrequest->enabled_capability)) { $launchrequest->enabled_capability[] = 'BasicOutcome.url'; } if ($requestsbasicoutcomes && !in_array('BasicOutcome.sourcedId', $launchrequest->enabled_capability)) { $launchrequest->enabled_capability[] = 'BasicOutcome.sourcedId'; } $type->enabledcapability = implode("\n", $launchrequest->enabled_capability); $type->parameter = self::lti_extract_parameters($launchrequest->parameter); if (!empty($tool->iconpath)) { $type->icon = "{$baseurl}{$tool->iconpath}"; if (!empty($securebaseurl)) { $type->secureicon = "{$securebaseurl}{$tool->iconpath}"; } } $ok = $ok && (lti_add_type($type, $config) !== false); } if (isset($toolproxyjson->custom)) { lti_set_tool_settings($toolproxyjson->custom, $toolproxy->id); } } if (!empty($toolproxy)) { if ($ok) { // If all went OK accept the tool proxy. $toolproxy->state = LTI_TOOL_PROXY_STATE_ACCEPTED; $toolproxy->toolproxy = $response->get_request_data(); $toolproxy->secret = $toolproxyjson->security_contract->shared_secret; $toolproxy->vendorcode = $toolproxyjson->tool_profile->product_instance->product_info->product_family->vendor->code; $url = $this->get_endpoint(); $body = <<< EOD { "@context" : "http://purl.imsglobal.org/ctx/lti/v2/ToolProxyId", "@type" : "ToolProxy", "@id" : "{$url}", "tool_proxy_guid" : "{$toolproxy->guid}" } EOD; $response->set_code(201); $response->set_content_type('application/vnd.ims.lti.v2.toolproxy.id+json'); $response->set_body($body); } else { // Otherwise reject the tool proxy. $toolproxy->state = LTI_TOOL_PROXY_STATE_REJECTED; $response->set_code(400); } lti_update_tool_proxy($toolproxy); } else { $response->set_code(400); } } /** * Extracts the message parameters from the tool proxy entry * * @param array $parameters Parameter section of a message * * @return String containing parameters */ private static function lti_extract_parameters($parameters) { $params = array(); foreach ($parameters as $parameter) { if (isset($parameter->variable)) { $value = '$' . $parameter->variable; } else { $value = $parameter->fixed; if (strlen($value) > 0) { $first = substr($value, 0, 1); if (($first == '$') || ($first == '\\')) { $value = '\\' . $value; } } } $params[] = "{$parameter->name}={$value}"; } return implode("\n", $params); } } toolproxy/classes/local/service/toolproxy.php 0000644 00000003605 15151264172 0015612 0 ustar 00 <?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 Tool Proxy service * * @package ltiservice_toolproxy * @copyright 2014 Vital Source Technologies http://vitalsource.com * @author Stephen Vickers * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace ltiservice_toolproxy\local\service; defined('MOODLE_INTERNAL') || die(); /** * A service implementing the Tool Proxy. * * @package ltiservice_toolproxy * @since Moodle 2.8 * @copyright 2014 Vital Source Technologies http://vitalsource.com * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class toolproxy extends \mod_lti\local\ltiservice\service_base { /** * Class constructor. */ public function __construct() { parent::__construct(); $this->id = 'toolproxy'; $this->name = 'Tool Proxy'; } /** * Get the resources for this service. * * @return array */ public function get_resources() { if (empty($this->resources)) { $this->resources = array(); $this->resources[] = new \ltiservice_toolproxy\local\resources\toolproxy($this); } return $this->resources; } } toolproxy/lang/en/ltiservice_toolproxy.php 0000644 00000002134 15151264172 0015173 0 ustar 00 <?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_toolproxy', language 'en' * * @package ltiservice_toolproxy * @copyright 2014 Vital Source Technologies http://vitalsource.com * @author Stephen Vickers * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['pluginname'] = 'Tool Proxy Service'; $string['privacy:metadata'] = 'The Tool Proxy Service plugin does not store any personal data.'; readme.txt 0000644 00000002154 15151264172 0006550 0 ustar 00 // 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 all necessary code to initiate a tool registration process * * @package mod_lti * @copyright 2014 Vital Source Technologies http://vitalsource.com * @author Stephen Vickers * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ LTI Service plugins directory ============================== This directory contains LTI service plugins which are discoverable from the Tool Consumer Profile. gradebookservices/db/upgrade.php 0000644 00000013773 15151264172 0013011 0 ustar 00 <?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 is part of BasicLTI4Moodle // // BasicLTI4Moodle is an IMS BasicLTI (Basic Learning Tools for Interoperability) // consumer for Moodle 1.9 and Moodle 2.0. BasicLTI is a IMS Standard that allows web // based learning tools to be easily integrated in LMS as native ones. The IMS BasicLTI // specification is part of the IMS standard Common Cartridge 1.1 Sakai and other main LMS // are already supporting or going to support BasicLTI. This project Implements the consumer // for Moodle. Moodle is a Free Open source Learning Management System by Martin Dougiamas. // BasicLTI4Moodle is a project iniciated and leaded by Ludo(Marc Alier) and Jordi Piguillem // at the GESSI research group at UPC. // SimpleLTI consumer for Moodle is an implementation of the early specification of LTI // by Charles Severance (Dr Chuck) htp://dr-chuck.com , developed by Jordi Piguillem in a // Google Summer of Code 2008 project co-mentored by Charles Severance and Marc Alier. // // BasicLTI4Moodle is copyright 2009 by Marc Alier Forment, Jordi Piguillem and Nikolas Galanis // of the Universitat Politecnica de Catalunya http://www.upc.edu // Contact info: Marc Alier Forment granludo @ gmail.com or marc.alier @ upc.edu. /** * This file defines tasks performed by the plugin. * * @package ltiservice_gradebookservices * @copyright 2020 Cengage Learning http://www.cengage.com * @author Claude Vervoort * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die; /** * xmldb_ltiservice_gradebookservices_upgrade is the function that upgrades * the gradebook lti service subplugin database when is needed. * * This function is automatically called when version number in * version.php changes. * * @param int $oldversion New old version number. * * @return boolean */ function xmldb_ltiservice_gradebookservices_upgrade($oldversion) { global $CFG, $DB, $OUTPUT; $dbman = $DB->get_manager(); if ($oldversion < 2020042401) { // Define field typeid to be added to lti_tool_settings. $table = new xmldb_table('ltiservice_gradebookservices'); $field = new xmldb_field('resourceid', XMLDB_TYPE_CHAR, "512", null, null, null, null); // Conditionally launch add field typeid. if (!$dbman->field_exists($table, $field)) { $dbman->add_field($table, $field); } // Lti savepoint reached. upgrade_plugin_savepoint(true, 2020042401, 'ltiservice', 'gradebookservices'); } if ($oldversion < 2020042402) { // Now that we have added the new column let's migrate it' // Prior implementation was storing the resourceid under the grade item idnumber, so moving it to lti_gradebookservices. // We only care for mod/lti grade items as manual columns would already have a matching gradebookservices record. $DB->execute("INSERT INTO {ltiservice_gradebookservices} (gradeitemid, courseid, typeid, ltilinkid, resourceid, baseurl, toolproxyid) SELECT gi.id, courseid, lti.typeid, lti.id, gi.idnumber, t.baseurl, t.toolproxyid FROM {grade_items} gi JOIN {lti} lti ON lti.id=gi.iteminstance AND gi.itemtype='mod' AND gi.itemmodule='lti' JOIN {lti_types} t ON t.id = lti.typeid WHERE gi.id NOT IN ( SELECT gradeitemid FROM {ltiservice_gradebookservices} ) AND gi.idnumber IS NOT NULL AND gi.idnumber <> ''"); // Lti savepoint reached. upgrade_plugin_savepoint(true, 2020042402, 'ltiservice', 'gradebookservices'); } if ($oldversion < 2020042403) { // Here updating the resourceid of pre-existing lti_gradebookservices. $DB->execute("UPDATE {ltiservice_gradebookservices} SET resourceid = (SELECT idnumber FROM {grade_items} WHERE id=gradeitemid) WHERE gradeitemid in (SELECT id FROM {grade_items} WHERE ((itemtype='mod' AND itemmodule='lti') OR itemtype='manual') AND idnumber IS NOT NULL AND idnumber <> '') AND (resourceid is null OR resourceid = '')"); // Lti savepoint reached. upgrade_plugin_savepoint(true, 2020042403, 'ltiservice', 'gradebookservices'); } // Automatically generated Moodle v3.9.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.0.0 release upgrade line. // Put any upgrade step following this. if ($oldversion < 2022051900) { $table = new xmldb_table('ltiservice_gradebookservices'); $field = new xmldb_field('subreviewurl', XMLDB_TYPE_TEXT, null, null, null, null, null); if (!$dbman->field_exists($table, $field)) { $dbman->add_field($table, $field); } $field = new xmldb_field('subreviewparams', XMLDB_TYPE_TEXT, null, null, null, null, null); if (!$dbman->field_exists($table, $field)) { $dbman->add_field($table, $field); } // Lti savepoint reached. upgrade_plugin_savepoint(true, 2022051900, 'ltiservice', 'gradebookservices'); } // Automatically generated Moodle v4.1.0 release upgrade line. // Put any upgrade step following this. return true; } gradebookservices/db/tasks.php 0000644 00000002430 15151264172 0012473 0 ustar 00 <?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' => '*' ) ); gradebookservices/db/install.xml 0000644 00000004430 15151264172 0013027 0 ustar 00 <?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="resourceid" TYPE="char" LENGTH="512" NOTNULL="false" SEQUENCE="false" COMMENT="Resource id for the line item"/> <FIELD NAME="tag" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="Tag type specified for the line item"/> <FIELD NAME="subreviewurl" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Submission review URL"/> <FIELD NAME="subreviewparams" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Submission review custom params"/> </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> gradebookservices/tests/privacy/provider_test.php 0000644 00000003406 15151264172 0016475 0 ustar 00 <?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/>. /** * Unit tests for ltiservice_gradebookservices privacy provider. * * @package ltiservice_gradebookservices * @copyright 2018 Mark Nelson <markn@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace ltiservice_gradebookservices\privacy; defined('MOODLE_INTERNAL') || die(); use core_privacy\tests\provider_testcase; /** * Unit tests for ltiservice_gradebookservices privacy provider. * * @copyright 2018 Mark Nelson <markn@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider_test extends provider_testcase { /** * Basic setup for these tests. */ public function setUp(): void { $this->resetAfterTest(true); } /** * Test getting the context for the user ID related to this plugin. */ public function test_get_contexts_for_userid() { $user = $this->getDataGenerator()->create_user(); $contextlist = \ltiservice_gradebookservices\privacy\provider::get_contexts_for_userid($user->id); $this->assertEmpty($contextlist); } } gradebookservices/tests/task/cleanup_test.php 0000644 00000010516 15151264172 0015557 0 ustar 00 <?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/>. namespace ltiservice_gradebookservices\task; /** * 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 cleanup_test extends \advanced_testcase { /** * Test set up. * * This is executed before running any test in this file. */ public function setUp(): void { $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 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 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); } /** * Test the cleanup task with a manual grade item. */ public function test_cleanup_task_with_manual_item() { global $CFG, $DB; // This is required when running the unit test in isolation. require_once($CFG->libdir . '/gradelib.php'); // Create a manual grade item for a course. $course = $this->getDataGenerator()->create_course(); $params = [ 'courseid' => $course->id, 'itemtype' => 'manual' ]; $gradeitem = new \grade_item($params); $gradeitem->insert(); // Insert it into the 'ltiservice_gradebookservices' table. $data = new \stdClass(); $data->gradeitemid = $gradeitem->id; $data->courseid = $course->id; $DB->insert_record('ltiservice_gradebookservices', $data); // Run the task. $task = new cleanup_task(); $task->execute(); // Check it still exist. $this->assertEquals(1, $DB->count_records('ltiservice_gradebookservices')); // Delete the manual item. $gradeitem->delete(); // Run the task again. $task = new cleanup_task(); $task->execute(); // Check it has been removed. $this->assertEquals(0, $DB->count_records('ltiservice_gradebookservices')); } } gradebookservices/tests/lineitem_test.php 0000644 00000023276 15151264172 0015003 0 ustar 00 <?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/>. namespace ltiservice_gradebookservices; use ltiservice_gradebookservices\local\resources\lineitem; use ltiservice_gradebookservices\local\service\gradebookservices; /** * Unit tests for lti lineitem. * * @package ltiservice_gradebookservices * @category test * @copyright 2022 Cengage Group <claude.vervoort@cengage.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @coversDefaultClass \mod_lti\service\gradebookservices\local\resources\lineitem */ class lineitem_test extends \advanced_testcase { /** * @covers ::execute * * Test updating the line item with submission review. */ public function test_execute_put_nosubreview() { global $CFG; require_once($CFG->dirroot . '/mod/lti/locallib.php'); $this->resetAfterTest(); $this->setAdminUser(); $resourceid = 'test-resource-id'; $tag = 'tag'; $course = $this->getDataGenerator()->create_course(); $typeid = $this->create_type(); // The 1st item in the array is the items count. $gbservice = new gradebookservices(); $gbservice->set_type(lti_get_type($typeid)); $this->create_graded_lti($typeid, $course, $resourceid, $tag); $gradeitems = $gbservice->get_lineitems($course->id, null, null, null, null, null, $typeid); $this->assertEquals(1, $gradeitems[0]); $lineitem = gradebookservices::item_for_json($gradeitems[1][0], '', $typeid); $this->assertFalse(isset($lineitem->submissionReview)); $lineitemresource = new lineitem($gbservice); $this->set_server_for_put($course, $typeid, $lineitem); $response = new \mod_lti\local\ltiservice\response(); $lineitem->resourceId = $resourceid.'modified'; $lineitem->tag = $tag.'modified'; $response->set_request_data(json_encode($lineitem)); $lineitemresource->execute($response); $lineitem = gradebookservices::item_for_json($gradeitems[1][0], '', $typeid); $this->assertFalse(isset($lineitem->submissionReview)); $this->assertEquals($resourceid.'modified', $lineitem->resourceId); $this->assertEquals($tag.'modified', $lineitem->tag); $responseitem = json_decode($response->get_body()); $this->assertEquals($resourceid.'modified', $responseitem->resourceId); } /** * @covers ::execute * * Test updating the line item with submission review. */ public function test_execute_put_withsubreview() { global $CFG; require_once($CFG->dirroot . '/mod/lti/locallib.php'); $this->resetAfterTest(); $this->setAdminUser(); $resourceid = 'test-resource-id'; $tag = 'tag'; $subreviewurl = 'https://subreview.example.com'; $subreviewparams = 'a=2'; $course = $this->getDataGenerator()->create_course(); $typeid = $this->create_type(); // The 1st item in the array is the items count. $gbservice = new gradebookservices(); $gbservice->set_type(lti_get_type($typeid)); $this->create_graded_lti($typeid, $course, $resourceid, $tag, $subreviewurl, $subreviewparams); $gradeitems = $gbservice->get_lineitems($course->id, null, null, null, null, null, $typeid); $this->assertEquals(1, $gradeitems[0]); $lineitem = gradebookservices::item_for_json($gradeitems[1][0], '', $typeid); $this->assertTrue(isset($lineitem->submissionReview)); $lineitemresource = new lineitem($gbservice); $this->set_server_for_put($course, $typeid, $lineitem); $response = new \mod_lti\local\ltiservice\response(); $lineitem->resourceId = $resourceid.'modified'; $lineitem->tag = $tag.'modified'; $lineitem->submissionReview->url = $subreviewurl.'modified'; $lineitem->submissionReview->custom = ['a' => '3']; $response->set_request_data(json_encode($lineitem)); $lineitemresource->execute($response); $lineitem = gradebookservices::item_for_json($gradeitems[1][0], '', $typeid); $this->assertEquals($resourceid.'modified', $lineitem->resourceId); $this->assertEquals($subreviewurl.'modified', $lineitem->submissionReview->url); $custom = $lineitem->submissionReview->custom; $this->assertEquals('a=3', join("\n", array_map(fn($k) => $k.'='.$custom[$k], array_keys($custom)))); $responseitem = json_decode($response->get_body()); $this->assertEquals($resourceid.'modified', $responseitem->resourceId); $this->assertEquals($subreviewurl.'modified', $responseitem->submissionReview->url); } /** * @covers ::execute * * Test updating the line item with submission review. */ public function test_execute_put_addsubreview() { global $CFG; require_once($CFG->dirroot . '/mod/lti/locallib.php'); $this->resetAfterTest(); $this->setAdminUser(); $resourceid = 'test-resource-id'; $tag = 'tag'; $subreviewurl = 'https://subreview.example.com'; $course = $this->getDataGenerator()->create_course(); $typeid = $this->create_type(); // The 1st item in the array is the items count. $gbservice = new gradebookservices(); $gbservice->set_type(lti_get_type($typeid)); $this->create_graded_lti($typeid, $course, $resourceid, $tag); $gradeitems = $gbservice->get_lineitems($course->id, null, null, null, null, null, $typeid); $this->assertEquals(1, $gradeitems[0]); $lineitem = gradebookservices::item_for_json($gradeitems[1][0], '', $typeid); $this->assertFalse(isset($lineitem->submissionReview)); $lineitemresource = new lineitem($gbservice); $this->set_server_for_put($course, $typeid, $lineitem); $response = new \mod_lti\local\ltiservice\response(); $lineitem->resourceId = $resourceid.'modified'; $lineitem->tag = $tag.'modified'; $lineitem->submissionReview = ['url' => $subreviewurl]; $response->set_request_data(json_encode($lineitem)); $lineitemresource->execute($response); $lineitem = gradebookservices::item_for_json($gradeitems[1][0], '', $typeid); $this->assertEquals($resourceid.'modified', $lineitem->resourceId); $this->assertEquals($subreviewurl, $lineitem->submissionReview->url); $this->assertFalse(isset($lineitem->submissionReview->custom)); $responseitem = json_decode($response->get_body()); $this->assertEquals($resourceid.'modified', $responseitem->resourceId); $this->assertEquals($subreviewurl, $responseitem->submissionReview->url); $this->assertFalse(isset($responseitem->submissionReview->custom)); } /** * Inserts a graded lti instance, which should create a grade_item and gradebookservices record. * * @param int $typeid Type ID of the LTI Tool. * @param object $course course where to add the lti instance. * @param string|null $resourceid resource id * @param string|null $tag tag * @param string|null $subreviewurl submission review url * @param string|null $subreviewparams submission review custom params * * @return object lti instance created */ private function create_graded_lti(int $typeid, object $course, ?string $resourceid, ?string $tag, ?string $subreviewurl = null, ?string $subreviewparams = null) : object { $lti = ['course' => $course->id, 'typeid' => $typeid, 'instructorchoiceacceptgrades' => LTI_SETTING_ALWAYS, 'grade' => 10, 'lineitemresourceid' => $resourceid, 'lineitemtag' => $tag, 'lineitemsubreviewurl' => $subreviewurl, 'lineitemsubreviewparams' => $subreviewparams]; return $this->getDataGenerator()->create_module('lti', $lti, array()); } /** * Creates a new LTI Tool Type. */ private function create_type() { $type = new \stdClass(); $type->state = LTI_TOOL_STATE_CONFIGURED; $type->name = "Test tool"; $type->description = "Example description"; $type->clientid = "Test client ID"; $type->baseurl = $this->getExternalTestFileUrl('/test.html'); $config = new \stdClass(); $config->ltiservice_gradesynchronization = 2; return lti_add_type($type, $config); } /** * Sets the server info and get to be configured for a PUT operation, * including having a proper auth token attached. * * @param object $course course where to add the lti instance. * @param int $typeid * @param object $lineitem */ private function set_server_for_put(object $course, int $typeid, object $lineitem) { $_SERVER['REQUEST_METHOD'] = \mod_lti\local\ltiservice\resource_base::HTTP_PUT; $_SERVER['PATH_INFO'] = "/$course->id/lineitems$lineitem->id"; $token = lti_new_access_token($typeid, ['https://purl.imsglobal.org/spec/lti-ags/scope/lineitem']); $_SERVER['HTTP_Authorization'] = 'Bearer '.$token->token; $_GET['type_id'] = (string)$typeid; } } gradebookservices/tests/gradebookservices_test.php 0000644 00000041245 15151264172 0016672 0 ustar 00 <?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/>. namespace ltiservice_gradebookservices; use ltiservice_gradebookservices\local\service\gradebookservices; /** * Unit tests for lti gradebookservices. * * @package ltiservice_gradebookservices * @category test * @copyright 2020 Claude Vervoort <claude.vervoort@cengage.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @coversDefaultClass \mod_lti\service\gradebookservices\local\gradebookservices */ class gradebookservices_test extends \advanced_testcase { /** * @covers ::instance_added * * Test saving a graded LTI with resource and tag info (as a result of * content item selection) creates a gradebookservices record * that can be retrieved using the gradebook service API. */ public function test_lti_add_coupled_lineitem() { global $CFG; require_once($CFG->dirroot . '/mod/lti/locallib.php'); $this->resetAfterTest(); $this->setAdminUser(); // Create a tool type, associated with that proxy. $typeid = $this->create_type(); $course = $this->getDataGenerator()->create_course(); $resourceid = 'test-resource-id'; $tag = 'tag'; $subreviewurl = 'https://subreview.example.com'; $subreviewparams = 'a=2'; $ltiinstance = $this->create_graded_lti($typeid, $course, $resourceid, $tag, $subreviewurl, $subreviewparams); $this->assertNotNull($ltiinstance); $gbs = gradebookservices::find_ltiservice_gradebookservice_for_lti($ltiinstance->id); $this->assertNotNull($gbs); $this->assertEquals($resourceid, $gbs->resourceid); $this->assertEquals($tag, $gbs->tag); $this->assertEquals($subreviewurl, $gbs->subreviewurl); $this->assertEquals($subreviewparams, $gbs->subreviewparams); $this->assert_lineitems($course, $typeid, $ltiinstance->name, $ltiinstance, $resourceid, $tag, $subreviewurl, $subreviewparams); } /** * @covers ::instance_added * * Test saving a graded LTI with resource and tag info (as a result of * content item selection) creates a gradebookservices record * that can be retrieved using the gradebook service API. */ public function test_lti_add_coupled_lineitem_default_subreview() { global $CFG; require_once($CFG->dirroot . '/mod/lti/locallib.php'); $this->resetAfterTest(); $this->setAdminUser(); // Create a tool type, associated with that proxy. $typeid = $this->create_type(); $course = $this->getDataGenerator()->create_course(); $resourceid = 'test-resource-id'; $tag = 'tag'; $ltiinstance = $this->create_graded_lti($typeid, $course, $resourceid, $tag, 'DEFAULT'); $this->assertNotNull($ltiinstance); $gbs = gradebookservices::find_ltiservice_gradebookservice_for_lti($ltiinstance->id); $this->assertNotNull($gbs); $this->assertEquals('DEFAULT', $gbs->subreviewurl); $this->assert_lineitems($course, $typeid, $ltiinstance->name, $ltiinstance, $resourceid, $tag, 'DEFAULT'); } /** * @covers ::add_standalone_lineitem * * Test saving a standalone LTI lineitem with resource and tag info * that can be retrieved using the gradebook service API. */ public function test_lti_add_standalone_lineitem() { $this->resetAfterTest(); $this->setAdminUser(); $course = $this->getDataGenerator()->create_course(); $resourceid = "test-resource-standalone"; $tag = "test-tag-standalone"; $typeid = $this->create_type(); $this->create_standalone_lineitem($course->id, $typeid, $resourceid, $tag); $this->assert_lineitems($course, $typeid, "manualtest", null, $resourceid, $tag); } /** * @covers ::find_ltiservice_gradebookservice_for_lti * * Test line item URL is populated for coupled line item only * if there is not another line item bound to the lti instance, * since in that case there would be no rule to define which of * the line items should be actually passed. */ public function test_get_launch_parameters_coupled() { global $CFG; require_once($CFG->dirroot . '/mod/lti/locallib.php'); $this->resetAfterTest(); $this->setAdminUser(); // Create a tool type, associated with that proxy. $typeid = $this->create_type(); $course = $this->getDataGenerator()->create_course(); $ltiinstance = $this->create_graded_lti($typeid, $course, 'resource-id', 'tag', 'https://subreview.url', 'sub=review'); $this->assertNotNull($ltiinstance); $gbservice = new gradebookservices(); $params = $gbservice->get_launch_parameters('basic-lti-launch-request', $course->id, 111, $typeid, $ltiinstance->id); $this->assertEquals('$LineItem.url', $params['lineitem_url']); $this->assertEquals('$LineItem.url', $params['lineitem_url']); $this->create_standalone_lineitem($course->id, $typeid, 'resource-id', 'tag', $ltiinstance->id); $params = $gbservice->get_launch_parameters('basic-lti-launch-request', $course->id, 111, $typeid, $ltiinstance->id); $this->assertEquals('$LineItems.url', $params['lineitems_url']); // 2 line items for a single link, we cannot return a single line item url. $this->assertFalse(array_key_exists('$LineItem.url', $params)); } /** * @covers ::override_endpoint * * Test Submission Review URL and custom parameter is applied when the * launch is submission review. */ public function test_get_launch_parameters_coupled_subreview_override() { global $CFG; require_once($CFG->dirroot . '/mod/lti/locallib.php'); $this->resetAfterTest(); $this->setAdminUser(); // Create a tool type, associated with that proxy. $typeid = $this->create_type(); $course = $this->getDataGenerator()->create_course(); $ltiinstance = $this->create_graded_lti($typeid, $course, 'resource-id', 'tag', 'https://example.com/subreview', 'action=review'); $this->assertNotNull($ltiinstance); $gbservice = new gradebookservices(); $overrides = $gbservice->override_endpoint('LtiSubmissionReviewRequest', 'https://example.com/lti', "color=blue", $course->id, $ltiinstance); $this->assertEquals('https://example.com/subreview', $overrides[0]); $this->assertEquals("color=blue\naction=review", $overrides[1]); } /** * @covers ::override_endpoint * * Test Submission Review URL and custom parameter is applied when the * launch is submission review. */ public function test_get_launch_parameters_coupled_subreview_override_default() { global $CFG; require_once($CFG->dirroot . '/mod/lti/locallib.php'); $this->resetAfterTest(); $this->setAdminUser(); // Create a tool type, associated with that proxy. $typeid = $this->create_type(); $course = $this->getDataGenerator()->create_course(); $ltiinstance = $this->create_graded_lti($typeid, $course, 'resource-id', 'tag', 'DEFAULT', ''); $this->assertNotNull($ltiinstance); $gbservice = new gradebookservices(); $overrides = $gbservice->override_endpoint('LtiSubmissionReviewRequest', 'https://example.com/lti', "color=blue", $course->id, $ltiinstance); $this->assertEquals('https://example.com/lti', $overrides[0]); $this->assertEquals("color=blue", $overrides[1]); } /** * @covers ::get_launch_parameters * * Test line item URL is populated for not coupled line item only * if there is a single line item attached to that lti instance. */ public function test_get_launch_parameters_decoupled() { global $CFG; require_once($CFG->dirroot . '/mod/lti/locallib.php'); $this->resetAfterTest(); $this->setAdminUser(); // Create a tool type, associated with that proxy. $typeid = $this->create_type(); $course = $this->getDataGenerator()->create_course(); $ltiinstance = $this->create_notgraded_lti($typeid, $course); $this->assertNotNull($ltiinstance); $gbservice = new gradebookservices(); $params = $gbservice->get_launch_parameters('basic-lti-launch-request', $course->id, 111, $typeid, $ltiinstance->id); $this->assertEquals('$LineItems.url', $params['lineitems_url']); $this->assertFalse(array_key_exists('$LineItem.url', $params)); $this->create_standalone_lineitem($course->id, $typeid, 'resource-id', 'tag', $ltiinstance->id); $params = $gbservice->get_launch_parameters('basic-lti-launch-request', $course->id, 111, $typeid, $ltiinstance->id); $this->assertEquals('$LineItems.url', $params['lineitems_url']); $this->assertEquals('$LineItem.url', $params['lineitem_url']); // 2 line items for a single link, we cannot return a single line item url. $this->create_standalone_lineitem($course->id, $typeid, 'resource-id', 'tag-2', $ltiinstance->id); $this->assertFalse(array_key_exists('$LineItem.url', $params)); } /** * @covers ::is_user_gradable_in_course * * Test if a user can be graded in a course. */ public function test_is_user_gradable_in_course() { $this->resetAfterTest(); $generator = $this->getDataGenerator(); $course = $generator->create_course(); $user1 = $generator->create_user(); $user2 = $generator->create_user(); $generator->enrol_user($user1->id, $course->id, 'student'); $generator->enrol_user($user2->id, $course->id, 'editingteacher'); $this->assertTrue(gradebookservices::is_user_gradable_in_course($course->id, $user1->id)); $this->assertFalse(gradebookservices::is_user_gradable_in_course($course->id, $user2->id)); } /** * Asserts a matching gradebookservices record exist with the matching tag and resourceid. * * @param object $course current course * @param int $typeid Type id of the tool * @param string $label Label of the line item * @param object|null $ltiinstance lti instance related to that line item * @param string|null $resourceid resourceid the line item should have * @param string|null $tag tag the line item should have * @param string|null $subreviewurl submission review url * @param string|null $subreviewparams submission review custom params */ private function assert_lineitems(object $course, int $typeid, string $label, ?object $ltiinstance, ?string $resourceid, ?string $tag, ?string $subreviewurl = null, ?string $subreviewparams = null) : void { $gbservice = new gradebookservices(); $gradeitems = $gbservice->get_lineitems($course->id, null, null, null, null, null, $typeid); // The 1st item in the array is the items count. $this->assertEquals(1, $gradeitems[0]); $lineitem = gradebookservices::item_for_json($gradeitems[1][0], '', $typeid); $this->assertEquals(10, $lineitem->scoreMaximum); $this->assertEquals($resourceid, $lineitem->resourceId); $this->assertEquals($tag, $lineitem->tag); $this->assertEquals($label, $lineitem->label); $this->assertEquals(!empty($subreviewurl), isset($lineitem->submissionReview)); if ($subreviewurl) { if ($subreviewurl == 'DEFAULT') { $this->assertFalse(isset($this->submissionReview->url)); } else { $this->assertEquals($subreviewurl, $lineitem->submissionReview->url); } if ($subreviewparams) { $custom = $lineitem->submissionReview->custom; $this->assertEquals($subreviewparams, join("\n", array_map(fn($k) => $k.'='.$custom[$k], array_keys($custom)))); } else { $this->assertFalse(isset($this->submissionReview->custom)); } } $gradeitems = $gbservice->get_lineitems($course->id, $resourceid, null, null, null, null, $typeid); $this->assertEquals(1, $gradeitems[0]); if (isset($ltiinstance)) { $gradeitems = $gbservice->get_lineitems($course->id, null, $ltiinstance->id, null, null, null, $typeid); $this->assertEquals(1, $gradeitems[0]); $gradeitems = $gbservice->get_lineitems($course->id, null, $ltiinstance->id + 1, null, null, null, $typeid); $this->assertEquals(0, $gradeitems[0]); } $gradeitems = $gbservice->get_lineitems($course->id, null, null, $tag, null, null, $typeid); $this->assertEquals(1, $gradeitems[0]); $gradeitems = $gbservice->get_lineitems($course->id, 'an unknown resource id', null, null, null, null, $typeid); $this->assertEquals(0, $gradeitems[0]); $gradeitems = $gbservice->get_lineitems($course->id, null, null, 'an unknown tag', null, null, $typeid); $this->assertEquals(0, $gradeitems[0]); } /** * Inserts a graded lti instance, which should create a grade_item and gradebookservices record. * * @param int $typeid Type ID of the LTI Tool. * @param object $course course where to add the lti instance. * @param string|null $resourceid resource id * @param string|null $tag tag * @param string|null $subreviewurl submission review url * @param string|null $subreviewparams submission review custom params * * @return object lti instance created */ private function create_graded_lti(int $typeid, object $course, ?string $resourceid, ?string $tag, ?string $subreviewurl = null, ?string $subreviewparams = null) : object { $lti = ['course' => $course->id, 'typeid' => $typeid, 'instructorchoiceacceptgrades' => LTI_SETTING_ALWAYS, 'grade' => 10, 'lineitemresourceid' => $resourceid, 'lineitemtag' => $tag, 'lineitemsubreviewurl' => $subreviewurl, 'lineitemsubreviewparams' => $subreviewparams]; return $this->getDataGenerator()->create_module('lti', $lti, array()); } /** * Inserts an lti instance that is not graded. * * @param int $typeid Type Id of the LTI Tool. * @param object $course course where to add the lti instance. * * @return object lti instance created */ private function create_notgraded_lti(int $typeid, object $course) : object { $lti = ['course' => $course->id, 'typeid' => $typeid, 'instructorchoiceacceptgrades' => LTI_SETTING_NEVER]; return $this->getDataGenerator()->create_module('lti', $lti, array()); } /** * Inserts a standalone lineitem (gradeitem, gradebookservices entries). * * @param int $courseid Id of the course where the standalone line item will be added. * @param int $typeid of the LTI Tool * @param string|null $resourceid resource id * @param string|null $tag tag * @param int|null $ltiinstanceid Id of the LTI instance the standalone line item will be related to. * */ private function create_standalone_lineitem(int $courseid, int $typeid, ?string $resourceid, ?string $tag, int $ltiinstanceid = null) : void { $gbservice = new gradebookservices(); $gbservice->add_standalone_lineitem($courseid, "manualtest", 10, "https://test.phpunit", $ltiinstanceid, $resourceid, $tag, $typeid, null /*toolproxyid*/); } /** * Creates a new LTI Tool Type. */ private function create_type() { $type = new \stdClass(); $type->state = LTI_TOOL_STATE_CONFIGURED; $type->name = "Test tool"; $type->description = "Example description"; $type->clientid = "Test client ID"; $type->baseurl = $this->getExternalTestFileUrl('/test.html'); $config = new \stdClass(); $config->ltiservice_gradesynchronization = 2; return lti_add_type($type, $config); } } gradebookservices/backup/moodle2/restore_ltiservice_gradebookservices_subplugin.class.php 0000644 00000024663 15151264172 0026474 0 ustar 00 <?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) { if ($data->ltilinkid != $this->get_old_parentid('lti')) { // This is a linked item, but not for the current lti link, so skip it. return; } $ltilinkid = $this->get_new_parentid('lti'); } else { $ltilinkid = null; } $resourceid = null; if (property_exists( $data, 'resourceid' )) { $resourceid = $data->resourceid; } // 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, 'resourceid' => $resourceid, 'tag' => $data->tag, 'subreviewparams' => $data->subreviewparams ?? '', 'subreviewurl' => $data->subreviewurl ?? '' )); $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; if (!isset($gbs->resourceid)) { // Before 3.9 resourceid was stored in grade_item->idnumber. $gbs->resourceid = $DB->get_field_select('grade_items', 'idnumber', "id=:id", ['id' => $newgradeitemid]); } $DB->update_record('ltiservice_gradebookservices', $gbs); } } // Pre 3.9 backups did not include a gradebookservices record. Adding one here if missing for the restored instance. $gi = $DB->get_record('grade_items', array('itemtype' => 'mod', 'itemmodule' => 'lti', 'courseid' => $courseid, 'iteminstance' => $this->task->get_activityid())); if ($gi) { $gbs = $DB->get_records('ltiservice_gradebookservices', ['gradeitemid' => $gi->id]); if (empty($gbs)) { // The currently restored LTI link has a grade item but no gbs, so let's create a gbs entry. if ($instance = $DB->get_record('lti', array('id' => $gi->iteminstance))) { if ($tool = lti_get_instance_type($instance)) { $DB->insert_record('ltiservice_gradebookservices', (object) array( 'gradeitemid' => $gi->id, 'courseid' => $courseid, 'toolproxyid' => $tool->toolproxyid, 'ltilinkid' => $gi->iteminstance, 'typeid' => $tool->id, 'baseurl' => $tool->baseurl, 'resourceid' => $gi->idnumber )); } } } } } } gradebookservices/backup/moodle2/backup_ltiservice_gradebookservices_subplugin.class.php 0000644 00000013550 15151264172 0026247 0 ustar 00 <?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', 'resourceid', 'tag', 'vendorcode', 'guid', 'subreviewurl', 'subreviewparams' ) ); // 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; } } gradebookservices/upgrade.txt 0000644 00000000401 15151264172 0012434 0 ustar 00 This files describes API changes in the lti code. === 4.1 === * The update_coupled_gradebookservices function now accept 2 additional optional parameters to update the newly added properties related to submission review support (url and custom params). gradebookservices/version.php 0000644 00000002217 15151264172 0012451 0 ustar 00 <?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 = 2022112800; $plugin->requires = 2022111800; $plugin->component = 'ltiservice_gradebookservices'; gradebookservices/classes/privacy/provider.php 0000644 00000010046 15151264172 0015727 0 ustar 00 <?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/>. /** * Privacy Subsystem implementation for ltiservice_gradebookservices. * * @package ltiservice_gradebookservices * @copyright 2018 Mark Nelson <markn@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace ltiservice_gradebookservices\privacy; use \core_privacy\local\metadata\collection; use \core_privacy\local\request\contextlist; use \core_privacy\local\request\approved_contextlist; use \core_privacy\local\request\userlist; use \core_privacy\local\request\approved_userlist; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for ltiservice_gradebookservices. * * @copyright 2018 Mark Nelson <markn@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\provider, \core_privacy\local\request\core_userlist_provider, \core_privacy\local\request\plugin\provider { /** * Returns meta data about this system. * * @param collection $collection The initialised collection to add items to. * @return collection A listing of user data stored through this system. */ public static function get_metadata(collection $collection) : collection { $collection->link_external_location('External LTI provider.', [ 'userid' => 'privacy:metadata:userid', 'grade' => 'privacy:metadata:grade', 'maxgrade' => 'privacy:metadata:maxgrade', 'feedback' => 'privacy:metadata:feedback', 'timemodified' => 'privacy:metadata:timemodified' ], 'privacy:metadata:externalpurpose'); return $collection; } /** * Get the list of contexts that contain user information for the specified user. * * @param int $userid The user to search. * @return contextlist The contextlist containing the list of contexts used in this plugin. */ public static function get_contexts_for_userid(int $userid) : contextlist { return new contextlist(); } /** * Get the list of users who have data within a context. * * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. */ public static function get_users_in_context(userlist $userlist) { } /** * Export all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts to export information for. */ public static function export_user_data(approved_contextlist $contextlist) { } /** * Delete all user data which matches the specified context. * * @param \context $context A user context. */ public static function delete_data_for_all_users_in_context(\context $context) { } /** * Delete multiple users within a single context. * * @param approved_userlist $userlist The approved context and user information to delete information for. */ public static function delete_data_for_users(approved_userlist $userlist) { } /** * Delete all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. */ public static function delete_data_for_user(approved_contextlist $contextlist) { } } gradebookservices/classes/task/cleanup_task.php 0000644 00000003556 15151264172 0016043 0 ustar 00 <?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(); } } gradebookservices/classes/local/resources/lineitem.php 0000644 00000033201 15151264172 0017330 0 ustar 00 <?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']; $isget = $response->get_request_method() === self::HTTP_GET; // 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_INT); $scopes = array(gradebookservices::SCOPE_GRADEBOOKSERVICES_LINEITEM); if ($response->get_request_method() === self::HTTP_GET) { $scopes[] = gradebookservices::SCOPE_GRADEBOOKSERVICES_LINEITEM_READ; } try { if (!$this->check_tool($typeid, $response->get_request_data(), $scopes)) { throw new \Exception(null, 401); } $typeid = $this->get_service()->get_type()->id; if (!($course = $DB->get_record('course', array('id' => $contextid), 'id', IGNORE_MISSING))) { throw new \Exception("Not Found: Course {$contextid} doesn't exist", 404); } if (!$this->get_service()->is_allowed_in_context($typeid, $course->id)) { throw new \Exception('Not allowed in context', 403); } if (!$DB->record_exists('grade_items', array('id' => $itemid))) { throw new \Exception("Not Found: Grade item {$itemid} doesn't exist", 404); } $item = $this->get_service()->get_lineitem($contextid, $itemid, $typeid); if ($item === false) { throw new \Exception('Line item does not exist', 404); } require_once($CFG->libdir.'/gradelib.php'); switch ($response->get_request_method()) { case self::HTTP_GET: $this->get_request($response, $item, $typeid); break; case self::HTTP_PUT: $json = $this->process_put_request($response->get_request_data(), $item, $typeid); $response->set_body($json); $response->set_code(200); break; case self::HTTP_DELETE: $this->process_delete_request($item); $response->set_code(204); break; } } catch (\Exception $e) { $response->set_code($e->getCode()); $response->set_reason($e->getMessage()); } } /** * 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); } if ($gbs) { $resourceid = (isset($json->resourceId)) ? $json->resourceId : ''; $tag = (isset($json->tag)) ? $json->tag : ''; if ($gbs->tag !== $tag || $gbs->resourceid !== $resourceid) { $upgradegradebookservices = true; } $gbs->tag = $tag; $gbs->resourceid = $resourceid; $incomingurl = null; $incomingparams = null; if (isset($json->submissionReview)) { $incomingurl = $json->submissionReview->url ?? 'DEFAULT'; if (isset($json->submissionReview->custom)) { $incomingparams = params_to_string($json->submissionReview->custom); } } if ($gbs->subreviewurl ?? null !== $incomingurl || $gbs->subreviewparams ?? null !== $incomingparams) { $upgradegradebookservices = true; $gbs->subreviewurl = $incomingurl; $gbs->subreviewparams = $incomingparams; } } $ltilinkid = null; if (isset($json->resourceLinkId)) { if (is_numeric($json->resourceLinkId)) { $ltilinkid = $json->resourceLinkId; if ($gbs) { if (intval($gbs->ltilinkid) !== intval($json->resourceLinkId)) { $gbs->ltilinkid = $json->resourceLinkId; $upgradegradebookservices = true; } } else { if (intval($item->iteminstance) !== intval($json->resourceLinkId)) { $item->iteminstance = intval($json->resourceLinkId); $updategradeitem = true; } } } else { throw new \Exception(null, 400); } } else 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, 'resourceid' => $resourceid, 'tag' => $gbs->tag, 'subreviewurl' => $gbs->subreviewurl, 'subreviewparams' => $gbs->subreviewparams )); } 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); } } } /** * 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; if ($tool = $this->get_service()->get_type()) { $this->params['tool_code'] = $tool->id; } $id = optional_param('id', 0, PARAM_INT); // Course Module ID. if (empty($id)) { $hint = optional_param('lti_message_hint', "", PARAM_TEXT); if ($hint) { $hintdec = json_decode($hint); if (isset($hintdec->cmid)) { $id = $hintdec->cmid; } } } 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(); $resolved .= "?type_id={$tool->id}"; } } $value = str_replace('$LineItem.url', $resolved, $value); } return $value; } } gradebookservices/classes/local/resources/scores.php 0000644 00000023407 15151264172 0017027 0 ustar 00 <?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); $scope = gradebookservices::SCOPE_GRADEBOOKSERVICES_SCORE; try { if (!$this->check_tool($typeid, $response->get_request_data(), array($scope))) { throw new \Exception(null, 401); } $typeid = $this->get_service()->get_type()->id; if (empty($contextid) || !($container ^ ($response->get_request_method() === self::HTTP_POST)) || (!empty($contenttype) && !in_array($contenttype, $this->formats))) { throw new \Exception('No context or unsupported content type', 400); } if (!($course = $DB->get_record('course', array('id' => $contextid), 'id', IGNORE_MISSING))) { throw new \Exception("Not Found: Course {$contextid} doesn't exist", 404); } if (!$this->get_service()->is_allowed_in_context($typeid, $course->id)) { throw new \Exception('Not allowed in context', 403); } if (!$DB->record_exists('grade_items', array('id' => $itemid))) { throw new \Exception("Not Found: Grade item {$itemid} doesn't exist", 404); } $item = $this->get_service()->get_lineitem($contextid, $itemid, $typeid); if ($item === false) { throw new \Exception('Line item does not exist', 404); } $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); } catch (\Exception $e) { $response->set_code($e->getCode()); $response->set_reason($e->getMessage()); } } /** * 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->scoreGiven) && !isset($score->scoreMaximum)) || (isset($score->scoreMaximum) && !is_numeric($score->scoreMaximum)) || (!gradebookservices::is_user_gradable_in_course($contextid, $score->userId)) ) { throw new \Exception('Incorrect score received' . $body, 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; } } $this->get_service()->save_grade_item($item, $score, $score->userId); } /** * 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; } } gradebookservices/classes/local/resources/lineitems.php 0000644 00000030553 15151264172 0017522 0 ustar 00 <?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 then we are in LTI 2. $typeid = optional_param('type_id', null, PARAM_INT); $scopes = array(gradebookservices::SCOPE_GRADEBOOKSERVICES_LINEITEM); if ($response->get_request_method() === self::HTTP_GET) { $scopes[] = gradebookservices::SCOPE_GRADEBOOKSERVICES_LINEITEM_READ; } try { if (!$this->check_tool($typeid, $response->get_request_data(), $scopes)) { throw new \Exception(null, 401); } $typeid = $this->get_service()->get_type()->id; if (empty($contextid) || !($container ^ ($response->get_request_method() === self::HTTP_POST)) || (!empty($contenttype) && !in_array($contenttype, $this->formats))) { throw new \Exception('No context or unsupported content type', 400); } if (!($course = $DB->get_record('course', array('id' => $contextid), 'id', IGNORE_MISSING))) { throw new \Exception("Not Found: Course {$contextid} doesn't exist", 404); } if (!$this->get_service()->is_allowed_in_context($typeid, $course->id)) { throw new \Exception('Not allowed in context', 403); } if ($response->get_request_method() !== self::HTTP_POST) { $resourceid = optional_param('resource_id', null, PARAM_TEXT); $ltilinkid = optional_param('resource_link_id', null, PARAM_TEXT); if (is_null($ltilinkid)) { $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]); } else { $json = $this->get_json_for_post_request($response->get_request_data(), $contextid, $typeid); $response->set_code(201); $response->set_content_type($this->formats[1]); } $response->set_body($json); } catch (\Exception $e) { $response->set_code($e->getCode()); $response->set_reason($e->getMessage()); } } /** * 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('resource_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('No label or Score Maximum', 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->resourceLinkId)) ? $json->resourceLinkId : null; if ($ltilinkid == null) { $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; } $gradebookservices = new gradebookservices(); $id = $gradebookservices->add_standalone_lineitem($contextid, $json->label, $max, $baseurl, $ltilinkid, $resourceid, $tag, $typeid, $toolproxyid); 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); } /** * 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; $query = ''; if (($tool = $this->get_service()->get_type())) { $query = "?type_id={$tool->id}"; } $value = str_replace('$LineItems.url', parent::get_endpoint() . $query, $value); } return $value; } } gradebookservices/classes/local/resources/results.php 0000644 00000027006 15151264172 0017231 0 ustar 00 <?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 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 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() === self::HTTP_GET; // 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_INT); $scope = gradebookservices::SCOPE_GRADEBOOKSERVICES_RESULT_READ; try { if (!$this->check_tool($typeid, $response->get_request_data(), array($scope))) { throw new \Exception(null, 401); } $typeid = $this->get_service()->get_type()->id; if (!($course = $DB->get_record('course', array('id' => $contextid), 'id', IGNORE_MISSING))) { throw new \Exception("Not Found: Course {$contextid} doesn't exist", 404); } if (!$this->get_service()->is_allowed_in_context($typeid, $course->id)) { throw new \Exception('Not allowed in context', 403); } if (!$DB->record_exists('grade_items', array('id' => $itemid))) { throw new \Exception("Not Found: Grade item {$itemid} doesn't exist", 404); } $item = $this->get_service()->get_lineitem($contextid, $itemid, $typeid); if ($item === false) { throw new \Exception('Line item does not exist', 404); } $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); } catch (\Exception $e) { $response->set_code($e->getCode()); $response->set_reason($e->getMessage()); } } /** * 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); } /** * 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; } } gradebookservices/classes/local/service/gradebookservices.php 0000644 00000111024 15151264172 0020651 0 ustar 00 <?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; use moodle_url; defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->dirroot . '/mod/lti/locallib.php'); /** * 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 { /** Read-only access to Gradebook services */ const GRADEBOOKSERVICES_READ = 1; /** Full access to Gradebook services */ const GRADEBOOKSERVICES_FULL = 2; /** Scope for full access to Lineitem service */ const SCOPE_GRADEBOOKSERVICES_LINEITEM = 'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem'; /** Scope for full access to Lineitem service */ const SCOPE_GRADEBOOKSERVICES_LINEITEM_READ = 'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem.readonly'; /** Scope for access to Result service */ const SCOPE_GRADEBOOKSERVICES_RESULT_READ = 'https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly'; /** Scope for access to Score service */ const SCOPE_GRADEBOOKSERVICES_SCORE = 'https://purl.imsglobal.org/spec/lti-ags/scope/score'; /** * Class constructor. */ public function __construct() { parent::__construct(); $this->id = 'gradebookservices'; $this->name = get_string($this->get_component_id(), $this->get_component_id()); } /** * 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; } /** * Get the scope(s) permitted for this service. * * @return array */ public function get_permitted_scopes() { $scopes = array(); $ok = !empty($this->get_type()); if ($ok && isset($this->get_typeconfig()['ltiservice_gradesynchronization'])) { if (!empty($setting = $this->get_typeconfig()['ltiservice_gradesynchronization'])) { $scopes[] = self::SCOPE_GRADEBOOKSERVICES_LINEITEM_READ; $scopes[] = self::SCOPE_GRADEBOOKSERVICES_RESULT_READ; $scopes[] = self::SCOPE_GRADEBOOKSERVICES_SCORE; if ($setting == self::GRADEBOOKSERVICES_FULL) { $scopes[] = self::SCOPE_GRADEBOOKSERVICES_LINEITEM; } } } return $scopes; } /** * Get the scopes defined by this service. * * @return array */ public function get_scopes() { return [self::SCOPE_GRADEBOOKSERVICES_LINEITEM_READ, self::SCOPE_GRADEBOOKSERVICES_RESULT_READ, self::SCOPE_GRADEBOOKSERVICES_SCORE, self::SCOPE_GRADEBOOKSERVICES_LINEITEM]; } /** * 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 = [ get_string('nevergs', $this->get_component_id()), get_string('partialgs', $this->get_component_id()), get_string('alwaysgs', $this->get_component_id()) ]; $mform->addElement('select', $selectelementname, get_string($identifier, $this->get_component_id()), $options); $mform->setType($selectelementname, 'int'); $mform->setDefault($selectelementname, 0); $mform->addHelpButton($selectelementname, $identifier, $this->get_component_id()); } /** * For submission review, if there is a dedicated URL, use it as the target link. * * @param string $messagetype message type for this launch * @param string $targetlinkuri current target link uri * @param string|null $customstr concatenated list of custom parameters * @param int $courseid * @param null|object $lti LTI Instance. * * @return array containing the target link URL and the custom params string to use. */ public function override_endpoint(string $messagetype, string $targetlinkuri, ?string $customstr, int $courseid, ?object $lti = null): array { global $DB; if ($messagetype == 'LtiSubmissionReviewRequest' && isset($lti->id)) { $conditions = array('courseid' => $courseid, 'ltilinkid' => $lti->id); $coupledlineitems = $DB->get_records('ltiservice_gradebookservices', $conditions); if (count($coupledlineitems) == 1) { $item = reset($coupledlineitems); $url = $item->subreviewurl; $subreviewparams = $item->subreviewparams; if (!empty($url) && $url != 'DEFAULT') { $targetlinkuri = $url; } if (!empty($subreviewparams)) { if (!empty($customstr)) { $customstr .= "\n{$subreviewparams}"; } else { $customstr = $subreviewparams; } } } } return [$targetlinkuri, $customstr]; } /** * Return an array of key/claim mapping allowing LTI 1.1 custom parameters * to be transformed to LTI 1.3 claims. * * @return array Key/value pairs of params to claim mapping. */ public function get_jwt_claim_mappings(): array { return [ 'custom_gradebookservices_scope' => [ 'suffix' => 'ags', 'group' => 'endpoint', 'claim' => 'scope', 'isarray' => true ], 'custom_lineitems_url' => [ 'suffix' => 'ags', 'group' => 'endpoint', 'claim' => 'lineitems', 'isarray' => false ], 'custom_lineitem_url' => [ 'suffix' => 'ags', 'group' => 'endpoint', 'claim' => 'lineitem', 'isarray' => false ], 'custom_results_url' => [ 'suffix' => 'ags', 'group' => 'endpoint', 'claim' => 'results', 'isarray' => false ], 'custom_result_url' => [ 'suffix' => 'ags', 'group' => 'endpoint', 'claim' => 'result', 'isarray' => false ], 'custom_scores_url' => [ 'suffix' => 'ags', 'group' => 'endpoint', 'claim' => 'scores', 'isarray' => false ], 'custom_score_url' => [ 'suffix' => 'ags', 'group' => 'endpoint', 'claim' => 'score', 'isarray' => false ] ]; } /** * 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(); $this->set_type(lti_get_type($typeid)); $this->set_typeconfig(lti_get_type_config($typeid)); // Only inject parameters if the service is enabled for this tool. if (isset($this->get_typeconfig()['ltiservice_gradesynchronization'])) { if ($this->get_typeconfig()['ltiservice_gradesynchronization'] == self::GRADEBOOKSERVICES_READ || $this->get_typeconfig()['ltiservice_gradesynchronization'] == self::GRADEBOOKSERVICES_FULL) { // 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)) { $id = null; if (!is_null($modlti)) { $conditions = array('courseid' => $courseid, 'itemtype' => 'mod', 'itemmodule' => 'lti', 'iteminstance' => $modlti); $coupledlineitems = $DB->get_records('grade_items', $conditions); $conditionsgbs = array('courseid' => $courseid, 'ltilinkid' => $modlti); $lineitemsgbs = $DB->get_records('ltiservice_gradebookservices', $conditionsgbs); // If a link has more that one attached grade items, per spec we do not populate line item url. if (count($lineitemsgbs) == 1) { $id = reset($lineitemsgbs)->gradeitemid; } if (count($lineitemsgbs) < 2 && count($coupledlineitems) == 1) { $coupledid = reset($coupledlineitems)->id; if (!is_null($id) && $id != $coupledid) { $id = null; } else { $id = $coupledid; } } } $launchparameters['gradebookservices_scope'] = implode(',', $this->get_permitted_scopes()); $launchparameters['lineitems_url'] = '$LineItems.url'; if (!is_null($id)) { $launchparameters['lineitem_url'] = '$LineItem.url'; } } } } 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); $sql = "SELECT i.* FROM {grade_items} i WHERE (i.courseid = :courseid) 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)) && (!isset($resourceid) || (isset($resourceid) && $gbs->resourceid == $resourceid))) { 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($resourceid) && !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; } /** * Adds a decoupled (standalone) line item. * Decoupled line items are not directly attached to * an lti instance activity. They are recorded in * the gradebook as manual activities and the * gradebookservices is used to associate that manual column * with the tool in addition to storing the LTI related * metadata (resource id, tag). * * @param string $courseid ID of course * @param string $label label of lineitem * @param float $maximumscore maximum score of lineitem * @param string $baseurl * @param int|null $ltilinkid id of lti instance this line item is associated with * @param string|null $resourceid resource id of lineitem * @param string|null $tag tag of lineitem * @param int $typeid lti type to which this line item is associated with * @param int|null $toolproxyid lti2 tool proxy to which this lineitem is associated to * * @return int id of the created gradeitem */ public function add_standalone_lineitem(string $courseid, string $label, float $maximumscore, string $baseurl, ?int $ltilinkid, ?string $resourceid, ?string $tag, int $typeid, int $toolproxyid = null) : int { global $DB; $params = array(); $params['itemname'] = $label; $params['gradetype'] = GRADE_TYPE_VALUE; $params['grademax'] = $maximumscore; $params['grademin'] = 0; $item = new \grade_item(array('id' => 0, 'courseid' => $courseid)); \grade_item::set_properties($item, $params); $item->itemtype = 'manual'; $item->grademax = $maximumscore; $id = $item->insert('mod/ltiservice_gradebookservices'); $DB->insert_record('ltiservice_gradebookservices', (object)array( 'gradeitemid' => $id, 'courseid' => $courseid, 'toolproxyid' => $toolproxyid, 'typeid' => $typeid, 'baseurl' => $baseurl, 'ltilinkid' => $ltilinkid, 'resourceid' => $resourceid, 'tag' => $tag )); return $id; } /** * Set a grade item. * * @param object $gradeitem Grade Item record * @param object $score Result object * @param int $userid User ID * * @throws \Exception * @deprecated since Moodle 3.7 MDL-62599 - please do not use this function any more. * @see gradebookservices::save_grade_item($gradeitem, $score, $userid) */ public static function save_score($gradeitem, $score, $userid) { $service = new gradebookservices(); $service->save_grade_item($gradeitem, $score, $userid); } /** * Saves a score received from the LTI tool. * * @param object $gradeitem Grade Item record * @param object $score Result object * @param int $userid User ID * * @throws \Exception */ public function save_grade_item($gradeitem, $score, $userid) { global $DB, $CFG; $source = 'mod' . $this->get_component_id(); 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)) { $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 (!empty($score->comment)) { $feedback = $score->comment; $feedbackformat = FORMAT_PLAIN; } if ($gradeitem->is_manual_item()) { $result = $gradeitem->update_final_grade($userid, $finalgrade, null, $feedback, FORMAT_PLAIN, null, $timemodified); } else { 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; $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); $gbs = self::find_ltiservice_gradebookservice_for_lineitem($item->id); if ($gbs) { $lineitem->resourceId = (!empty($gbs->resourceid)) ? $gbs->resourceid : ''; $lineitem->tag = (!empty($gbs->tag)) ? $gbs->tag : ''; if (isset($gbs->ltilinkid)) { $lineitem->resourceLinkId = strval($gbs->ltilinkid); $lineitem->ltiLinkId = strval($gbs->ltilinkid); } if (!empty($gbs->subreviewurl)) { $submissionreview = new \stdClass(); if ($gbs->subreviewurl != 'DEFAULT') { $submissionreview->url = $gbs->subreviewurl; } if (!empty($gbs->subreviewparams)) { $submissionreview->custom = lti_split_parameters($gbs->subreviewparams); } $lineitem->submissionReview = $submissionreview; } } else { $lineitem->tag = ''; if (isset($item->iteminstance)) { $lineitem->resourceLinkId = strval($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; } } /** * Updates the tag, resourceid and submission review values for a grade item coupled to an lti link instance. * * @param object $ltiinstance The lti instance to which the grade item is coupled to * @param string|null $resourceid The resourceid to apply to the lineitem. If empty string which will be stored as null. * @param string|null $tag The tag to apply to the lineitem. If empty string which will be stored as null. * @param moodle_url|null $subreviewurl The submission review target link URL * @param string|null $subreviewparams The submission review custom parameters. * */ public static function update_coupled_gradebookservices(object $ltiinstance, ?string $resourceid, ?string $tag, ?\moodle_url $subreviewurl, ?string $subreviewparams) : void { global $DB; if ($ltiinstance && $ltiinstance->typeid) { $gradeitem = $DB->get_record('grade_items', array('itemmodule' => 'lti', 'iteminstance' => $ltiinstance->id)); if ($gradeitem) { $resourceid = (isset($resourceid) && empty(trim($resourceid))) ? null : $resourceid; $subreviewurlstr = $subreviewurl ? $subreviewurl->out(false) : null; $tag = (isset($tag) && empty(trim($tag))) ? null : $tag; $gbs = self::find_ltiservice_gradebookservice_for_lineitem($gradeitem->id); if ($gbs) { $gbs->resourceid = $resourceid; $gbs->tag = $tag; $gbs->subreviewurl = $subreviewurlstr; $gbs->subreviewparams = $subreviewparams; $DB->update_record('ltiservice_gradebookservices', $gbs); } else { $baseurl = lti_get_type_type_config($ltiinstance->typeid)->lti_toolurl; $DB->insert_record('ltiservice_gradebookservices', (object)array( 'gradeitemid' => $gradeitem->id, 'courseid' => $gradeitem->courseid, 'typeid' => $ltiinstance->typeid, 'baseurl' => $baseurl, 'ltilinkid' => $ltiinstance->id, 'resourceid' => $resourceid, 'tag' => $tag, 'subreviewurl' => $subreviewurlstr, 'subreviewparams' => $subreviewparams )); } } } } /** * Called when a new LTI Instance is added. * * @param object $lti LTI Instance. */ public function instance_added(object $lti): void { self::update_coupled_gradebookservices($lti, $lti->lineitemresourceid ?? null, $lti->lineitemtag ?? null, isset($lti->lineitemsubreviewurl) ? new moodle_url($lti->lineitemsubreviewurl) : null, $lti->lineitemsubreviewparams ?? null); } /** * Called when a new LTI Instance is updated. * * @param object $lti LTI Instance. */ public function instance_updated(object $lti): void { self::update_coupled_gradebookservices($lti, $lti->lineitemresourceid ?? null, $lti->lineitemtag ?? null, isset($lti->lineitemsubreviewurl) ? new moodle_url($lti->lineitemsubreviewurl) : null, $lti->lineitemsubreviewparams ?? null); } /** * Set the form data when displaying the LTI Instance form. * * @param object $defaultvalues Default form values. */ public function set_instance_form_values(object $defaultvalues): void { $defaultvalues->lineitemresourceid = ''; $defaultvalues->lineitemtag = ''; $defaultvalues->subreviewurl = ''; $defaultvalues->subreviewparams = ''; if (is_object($defaultvalues) && $defaultvalues->instance) { $gbs = self::find_ltiservice_gradebookservice_for_lti($defaultvalues->instance); if ($gbs) { $defaultvalues->lineitemresourceid = $gbs->resourceid; $defaultvalues->lineitemtag = $gbs->tag; $defaultvalues->lineitemsubreviewurl = $gbs->subreviewurl; $defaultvalues->lineitemsubreviewparams = $gbs->subreviewparams; } } } /** * 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)"; $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 an lti instance * * @param string $instanceid The LTI module instance id * @return object gradebookservice for this line item */ public static function find_ltiservice_gradebookservice_for_lti($instanceid) { global $DB; if ($instanceid) { $gradeitem = $DB->get_record('grade_items', array('itemmodule' => 'lti', 'iteminstance' => $instanceid)); if ($gradeitem) { return self::find_ltiservice_gradebookservice_for_lineitem($gradeitem->id); } } } /** * Find the right element in the ltiservice_gradebookservice table for a lineitem * * @param string $lineitemid The lineitem (gradeitem) id * @return object gradebookservice if it exists */ public static function find_ltiservice_gradebookservice_for_lineitem($lineitemid) { global $DB; if ($lineitemid) { return $DB->get_record('ltiservice_gradebookservices', array('gradeitemid' => $lineitemid)); } } /** * 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; } } } gradebookservices/lang/en/ltiservice_gradebookservices.php 0000644 00000005156 15151264172 0020246 0 ustar 00 <?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'] = 'Whether to use the IMS LTI Assignment and Grade Services to synchronise grades instead of the Basic Outcomes service. * **Do not use this service** - Basic Outcomes features and configuration will be used * **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['ltiservice_gradebookservices'] = 'IMS LTI Assignment and Grade Services'; $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['privacy:metadata:externalpurpose'] = 'This information is sent to an external LTI provider.'; $string['privacy:metadata:feedback'] = 'The feedback the user received for this LTI activity.'; $string['privacy:metadata:grade'] = 'The grade the user received in Moodle for this LTI activity.'; $string['privacy:metadata:maxgrade'] = 'The max grade that can be achieved for this LTI activity.'; $string['privacy:metadata:timemodified'] = 'The last time the grade was updated'; $string['privacy:metadata:userid'] = 'The ID of the user using the LTI consumer.'; $string['taskcleanup'] = 'LTI Assignment and Grade Services table cleanup'; content_item_service.php 0000644 00000040526 15152357346 0011507 0 ustar 00 <?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/>. /** * Contains the content_item_service class. * * @package core * @subpackage course * @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_course\local\service; defined('MOODLE_INTERNAL') || die(); use core_course\local\exporters\course_content_items_exporter; use core_course\local\repository\content_item_readonly_repository_interface; /** * The content_item_service class, providing the api for interacting with content items. * * @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class content_item_service { /** @var content_item_readonly_repository_interface $repository a repository for content items. */ private $repository; /** string the component for this favourite. */ public const COMPONENT = 'core_course'; /** string the favourite prefix itemtype in the favourites table. */ public const FAVOURITE_PREFIX = 'contentitem_'; /** string the recommendation prefix itemtype in the favourites table. */ public const RECOMMENDATION_PREFIX = 'recommend_'; /** string the cache name for recommendations. */ public const RECOMMENDATION_CACHE = 'recommendation_favourite_course_content_items'; /** * The content_item_service constructor. * * @param content_item_readonly_repository_interface $repository a content item repository. */ public function __construct(content_item_readonly_repository_interface $repository) { $this->repository = $repository; } /** * Returns an array of objects representing favourited content items. * * Each object contains the following properties: * itemtype: a string containing the 'itemtype' key used by the favourites subsystem. * ids[]: an array of ids, representing the content items within a component. * * Since two components can return (via their hook implementation) the same id, the itemtype is used for uniqueness. * * @param \stdClass $user * @return array */ private function get_favourite_content_items_for_user(\stdClass $user): array { $favcache = \cache::make('core', 'user_favourite_course_content_items'); $key = $user->id; $favmods = $favcache->get($key); if ($favmods !== false) { return $favmods; } $favourites = $this->get_content_favourites(self::FAVOURITE_PREFIX, \context_user::instance($user->id)); $favcache->set($key, $favourites); return $favourites; } /** * Returns an array of objects representing recommended content items. * * Each object contains the following properties: * itemtype: a string containing the 'itemtype' key used by the favourites subsystem. * ids[]: an array of ids, representing the content items within a component. * * Since two components can return (via their hook implementation) the same id, the itemtype is used for uniqueness. * * @return array */ private function get_recommendations(): array { global $CFG; $recommendationcache = \cache::make('core', self::RECOMMENDATION_CACHE); $key = $CFG->siteguest; $favmods = $recommendationcache->get($key); if ($favmods !== false) { return $favmods; } // Make sure the guest user exists in the database. if (!\core_user::get_user($CFG->siteguest)) { throw new \coding_exception('The guest user does not exist in the database.'); } // Make sure the guest user context exists. if (!$guestusercontext = \context_user::instance($CFG->siteguest, false)) { throw new \coding_exception('The guest user context does not exist.'); } $favourites = $this->get_content_favourites(self::RECOMMENDATION_PREFIX, $guestusercontext); $recommendationcache->set($CFG->siteguest, $favourites); return $favourites; } /** * Gets content favourites from the favourites system depending on the area. * * @param string $prefix Prefix for the item type. * @param \context_user $usercontext User context for the favourite * @return array An array of favourite objects. */ private function get_content_favourites(string $prefix, \context_user $usercontext): array { // Get all modules and any submodules which implement get_course_content_items() hook. // This gives us the set of all itemtypes which we'll use to register favourite content items. // The ids that each plugin returns will be used together with the itemtype to uniquely identify // each content item for favouriting. $pluginmanager = \core_plugin_manager::instance(); $plugins = $pluginmanager->get_plugins_of_type('mod'); $itemtypes = []; foreach ($plugins as $plugin) { // Add the mod itself. $itemtypes[] = $prefix . 'mod_' . $plugin->name; // Add any subplugins to the list of item types. $subplugins = $pluginmanager->get_subplugins_of_plugin('mod_' . $plugin->name); foreach ($subplugins as $subpluginname => $subplugininfo) { try { if (component_callback_exists($subpluginname, 'get_course_content_items')) { $itemtypes[] = $prefix . $subpluginname; } } catch (\moodle_exception $e) { debugging('Cannot get_course_content_items: ' . $e->getMessage(), DEBUG_DEVELOPER); } } } $ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext); $favourites = []; $favs = $ufservice->find_all_favourites(self::COMPONENT, $itemtypes); $favsreduced = array_reduce($favs, function($carry, $item) { $carry[$item->itemtype][$item->itemid] = 0; return $carry; }, []); foreach ($itemtypes as $type) { $favourites[] = (object) [ 'itemtype' => $type, 'ids' => isset($favsreduced[$type]) ? array_keys($favsreduced[$type]) : [] ]; } return $favourites; } /** * Get all content items which may be added to courses, irrespective of course caps, for site admin views, etc. * * @param \stdClass $user the user object. * @return array the array of exported content items. */ public function get_all_content_items(\stdClass $user): array { $allcontentitems = $this->repository->find_all(); return $this->export_content_items($user, $allcontentitems); } /** * Get content items which name matches a certain pattern and may be added to courses, * irrespective of course caps, for site admin views, etc. * * @param \stdClass $user The user object. * @param string $pattern The search pattern. * @return array The array of exported content items. */ public function get_content_items_by_name_pattern(\stdClass $user, string $pattern): array { $allcontentitems = $this->repository->find_all(); $filteredcontentitems = array_filter($allcontentitems, function($contentitem) use ($pattern) { return preg_match("/$pattern/i", $contentitem->get_title()->get_value()); }); return $this->export_content_items($user, $filteredcontentitems); } /** * Export content items. * * @param \stdClass $user The user object. * @param array $contentitems The content items array. * @return array The array of exported content items. */ private function export_content_items(\stdClass $user, $contentitems) { global $PAGE; // Export the objects to get the formatted objects for transfer/display. $favourites = $this->get_favourite_content_items_for_user($user); $recommendations = $this->get_recommendations(); $ciexporter = new course_content_items_exporter( $contentitems, [ 'context' => \context_system::instance(), 'favouriteitems' => $favourites, 'recommended' => $recommendations ] ); $exported = $ciexporter->export($PAGE->get_renderer('core')); // Sort by title for return. \core_collator::asort_objects_by_property($exported->content_items, 'title'); return array_values($exported->content_items); } /** * Return a representation of the available content items, for a user in a course. * * @param \stdClass $user the user to check access for. * @param \stdClass $course the course to scope the content items to. * @param array $linkparams the desired section to return to. * @return \stdClass[] the content items, scoped to a course. */ public function get_content_items_for_user_in_course(\stdClass $user, \stdClass $course, array $linkparams = []): array { global $PAGE; if (!has_capability('moodle/course:manageactivities', \context_course::instance($course->id), $user)) { return []; } // Get all the visible content items. $allcontentitems = $this->repository->find_all_for_course($course, $user); // Content items can only originate from modules or submodules. $pluginmanager = \core_plugin_manager::instance(); $components = \core_component::get_component_list(); $parents = []; foreach ($allcontentitems as $contentitem) { if (!in_array($contentitem->get_component_name(), array_keys($components['mod']))) { // It could be a subplugin. $info = $pluginmanager->get_plugin_info($contentitem->get_component_name()); if (!is_null($info)) { $parent = $info->get_parent_plugin(); if ($parent != false) { if (in_array($parent, array_keys($components['mod']))) { $parents[$contentitem->get_component_name()] = $parent; continue; } } } throw new \moodle_exception('Only modules and submodules can generate content items. \'' . $contentitem->get_component_name() . '\' is neither.'); } $parents[$contentitem->get_component_name()] = $contentitem->get_component_name(); } // Now, check access to these items for the user. $availablecontentitems = array_filter($allcontentitems, function($contentitem) use ($course, $user, $parents) { // Check the parent module access for the user. return course_allowed_module($course, explode('_', $parents[$contentitem->get_component_name()])[1], $user); }); // Add the link params to the link, if any have been provided. if (!empty($linkparams)) { $availablecontentitems = array_map(function ($item) use ($linkparams) { $item->get_link()->params($linkparams); return $item; }, $availablecontentitems); } // Export the objects to get the formatted objects for transfer/display. $favourites = $this->get_favourite_content_items_for_user($user); $recommended = $this->get_recommendations(); $ciexporter = new course_content_items_exporter( $availablecontentitems, [ 'context' => \context_course::instance($course->id), 'favouriteitems' => $favourites, 'recommended' => $recommended ] ); $exported = $ciexporter->export($PAGE->get_renderer('course')); // Sort by title for return. \core_collator::asort_objects_by_property($exported->content_items, 'title'); return array_values($exported->content_items); } /** * Add a content item to a user's favourites. * * @param \stdClass $user the user whose favourite this is. * @param string $componentname the name of the component from which the content item originates. * @param int $contentitemid the id of the content item. * @return \stdClass the exported content item. */ public function add_to_user_favourites(\stdClass $user, string $componentname, int $contentitemid): \stdClass { $usercontext = \context_user::instance($user->id); $ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext); // Because each plugin decides its own ids for content items, a combination of // itemtype and id is used to guarantee uniqueness across all content items. $itemtype = self::FAVOURITE_PREFIX . $componentname; $ufservice->create_favourite(self::COMPONENT, $itemtype, $contentitemid, $usercontext); $favcache = \cache::make('core', 'user_favourite_course_content_items'); $favcache->delete($user->id); $items = $this->get_all_content_items($user); return $items[array_search($contentitemid, array_column($items, 'id'))]; } /** * Remove the content item from a user's favourites. * * @param \stdClass $user the user whose favourite this is. * @param string $componentname the name of the component from which the content item originates. * @param int $contentitemid the id of the content item. * @return \stdClass the exported content item. */ public function remove_from_user_favourites(\stdClass $user, string $componentname, int $contentitemid): \stdClass { $usercontext = \context_user::instance($user->id); $ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext); // Because each plugin decides its own ids for content items, a combination of // itemtype and id is used to guarantee uniqueness across all content items. $itemtype = self::FAVOURITE_PREFIX . $componentname; $ufservice->delete_favourite(self::COMPONENT, $itemtype, $contentitemid, $usercontext); $favcache = \cache::make('core', 'user_favourite_course_content_items'); $favcache->delete($user->id); $items = $this->get_all_content_items($user); return $items[array_search($contentitemid, array_column($items, 'id'))]; } /** * Toggle an activity to being recommended or not. * * @param string $itemtype The component such as mod_assign, or assignsubmission_file * @param int $itemid The id related to this component item. * @return bool True on creating a favourite, false on deleting it. */ public function toggle_recommendation(string $itemtype, int $itemid): bool { global $CFG; $context = \context_system::instance(); $itemtype = self::RECOMMENDATION_PREFIX . $itemtype; // Favourites are created using a user context. We'll use the site guest user ID as that should not change and there // can be only one. $usercontext = \context_user::instance($CFG->siteguest); $recommendationcache = \cache::make('core', self::RECOMMENDATION_CACHE); $favouritefactory = \core_favourites\service_factory::get_service_for_user_context($usercontext); if ($favouritefactory->favourite_exists(self::COMPONENT, $itemtype, $itemid, $context)) { $favouritefactory->delete_favourite(self::COMPONENT, $itemtype, $itemid, $context); $result = $recommendationcache->delete($CFG->siteguest); return false; } else { $favouritefactory->create_favourite(self::COMPONENT, $itemtype, $itemid, $context); $result = $recommendationcache->delete($CFG->siteguest); return true; } } }
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | ���֧ߧ֧�ѧ�ڧ� ����ѧߧڧ��: 0 |
proxy
|
phpinfo
|
���ѧ����ۧܧ�