├── LICENSE ├── README.md ├── composer.json └── src ├── HTTPMessage.php ├── OAuth ├── OAuthConsumer.php ├── OAuthDataStore.php ├── OAuthException.php ├── OAuthRequest.php ├── OAuthServer.php ├── OAuthSignatureMethod.php ├── OAuthSignatureMethod_HMAC_SHA1.php ├── OAuthSignatureMethod_HMAC_SHA256.php ├── OAuthToken.php └── OAuthUtil.php ├── Profile ├── Item.php ├── Message.php ├── ResourceHandler.php └── ServiceDefinition.php └── ToolProvider ├── ConsumerNonce.php ├── ContentItem.php ├── ContentItemImage.php ├── ContentItemPlacement.php ├── Context.php ├── DataConnector ├── DataConnector.php ├── DataConnector_mysql.php ├── DataConnector_pdo.php └── DataConnector_pdo_sqlite.php ├── MediaType ├── Message.php ├── ResourceHandler.php ├── SecurityContract.php ├── ToolProfile.php └── ToolProxy.php ├── OAuthDataStore.php ├── Outcome.php ├── ResourceLink.php ├── ResourceLinkShare.php ├── ResourceLinkShareKey.php ├── Service ├── Membership.php ├── Service.php └── ToolSettings.php ├── ToolConsumer.php ├── ToolProvider.php ├── ToolProxy.php └── User.php /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This set of PHP classes encapsulates the code required by a Learning Tools Interoperability® (LTI®) compliant tool provider to communicate with an LTI tool consumer. 2 | It includes support for LTI 1.1 and the unofficial extensions to LTI 1.0, as well as the registration process and services of LTI 2.0. 3 | These classes are an extension of the LTI Tool Provider class library created by the ceLTIc project (http://www.spvsoftwareproducts.com/php/lti_tool_provider/). 4 | 5 | Whilst supporting LTI is relatively simple, the benefits to using a class library like this one are: 6 | * the abstraction layer provided by the classes keeps the LTI communications separate from the application code; 7 | * the code can be re-used between multiple tool providers; 8 | * LTI data is transformed into useful objects and missing data automatically replaced with sensible defaults; 9 | * the outcomes service function uses LTI 1.1 or the unofficial outcomes extension according to whichever is supported by the tool consumer; 10 | * the unofficial extensions for memberships and setting services are supported; 11 | * additional functionality is included to: 12 | * enable/disable a consumer key; 13 | * set start and end times for enabling access for each consumer key; 14 | * set up arrangements such that users from different resource links can all collaborate together within a single tool provider link; 15 | * tool providers can take advantage of LTI updates with minimal impact on their application code. 16 | 17 | The wiki area of this repository contains [documentation](https://github.com/IMSGlobal/LTI-Tool-Provider-Library-PHP/wiki) for this library. The [rating LTI application](https://github.com/IMSGlobal/LTI-Sample-Tool-Provider-PHP) is based on this library to further illustrate how it can be used. 18 | 19 | © 2016 IMS Global Learning Consortium Inc. All Rights Reserved. Trademark Policy - (www.imsglobal.org/trademarks) 20 | 21 | Learning Tools Interoperability and LTI are registered trademarks of IMS Global Learning Consortium Inc. 22 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "imsglobal/lti", 3 | "version" : "3.0.2", 4 | "description": "LTI Tool Provider Library", 5 | "keywords": ["lti"], 6 | "homepage": "https://www.imsglobal.org/lti", 7 | "type": "library", 8 | "license": "Apache-2.0", 9 | "authors":[ 10 | { 11 | "name": "Stephen Vickers", 12 | "email": "svickers@imsglobal.org" 13 | } 14 | ], 15 | "require":{ 16 | "php": ">=5.6.0" 17 | }, 18 | "autoload":{ 19 | "psr-4": { 20 | "IMSGlobal\\LTI\\": "src/" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/HTTPMessage.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright IMS Global Learning Consortium Inc 10 | * @date 2016 11 | * @version 3.0.0 12 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 13 | */ 14 | class HTTPMessage 15 | { 16 | 17 | /** 18 | * True if message was sent successfully. 19 | * 20 | * @var boolean $ok 21 | */ 22 | public $ok = false; 23 | 24 | /** 25 | * Request body. 26 | * 27 | * @var request $request 28 | */ 29 | public $request = null; 30 | 31 | /** 32 | * Request headers. 33 | * 34 | * @var request_headers $requestHeaders 35 | */ 36 | public $requestHeaders = ''; 37 | 38 | /** 39 | * Response body. 40 | * 41 | * @var response $response 42 | */ 43 | public $response = null; 44 | 45 | /** 46 | * Response headers. 47 | * 48 | * @var response_headers $responseHeaders 49 | */ 50 | public $responseHeaders = ''; 51 | 52 | /** 53 | * Status of response (0 if undetermined). 54 | * 55 | * @var status $status 56 | */ 57 | public $status = 0; 58 | 59 | /** 60 | * Error message 61 | * 62 | * @var error $error 63 | */ 64 | public $error = ''; 65 | 66 | /** 67 | * Request URL. 68 | * 69 | * @var url $url 70 | */ 71 | private $url = null; 72 | 73 | /** 74 | * Request method. 75 | * 76 | * @var method $method 77 | */ 78 | private $method = null; 79 | 80 | /** 81 | * Class constructor. 82 | * 83 | * @param string $url URL to send request to 84 | * @param string $method Request method to use (optional, default is GET) 85 | * @param mixed $params Associative array of parameter values to be passed or message body (optional, default is none) 86 | * @param string $header Values to include in the request header (optional, default is none) 87 | */ 88 | function __construct($url, $method = 'GET', $params = null, $header = null) 89 | { 90 | 91 | $this->url = $url; 92 | $this->method = strtoupper($method); 93 | if (is_array($params)) { 94 | $this->request = http_build_query($params); 95 | } else { 96 | $this->request = $params; 97 | } 98 | if (!empty($header)) { 99 | $this->requestHeaders = explode("\n", $header); 100 | } 101 | 102 | } 103 | 104 | /** 105 | * Send the request to the target URL. 106 | * 107 | * @return boolean True if the request was successful 108 | */ 109 | public function send() 110 | { 111 | 112 | $this->ok = false; 113 | // Try using curl if available 114 | if (function_exists('curl_init')) { 115 | $resp = ''; 116 | $ch = curl_init(); 117 | curl_setopt($ch, CURLOPT_URL, $this->url); 118 | if (!empty($this->requestHeaders)) { 119 | curl_setopt($ch, CURLOPT_HTTPHEADER, $this->requestHeaders); 120 | } else { 121 | curl_setopt($ch, CURLOPT_HEADER, 0); 122 | } 123 | if ($this->method === 'POST') { 124 | curl_setopt($ch, CURLOPT_POST, true); 125 | curl_setopt($ch, CURLOPT_POSTFIELDS, $this->request); 126 | } else if ($this->method !== 'GET') { 127 | curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->method); 128 | if (!is_null($this->request)) { 129 | curl_setopt($ch, CURLOPT_POSTFIELDS, $this->request); 130 | } 131 | } 132 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 133 | curl_setopt($ch, CURLINFO_HEADER_OUT, true); 134 | curl_setopt($ch, CURLOPT_HEADER, true); 135 | //curl_setopt($ch, CURLOPT_SSLVERSION,3); 136 | $chResp = curl_exec($ch); 137 | $this->ok = $chResp !== false; 138 | if ($this->ok) { 139 | $chResp = str_replace("\r\n", "\n", $chResp); 140 | $chRespSplit = explode("\n\n", $chResp, 2); 141 | if ((count($chRespSplit) > 1) && (substr($chRespSplit[1], 0, 5) === 'HTTP/')) { 142 | $chRespSplit = explode("\n\n", $chRespSplit[1], 2); 143 | } 144 | $this->responseHeaders = $chRespSplit[0]; 145 | $resp = $chRespSplit[1]; 146 | $this->status = curl_getinfo($ch, CURLINFO_HTTP_CODE); 147 | $this->ok = $this->status < 400; 148 | if (!$this->ok) { 149 | $this->error = curl_error($ch); 150 | } 151 | } 152 | $this->requestHeaders = str_replace("\r\n", "\n", curl_getinfo($ch, CURLINFO_HEADER_OUT)); 153 | curl_close($ch); 154 | $this->response = $resp; 155 | } else { 156 | // Try using fopen if curl was not available 157 | $opts = array('method' => $this->method, 158 | 'content' => $this->request 159 | ); 160 | if (!empty($this->requestHeaders)) { 161 | $opts['header'] = $this->requestHeaders; 162 | } 163 | try { 164 | $ctx = stream_context_create(array('http' => $opts)); 165 | $fp = @fopen($this->url, 'rb', false, $ctx); 166 | if ($fp) { 167 | $resp = @stream_get_contents($fp); 168 | $this->ok = $resp !== false; 169 | } 170 | } catch (\Exception $e) { 171 | $this->ok = false; 172 | } 173 | } 174 | 175 | return $this->ok; 176 | 177 | } 178 | 179 | } 180 | -------------------------------------------------------------------------------- /src/OAuth/OAuthConsumer.php: -------------------------------------------------------------------------------- 1 | key = $key; 19 | $this->secret = $secret; 20 | $this->callback_url = $callback_url; 21 | } 22 | 23 | function __toString() { 24 | return "OAuthConsumer[key=$this->key,secret=$this->secret]"; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/OAuth/OAuthDataStore.php: -------------------------------------------------------------------------------- 1 | parameters = $parameters; 27 | $this->http_method = $http_method; 28 | $this->http_url = $http_url; 29 | 30 | } 31 | 32 | 33 | /** 34 | * attempt to build up a request from what was passed to the server 35 | */ 36 | public static function from_request($http_method = null, $http_url = null, $parameters = null) { 37 | 38 | $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on") 39 | ? 'http' 40 | : 'https'; 41 | $http_url = ($http_url) ? $http_url : $scheme . 42 | '://' . $_SERVER['SERVER_NAME'] . 43 | ':' . 44 | $_SERVER['SERVER_PORT'] . 45 | $_SERVER['REQUEST_URI']; 46 | $http_method = ($http_method) ? $http_method : $_SERVER['REQUEST_METHOD']; 47 | 48 | // We weren't handed any parameters, so let's find the ones relevant to 49 | // this request. 50 | // If you run XML-RPC or similar you should use this to provide your own 51 | // parsed parameter-list 52 | if (!$parameters) { 53 | // Find request headers 54 | $request_headers = OAuthUtil::get_headers(); 55 | 56 | // Parse the query-string to find GET parameters 57 | if (isset($_SERVER['QUERY_STRING'])) { 58 | $parameters = OAuthUtil::parse_parameters($_SERVER['QUERY_STRING']); 59 | } else { 60 | $parameters = array(); 61 | } 62 | 63 | // It's a POST request of the proper content-type, so parse POST 64 | // parameters and add those overriding any duplicates from GET 65 | if ($http_method == "POST" 66 | && isset($request_headers['Content-Type']) 67 | && strstr($request_headers['Content-Type'], 'application/x-www-form-urlencoded')) { 68 | $post_data = OAuthUtil::parse_parameters(file_get_contents(self::$POST_INPUT)); 69 | $parameters = array_merge($parameters, $post_data); 70 | } 71 | 72 | // We have a Authorization-header with OAuth data. Parse the header 73 | // and add those overriding any duplicates from GET or POST 74 | if (isset($request_headers['Authorization']) && substr($request_headers['Authorization'], 0, 6) == 'OAuth ') { 75 | $header_parameters = OAuthUtil::split_header($request_headers['Authorization']); 76 | $parameters = array_merge($parameters, $header_parameters); 77 | } 78 | 79 | } 80 | 81 | return new OAuthRequest($http_method, $http_url, $parameters); 82 | } 83 | 84 | /** 85 | * pretty much a helper function to set up the request 86 | */ 87 | public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters = null) { 88 | 89 | $parameters = ($parameters) ? $parameters : array(); 90 | $defaults = array('oauth_version' => OAuthRequest::$version, 91 | 'oauth_nonce' => OAuthRequest::generate_nonce(), 92 | 'oauth_timestamp' => OAuthRequest::generate_timestamp(), 93 | 'oauth_consumer_key' => $consumer->key); 94 | if ($token) 95 | $defaults['oauth_token'] = $token->key; 96 | 97 | $parameters = array_merge($defaults, $parameters); 98 | 99 | return new OAuthRequest($http_method, $http_url, $parameters); 100 | 101 | } 102 | 103 | public function set_parameter($name, $value, $allow_duplicates = true) { 104 | 105 | if ($allow_duplicates && isset($this->parameters[$name])) { 106 | // We have already added parameter(s) with this name, so add to the list 107 | if (is_scalar($this->parameters[$name])) { 108 | // This is the first duplicate, so transform scalar (string) 109 | // into an array so we can add the duplicates 110 | $this->parameters[$name] = array($this->parameters[$name]); 111 | } 112 | 113 | $this->parameters[$name][] = $value; 114 | } else { 115 | $this->parameters[$name] = $value; 116 | } 117 | } 118 | 119 | public function get_parameter($name) { 120 | return isset($this->parameters[$name]) ? $this->parameters[$name] : null; 121 | } 122 | 123 | public function get_parameters() { 124 | return $this->parameters; 125 | } 126 | 127 | public function unset_parameter($name) { 128 | unset($this->parameters[$name]); 129 | } 130 | 131 | /** 132 | * The request parameters, sorted and concatenated into a normalized string. 133 | * @return string 134 | */ 135 | public function get_signable_parameters() { 136 | 137 | // Grab all parameters 138 | $params = $this->parameters; 139 | 140 | // Remove oauth_signature if present 141 | // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.") 142 | if (isset($params['oauth_signature'])) { 143 | unset($params['oauth_signature']); 144 | } 145 | 146 | return OAuthUtil::build_http_query($params); 147 | 148 | } 149 | 150 | /** 151 | * Returns the base string of this request 152 | * 153 | * The base string defined as the method, the url 154 | * and the parameters (normalized), each urlencoded 155 | * and the concated with &. 156 | */ 157 | public function get_signature_base_string() { 158 | $parts = array( 159 | $this->get_normalized_http_method(), 160 | $this->get_normalized_http_url(), 161 | $this->get_signable_parameters() 162 | ); 163 | 164 | $parts = OAuthUtil::urlencode_rfc3986($parts); 165 | 166 | return implode('&', $parts); 167 | 168 | } 169 | 170 | /** 171 | * just uppercases the http method 172 | */ 173 | public function get_normalized_http_method() { 174 | return strtoupper($this->http_method); 175 | } 176 | 177 | /** 178 | * parses the url and rebuilds it to be 179 | * scheme://host/path 180 | */ 181 | public function get_normalized_http_url() { 182 | 183 | $parts = parse_url($this->http_url); 184 | 185 | $scheme = (isset($parts['scheme'])) ? $parts['scheme'] : 'http'; 186 | $port = (isset($parts['port'])) ? $parts['port'] : (($scheme == 'https') ? '443' : '80'); 187 | $host = (isset($parts['host'])) ? strtolower($parts['host']) : ''; 188 | $path = (isset($parts['path'])) ? $parts['path'] : ''; 189 | 190 | if (($scheme == 'https' && $port != '443') 191 | || ($scheme == 'http' && $port != '80')) { 192 | $host = "$host:$port"; 193 | } 194 | 195 | return "$scheme://$host$path"; 196 | 197 | } 198 | 199 | /** 200 | * builds a url usable for a GET request 201 | */ 202 | public function to_url() { 203 | 204 | $post_data = $this->to_postdata(); 205 | $out = $this->get_normalized_http_url(); 206 | if ($post_data) { 207 | $out .= '?'.$post_data; 208 | } 209 | 210 | return $out; 211 | 212 | } 213 | 214 | /** 215 | * builds the data one would send in a POST request 216 | */ 217 | public function to_postdata() { 218 | return OAuthUtil::build_http_query($this->parameters); 219 | } 220 | 221 | /** 222 | * builds the Authorization: header 223 | */ 224 | public function to_header($realm = null) { 225 | 226 | $first = true; 227 | if($realm) { 228 | $out = 'Authorization: OAuth realm="' . OAuthUtil::urlencode_rfc3986($realm) . '"'; 229 | $first = false; 230 | } else 231 | $out = 'Authorization: OAuth'; 232 | 233 | $total = array(); 234 | foreach ($this->parameters as $k => $v) { 235 | if (substr($k, 0, 5) != "oauth") continue; 236 | if (is_array($v)) { 237 | throw new OAuthException('Arrays not supported in headers'); 238 | } 239 | $out .= ($first) ? ' ' : ','; 240 | $out .= OAuthUtil::urlencode_rfc3986($k) . 241 | '="' . 242 | OAuthUtil::urlencode_rfc3986($v) . 243 | '"'; 244 | $first = false; 245 | } 246 | 247 | return $out; 248 | 249 | } 250 | 251 | public function __toString() { 252 | return $this->to_url(); 253 | } 254 | 255 | 256 | public function sign_request($signature_method, $consumer, $token) { 257 | 258 | $this->set_parameter( 259 | "oauth_signature_method", 260 | $signature_method->get_name(), 261 | false 262 | ); 263 | $signature = $this->build_signature($signature_method, $consumer, $token); 264 | $this->set_parameter("oauth_signature", $signature, false); 265 | 266 | } 267 | 268 | public function build_signature($signature_method, $consumer, $token) { 269 | $signature = $signature_method->build_signature($this, $consumer, $token); 270 | return $signature; 271 | } 272 | 273 | /** 274 | * util function: current timestamp 275 | */ 276 | private static function generate_timestamp() { 277 | return time(); 278 | } 279 | 280 | /** 281 | * util function: current nonce 282 | */ 283 | private static function generate_nonce() { 284 | $mt = microtime(); 285 | $rand = mt_rand(); 286 | 287 | return md5($mt . $rand); // md5s look nicer than numbers 288 | } 289 | 290 | } 291 | -------------------------------------------------------------------------------- /src/OAuth/OAuthServer.php: -------------------------------------------------------------------------------- 1 | data_store = $data_store; 22 | } 23 | 24 | public function add_signature_method($signature_method) { 25 | $this->signature_methods[$signature_method->get_name()] = $signature_method; 26 | } 27 | 28 | // high level functions 29 | 30 | /** 31 | * process a request_token request 32 | * returns the request token on success 33 | */ 34 | public function fetch_request_token(&$request) { 35 | 36 | $this->get_version($request); 37 | 38 | $consumer = $this->get_consumer($request); 39 | 40 | // no token required for the initial token request 41 | $token = NULL; 42 | 43 | $this->check_signature($request, $consumer, $token); 44 | 45 | // Rev A change 46 | $callback = $request->get_parameter('oauth_callback'); 47 | $new_token = $this->data_store->new_request_token($consumer, $callback); 48 | 49 | return $new_token; 50 | 51 | } 52 | 53 | /** 54 | * process an access_token request 55 | * returns the access token on success 56 | */ 57 | public function fetch_access_token(&$request) { 58 | 59 | $this->get_version($request); 60 | 61 | $consumer = $this->get_consumer($request); 62 | 63 | // requires authorized request token 64 | $token = $this->get_token($request, $consumer, "request"); 65 | 66 | $this->check_signature($request, $consumer, $token); 67 | 68 | // Rev A change 69 | $verifier = $request->get_parameter('oauth_verifier'); 70 | $new_token = $this->data_store->new_access_token($token, $consumer, $verifier); 71 | 72 | return $new_token; 73 | 74 | } 75 | 76 | /** 77 | * verify an api call, checks all the parameters 78 | */ 79 | public function verify_request(&$request) { 80 | 81 | $this->get_version($request); 82 | $consumer = $this->get_consumer($request); 83 | $token = $this->get_token($request, $consumer, "access"); 84 | $this->check_signature($request, $consumer, $token); 85 | 86 | return array($consumer, $token); 87 | 88 | } 89 | 90 | // Internals from here 91 | /** 92 | * version 1 93 | */ 94 | private function get_version(&$request) { 95 | 96 | $version = $request->get_parameter("oauth_version"); 97 | if (!$version) { 98 | // Service Providers MUST assume the protocol version to be 1.0 if this parameter is not present. 99 | // Chapter 7.0 ("Accessing Protected Ressources") 100 | $version = '1.0'; 101 | } 102 | if ($version !== $this->version) { 103 | throw new OAuthException("OAuth version '$version' not supported"); 104 | } 105 | 106 | return $version; 107 | 108 | } 109 | 110 | /** 111 | * figure out the signature with some defaults 112 | */ 113 | private function get_signature_method($request) { 114 | 115 | $signature_method = $request instanceof OAuthRequest 116 | ? $request->get_parameter('oauth_signature_method') : NULL; 117 | 118 | if (!$signature_method) { 119 | // According to chapter 7 ("Accessing Protected Ressources") the signature-method 120 | // parameter is required, and we can't just fallback to PLAINTEXT 121 | throw new OAuthException('No signature method parameter. This parameter is required'); 122 | } 123 | 124 | if (!in_array($signature_method, 125 | array_keys($this->signature_methods))) { 126 | throw new OAuthException( 127 | "Signature method '$signature_method' not supported " . 128 | 'try one of the following: ' . 129 | implode(', ', array_keys($this->signature_methods)) 130 | ); 131 | } 132 | 133 | return $this->signature_methods[$signature_method]; 134 | 135 | } 136 | 137 | /** 138 | * try to find the consumer for the provided request's consumer key 139 | */ 140 | private function get_consumer($request) { 141 | 142 | $consumer_key = $request instanceof OAuthRequest 143 | ? $request->get_parameter('oauth_consumer_key') : NULL; 144 | 145 | if (!$consumer_key) { 146 | throw new OAuthException('Invalid consumer key'); 147 | } 148 | 149 | $consumer = $this->data_store->lookup_consumer($consumer_key); 150 | if (!$consumer) { 151 | throw new OAuthException('Invalid consumer'); 152 | } 153 | 154 | return $consumer; 155 | 156 | } 157 | 158 | /** 159 | * try to find the token for the provided request's token key 160 | */ 161 | private function get_token($request, $consumer, $token_type="access") { 162 | 163 | $token_field = $request instanceof OAuthRequest 164 | ? $request->get_parameter('oauth_token') : NULL; 165 | 166 | $token = $this->data_store->lookup_token($consumer, $token_type, $token_field); 167 | if (!$token) { 168 | throw new OAuthException("Invalid $token_type token: $token_field"); 169 | } 170 | 171 | return $token; 172 | 173 | } 174 | 175 | /** 176 | * all-in-one function to check the signature on a request 177 | * should guess the signature method appropriately 178 | */ 179 | private function check_signature($request, $consumer, $token) { 180 | 181 | // this should probably be in a different method 182 | $timestamp = $request instanceof OAuthRequest 183 | ? $request->get_parameter('oauth_timestamp') 184 | : NULL; 185 | $nonce = $request instanceof OAuthRequest 186 | ? $request->get_parameter('oauth_nonce') 187 | : NULL; 188 | 189 | $this->check_timestamp($timestamp); 190 | $this->check_nonce($consumer, $token, $nonce, $timestamp); 191 | 192 | $signature_method = $this->get_signature_method($request); 193 | 194 | $signature = $request->get_parameter('oauth_signature'); 195 | $valid_sig = $signature_method->check_signature($request, $consumer, $token, $signature); 196 | 197 | if (!$valid_sig) { 198 | throw new OAuthException('Invalid signature'); 199 | } 200 | } 201 | 202 | /** 203 | * check that the timestamp is new enough 204 | */ 205 | private function check_timestamp($timestamp) { 206 | if(!$timestamp) 207 | throw new OAuthException('Missing timestamp parameter. The parameter is required'); 208 | 209 | // verify that timestamp is recentish 210 | $now = time(); 211 | if (abs($now - $timestamp) > $this->timestamp_threshold) { 212 | throw new OAuthException("Expired timestamp, yours $timestamp, ours $now"); 213 | } 214 | 215 | } 216 | 217 | /** 218 | * check that the nonce is not repeated 219 | */ 220 | private function check_nonce($consumer, $token, $nonce, $timestamp) { 221 | 222 | if(!$nonce) 223 | throw new OAuthException('Missing nonce parameter. The parameter is required'); 224 | 225 | // verify that the nonce is uniqueish 226 | $found = $this->data_store->lookup_nonce($consumer, $token, $nonce, $timestamp); 227 | if ($found) { 228 | throw new OAuthException("Nonce already used: $nonce"); 229 | } 230 | 231 | } 232 | 233 | } 234 | -------------------------------------------------------------------------------- /src/OAuth/OAuthSignatureMethod.php: -------------------------------------------------------------------------------- 1 | build_signature($request, $consumer, $token); 46 | 47 | // Check for zero length, although unlikely here 48 | if (strlen($built) == 0 || strlen($signature) == 0) { 49 | return false; 50 | } 51 | 52 | if (strlen($built) != strlen($signature)) { 53 | return false; 54 | } 55 | 56 | // Avoid a timing leak with a (hopefully) time insensitive compare 57 | $result = 0; 58 | for ($i = 0; $i < strlen($signature); $i++) { 59 | $result |= ord($built{$i}) ^ ord($signature{$i}); 60 | } 61 | 62 | return $result == 0; 63 | 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/OAuth/OAuthSignatureMethod_HMAC_SHA1.php: -------------------------------------------------------------------------------- 1 | get_signature_base_string(); 28 | $request->base_string = $base_string; 29 | 30 | $key_parts = array( 31 | $consumer->secret, 32 | ($token) ? $token->secret : "" 33 | ); 34 | 35 | $key_parts = OAuthUtil::urlencode_rfc3986($key_parts); 36 | $key = implode('&', $key_parts); 37 | 38 | return base64_encode(hash_hmac('sha1', $base_string, $key, true)); 39 | 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/OAuth/OAuthSignatureMethod_HMAC_SHA256.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright IMS Global Learning Consortium Inc 10 | * @date 2016 11 | * @version 2015-11-30 12 | * @license https://opensource.org/licenses/MIT The MIT License 13 | */ 14 | /** 15 | * The HMAC-SHA256 signature method uses the HMAC-SHA256 signature algorithm as defined in [RFC6234] 16 | * where the Signature Base String is the text and the key is the concatenated values (each first 17 | * encoded per Parameter Encoding) of the Consumer Secret and Token Secret, separated by an '&' 18 | * character (ASCII code 38) even if empty. 19 | */ 20 | class OAuthSignatureMethod_HMAC_SHA256 extends OAuthSignatureMethod { 21 | 22 | function get_name() { 23 | return "HMAC-SHA256"; 24 | } 25 | 26 | public function build_signature($request, $consumer, $token) { 27 | 28 | $base_string = $request->get_signature_base_string(); 29 | $request->base_string = $base_string; 30 | 31 | $key_parts = array( 32 | $consumer->secret, 33 | ($token) ? $token->secret : "" 34 | ); 35 | 36 | $key_parts = OAuthUtil::urlencode_rfc3986($key_parts); 37 | $key = implode('&', $key_parts); 38 | 39 | return base64_encode(hash_hmac('sha256', $base_string, $key, true)); 40 | 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/OAuth/OAuthToken.php: -------------------------------------------------------------------------------- 1 | key = $key; 24 | $this->secret = $secret; 25 | } 26 | 27 | /** 28 | * generates the basic string serialization of a token that a server 29 | * would respond to request_token and access_token calls with 30 | */ 31 | function to_string() { 32 | return 'oauth_token=' . 33 | OAuthUtil::urlencode_rfc3986($this->key) . 34 | '&oauth_token_secret=' . 35 | OAuthUtil::urlencode_rfc3986($this->secret); 36 | } 37 | 38 | function __toString() { 39 | return $this->to_string(); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/OAuth/OAuthUtil.php: -------------------------------------------------------------------------------- 1 | $h) { 42 | $params[$h] = OAuthUtil::urldecode_rfc3986(empty($matches[3][$i]) ? $matches[4][$i] : $matches[3][$i]); 43 | } 44 | if (isset($params['realm'])) { 45 | unset($params['realm']); 46 | } 47 | } 48 | 49 | return $params; 50 | 51 | } 52 | 53 | // helper to try to sort out headers for people who aren't running apache 54 | public static function get_headers() { 55 | 56 | if (function_exists('apache_request_headers')) { 57 | // we need this to get the actual Authorization: header 58 | // because apache tends to tell us it doesn't exist 59 | $headers = apache_request_headers(); 60 | 61 | // sanitize the output of apache_request_headers because 62 | // we always want the keys to be Cased-Like-This and arh() 63 | // returns the headers in the same case as they are in the 64 | // request 65 | $out = array(); 66 | foreach ($headers AS $key => $value) { 67 | $key = str_replace(" ", "-", ucwords(strtolower(str_replace("-", " ", $key)))); 68 | $out[$key] = $value; 69 | } 70 | } else { 71 | // otherwise we don't have apache and are just going to have to hope 72 | // that $_SERVER actually contains what we need 73 | $out = array(); 74 | if( isset($_SERVER['CONTENT_TYPE']) ) 75 | $out['Content-Type'] = $_SERVER['CONTENT_TYPE']; 76 | if( isset($_ENV['CONTENT_TYPE']) ) 77 | $out['Content-Type'] = $_ENV['CONTENT_TYPE']; 78 | 79 | foreach ($_SERVER as $key => $value) { 80 | if (substr($key, 0, 5) == 'HTTP_') { 81 | // this is chaos, basically it is just there to capitalize the first 82 | // letter of every word that is not an initial HTTP and strip HTTP 83 | // code from przemek 84 | $key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key, 5))))); 85 | $out[$key] = $value; 86 | } 87 | } 88 | } 89 | return $out; 90 | } 91 | 92 | // This function takes a input like a=b&a=c&d=e and returns the parsed 93 | // parameters like this 94 | // array('a' => array('b','c'), 'd' => 'e') 95 | public static function parse_parameters( $input ) { 96 | 97 | if (!isset($input) || !$input) return array(); 98 | 99 | $pairs = explode('&', $input); 100 | 101 | $parsed_parameters = array(); 102 | foreach ($pairs as $pair) { 103 | $split = explode('=', $pair, 2); 104 | $parameter = self::urldecode_rfc3986($split[0]); 105 | $value = isset($split[1]) ? self::urldecode_rfc3986($split[1]) : ''; 106 | 107 | if (isset($parsed_parameters[$parameter])) { 108 | // We have already recieved parameter(s) with this name, so add to the list 109 | // of parameters with this name 110 | 111 | if (is_scalar($parsed_parameters[$parameter])) { 112 | // This is the first duplicate, so transform scalar (string) into an array 113 | // so we can add the duplicates 114 | $parsed_parameters[$parameter] = array($parsed_parameters[$parameter]); 115 | } 116 | 117 | $parsed_parameters[$parameter][] = $value; 118 | } else { 119 | $parsed_parameters[$parameter] = $value; 120 | } 121 | } 122 | 123 | return $parsed_parameters; 124 | 125 | } 126 | 127 | public static function build_http_query($params) { 128 | 129 | if (!$params) return ''; 130 | 131 | // Urlencode both keys and values 132 | $keys = OAuthUtil::urlencode_rfc3986(array_keys($params)); 133 | $values = OAuthUtil::urlencode_rfc3986(array_values($params)); 134 | $params = array_combine($keys, $values); 135 | 136 | // Parameters are sorted by name, using lexicographical byte value ordering. 137 | // Ref: Spec: 9.1.1 (1) 138 | uksort($params, 'strcmp'); 139 | 140 | $pairs = array(); 141 | foreach ($params as $parameter => $value) { 142 | if (is_array($value)) { 143 | // If two or more parameters share the same name, they are sorted by their value 144 | // Ref: Spec: 9.1.1 (1) 145 | // June 12th, 2010 - changed to sort because of issue 164 by hidetaka 146 | sort($value, SORT_STRING); 147 | foreach ($value as $duplicate_value) { 148 | $pairs[] = $parameter . '=' . $duplicate_value; 149 | } 150 | } else { 151 | $pairs[] = $parameter . '=' . $value; 152 | } 153 | } 154 | 155 | // For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61) 156 | // Each name-value pair is separated by an '&' character (ASCII code 38) 157 | return implode('&', $pairs); 158 | 159 | } 160 | 161 | } 162 | -------------------------------------------------------------------------------- /src/Profile/Item.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright IMS Global Learning Consortium Inc 10 | * @date 2016 11 | * @version 3.0.0 12 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 13 | */ 14 | class Item 15 | { 16 | 17 | /** 18 | * ID of item. 19 | * 20 | * @var string $id 21 | */ 22 | public $id = null; 23 | /** 24 | * Name of item. 25 | * 26 | * @var string $name 27 | */ 28 | public $name = null; 29 | /** 30 | * Description of item. 31 | * 32 | * @var string $description 33 | */ 34 | public $description = null; 35 | /** 36 | * URL of item. 37 | * 38 | * @var string $url 39 | */ 40 | public $url = null; 41 | /** 42 | * Version of item. 43 | * 44 | * @var string $version 45 | */ 46 | public $version = null; 47 | /** 48 | * Timestamp of item. 49 | * 50 | * @var int $timestamp 51 | */ 52 | public $timestamp = null; 53 | 54 | /** 55 | * Class constructor. 56 | * 57 | * @param string $id ID of item (optional) 58 | * @param string $name Name of item (optional) 59 | * @param string $description Description of item (optional) 60 | * @param string $url URL of item (optional) 61 | * @param string $version Version of item (optional) 62 | * @param int $timestamp Timestamp of item (optional) 63 | */ 64 | 65 | function __construct($id = null, $name = null, $description = null, $url = null, $version = null, $timestamp = null) 66 | { 67 | 68 | $this->id = $id; 69 | $this->name = $name; 70 | $this->description = $description; 71 | $this->url = $url; 72 | $this->version = $version; 73 | $this->timestamp = $timestamp; 74 | 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/Profile/Message.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright IMS Global Learning Consortium Inc 11 | * @date 2016 12 | * @version 3.0.0 13 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 14 | */ 15 | 16 | class Message 17 | { 18 | 19 | /** 20 | * LTI message type. 21 | * 22 | * @var string $type 23 | */ 24 | public $type = null; 25 | /** 26 | * Path to send message request to (used in conjunction with a base URL for the Tool Provider). 27 | * 28 | * @var string $path 29 | */ 30 | public $path = null; 31 | /** 32 | * Capabilities required by message. 33 | * 34 | * @var array $capabilities 35 | */ 36 | public $capabilities = null; 37 | /** 38 | * Variable parameters to accompany message request. 39 | * 40 | * @var array $variables 41 | */ 42 | public $variables = null; 43 | /** 44 | * Fixed parameters to accompany message request. 45 | * 46 | * @var array $constants 47 | */ 48 | public $constants = null; 49 | 50 | 51 | /** 52 | * Class constructor. 53 | * 54 | * @param string $type LTI message type 55 | * @param string $path Path to send message request to 56 | * @param array $capabilities Array of capabilities required by message 57 | * @param array $variables Array of variable parameters to accompany message request 58 | * @param array $constants Array of fixed parameters to accompany message request 59 | */ 60 | function __construct($type, $path, $capabilities = array(), $variables = array(), $constants = array()) 61 | { 62 | 63 | $this->type = $type; 64 | $this->path = $path; 65 | $this->capabilities = $capabilities; 66 | $this->variables = $variables; 67 | $this->constants = $constants; 68 | 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/Profile/ResourceHandler.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright IMS Global Learning Consortium Inc 10 | * @date 2016 11 | * @version 3.0.0 12 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 13 | */ 14 | 15 | class ResourceHandler 16 | { 17 | 18 | /** 19 | * General details of resource handler. 20 | * 21 | * @var Item $item 22 | */ 23 | public $item = null; 24 | /** 25 | * URL of icon. 26 | * 27 | * @var string $icon 28 | */ 29 | public $icon = null; 30 | /** 31 | * Required Message objects for resource handler. 32 | * 33 | * @var array $requiredMessages 34 | */ 35 | public $requiredMessages = null; 36 | /** 37 | * Optional Message objects for resource handler. 38 | * 39 | * @var array $optionalMessages 40 | */ 41 | public $optionalMessages = null; 42 | 43 | /** 44 | * Class constructor. 45 | * 46 | * @param Item $item General details of resource handler 47 | * @param string $icon URL of icon 48 | * @param array $requiredMessages Array of required Message objects for resource handler 49 | * @param array $optionalMessages Array of optional Message objects for resource handler 50 | */ 51 | function __construct($item, $icon, $requiredMessages, $optionalMessages) 52 | { 53 | 54 | $this->item = $item; 55 | $this->icon = $icon; 56 | $this->requiredMessages = $requiredMessages; 57 | $this->optionalMessages = $optionalMessages; 58 | 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/Profile/ServiceDefinition.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright IMS Global Learning Consortium Inc 10 | * @date 2016 11 | * @version 3.0.0 12 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 13 | */ 14 | 15 | class ServiceDefinition 16 | { 17 | 18 | /** 19 | * Media types supported by service. 20 | * 21 | * @var array $formats 22 | */ 23 | public $formats = null; 24 | /** 25 | * HTTP actions accepted by service. 26 | * 27 | * @var array $actions 28 | */ 29 | public $actions = null; 30 | /** 31 | * ID of service. 32 | * 33 | * @var string $id 34 | */ 35 | public $id = null; 36 | /** 37 | * URL for service requests. 38 | * 39 | * @var string $endpoint 40 | */ 41 | public $endpoint = null; 42 | 43 | /** 44 | * Class constructor. 45 | * 46 | * @param array $formats Array of media types supported by service 47 | * @param array $actions Array of HTTP actions accepted by service 48 | * @param string $id ID of service (optional) 49 | * @param string $endpoint URL for service requests (optional) 50 | */ 51 | 52 | function __construct($formats, $actions, $id = null, $endpoint = null) 53 | { 54 | 55 | $this->formats = $formats; 56 | $this->actions = $actions; 57 | $this->id = $id; 58 | $this->endpoint = $endpoint; 59 | 60 | } 61 | 62 | function setId($id) { 63 | 64 | $this->id = $id; 65 | 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/ToolProvider/ConsumerNonce.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright IMS Global Learning Consortium Inc 10 | * @date 2016 11 | * @version 3.0.2 12 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 13 | */ 14 | class ConsumerNonce 15 | { 16 | 17 | /** 18 | * Maximum age nonce values will be retained for (in minutes). 19 | */ 20 | const MAX_NONCE_AGE = 30; // in minutes 21 | 22 | /** 23 | * Date/time when the nonce value expires. 24 | * 25 | * @var int $expires 26 | */ 27 | public $expires = null; 28 | 29 | /** 30 | * Tool Consumer to which this nonce applies. 31 | * 32 | * @var ToolConsumer $consumer 33 | */ 34 | private $consumer = null; 35 | /** 36 | * Nonce value. 37 | * 38 | * @var string $value 39 | */ 40 | private $value = null; 41 | 42 | /** 43 | * Class constructor. 44 | * 45 | * @param ToolConsumer $consumer Consumer object 46 | * @param string $value Nonce value (optional, default is null) 47 | */ 48 | public function __construct($consumer, $value = null) 49 | { 50 | 51 | $this->consumer = $consumer; 52 | $this->value = $value; 53 | $this->expires = time() + (self::MAX_NONCE_AGE * 60); 54 | 55 | } 56 | 57 | /** 58 | * Load a nonce value from the database. 59 | * 60 | * @return boolean True if the nonce value was successfully loaded 61 | */ 62 | public function load() 63 | { 64 | 65 | return $this->consumer->getDataConnector()->loadConsumerNonce($this); 66 | 67 | } 68 | 69 | /** 70 | * Save a nonce value in the database. 71 | * 72 | * @return boolean True if the nonce value was successfully saved 73 | */ 74 | public function save() 75 | { 76 | 77 | return $this->consumer->getDataConnector()->saveConsumerNonce($this); 78 | 79 | } 80 | 81 | /** 82 | * Get tool consumer. 83 | * 84 | * @return ToolConsumer Consumer for this nonce 85 | */ 86 | public function getConsumer() 87 | { 88 | 89 | return $this->consumer; 90 | 91 | } 92 | 93 | /** 94 | * Get outcome value. 95 | * 96 | * @return string Outcome value 97 | */ 98 | public function getValue() 99 | { 100 | 101 | return $this->value; 102 | 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/ToolProvider/ContentItem.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright IMS Global Learning Consortium Inc 10 | * @date 2016 11 | * @version 3.0.2 12 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 13 | */ 14 | class ContentItem 15 | { 16 | 17 | /** 18 | * Media type for LTI launch links. 19 | */ 20 | const LTI_LINK_MEDIA_TYPE = 'application/vnd.ims.lti.v1.ltilink'; 21 | 22 | /** 23 | * Class constructor. 24 | * 25 | * @param string $type Class type of content-item 26 | * @param ContentItemPlacement $placementAdvice Placement object for item (optional) 27 | * @param string $id URL of content-item (optional) 28 | */ 29 | function __construct($type, $placementAdvice = null, $id = null) 30 | { 31 | 32 | $this->{'@type'} = $type; 33 | if (is_object($placementAdvice) && (count(get_object_vars($placementAdvice)) > 0)) { 34 | $this->placementAdvice = $placementAdvice; 35 | } 36 | if (!empty($id)) { 37 | $this->{'@id'} = $id; 38 | } 39 | 40 | } 41 | 42 | /** 43 | * Set a URL value for the content-item. 44 | * 45 | * @param string $url URL value 46 | */ 47 | public function setUrl($url) 48 | { 49 | 50 | if (!empty($url)) { 51 | $this->url = $url; 52 | } else { 53 | unset($this->url); 54 | } 55 | 56 | } 57 | 58 | /** 59 | * Set a media type value for the content-item. 60 | * 61 | * @param string $mediaType Media type value 62 | */ 63 | public function setMediaType($mediaType) 64 | { 65 | 66 | if (!empty($mediaType)) { 67 | $this->mediaType = $mediaType; 68 | } else { 69 | unset($this->mediaType); 70 | } 71 | 72 | } 73 | 74 | /** 75 | * Set a title value for the content-item. 76 | * 77 | * @param string $title Title value 78 | */ 79 | public function setTitle($title) 80 | { 81 | 82 | if (!empty($title)) { 83 | $this->title = $title; 84 | } else if (isset($this->title)) { 85 | unset($this->title); 86 | } 87 | 88 | } 89 | 90 | /** 91 | * Set a link text value for the content-item. 92 | * 93 | * @param string $text Link text value 94 | */ 95 | public function setText($text) 96 | { 97 | 98 | if (!empty($text)) { 99 | $this->text = $text; 100 | } else if (isset($this->text)) { 101 | unset($this->text); 102 | } 103 | 104 | } 105 | 106 | /** 107 | * Wrap the content items to form a complete application/vnd.ims.lti.v1.contentitems+json media type instance. 108 | * 109 | * @param mixed $items An array of content items or a single item 110 | * @return string 111 | */ 112 | public static function toJson($items) 113 | { 114 | /* 115 | $data = array(); 116 | if (!is_array($items)) { 117 | $data[] = json_encode($items); 118 | } else { 119 | foreach ($items as $item) { 120 | $data[] = json_encode($item); 121 | } 122 | } 123 | $json = '{ "@context" : "http://purl.imsglobal.org/ctx/lti/v1/ContentItem", "@graph" : [' . implode(", ", $data) . '] }'; 124 | */ 125 | $obj = new \stdClass(); 126 | $obj->{'@context'} = 'http://purl.imsglobal.org/ctx/lti/v1/ContentItem'; 127 | if (!is_array($items)) { 128 | $obj->{'@graph'} = array(); 129 | $obj->{'@graph'}[] = $items; 130 | } else { 131 | $obj->{'@graph'} = $items; 132 | } 133 | 134 | return json_encode($obj); 135 | 136 | } 137 | 138 | } 139 | -------------------------------------------------------------------------------- /src/ToolProvider/ContentItemImage.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright IMS Global Learning Consortium Inc 10 | * @date 2016 11 | * @version 3.0.2 12 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 13 | */ 14 | class ContentItemImage 15 | { 16 | 17 | /** 18 | * Class constructor. 19 | * 20 | * @param string $id URL of image 21 | * @param int $height Height of image in pixels (optional) 22 | * @param int $width Width of image in pixels (optional) 23 | */ 24 | function __construct($id, $height = null, $width = null) 25 | { 26 | 27 | $this->{'@id'} = $id; 28 | if (!is_null($height)) { 29 | $this->height = $height; 30 | } 31 | if (!is_null($width)) { 32 | $this->width = $width; 33 | } 34 | 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/ToolProvider/ContentItemPlacement.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright IMS Global Learning Consortium Inc 10 | * @date 2016 11 | * @version 3.0.2 12 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 13 | */ 14 | class ContentItemPlacement 15 | { 16 | 17 | /** 18 | * Class constructor. 19 | * 20 | * @param int $displayWidth Width of item location 21 | * @param int $displayHeight Height of item location 22 | * @param string $documentTarget Location to open content in 23 | * @param string $windowTarget Name of window target 24 | */ 25 | function __construct($displayWidth, $displayHeight, $documentTarget, $windowTarget) 26 | { 27 | 28 | if (!empty($displayWidth)) { 29 | $this->displayWidth = $displayWidth; 30 | } 31 | if (!empty($displayHeight)) { 32 | $this->displayHeight = $displayHeight; 33 | } 34 | if (!empty($documentTarget)) { 35 | $this->documentTarget = $documentTarget; 36 | } 37 | if (!empty($windowTarget)) { 38 | $this->windowTarget = $windowTarget; 39 | } 40 | 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/ToolProvider/Context.php: -------------------------------------------------------------------------------- 1 | 12 | * @copyright IMS Global Learning Consortium Inc 13 | * @date 2016 14 | * @version 3.0.2 15 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 16 | */ 17 | class Context 18 | { 19 | 20 | /** 21 | * Context ID as supplied in the last connection request. 22 | * 23 | * @var string $ltiContextId 24 | */ 25 | public $ltiContextId = null; 26 | /** 27 | * Context title. 28 | * 29 | * @var string $title 30 | */ 31 | public $title = null; 32 | /** 33 | * Setting values (LTI parameters, custom parameters and local parameters). 34 | * 35 | * @var array $settings 36 | */ 37 | public $settings = null; 38 | /** 39 | * Date/time when the object was created. 40 | * 41 | * @var int $created 42 | */ 43 | public $created = null; 44 | /** 45 | * Date/time when the object was last updated. 46 | * 47 | * @var int $updated 48 | */ 49 | public $updated = null; 50 | 51 | /** 52 | * Tool Consumer for this context. 53 | * 54 | * @var ToolConsumer $consumer 55 | */ 56 | private $consumer = null; 57 | /** 58 | * Tool Consumer ID for this context. 59 | * 60 | * @var int $consumerId 61 | */ 62 | private $consumerId = null; 63 | /** 64 | * ID for this context. 65 | * 66 | * @var int $id 67 | */ 68 | private $id = null; 69 | /** 70 | * Whether the settings value have changed since last saved. 71 | * 72 | * @var boolean $settingsChanged 73 | */ 74 | private $settingsChanged = false; 75 | /** 76 | * Data connector object or string. 77 | * 78 | * @var mixed $dataConnector 79 | */ 80 | private $dataConnector = null; 81 | 82 | /** 83 | * Class constructor. 84 | */ 85 | public function __construct() 86 | { 87 | 88 | $this->initialize(); 89 | 90 | } 91 | 92 | /** 93 | * Initialise the context. 94 | */ 95 | public function initialize() 96 | { 97 | 98 | $this->title = ''; 99 | $this->settings = array(); 100 | $this->created = null; 101 | $this->updated = null; 102 | 103 | } 104 | 105 | /** 106 | * Initialise the context. 107 | * 108 | * Pseudonym for initialize(). 109 | */ 110 | public function initialise() 111 | { 112 | 113 | $this->initialize(); 114 | 115 | } 116 | 117 | /** 118 | * Save the context to the database. 119 | * 120 | * @return boolean True if the context was successfully saved. 121 | */ 122 | public function save() 123 | { 124 | 125 | $ok = $this->getDataConnector()->saveContext($this); 126 | if ($ok) { 127 | $this->settingsChanged = false; 128 | } 129 | 130 | return $ok; 131 | 132 | } 133 | 134 | /** 135 | * Delete the context from the database. 136 | * 137 | * @return boolean True if the context was successfully deleted. 138 | */ 139 | public function delete() 140 | { 141 | 142 | return $this->getDataConnector()->deleteContext($this); 143 | 144 | } 145 | 146 | /** 147 | * Get tool consumer. 148 | * 149 | * @return ToolConsumer Tool consumer object for this context. 150 | */ 151 | public function getConsumer() 152 | { 153 | 154 | if (is_null($this->consumer)) { 155 | $this->consumer = ToolConsumer::fromRecordId($this->consumerId, $this->getDataConnector()); 156 | } 157 | 158 | return $this->consumer; 159 | 160 | } 161 | /** 162 | * Set tool consumer ID. 163 | * 164 | * @param int $consumerId Tool Consumer ID for this resource link. 165 | */ 166 | public function setConsumerId($consumerId) 167 | { 168 | 169 | $this->consumer = null; 170 | $this->consumerId = $consumerId; 171 | 172 | } 173 | 174 | /** 175 | * Get tool consumer key. 176 | * 177 | * @return string Consumer key value for this context. 178 | */ 179 | public function getKey() 180 | { 181 | 182 | return $this->getConsumer()->getKey(); 183 | 184 | } 185 | 186 | /** 187 | * Get context ID. 188 | * 189 | * @return string ID for this context. 190 | */ 191 | public function getId() 192 | { 193 | 194 | return $this->ltiContextId; 195 | 196 | } 197 | 198 | /** 199 | * Get the context record ID. 200 | * 201 | * @return int Context record ID value 202 | */ 203 | public function getRecordId() 204 | { 205 | 206 | return $this->id; 207 | 208 | } 209 | 210 | /** 211 | * Sets the context record ID. 212 | * 213 | * @return int $id Context record ID value 214 | */ 215 | public function setRecordId($id) 216 | { 217 | 218 | $this->id = $id; 219 | 220 | } 221 | 222 | /** 223 | * Get the data connector. 224 | * 225 | * @return mixed Data connector object or string 226 | */ 227 | public function getDataConnector() 228 | { 229 | 230 | return $this->dataConnector; 231 | 232 | } 233 | 234 | /** 235 | * Get a setting value. 236 | * 237 | * @param string $name Name of setting 238 | * @param string $default Value to return if the setting does not exist (optional, default is an empty string) 239 | * 240 | * @return string Setting value 241 | */ 242 | public function getSetting($name, $default = '') 243 | { 244 | 245 | if (array_key_exists($name, $this->settings)) { 246 | $value = $this->settings[$name]; 247 | } else { 248 | $value = $default; 249 | } 250 | 251 | return $value; 252 | 253 | } 254 | 255 | /** 256 | * Set a setting value. 257 | * 258 | * @param string $name Name of setting 259 | * @param string $value Value to set, use an empty value to delete a setting (optional, default is null) 260 | */ 261 | public function setSetting($name, $value = null) 262 | { 263 | 264 | $old_value = $this->getSetting($name); 265 | if ($value !== $old_value) { 266 | if (!empty($value)) { 267 | $this->settings[$name] = $value; 268 | } else { 269 | unset($this->settings[$name]); 270 | } 271 | $this->settingsChanged = true; 272 | } 273 | 274 | } 275 | 276 | /** 277 | * Get an array of all setting values. 278 | * 279 | * @return array Associative array of setting values 280 | */ 281 | public function getSettings() 282 | { 283 | 284 | return $this->settings; 285 | 286 | } 287 | 288 | /** 289 | * Set an array of all setting values. 290 | * 291 | * @param array $settings Associative array of setting values 292 | */ 293 | public function setSettings($settings) 294 | { 295 | 296 | $this->settings = $settings; 297 | 298 | } 299 | 300 | /** 301 | * Save setting values. 302 | * 303 | * @return boolean True if the settings were successfully saved 304 | */ 305 | public function saveSettings() 306 | { 307 | 308 | if ($this->settingsChanged) { 309 | $ok = $this->save(); 310 | } else { 311 | $ok = true; 312 | } 313 | 314 | return $ok; 315 | 316 | } 317 | 318 | /** 319 | * Check if the Tool Settings service is supported. 320 | * 321 | * @return boolean True if this context supports the Tool Settings service 322 | */ 323 | public function hasToolSettingsService() 324 | { 325 | 326 | $url = $this->getSetting('custom_context_setting_url'); 327 | 328 | return !empty($url); 329 | 330 | } 331 | 332 | /** 333 | * Get Tool Settings. 334 | * 335 | * @param int $mode Mode for request (optional, default is current level only) 336 | * @param boolean $simple True if all the simple media type is to be used (optional, default is true) 337 | * 338 | * @return mixed The array of settings if successful, otherwise false 339 | */ 340 | public function getToolSettings($mode = Service\ToolSettings::MODE_CURRENT_LEVEL, $simple = true) 341 | { 342 | 343 | $url = $this->getSetting('custom_context_setting_url'); 344 | $service = new Service\ToolSettings($this, $url, $simple); 345 | $response = $service->get($mode); 346 | 347 | return $response; 348 | 349 | } 350 | 351 | /** 352 | * Perform a Tool Settings service request. 353 | * 354 | * @param array $settings An associative array of settings (optional, default is none) 355 | * 356 | * @return boolean True if action was successful, otherwise false 357 | */ 358 | public function setToolSettings($settings = array()) 359 | { 360 | 361 | $url = $this->getSetting('custom_context_setting_url'); 362 | $service = new Service\ToolSettings($this, $url); 363 | $response = $service->set($settings); 364 | 365 | return $response; 366 | 367 | } 368 | 369 | /** 370 | * Check if the Membership service is supported. 371 | * 372 | * @return boolean True if this context supports the Membership service 373 | */ 374 | public function hasMembershipService() 375 | { 376 | 377 | $url = $this->getSetting('custom_context_memberships_url'); 378 | 379 | return !empty($url); 380 | 381 | } 382 | 383 | /** 384 | * Get Memberships. 385 | * 386 | * @return mixed The array of User objects if successful, otherwise false 387 | */ 388 | public function getMembership() 389 | { 390 | 391 | $url = $this->getSetting('custom_context_memberships_url'); 392 | $service = new Service\Membership($this, $url); 393 | $response = $service->get(); 394 | 395 | return $response; 396 | 397 | } 398 | 399 | /** 400 | * Load the context from the database. 401 | * 402 | * @param int $id Record ID of context 403 | * @param DataConnector $dataConnector Database connection object 404 | * 405 | * @return Context Context object 406 | */ 407 | public static function fromRecordId($id, $dataConnector) 408 | { 409 | 410 | $context = new Context(); 411 | $context->dataConnector = $dataConnector; 412 | $context->load($id); 413 | 414 | return $context; 415 | 416 | } 417 | 418 | /** 419 | * Class constructor from consumer. 420 | * 421 | * @param ToolConsumer $consumer Consumer instance 422 | * @param string $ltiContextId LTI Context ID value 423 | * @return Context 424 | */ 425 | public static function fromConsumer($consumer, $ltiContextId) 426 | { 427 | 428 | $context = new Context(); 429 | $context->consumer = $consumer; 430 | $context->dataConnector = $consumer->getDataConnector(); 431 | $context->ltiContextId = $ltiContextId; 432 | if (!empty($ltiContextId)) { 433 | $context->load(); 434 | } 435 | 436 | return $context; 437 | 438 | } 439 | 440 | ### 441 | ### PRIVATE METHODS 442 | ### 443 | 444 | /** 445 | * Load the context from the database. 446 | * 447 | * @param int $id Record ID of context (optional, default is null) 448 | * 449 | * @return boolean True if context was successfully loaded 450 | */ 451 | private function load($id = null) 452 | { 453 | 454 | $this->initialize(); 455 | $this->id = $id; 456 | return $this->getDataConnector()->loadContext($this); 457 | 458 | } 459 | 460 | } 461 | -------------------------------------------------------------------------------- /src/ToolProvider/DataConnector/DataConnector.php: -------------------------------------------------------------------------------- 1 | 20 | * @copyright IMS Global Learning Consortium Inc 21 | * @date 2016 22 | * @version 3.0.0 23 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 24 | */ 25 | class DataConnector 26 | { 27 | 28 | /** 29 | * Default name for database table used to store tool consumers. 30 | */ 31 | const CONSUMER_TABLE_NAME = 'lti2_consumer'; 32 | /** 33 | * Default name for database table used to store pending tool proxies. 34 | */ 35 | const TOOL_PROXY_TABLE_NAME = 'lti2_tool_proxy'; 36 | /** 37 | * Default name for database table used to store contexts. 38 | */ 39 | const CONTEXT_TABLE_NAME = 'lti2_context'; 40 | /** 41 | * Default name for database table used to store resource links. 42 | */ 43 | const RESOURCE_LINK_TABLE_NAME = 'lti2_resource_link'; 44 | /** 45 | * Default name for database table used to store users. 46 | */ 47 | const USER_RESULT_TABLE_NAME = 'lti2_user_result'; 48 | /** 49 | * Default name for database table used to store resource link share keys. 50 | */ 51 | const RESOURCE_LINK_SHARE_KEY_TABLE_NAME = 'lti2_share_key'; 52 | /** 53 | * Default name for database table used to store nonce values. 54 | */ 55 | const NONCE_TABLE_NAME = 'lti2_nonce'; 56 | 57 | /** 58 | * Database object. 59 | * 60 | * @var object $db 61 | */ 62 | protected $db = null; 63 | /** 64 | * Prefix for database table names. 65 | * 66 | * @var string $dbTableNamePrefix 67 | */ 68 | protected $dbTableNamePrefix = ''; 69 | /** 70 | * SQL date format (default = 'Y-m-d') 71 | * 72 | * @var string $dateFormat 73 | */ 74 | protected $dateFormat = 'Y-m-d'; 75 | /** 76 | * SQL time format (default = 'H:i:s') 77 | * 78 | * @var string $timeFormat 79 | */ 80 | protected $timeFormat = 'H:i:s'; 81 | 82 | /** 83 | * Class constructor 84 | * 85 | * @param object $db Database connection object 86 | * @param string $dbTableNamePrefix Prefix for database table names (optional, default is none) 87 | */ 88 | public function __construct($db, $dbTableNamePrefix = '') 89 | { 90 | 91 | $this->db = $db; 92 | $this->dbTableNamePrefix = $dbTableNamePrefix; 93 | 94 | } 95 | 96 | ### 97 | ### ToolConsumer methods 98 | ### 99 | 100 | /** 101 | * Load tool consumer object. 102 | * 103 | * @param ToolConsumer $consumer ToolConsumer object 104 | * 105 | * @return boolean True if the tool consumer object was successfully loaded 106 | */ 107 | public function loadToolConsumer($consumer) 108 | { 109 | 110 | $consumer->secret = 'secret'; 111 | $consumer->enabled = true; 112 | $now = time(); 113 | $consumer->created = $now; 114 | $consumer->updated = $now; 115 | 116 | return true; 117 | 118 | } 119 | 120 | /** 121 | * Save tool consumer object. 122 | * 123 | * @param ToolConsumer $consumer Consumer object 124 | * 125 | * @return boolean True if the tool consumer object was successfully saved 126 | */ 127 | public function saveToolConsumer($consumer) 128 | { 129 | 130 | $consumer->updated = time(); 131 | 132 | return true; 133 | 134 | } 135 | 136 | /** 137 | * Delete tool consumer object. 138 | * 139 | * @param ToolConsumer $consumer Consumer object 140 | * 141 | * @return boolean True if the tool consumer object was successfully deleted 142 | */ 143 | public function deleteToolConsumer($consumer) 144 | { 145 | 146 | $consumer->initialize(); 147 | 148 | return true; 149 | 150 | } 151 | 152 | /** 153 | * Load tool consumer objects. 154 | * 155 | * @return array Array of all defined ToolConsumer objects 156 | */ 157 | public function getToolConsumers() 158 | { 159 | 160 | return array(); 161 | 162 | } 163 | 164 | 165 | ### 166 | ### ToolProxy methods 167 | ### 168 | 169 | /** 170 | * Load tool proxy object. 171 | * 172 | * @param ToolProxy $toolProxy ToolProxy object 173 | * 174 | * @return boolean True if the tool proxy object was successfully loaded 175 | */ 176 | public function loadToolProxy($toolProxy) 177 | { 178 | 179 | $now = time(); 180 | $toolProxy->created = $now; 181 | $toolProxy->updated = $now; 182 | 183 | return true; 184 | 185 | } 186 | 187 | /** 188 | * Save tool proxy object. 189 | * 190 | * @param ToolProxy $toolProxy ToolProxy object 191 | * 192 | * @return boolean True if the tool proxy object was successfully saved 193 | */ 194 | public function saveToolProxy($toolProxy) 195 | { 196 | 197 | $toolProxy->updated = time(); 198 | 199 | return true; 200 | 201 | } 202 | 203 | /** 204 | * Delete tool proxy object. 205 | * 206 | * @param ToolProxy $toolProxy ToolProxy object 207 | * 208 | * @return boolean True if the tool proxy object was successfully deleted 209 | */ 210 | public function deleteToolProxy($toolProxy) 211 | { 212 | 213 | $toolProxy->initialize(); 214 | 215 | return true; 216 | 217 | } 218 | 219 | ### 220 | ### Context methods 221 | ### 222 | 223 | /** 224 | * Load context object. 225 | * 226 | * @param Context $context Context object 227 | * 228 | * @return boolean True if the context object was successfully loaded 229 | */ 230 | public function loadContext($context) 231 | { 232 | 233 | $now = time(); 234 | $context->created = $now; 235 | $context->updated = $now; 236 | 237 | return true; 238 | 239 | } 240 | 241 | /** 242 | * Save context object. 243 | * 244 | * @param Context $context Context object 245 | * 246 | * @return boolean True if the context object was successfully saved 247 | */ 248 | public function saveContext($context) 249 | { 250 | 251 | $context->updated = time(); 252 | 253 | return true; 254 | 255 | } 256 | 257 | /** 258 | * Delete context object. 259 | * 260 | * @param Context $context Context object 261 | * 262 | * @return boolean True if the Context object was successfully deleted 263 | */ 264 | public function deleteContext($context) 265 | { 266 | 267 | $context->initialize(); 268 | 269 | return true; 270 | 271 | } 272 | 273 | ### 274 | ### ResourceLink methods 275 | ### 276 | 277 | /** 278 | * Load resource link object. 279 | * 280 | * @param ResourceLink $resourceLink Resource_Link object 281 | * 282 | * @return boolean True if the resource link object was successfully loaded 283 | */ 284 | public function loadResourceLink($resourceLink) 285 | { 286 | 287 | $now = time(); 288 | $resourceLink->created = $now; 289 | $resourceLink->updated = $now; 290 | 291 | return true; 292 | 293 | } 294 | 295 | /** 296 | * Save resource link object. 297 | * 298 | * @param ResourceLink $resourceLink Resource_Link object 299 | * 300 | * @return boolean True if the resource link object was successfully saved 301 | */ 302 | public function saveResourceLink($resourceLink) 303 | { 304 | 305 | $resourceLink->updated = time(); 306 | 307 | return true; 308 | 309 | } 310 | 311 | /** 312 | * Delete resource link object. 313 | * 314 | * @param ResourceLink $resourceLink Resource_Link object 315 | * 316 | * @return boolean True if the resource link object was successfully deleted 317 | */ 318 | public function deleteResourceLink($resourceLink) 319 | { 320 | 321 | $resourceLink->initialize(); 322 | 323 | return true; 324 | 325 | } 326 | 327 | /** 328 | * Get array of user objects. 329 | * 330 | * Obtain an array of User objects for users with a result sourcedId. The array may include users from other 331 | * resource links which are sharing this resource link. It may also be optionally indexed by the user ID of a specified scope. 332 | * 333 | * @param ResourceLink $resourceLink Resource link object 334 | * @param boolean $localOnly True if only users within the resource link are to be returned (excluding users sharing this resource link) 335 | * @param int $idScope Scope value to use for user IDs 336 | * 337 | * @return array Array of User objects 338 | */ 339 | public function getUserResultSourcedIDsResourceLink($resourceLink, $localOnly, $idScope) 340 | { 341 | 342 | return array(); 343 | 344 | } 345 | 346 | /** 347 | * Get array of shares defined for this resource link. 348 | * 349 | * @param ResourceLink $resourceLink Resource_Link object 350 | * 351 | * @return array Array of ResourceLinkShare objects 352 | */ 353 | public function getSharesResourceLink($resourceLink) 354 | { 355 | 356 | return array(); 357 | 358 | } 359 | 360 | ### 361 | ### ConsumerNonce methods 362 | ### 363 | 364 | /** 365 | * Load nonce object. 366 | * 367 | * @param ConsumerNonce $nonce Nonce object 368 | * 369 | * @return boolean True if the nonce object was successfully loaded 370 | */ 371 | public function loadConsumerNonce($nonce) 372 | { 373 | return false; // assume the nonce does not already exist 374 | 375 | } 376 | 377 | /** 378 | * Save nonce object. 379 | * 380 | * @param ConsumerNonce $nonce Nonce object 381 | * 382 | * @return boolean True if the nonce object was successfully saved 383 | */ 384 | public function saveConsumerNonce($nonce) 385 | { 386 | 387 | return true; 388 | 389 | } 390 | 391 | ### 392 | ### ResourceLinkShareKey methods 393 | ### 394 | 395 | /** 396 | * Load resource link share key object. 397 | * 398 | * @param ResourceLinkShareKey $shareKey Resource_Link share key object 399 | * 400 | * @return boolean True if the resource link share key object was successfully loaded 401 | */ 402 | public function loadResourceLinkShareKey($shareKey) 403 | { 404 | 405 | return true; 406 | 407 | } 408 | 409 | /** 410 | * Save resource link share key object. 411 | * 412 | * @param ResourceLinkShareKey $shareKey Resource link share key object 413 | * 414 | * @return boolean True if the resource link share key object was successfully saved 415 | */ 416 | public function saveResourceLinkShareKey($shareKey) 417 | { 418 | 419 | return true; 420 | 421 | } 422 | 423 | /** 424 | * Delete resource link share key object. 425 | * 426 | * @param ResourceLinkShareKey $shareKey Resource link share key object 427 | * 428 | * @return boolean True if the resource link share key object was successfully deleted 429 | */ 430 | public function deleteResourceLinkShareKey($shareKey) 431 | { 432 | 433 | return true; 434 | 435 | } 436 | 437 | ### 438 | ### User methods 439 | ### 440 | 441 | /** 442 | * Load user object. 443 | * 444 | * @param User $user User object 445 | * 446 | * @return boolean True if the user object was successfully loaded 447 | */ 448 | public function loadUser($user) 449 | { 450 | 451 | $now = time(); 452 | $user->created = $now; 453 | $user->updated = $now; 454 | 455 | return true; 456 | 457 | } 458 | 459 | /** 460 | * Save user object. 461 | * 462 | * @param User $user User object 463 | * 464 | * @return boolean True if the user object was successfully saved 465 | */ 466 | public function saveUser($user) 467 | { 468 | 469 | $user->updated = time(); 470 | 471 | return true; 472 | 473 | } 474 | 475 | /** 476 | * Delete user object. 477 | * 478 | * @param User $user User object 479 | * 480 | * @return boolean True if the user object was successfully deleted 481 | */ 482 | public function deleteUser($user) 483 | { 484 | 485 | $user->initialize(); 486 | 487 | return true; 488 | 489 | } 490 | 491 | ### 492 | ### Other methods 493 | ### 494 | 495 | /** 496 | * Return a hash of a consumer key for values longer than 255 characters. 497 | * 498 | * @param string $key 499 | * @return string 500 | */ 501 | protected static function getConsumerKey($key) 502 | { 503 | 504 | $len = strlen($key); 505 | if ($len > 255) { 506 | $key = 'sha512:' . hash('sha512', $key); 507 | } 508 | 509 | return $key; 510 | 511 | } 512 | 513 | /** 514 | * Create data connector object. 515 | * 516 | * A data connector provides access to persistent storage for the different objects. 517 | * 518 | * Names of tables may be given a prefix to allow multiple versions to share the same schema. A separate sub-class is defined for 519 | * each different database connection - the class to use is determined by inspecting the database object passed, but this can be overridden 520 | * (for example, to use a bespoke connector) by specifying a type. If no database is passed then this class is used which acts as a dummy 521 | * connector with no persistence. 522 | * 523 | * @param string $dbTableNamePrefix Prefix for database table names (optional, default is none) 524 | * @param object $db A database connection object or string (optional, default is no persistence) 525 | * @param string $type The type of data connector (optional, default is based on $db parameter) 526 | * 527 | * @return DataConnector Data connector object 528 | */ 529 | public static function getDataConnector($dbTableNamePrefix = '', $db = null, $type = '') 530 | { 531 | 532 | if (is_null($dbTableNamePrefix)) { 533 | $dbTableNamePrefix = ''; 534 | } 535 | if (!is_null($db) && empty($type)) { 536 | if (is_object($db)) { 537 | $type = get_class($db); 538 | } 539 | } 540 | $type = strtolower($type); 541 | if (($type === 'pdo') && ($db->getAttribute(PDO::ATTR_DRIVER_NAME) === 'sqlite')) { 542 | $type .= '_sqlite'; 543 | } 544 | if (!empty($type)) { 545 | $type ="DataConnector_{$type}"; 546 | } else { 547 | $type ='DataConnector'; 548 | } 549 | $type = "\\IMSGlobal\\LTI\\ToolProvider\\DataConnector\\{$type}"; 550 | $dataConnector = new $type($db, $dbTableNamePrefix); 551 | 552 | return $dataConnector; 553 | 554 | } 555 | 556 | /** 557 | * Generate a random string. 558 | * 559 | * The generated string will only comprise letters (upper- and lower-case) and digits. 560 | * 561 | * @param int $length Length of string to be generated (optional, default is 8 characters) 562 | * 563 | * @return string Random string 564 | */ 565 | static function getRandomString($length = 8) 566 | { 567 | 568 | $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 569 | 570 | $value = ''; 571 | $charsLength = strlen($chars) - 1; 572 | 573 | for ($i = 1 ; $i <= $length; $i++) { 574 | $value .= $chars[rand(0, $charsLength)]; 575 | } 576 | 577 | return $value; 578 | 579 | } 580 | 581 | /** 582 | * Quote a string for use in a database query. 583 | * 584 | * Any single quotes in the value passed will be replaced with two single quotes. If a null value is passed, a string 585 | * of 'null' is returned (which will never be enclosed in quotes irrespective of the value of the $addQuotes parameter. 586 | * 587 | * @param string $value Value to be quoted 588 | * @param bool $addQuotes If true the returned string will be enclosed in single quotes (optional, default is true) 589 | * @return string The quoted string. 590 | */ 591 | static function quoted($value, $addQuotes = true) 592 | { 593 | 594 | if (is_null($value)) { 595 | $value = 'null'; 596 | } else { 597 | $value = str_replace('\'', '\'\'', $value); 598 | if ($addQuotes) { 599 | $value = "'{$value}'"; 600 | } 601 | } 602 | 603 | return $value; 604 | 605 | } 606 | 607 | } 608 | -------------------------------------------------------------------------------- /src/ToolProvider/DataConnector/DataConnector_pdo_sqlite.php: -------------------------------------------------------------------------------- 1 | 14 | * @copyright IMS Global Learning Consortium Inc 15 | * @date 2016 16 | * @version 3.0.0 17 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 18 | */ 19 | 20 | 21 | class DataConnector_pdo_sqlite extends DataConnector_pdo 22 | { 23 | 24 | ### 25 | ### ToolConsumer methods 26 | ### 27 | 28 | /** 29 | * Delete tool consumer object. 30 | * 31 | * @param ToolConsumer $consumer Consumer object 32 | * 33 | * @return boolean True if the tool consumer object was successfully deleted 34 | */ 35 | public function deleteToolConsumer($consumer) 36 | { 37 | 38 | $id = $consumer->getRecordId(); 39 | 40 | // Delete any nonce values for this consumer 41 | $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::NONCE_TABLE_NAME . ' WHERE consumer_pk = :id'; 42 | $query = $this->db->prepare($sql); 43 | $query->bindValue('id', $id, PDO::PARAM_INT); 44 | $query->execute(); 45 | 46 | // Delete any outstanding share keys for resource links for this consumer 47 | $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . ' ' . 48 | "WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' . 49 | "WHERE ({$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . '.resource_link_pk = rl.resource_link_pk) AND (rl.consumer_pk = :id))'; 50 | $query = $this->db->prepare($sql); 51 | $query->bindValue('id', $id, PDO::PARAM_INT); 52 | $query->execute(); 53 | 54 | // Delete any outstanding share keys for resource links for contexts in this consumer 55 | $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . ' ' . 56 | "WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' . 57 | "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' c ON rl.context_pk = c.context_pk ' . 58 | "WHERE ({$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . '.resource_link_pk = rl.resource_link_pk) AND (c.consumer_pk = :id))'; 59 | $query = $this->db->prepare($sql); 60 | $query->bindValue('id', $id, PDO::PARAM_INT); 61 | $query->execute(); 62 | 63 | // Delete any users in resource links for this consumer 64 | $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . ' ' . 65 | "WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' . 66 | "WHERE ({$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . '.resource_link_pk = rl.resource_link_pk) AND (rl.consumer_pk = :id))'; 67 | $query = $this->db->prepare($sql); 68 | $query->bindValue('id', $id, PDO::PARAM_INT); 69 | $query->execute(); 70 | 71 | // Delete any users in resource links for contexts in this consumer 72 | $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . ' ' . 73 | "WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' . 74 | "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' c ON rl.context_pk = c.context_pk ' . 75 | "WHERE ({$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . '.resource_link_pk = rl.resource_link_pk) AND (c.consumer_pk = :id))'; 76 | $query = $this->db->prepare($sql); 77 | $query->bindValue('id', $id, PDO::PARAM_INT); 78 | $query->execute(); 79 | 80 | // Update any resource links for which this consumer is acting as a primary resource link 81 | $sql = "UPDATE {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' ' . 82 | 'SET primary_resource_link_pk = NULL, share_approved = NULL ' . 83 | "WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' . 84 | "WHERE ({$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . '.primary_resource_link_pk = rl.resource_link_pk) AND (rl.consumer_pk = :id))'; 85 | $query = $this->db->prepare($sql); 86 | $query->bindValue('id', $id, PDO::PARAM_INT); 87 | $query->execute(); 88 | 89 | // Update any resource links for contexts in which this consumer is acting as a primary resource link 90 | $sql = "UPDATE {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' ' . 91 | 'SET primary_resource_link_pk = NULL, share_approved = NULL ' . 92 | "WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' . 93 | "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' c ON rl.context_pk = c.context_pk ' . 94 | "WHERE ({$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . '.primary_resource_link_pk = rl.resource_link_pk) AND (c.consumer_pk = :id))'; 95 | $query = $this->db->prepare($sql); 96 | $query->bindValue('id', $id, PDO::PARAM_INT); 97 | $query->execute(); 98 | 99 | // Delete any resource links for this consumer 100 | $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' ' . 101 | 'WHERE consumer_pk = :id'; 102 | $query = $this->db->prepare($sql); 103 | $query->bindValue('id', $id, PDO::PARAM_INT); 104 | $query->execute(); 105 | 106 | // Delete any resource links for contexts in this consumer 107 | $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' ' . 108 | "WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' c ' . 109 | "WHERE ({$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . '.context_pk = c.context_pk) AND (c.consumer_pk = :id))'; 110 | $query = $this->db->prepare($sql); 111 | $query->bindValue('id', $id, PDO::PARAM_INT); 112 | $query->execute(); 113 | 114 | // Delete any contexts for this consumer 115 | $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' ' . 116 | 'WHERE consumer_pk = :id'; 117 | $query = $this->db->prepare($sql); 118 | $query->bindValue('id', $id, PDO::PARAM_INT); 119 | $query->execute(); 120 | 121 | // Delete consumer 122 | $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::CONSUMER_TABLE_NAME . ' ' . 123 | 'WHERE consumer_pk = :id'; 124 | $query = $this->db->prepare($sql); 125 | $query->bindValue('id', $id, PDO::PARAM_INT); 126 | $ok = $query->execute(); 127 | 128 | if ($ok) { 129 | $consumer->initialize(); 130 | } 131 | 132 | return $ok; 133 | 134 | } 135 | 136 | ### 137 | ### Context methods 138 | ### 139 | 140 | /** 141 | * Delete context object. 142 | * 143 | * @param Context $context Context object 144 | * 145 | * @return boolean True if the Context object was successfully deleted 146 | */ 147 | public function deleteContext($context) 148 | { 149 | 150 | $id = $context->getRecordId(); 151 | 152 | // Delete any outstanding share keys for resource links for this context 153 | $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . ' ' . 154 | "WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' . 155 | "WHERE ({$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . '.resource_link_pk = rl.resource_link_pk) AND (rl.context_pk = :id))'; 156 | $query = $this->db->prepare($sql); 157 | $query->bindValue('id', $id, PDO::PARAM_INT); 158 | $query->execute(); 159 | 160 | // Delete any users in resource links for this context 161 | $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . ' ' . 162 | "WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' . 163 | "WHERE ({$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . '.resource_link_pk = rl.resource_link_pk) AND (rl.context_pk = :id))'; 164 | $query = $this->db->prepare($sql); 165 | $query->bindValue('id', $id, PDO::PARAM_INT); 166 | $query->execute(); 167 | 168 | // Update any resource links for which this consumer is acting as a primary resource link 169 | $sql = "UPDATE {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' ' . 170 | 'SET primary_resource_link_pk = null, share_approved = null ' . 171 | "WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' . 172 | "WHERE ({$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . '.primary_resource_link_pk = rl.resource_link_pk) AND (rl.context_pk = :id))'; 173 | $query = $this->db->prepare($sql); 174 | $query->bindValue('id', $id, PDO::PARAM_INT); 175 | $query->execute(); 176 | 177 | // Delete any resource links for this consumer 178 | $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' ' . 179 | 'WHERE context_pk = :id'; 180 | $query = $this->db->prepare($sql); 181 | $query->bindValue('id', $id, PDO::PARAM_INT); 182 | $query->execute(); 183 | 184 | // Delete context 185 | $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' ' . 186 | 'WHERE context_pk = :id'; 187 | $query = $this->db->prepare($sql); 188 | $query->bindValue('id', $id, PDO::PARAM_INT); 189 | $ok = $query->execute(); 190 | 191 | if ($ok) { 192 | $context->initialize(); 193 | } 194 | 195 | return $ok; 196 | 197 | } 198 | 199 | } 200 | -------------------------------------------------------------------------------- /src/ToolProvider/MediaType/Message.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright IMS Global Learning Consortium Inc 10 | * @date 2016 11 | * @version 3.0.0 12 | * @license GNU Lesser General Public License, version 3 () 13 | */ 14 | class Message 15 | { 16 | 17 | /** 18 | * Class constructor. 19 | * 20 | * @param Message $message Message object 21 | * @param array $capabilitiesOffered Capabilities offered 22 | */ 23 | function __construct($message, $capabilitiesOffered) 24 | { 25 | 26 | $this->message_type = $message->type; 27 | $this->path = $message->path; 28 | $this->enabled_capability = array(); 29 | foreach ($message->capabilities as $capability) { 30 | if (in_array($capability, $capabilitiesOffered)) { 31 | $this->enabled_capability[] = $capability; 32 | } 33 | } 34 | $this->parameter = array(); 35 | foreach ($message->constants as $name => $value) { 36 | $parameter = new \stdClass; 37 | $parameter->name = $name; 38 | $parameter->fixed = $value; 39 | $this->parameter[] = $parameter; 40 | } 41 | foreach ($message->variables as $name => $value) { 42 | if (in_array($value, $capabilitiesOffered)) { 43 | $parameter = new \stdClass; 44 | $parameter->name = $name; 45 | $parameter->variable = $value; 46 | $this->parameter[] = $parameter; 47 | } 48 | } 49 | 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/ToolProvider/MediaType/ResourceHandler.php: -------------------------------------------------------------------------------- 1 | 11 | * @copyright IMS Global Learning Consortium Inc 12 | * @date 2016 13 | * @version 3.0.0 14 | * @license GNU Lesser General Public License, version 3 () 15 | */ 16 | class ResourceHandler 17 | { 18 | 19 | /** 20 | * Class constructor. 21 | * 22 | * @param ToolProvider $toolProvider Tool Provider object 23 | * @param ProfileResourceHandler $resourceHandler Resource handler object 24 | */ 25 | function __construct($toolProvider, $resourceHandler) 26 | { 27 | 28 | $this->resource_type = new \stdClass; 29 | $this->resource_type->code = $resourceHandler->item->id; 30 | $this->resource_name = new \stdClass; 31 | $this->resource_name->default_value = $resourceHandler->item->name; 32 | $this->resource_name->key = "{$resourceHandler->item->id}.resource.name"; 33 | $this->description = new \stdClass; 34 | $this->description->default_value = $resourceHandler->item->description; 35 | $this->description->key = "{$resourceHandler->item->id}.resource.description"; 36 | $this->icon_info = new \stdClass; 37 | $this->icon_info->default_location = new \stdClass; 38 | $this->icon_info->default_location->path = $resourceHandler->icon; 39 | $this->icon_info->key = "{$resourceHandler->item->id}.icon.path"; 40 | $this->message = array(); 41 | foreach ($resourceHandler->requiredMessages as $message) { 42 | $this->message[] = new Message($message, $toolProvider->consumer->profile->capability_offered); 43 | } 44 | foreach ($resourceHandler->optionalMessages as $message) { 45 | if (in_array($message->type, $toolProvider->consumer->profile->capability_offered)) { 46 | $this->message[] = new Message($message, $toolProvider->consumer->profile->capability_offered); 47 | } 48 | } 49 | 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/ToolProvider/MediaType/SecurityContract.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright IMS Global Learning Consortium Inc 11 | * @date 2016 12 | * @version 3.0.0 13 | * @license GNU Lesser General Public License, version 3 () 14 | */ 15 | class SecurityContract 16 | { 17 | 18 | /** 19 | * Class constructor. 20 | * 21 | * @param ToolProvider $toolProvider Tool Provider instance 22 | * @param string $secret Shared secret 23 | */ 24 | function __construct($toolProvider, $secret) 25 | { 26 | 27 | $tcContexts = array(); 28 | foreach ($toolProvider->consumer->profile->{'@context'} as $context) { 29 | if (is_object($context)) { 30 | $tcContexts = array_merge(get_object_vars($context), $tcContexts); 31 | } 32 | } 33 | 34 | $this->shared_secret = $secret; 35 | $toolServices = array(); 36 | foreach ($toolProvider->requiredServices as $requiredService) { 37 | foreach ($requiredService->formats as $format) { 38 | $service = $toolProvider->findService($format, $requiredService->actions); 39 | if (($service !== false) && !array_key_exists($service->{'@id'}, $toolServices)) { 40 | $id = $service->{'@id'}; 41 | $parts = explode(':', $id, 2); 42 | if (count($parts) > 1) { 43 | if (array_key_exists($parts[0], $tcContexts)) { 44 | $id = "{$tcContexts[$parts[0]]}{$parts[1]}"; 45 | } 46 | } 47 | $toolService = new \stdClass; 48 | $toolService->{'@type'} = 'RestServiceProfile'; 49 | $toolService->service = $id; 50 | $toolService->action = $requiredService->actions; 51 | $toolServices[$service->{'@id'}] = $toolService; 52 | } 53 | } 54 | } 55 | foreach ($toolProvider->optionalServices as $optionalService) { 56 | foreach ($optionalService->formats as $format) { 57 | $service = $toolProvider->findService($format, $optionalService->actions); 58 | if (($service !== false) && !array_key_exists($service->{'@id'}, $toolServices)) { 59 | $id = $service->{'@id'}; 60 | $parts = explode(':', $id, 2); 61 | if (count($parts) > 1) { 62 | if (array_key_exists($parts[0], $tcContexts)) { 63 | $id = "{$tcContexts[$parts[0]]}{$parts[1]}"; 64 | } 65 | } 66 | $toolService = new \stdClass; 67 | $toolService->{'@type'} = 'RestServiceProfile'; 68 | $toolService->service = $id; 69 | $toolService->action = $optionalService->actions; 70 | $toolServices[$service->{'@id'}] = $toolService; 71 | } 72 | } 73 | } 74 | $this->tool_service = array_values($toolServices); 75 | 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/ToolProvider/MediaType/ToolProfile.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright IMS Global Learning Consortium Inc 11 | * @date 2016 12 | * @version 3.0.0 13 | * @license GNU Lesser General Public License, version 3 () 14 | */ 15 | class ToolProfile 16 | { 17 | 18 | public $product_instance; 19 | 20 | /** 21 | * Class constructor. 22 | * 23 | * @param ToolProvider $toolProvider Tool Provider object 24 | */ 25 | function __construct($toolProvider) 26 | { 27 | 28 | $this->lti_version = 'LTI-2p0'; 29 | 30 | if (!empty($toolProvider->product)) { 31 | $this->product_instance = new \stdClass; 32 | } 33 | if (!empty($toolProvider->product->id)) { 34 | $this->product_instance->guid = $toolProvider->product->id; 35 | } 36 | if (!empty($toolProvider->product->name)) { 37 | $this->product_instance->product_info = new \stdClass; 38 | $this->product_instance->product_info->product_name = new \stdClass; 39 | $this->product_instance->product_info->product_name->default_value = $toolProvider->product->name; 40 | $this->product_instance->product_info->product_name->key = 'tool.name'; 41 | } 42 | if (!empty($toolProvider->product->description)) { 43 | $this->product_instance->product_info->description = new \stdClass; 44 | $this->product_instance->product_info->description->default_value = $toolProvider->product->description; 45 | $this->product_instance->product_info->description->key = 'tool.description'; 46 | } 47 | if (!empty($toolProvider->product->url)) { 48 | $this->product_instance->guid = $toolProvider->product->url; 49 | } 50 | if (!empty($toolProvider->product->version)) { 51 | $this->product_instance->product_info->product_version = $toolProvider->product->version; 52 | } 53 | if (!empty($toolProvider->vendor)) { 54 | $this->product_instance->product_info->product_family = new \stdClass; 55 | $this->product_instance->product_info->product_family->vendor = new \stdClass; 56 | } 57 | if (!empty($toolProvider->vendor->id)) { 58 | $this->product_instance->product_info->product_family->vendor->code = $toolProvider->vendor->id; 59 | } 60 | if (!empty($toolProvider->vendor->name)) { 61 | $this->product_instance->product_info->product_family->vendor->vendor_name = new \stdClass; 62 | $this->product_instance->product_info->product_family->vendor->vendor_name->default_value = $toolProvider->vendor->name; 63 | $this->product_instance->product_info->product_family->vendor->vendor_name->key = 'tool.vendor.name'; 64 | } 65 | if (!empty($toolProvider->vendor->description)) { 66 | $this->product_instance->product_info->product_family->vendor->description = new \stdClass; 67 | $this->product_instance->product_info->product_family->vendor->description->default_value = $toolProvider->vendor->description; 68 | $this->product_instance->product_info->product_family->vendor->description->key = 'tool.vendor.description'; 69 | } 70 | if (!empty($toolProvider->vendor->url)) { 71 | $this->product_instance->product_info->product_family->vendor->website = $toolProvider->vendor->url; 72 | } 73 | if (!empty($toolProvider->vendor->timestamp)) { 74 | $this->product_instance->product_info->product_family->vendor->timestamp = date('Y-m-d\TH:i:sP', $toolProvider->vendor->timestamp); 75 | } 76 | 77 | $this->resource_handler = array(); 78 | foreach ($toolProvider->resourceHandlers as $resourceHandler) { 79 | $this->resource_handler[] = new ResourceHandler($toolProvider, $resourceHandler); 80 | } 81 | if (!empty($toolProvider->baseUrl)) { 82 | $this->base_url_choice = array(); 83 | $this->base_url_choice[] = new \stdClass; 84 | $this->base_url_choice[0]->default_base_url = $toolProvider->baseUrl; 85 | } 86 | 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/ToolProvider/MediaType/ToolProxy.php: -------------------------------------------------------------------------------- 1 | 12 | * @copyright IMS Global Learning Consortium Inc 13 | * @date 2016 14 | * @version 3.0.0 15 | * @license GNU Lesser General Public License, version 3 () 16 | */ 17 | class ToolProxy 18 | { 19 | 20 | /** 21 | * Class constructor. 22 | * 23 | * @param ToolProvider $toolProvider Tool Provider object 24 | * @param ServiceDefinition $toolProxyService Tool Proxy service 25 | * @param string $secret Shared secret 26 | */ 27 | function __construct($toolProvider, $toolProxyService, $secret) 28 | { 29 | 30 | $contexts = array(); 31 | 32 | $this->{'@context'} = array_merge(array('http://purl.imsglobal.org/ctx/lti/v2/ToolProxy'), $contexts); 33 | $this->{'@type'} = 'ToolProxy'; 34 | $this->{'@id'} = "{$toolProxyService->endpoint}"; 35 | $this->lti_version = 'LTI-2p0'; 36 | $this->tool_consumer_profile = $toolProvider->consumer->profile->{'@id'}; 37 | $this->tool_profile = new ToolProfile($toolProvider); 38 | $this->security_contract = new SecurityContract($toolProvider, $secret); 39 | 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/ToolProvider/OAuthDataStore.php: -------------------------------------------------------------------------------- 1 | 11 | * @copyright IMS Global Learning Consortium Inc 12 | * @date 2016 13 | * @version 3.0.2 14 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 15 | */ 16 | class OAuthDataStore extends OAuth\OAuthDataStore 17 | { 18 | 19 | /** 20 | * Tool Provider object. 21 | * 22 | * @var ToolProvider $toolProvider 23 | */ 24 | private $toolProvider = null; 25 | 26 | /** 27 | * Class constructor. 28 | * 29 | * @param ToolProvider $toolProvider Tool_Provider object 30 | */ 31 | public function __construct($toolProvider) 32 | { 33 | 34 | $this->toolProvider = $toolProvider; 35 | 36 | } 37 | 38 | /** 39 | * Create an OAuthConsumer object for the tool consumer. 40 | * 41 | * @param string $consumerKey Consumer key value 42 | * 43 | * @return OAuthConsumer OAuthConsumer object 44 | */ 45 | function lookup_consumer($consumerKey) 46 | { 47 | 48 | return new OAuth\OAuthConsumer($this->toolProvider->consumer->getKey(), 49 | $this->toolProvider->consumer->secret); 50 | 51 | } 52 | 53 | /** 54 | * Create an OAuthToken object for the tool consumer. 55 | * 56 | * @param string $consumer OAuthConsumer object 57 | * @param string $tokenType Token type 58 | * @param string $token Token value 59 | * 60 | * @return OAuthToken OAuthToken object 61 | */ 62 | function lookup_token($consumer, $tokenType, $token) 63 | { 64 | 65 | return new OAuth\OAuthToken($consumer, ''); 66 | 67 | } 68 | 69 | /** 70 | * Lookup nonce value for the tool consumer. 71 | * 72 | * @param OAuthConsumer $consumer OAuthConsumer object 73 | * @param string $token Token value 74 | * @param string $value Nonce value 75 | * @param string $timestamp Date/time of request 76 | * 77 | * @return boolean True if the nonce value already exists 78 | */ 79 | function lookup_nonce($consumer, $token, $value, $timestamp) 80 | { 81 | 82 | $nonce = new ConsumerNonce($this->toolProvider->consumer, $value); 83 | $ok = !$nonce->load(); 84 | if ($ok) { 85 | $ok = $nonce->save(); 86 | } 87 | if (!$ok) { 88 | $this->toolProvider->reason = 'Invalid nonce.'; 89 | } 90 | 91 | return !$ok; 92 | 93 | } 94 | 95 | /** 96 | * Get new request token. 97 | * 98 | * @param OAuthConsumer $consumer OAuthConsumer object 99 | * @param string $callback Callback URL 100 | * 101 | * @return string Null value 102 | */ 103 | function new_request_token($consumer, $callback = null) 104 | { 105 | 106 | return null; 107 | 108 | } 109 | 110 | /** 111 | * Get new access token. 112 | * 113 | * @param string $token Token value 114 | * @param OAuthConsumer $consumer OAuthConsumer object 115 | * @param string $verifier Verification code 116 | * 117 | * @return string Null value 118 | */ 119 | function new_access_token($token, $consumer, $verifier = null) 120 | { 121 | 122 | return null; 123 | 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /src/ToolProvider/Outcome.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright IMS Global Learning Consortium Inc 10 | * @date 2016 11 | * @version 3.0.2 12 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 13 | */ 14 | class Outcome 15 | { 16 | 17 | /** 18 | * Language value. 19 | * 20 | * @var string $language 21 | */ 22 | public $language = null; 23 | /** 24 | * Outcome status value. 25 | * 26 | * @var string $status 27 | */ 28 | public $status = null; 29 | /** 30 | * Outcome date value. 31 | * 32 | * @var string $date 33 | */ 34 | public $date = null; 35 | /** 36 | * Outcome type value. 37 | * 38 | * @var string $type 39 | */ 40 | public $type = null; 41 | /** 42 | * Outcome data source value. 43 | * 44 | * @var string $dataSource 45 | */ 46 | public $dataSource = null; 47 | 48 | /** 49 | * Outcome value. 50 | * 51 | * @var string $value 52 | */ 53 | private $value = null; 54 | 55 | /** 56 | * Class constructor. 57 | * 58 | * @param string $value Outcome value (optional, default is none) 59 | */ 60 | public function __construct($value = null) 61 | { 62 | 63 | $this->value = $value; 64 | $this->language = 'en-US'; 65 | $this->date = gmdate('Y-m-d\TH:i:s\Z', time()); 66 | $this->type = 'decimal'; 67 | 68 | } 69 | 70 | /** 71 | * Get the outcome value. 72 | * 73 | * @return string Outcome value 74 | */ 75 | public function getValue() 76 | { 77 | 78 | return $this->value; 79 | 80 | } 81 | 82 | /** 83 | * Set the outcome value. 84 | * 85 | * @param string $value Outcome value 86 | */ 87 | public function setValue($value) 88 | { 89 | 90 | $this->value = $value; 91 | 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /src/ToolProvider/ResourceLink.php: -------------------------------------------------------------------------------- 1 | 16 | * @copyright IMS Global Learning Consortium Inc 17 | * @date 2016 18 | * @version 3.0.2 19 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 20 | */ 21 | class ResourceLink 22 | { 23 | 24 | /** 25 | * Read action. 26 | */ 27 | const EXT_READ = 1; 28 | /** 29 | * Write (create/update) action. 30 | */ 31 | const EXT_WRITE = 2; 32 | /** 33 | * Delete action. 34 | */ 35 | const EXT_DELETE = 3; 36 | /** 37 | * Create action. 38 | */ 39 | const EXT_CREATE = 4; 40 | /** 41 | * Update action. 42 | */ 43 | const EXT_UPDATE = 5; 44 | 45 | /** 46 | * Decimal outcome type. 47 | */ 48 | const EXT_TYPE_DECIMAL = 'decimal'; 49 | /** 50 | * Percentage outcome type. 51 | */ 52 | const EXT_TYPE_PERCENTAGE = 'percentage'; 53 | /** 54 | * Ratio outcome type. 55 | */ 56 | const EXT_TYPE_RATIO = 'ratio'; 57 | /** 58 | * Letter (A-F) outcome type. 59 | */ 60 | const EXT_TYPE_LETTER_AF = 'letteraf'; 61 | /** 62 | * Letter (A-F) with optional +/- outcome type. 63 | */ 64 | const EXT_TYPE_LETTER_AF_PLUS = 'letterafplus'; 65 | /** 66 | * Pass/fail outcome type. 67 | */ 68 | const EXT_TYPE_PASS_FAIL = 'passfail'; 69 | /** 70 | * Free text outcome type. 71 | */ 72 | const EXT_TYPE_TEXT = 'freetext'; 73 | 74 | /** 75 | * Context title. 76 | * 77 | * @var string $title 78 | */ 79 | public $title = null; 80 | /** 81 | * Resource link ID as supplied in the last connection request. 82 | * 83 | * @var string $ltiResourceLinkId 84 | */ 85 | public $ltiResourceLinkId = null; 86 | /** 87 | * User group sets (null if the consumer does not support the groups enhancement) 88 | * 89 | * @var array $groupSets 90 | */ 91 | public $groupSets = null; 92 | /** 93 | * User groups (null if the consumer does not support the groups enhancement) 94 | * 95 | * @var array $groups 96 | */ 97 | public $groups = null; 98 | /** 99 | * Request for last service request. 100 | * 101 | * @var string $extRequest 102 | */ 103 | public $extRequest = null; 104 | /** 105 | * Request headers for last service request. 106 | * 107 | * @var array $extRequestHeaders 108 | */ 109 | public $extRequestHeaders = null; 110 | /** 111 | * Response from last service request. 112 | * 113 | * @var string $extResponse 114 | */ 115 | public $extResponse = null; 116 | /** 117 | * Response header from last service request. 118 | * 119 | * @var array $extResponseHeaders 120 | */ 121 | public $extResponseHeaders = null; 122 | /** 123 | * Consumer key value for resource link being shared (if any). 124 | * 125 | * @var string $primaryResourceLinkId 126 | */ 127 | public $primaryResourceLinkId = null; 128 | /** 129 | * Whether the sharing request has been approved by the primary resource link. 130 | * 131 | * @var boolean $shareApproved 132 | */ 133 | public $shareApproved = null; 134 | /** 135 | * Date/time when the object was created. 136 | * 137 | * @var int $created 138 | */ 139 | public $created = null; 140 | /** 141 | * Date/time when the object was last updated. 142 | * 143 | * @var int $updated 144 | */ 145 | public $updated = null; 146 | 147 | /** 148 | * Record ID for this resource link. 149 | * 150 | * @var int $id 151 | */ 152 | private $id = null; 153 | /** 154 | * Tool Consumer for this resource link. 155 | * 156 | * @var ToolConsumer $consumer 157 | */ 158 | private $consumer = null; 159 | /** 160 | * Tool Consumer ID for this resource link. 161 | * 162 | * @var int $consumerId 163 | */ 164 | private $consumerId = null; 165 | /** 166 | * Context for this resource link. 167 | * 168 | * @var Context $context 169 | */ 170 | private $context = null; 171 | /** 172 | * Context ID for this resource link. 173 | * 174 | * @var int $contextId 175 | */ 176 | private $contextId = null; 177 | /** 178 | * Setting values (LTI parameters, custom parameters and local parameters). 179 | * 180 | * @var array $settings 181 | */ 182 | private $settings = null; 183 | /** 184 | * Whether the settings value have changed since last saved. 185 | * 186 | * @var boolean $settingsChanged 187 | */ 188 | private $settingsChanged = false; 189 | /** 190 | * XML document for the last extension service request. 191 | * 192 | * @var string $extDoc 193 | */ 194 | private $extDoc = null; 195 | /** 196 | * XML node array for the last extension service request. 197 | * 198 | * @var array $extNodes 199 | */ 200 | private $extNodes = null; 201 | /** 202 | * Data connector object or string. 203 | * 204 | * @var mixed $dataConnector 205 | */ 206 | private $dataConnector = null; 207 | 208 | /** 209 | * Class constructor. 210 | */ 211 | public function __construct() 212 | { 213 | 214 | $this->initialize(); 215 | 216 | } 217 | 218 | /** 219 | * Initialise the resource link. 220 | */ 221 | public function initialize() 222 | { 223 | 224 | $this->title = ''; 225 | $this->settings = array(); 226 | $this->groupSets = null; 227 | $this->groups = null; 228 | $this->primaryResourceLinkId = null; 229 | $this->shareApproved = null; 230 | $this->created = null; 231 | $this->updated = null; 232 | 233 | } 234 | 235 | /** 236 | * Initialise the resource link. 237 | * 238 | * Pseudonym for initialize(). 239 | */ 240 | public function initialise() 241 | { 242 | 243 | $this->initialize(); 244 | 245 | } 246 | 247 | /** 248 | * Save the resource link to the database. 249 | * 250 | * @return boolean True if the resource link was successfully saved. 251 | */ 252 | public function save() 253 | { 254 | 255 | $ok = $this->getDataConnector()->saveResourceLink($this); 256 | if ($ok) { 257 | $this->settingsChanged = false; 258 | } 259 | 260 | return $ok; 261 | 262 | } 263 | 264 | /** 265 | * Delete the resource link from the database. 266 | * 267 | * @return boolean True if the resource link was successfully deleted. 268 | */ 269 | public function delete() 270 | { 271 | 272 | return $this->getDataConnector()->deleteResourceLink($this); 273 | 274 | } 275 | 276 | /** 277 | * Get tool consumer. 278 | * 279 | * @return ToolConsumer Tool consumer object for this resource link. 280 | */ 281 | public function getConsumer() 282 | { 283 | 284 | if (is_null($this->consumer)) { 285 | if (!is_null($this->context) || !is_null($this->contextId)) { 286 | $this->consumer = $this->getContext()->getConsumer(); 287 | } else { 288 | $this->consumer = ToolConsumer::fromRecordId($this->consumerId, $this->getDataConnector()); 289 | } 290 | } 291 | 292 | return $this->consumer; 293 | 294 | } 295 | 296 | /** 297 | * Set tool consumer ID. 298 | * 299 | * @param int $consumerId Tool Consumer ID for this resource link. 300 | */ 301 | public function setConsumerId($consumerId) 302 | { 303 | 304 | $this->consumer = null; 305 | $this->consumerId = $consumerId; 306 | 307 | } 308 | 309 | /** 310 | * Get context. 311 | * 312 | * @return object LTIContext object for this resource link. 313 | */ 314 | public function getContext() 315 | { 316 | 317 | if (is_null($this->context) && !is_null($this->contextId)) { 318 | $this->context = Context::fromRecordId($this->contextId, $this->getDataConnector()); 319 | } 320 | 321 | return $this->context; 322 | 323 | } 324 | 325 | /** 326 | * Get context record ID. 327 | * 328 | * @return int Context record ID for this resource link. 329 | */ 330 | public function getContextId() 331 | { 332 | 333 | return $this->contextId; 334 | 335 | } 336 | 337 | /** 338 | * Set context ID. 339 | * 340 | * @param int $contextId Context ID for this resource link. 341 | */ 342 | public function setContextId($contextId) 343 | { 344 | 345 | $this->context = null; 346 | $this->contextId = $contextId; 347 | 348 | } 349 | 350 | /** 351 | * Get tool consumer key. 352 | * 353 | * @return string Consumer key value for this resource link. 354 | */ 355 | public function getKey() 356 | { 357 | 358 | return $this->getConsumer()->getKey(); 359 | 360 | } 361 | 362 | /** 363 | * Get resource link ID. 364 | * 365 | * @return string ID for this resource link. 366 | */ 367 | public function getId() 368 | { 369 | 370 | return $this->ltiResourceLinkId; 371 | 372 | } 373 | 374 | /** 375 | * Get resource link record ID. 376 | * 377 | * @return int Record ID for this resource link. 378 | */ 379 | public function getRecordId() 380 | { 381 | 382 | return $this->id; 383 | 384 | } 385 | 386 | /** 387 | * Set resource link record ID. 388 | * 389 | * @param int $id Record ID for this resource link. 390 | */ 391 | public function setRecordId($id) 392 | { 393 | 394 | $this->id = $id; 395 | 396 | } 397 | 398 | /** 399 | * Get the data connector. 400 | * 401 | * @return mixed Data connector object or string 402 | */ 403 | public function getDataConnector() 404 | { 405 | 406 | return $this->dataConnector; 407 | 408 | } 409 | 410 | /** 411 | * Get a setting value. 412 | * 413 | * @param string $name Name of setting 414 | * @param string $default Value to return if the setting does not exist (optional, default is an empty string) 415 | * 416 | * @return string Setting value 417 | */ 418 | public function getSetting($name, $default = '') 419 | { 420 | 421 | if (array_key_exists($name, $this->settings)) { 422 | $value = $this->settings[$name]; 423 | } else { 424 | $value = $default; 425 | } 426 | 427 | return $value; 428 | 429 | } 430 | 431 | /** 432 | * Set a setting value. 433 | * 434 | * @param string $name Name of setting 435 | * @param string $value Value to set, use an empty value to delete a setting (optional, default is null) 436 | */ 437 | public function setSetting($name, $value = null) 438 | { 439 | 440 | $old_value = $this->getSetting($name); 441 | if ($value !== $old_value) { 442 | if (!empty($value)) { 443 | $this->settings[$name] = $value; 444 | } else { 445 | unset($this->settings[$name]); 446 | } 447 | $this->settingsChanged = true; 448 | } 449 | 450 | } 451 | 452 | /** 453 | * Get an array of all setting values. 454 | * 455 | * @return array Associative array of setting values 456 | */ 457 | public function getSettings() 458 | { 459 | 460 | return $this->settings; 461 | 462 | } 463 | 464 | /** 465 | * Set an array of all setting values. 466 | * 467 | * @param array $settings Associative array of setting values 468 | */ 469 | public function setSettings($settings) 470 | { 471 | 472 | $this->settings = $settings; 473 | 474 | } 475 | 476 | /** 477 | * Save setting values. 478 | * 479 | * @return boolean True if the settings were successfully saved 480 | */ 481 | public function saveSettings() 482 | { 483 | 484 | if ($this->settingsChanged) { 485 | $ok = $this->save(); 486 | } else { 487 | $ok = true; 488 | } 489 | 490 | return $ok; 491 | 492 | } 493 | 494 | /** 495 | * Check if the Outcomes service is supported. 496 | * 497 | * @return boolean True if this resource link supports the Outcomes service (either the LTI 1.1 or extension service) 498 | */ 499 | public function hasOutcomesService() 500 | { 501 | 502 | $url = $this->getSetting('ext_ims_lis_basic_outcome_url') . $this->getSetting('lis_outcome_service_url'); 503 | 504 | return !empty($url); 505 | 506 | } 507 | 508 | /** 509 | * Check if the Memberships extension service is supported. 510 | * 511 | * @return boolean True if this resource link supports the Memberships extension service 512 | */ 513 | public function hasMembershipsService() 514 | { 515 | 516 | $url = $this->getSetting('ext_ims_lis_memberships_url'); 517 | 518 | return !empty($url); 519 | 520 | } 521 | 522 | /** 523 | * Check if the Setting extension service is supported. 524 | * 525 | * @return boolean True if this resource link supports the Setting extension service 526 | */ 527 | public function hasSettingService() 528 | { 529 | 530 | $url = $this->getSetting('ext_ims_lti_tool_setting_url'); 531 | 532 | return !empty($url); 533 | 534 | } 535 | 536 | /** 537 | * Perform an Outcomes service request. 538 | * 539 | * @param int $action The action type constant 540 | * @param Outcome $ltiOutcome Outcome object 541 | * @param User $user User object 542 | * 543 | * @return boolean True if the request was successfully processed 544 | */ 545 | public function doOutcomesService($action, $ltiOutcome, $user) 546 | { 547 | 548 | $response = false; 549 | $this->extResponse = null; 550 | 551 | // Lookup service details from the source resource link appropriate to the user (in case the destination is being shared) 552 | $sourceResourceLink = $user->getResourceLink(); 553 | $sourcedId = $user->ltiResultSourcedId; 554 | 555 | // Use LTI 1.1 service in preference to extension service if it is available 556 | $urlLTI11 = $sourceResourceLink->getSetting('lis_outcome_service_url'); 557 | $urlExt = $sourceResourceLink->getSetting('ext_ims_lis_basic_outcome_url'); 558 | if ($urlExt || $urlLTI11) { 559 | switch ($action) { 560 | case self::EXT_READ: 561 | if ($urlLTI11 && ($ltiOutcome->type === self::EXT_TYPE_DECIMAL)) { 562 | $do = 'readResult'; 563 | } else if ($urlExt) { 564 | $urlLTI11 = null; 565 | $do = 'basic-lis-readresult'; 566 | } 567 | break; 568 | case self::EXT_WRITE: 569 | if ($urlLTI11 && $this->checkValueType($ltiOutcome, array(self::EXT_TYPE_DECIMAL))) { 570 | $do = 'replaceResult'; 571 | } else if ($this->checkValueType($ltiOutcome)) { 572 | $urlLTI11 = null; 573 | $do = 'basic-lis-updateresult'; 574 | } 575 | break; 576 | case self::EXT_DELETE: 577 | if ($urlLTI11 && ($ltiOutcome->type === self::EXT_TYPE_DECIMAL)) { 578 | $do = 'deleteResult'; 579 | } else if ($urlExt) { 580 | $urlLTI11 = null; 581 | $do = 'basic-lis-deleteresult'; 582 | } 583 | break; 584 | } 585 | } 586 | if (isset($do)) { 587 | $value = $ltiOutcome->getValue(); 588 | if (is_null($value)) { 589 | $value = ''; 590 | } 591 | if ($urlLTI11) { 592 | $xml = ''; 593 | if ($action === self::EXT_WRITE) { 594 | $xml = << 597 | 598 | {$ltiOutcome->language} 599 | {$value} 600 | 601 | 602 | EOF; 603 | } 604 | $sourcedId = htmlentities($sourcedId); 605 | $xml = << 607 | 608 | {$sourcedId} 609 | {$xml} 610 | 611 | EOF; 612 | if ($this->doLTI11Service($do, $urlLTI11, $xml)) { 613 | switch ($action) { 614 | case self::EXT_READ: 615 | if (!isset($this->extNodes['imsx_POXBody']["{$do}Response"]['result']['resultScore']['textString'])) { 616 | break; 617 | } else { 618 | $ltiOutcome->setValue($this->extNodes['imsx_POXBody']["{$do}Response"]['result']['resultScore']['textString']); 619 | } 620 | case self::EXT_WRITE: 621 | case self::EXT_DELETE: 622 | $response = true; 623 | break; 624 | } 625 | } 626 | } else { 627 | $params = array(); 628 | $params['sourcedid'] = $sourcedId; 629 | $params['result_resultscore_textstring'] = $value; 630 | if (!empty($ltiOutcome->language)) { 631 | $params['result_resultscore_language'] = $ltiOutcome->language; 632 | } 633 | if (!empty($ltiOutcome->status)) { 634 | $params['result_statusofresult'] = $ltiOutcome->status; 635 | } 636 | if (!empty($ltiOutcome->date)) { 637 | $params['result_date'] = $ltiOutcome->date; 638 | } 639 | if (!empty($ltiOutcome->type)) { 640 | $params['result_resultvaluesourcedid'] = $ltiOutcome->type; 641 | } 642 | if (!empty($ltiOutcome->data_source)) { 643 | $params['result_datasource'] = $ltiOutcome->data_source; 644 | } 645 | if ($this->doService($do, $urlExt, $params)) { 646 | switch ($action) { 647 | case self::EXT_READ: 648 | if (isset($this->extNodes['result']['resultscore']['textstring'])) { 649 | $response = $this->extNodes['result']['resultscore']['textstring']; 650 | } 651 | break; 652 | case self::EXT_WRITE: 653 | case self::EXT_DELETE: 654 | $response = true; 655 | break; 656 | } 657 | } 658 | } 659 | if (is_array($response) && (count($response) <= 0)) { 660 | $response = ''; 661 | } 662 | } 663 | 664 | return $response; 665 | 666 | } 667 | 668 | /** 669 | * Perform a Memberships service request. 670 | * 671 | * The user table is updated with the new list of user objects. 672 | * 673 | * @param boolean $withGroups True is group information is to be requested as well 674 | * 675 | * @return mixed Array of User objects or False if the request was not successful 676 | */ 677 | public function doMembershipsService($withGroups = false) 678 | { 679 | 680 | $users = array(); 681 | $oldUsers = $this->getUserResultSourcedIDs(true, ToolProvider::ID_SCOPE_RESOURCE); 682 | $this->extResponse = null; 683 | $url = $this->getSetting('ext_ims_lis_memberships_url'); 684 | $params = array(); 685 | $params['id'] = $this->getSetting('ext_ims_lis_memberships_id'); 686 | $ok = false; 687 | if ($withGroups) { 688 | $ok = $this->doService('basic-lis-readmembershipsforcontextwithgroups', $url, $params); 689 | } 690 | if ($ok) { 691 | $this->groupSets = array(); 692 | $this->groups = array(); 693 | } else { 694 | $ok = $this->doService('basic-lis-readmembershipsforcontext', $url, $params); 695 | } 696 | 697 | if ($ok) { 698 | if (!isset($this->extNodes['memberships']['member'])) { 699 | $members = array(); 700 | } else if (!isset($this->extNodes['memberships']['member'][0])) { 701 | $members = array(); 702 | $members[0] = $this->extNodes['memberships']['member']; 703 | } else { 704 | $members = $this->extNodes['memberships']['member']; 705 | } 706 | 707 | for ($i = 0; $i < count($members); $i++) { 708 | 709 | $user = User::fromResourceLink($this, $members[$i]['user_id']); 710 | 711 | // Set the user name 712 | $firstname = (isset($members[$i]['person_name_given'])) ? $members[$i]['person_name_given'] : ''; 713 | $lastname = (isset($members[$i]['person_name_family'])) ? $members[$i]['person_name_family'] : ''; 714 | $fullname = (isset($members[$i]['person_name_full'])) ? $members[$i]['person_name_full'] : ''; 715 | $user->setNames($firstname, $lastname, $fullname); 716 | 717 | // Set the user email 718 | $email = (isset($members[$i]['person_contact_email_primary'])) ? $members[$i]['person_contact_email_primary'] : ''; 719 | $user->setEmail($email, $this->getConsumer()->defaultEmail); 720 | 721 | /// Set the user roles 722 | if (isset($members[$i]['roles'])) { 723 | $user->roles = ToolProvider::parseRoles($members[$i]['roles']); 724 | } 725 | 726 | // Set the user groups 727 | if (!isset($members[$i]['groups']['group'])) { 728 | $groups = array(); 729 | } else if (!isset($members[$i]['groups']['group'][0])) { 730 | $groups = array(); 731 | $groups[0] = $members[$i]['groups']['group']; 732 | } else { 733 | $groups = $members[$i]['groups']['group']; 734 | } 735 | for ($j = 0; $j < count($groups); $j++) { 736 | $group = $groups[$j]; 737 | if (isset($group['set'])) { 738 | $set_id = $group['set']['id']; 739 | if (!isset($this->groupSets[$set_id])) { 740 | $this->groupSets[$set_id] = array('title' => $group['set']['title'], 'groups' => array(), 741 | 'num_members' => 0, 'num_staff' => 0, 'num_learners' => 0); 742 | } 743 | $this->groupSets[$set_id]['num_members']++; 744 | if ($user->isStaff()) { 745 | $this->groupSets[$set_id]['num_staff']++; 746 | } 747 | if ($user->isLearner()) { 748 | $this->groupSets[$set_id]['num_learners']++; 749 | } 750 | if (!in_array($group['id'], $this->groupSets[$set_id]['groups'])) { 751 | $this->groupSets[$set_id]['groups'][] = $group['id']; 752 | } 753 | $this->groups[$group['id']] = array('title' => $group['title'], 'set' => $set_id); 754 | } else { 755 | $this->groups[$group['id']] = array('title' => $group['title']); 756 | } 757 | $user->groups[] = $group['id']; 758 | } 759 | 760 | // If a result sourcedid is provided save the user 761 | if (isset($members[$i]['lis_result_sourcedid'])) { 762 | $user->ltiResultSourcedId = $members[$i]['lis_result_sourcedid']; 763 | $user->save(); 764 | } 765 | $users[] = $user; 766 | 767 | // Remove old user (if it exists) 768 | unset($oldUsers[$user->getId(ToolProvider::ID_SCOPE_RESOURCE)]); 769 | } 770 | 771 | // Delete any old users which were not in the latest list from the tool consumer 772 | foreach ($oldUsers as $id => $user) { 773 | $user->delete(); 774 | } 775 | } else { 776 | $users = false; 777 | } 778 | 779 | return $users; 780 | 781 | } 782 | 783 | /** 784 | * Perform a Setting service request. 785 | * 786 | * @param int $action The action type constant 787 | * @param string $value The setting value (optional, default is null) 788 | * 789 | * @return mixed The setting value for a read action, true if a write or delete action was successful, otherwise false 790 | */ 791 | public function doSettingService($action, $value = null) 792 | { 793 | 794 | $response = false; 795 | $this->extResponse = null; 796 | switch ($action) { 797 | case self::EXT_READ: 798 | $do = 'basic-lti-loadsetting'; 799 | break; 800 | case self::EXT_WRITE: 801 | $do = 'basic-lti-savesetting'; 802 | break; 803 | case self::EXT_DELETE: 804 | $do = 'basic-lti-deletesetting'; 805 | break; 806 | } 807 | if (isset($do)) { 808 | 809 | $url = $this->getSetting('ext_ims_lti_tool_setting_url'); 810 | $params = array(); 811 | $params['id'] = $this->getSetting('ext_ims_lti_tool_setting_id'); 812 | if (is_null($value)) { 813 | $value = ''; 814 | } 815 | $params['setting'] = $value; 816 | 817 | if ($this->doService($do, $url, $params)) { 818 | switch ($action) { 819 | case self::EXT_READ: 820 | if (isset($this->extNodes['setting']['value'])) { 821 | $response = $this->extNodes['setting']['value']; 822 | if (is_array($response)) { 823 | $response = ''; 824 | } 825 | } 826 | break; 827 | case self::EXT_WRITE: 828 | $this->setSetting('ext_ims_lti_tool_setting', $value); 829 | $this->saveSettings(); 830 | $response = true; 831 | break; 832 | case self::EXT_DELETE: 833 | $response = true; 834 | break; 835 | } 836 | } 837 | } 838 | 839 | return $response; 840 | 841 | } 842 | 843 | /** 844 | * Check if the Tool Settings service is supported. 845 | * 846 | * @return boolean True if this resource link supports the Tool Settings service 847 | */ 848 | public function hasToolSettingsService() 849 | { 850 | 851 | $url = $this->getSetting('custom_link_setting_url'); 852 | 853 | return !empty($url); 854 | 855 | } 856 | 857 | /** 858 | * Get Tool Settings. 859 | * 860 | * @param int $mode Mode for request (optional, default is current level only) 861 | * @param boolean $simple True if all the simple media type is to be used (optional, default is true) 862 | * 863 | * @return mixed The array of settings if successful, otherwise false 864 | */ 865 | public function getToolSettings($mode = Service\ToolSettings::MODE_CURRENT_LEVEL, $simple = true) 866 | { 867 | 868 | $url = $this->getSetting('custom_link_setting_url'); 869 | $service = new Service\ToolSettings($this, $url, $simple); 870 | $response = $service->get($mode); 871 | 872 | return $response; 873 | 874 | } 875 | 876 | /** 877 | * Perform a Tool Settings service request. 878 | * 879 | * @param array $settings An associative array of settings (optional, default is none) 880 | * 881 | * @return boolean True if action was successful, otherwise false 882 | */ 883 | public function setToolSettings($settings = array()) 884 | { 885 | 886 | $url = $this->getSetting('custom_link_setting_url'); 887 | $service = new Service\ToolSettings($this, $url); 888 | $response = $service->set($settings); 889 | 890 | return $response; 891 | 892 | } 893 | 894 | /** 895 | * Check if the Membership service is supported. 896 | * 897 | * @return boolean True if this resource link supports the Membership service 898 | */ 899 | public function hasMembershipService() 900 | { 901 | 902 | $has = !empty($this->contextId); 903 | if ($has) { 904 | $has = !empty($this->getContext()->getSetting('custom_context_memberships_url')); 905 | } 906 | 907 | return $has; 908 | 909 | } 910 | 911 | /** 912 | * Get Memberships. 913 | * 914 | * @return mixed The array of User objects if successful, otherwise false 915 | */ 916 | public function getMembership() 917 | { 918 | 919 | $response = false; 920 | if (!empty($this->contextId)) { 921 | $url = $this->getContext()->getSetting('custom_context_memberships_url'); 922 | if (!empty($url)) { 923 | $service = new Service\Membership($this, $url); 924 | $response = $service->get(); 925 | } 926 | } 927 | 928 | return $response; 929 | 930 | } 931 | 932 | /** 933 | * Obtain an array of User objects for users with a result sourcedId. 934 | * 935 | * The array may include users from other resource links which are sharing this resource link. 936 | * It may also be optionally indexed by the user ID of a specified scope. 937 | * 938 | * @param boolean $localOnly True if only users from this resource link are to be returned, not users from shared resource links (optional, default is false) 939 | * @param int $idScope Scope to use for ID values (optional, default is null for consumer default) 940 | * 941 | * @return array Array of User objects 942 | */ 943 | public function getUserResultSourcedIDs($localOnly = false, $idScope = null) 944 | { 945 | 946 | return $this->getDataConnector()->getUserResultSourcedIDsResourceLink($this, $localOnly, $idScope); 947 | 948 | } 949 | 950 | /** 951 | * Get an array of ResourceLinkShare objects for each resource link which is sharing this context. 952 | * 953 | * @return array Array of ResourceLinkShare objects 954 | */ 955 | public function getShares() 956 | { 957 | 958 | return $this->getDataConnector()->getSharesResourceLink($this); 959 | 960 | } 961 | 962 | /** 963 | * Class constructor from consumer. 964 | * 965 | * @param ToolConsumer $consumer Consumer object 966 | * @param string $ltiResourceLinkId Resource link ID value 967 | * @param string $tempId Temporary Resource link ID value (optional, default is null) 968 | * @return ResourceLink 969 | */ 970 | public static function fromConsumer($consumer, $ltiResourceLinkId, $tempId = null) 971 | { 972 | 973 | $resourceLink = new ResourceLink(); 974 | $resourceLink->consumer = $consumer; 975 | $resourceLink->dataConnector = $consumer->getDataConnector(); 976 | $resourceLink->ltiResourceLinkId = $ltiResourceLinkId; 977 | if (!empty($ltiResourceLinkId)) { 978 | $resourceLink->load(); 979 | if (is_null($resourceLink->id) && !empty($tempId)) { 980 | $resourceLink->ltiResourceLinkId = $tempId; 981 | $resourceLink->load(); 982 | $resourceLink->ltiResourceLinkId = $ltiResourceLinkId; 983 | } 984 | } 985 | 986 | return $resourceLink; 987 | 988 | } 989 | 990 | /** 991 | * Class constructor from context. 992 | * 993 | * @param Context $context Context object 994 | * @param string $ltiResourceLinkId Resource link ID value 995 | * @param string $tempId Temporary Resource link ID value (optional, default is null) 996 | * @return ResourceLink 997 | */ 998 | public static function fromContext($context, $ltiResourceLinkId, $tempId = null) 999 | { 1000 | 1001 | $resourceLink = new ResourceLink(); 1002 | $resourceLink->setContextId($context->getRecordId()); 1003 | $resourceLink->context = $context; 1004 | $resourceLink->dataConnector = $context->getDataConnector(); 1005 | $resourceLink->ltiResourceLinkId = $ltiResourceLinkId; 1006 | if (!empty($ltiResourceLinkId)) { 1007 | $resourceLink->load(); 1008 | if (is_null($resourceLink->id) && !empty($tempId)) { 1009 | $resourceLink->ltiResourceLinkId = $tempId; 1010 | $resourceLink->load(); 1011 | $resourceLink->ltiResourceLinkId = $ltiResourceLinkId; 1012 | } 1013 | } 1014 | 1015 | return $resourceLink; 1016 | 1017 | } 1018 | 1019 | /** 1020 | * Load the resource link from the database. 1021 | * 1022 | * @param int $id Record ID of resource link 1023 | * @param DataConnector $dataConnector Database connection object 1024 | * 1025 | * @return ResourceLink ResourceLink object 1026 | */ 1027 | public static function fromRecordId($id, $dataConnector) 1028 | { 1029 | 1030 | $resourceLink = new ResourceLink(); 1031 | $resourceLink->dataConnector = $dataConnector; 1032 | $resourceLink->load($id); 1033 | 1034 | return $resourceLink; 1035 | 1036 | } 1037 | 1038 | ### 1039 | ### PRIVATE METHODS 1040 | ### 1041 | 1042 | /** 1043 | * Load the resource link from the database. 1044 | * 1045 | * @param int $id Record ID of resource link (optional, default is null) 1046 | * 1047 | * @return boolean True if resource link was successfully loaded 1048 | */ 1049 | private function load($id = null) 1050 | { 1051 | 1052 | $this->initialize(); 1053 | $this->id = $id; 1054 | 1055 | return $this->getDataConnector()->loadResourceLink($this); 1056 | 1057 | } 1058 | 1059 | /** 1060 | * Convert data type of value to a supported type if possible. 1061 | * 1062 | * @param Outcome $ltiOutcome Outcome object 1063 | * @param string[] $supportedTypes Array of outcome types to be supported (optional, default is null to use supported types reported in the last launch for this resource link) 1064 | * 1065 | * @return boolean True if the type/value are valid and supported 1066 | */ 1067 | private function checkValueType($ltiOutcome, $supportedTypes = null) 1068 | { 1069 | 1070 | if (empty($supportedTypes)) { 1071 | $supportedTypes = explode(',', str_replace(' ', '', strtolower($this->getSetting('ext_ims_lis_resultvalue_sourcedids', self::EXT_TYPE_DECIMAL)))); 1072 | } 1073 | $type = $ltiOutcome->type; 1074 | $value = $ltiOutcome->getValue(); 1075 | // Check whether the type is supported or there is no value 1076 | $ok = in_array($type, $supportedTypes) || (strlen($value) <= 0); 1077 | if (!$ok) { 1078 | // Convert numeric values to decimal 1079 | if ($type === self::EXT_TYPE_PERCENTAGE) { 1080 | if (substr($value, -1) === '%') { 1081 | $value = substr($value, 0, -1); 1082 | } 1083 | $ok = is_numeric($value) && ($value >= 0) && ($value <= 100); 1084 | if ($ok) { 1085 | $ltiOutcome->setValue($value / 100); 1086 | $ltiOutcome->type = self::EXT_TYPE_DECIMAL; 1087 | } 1088 | } else if ($type === self::EXT_TYPE_RATIO) { 1089 | $parts = explode('/', $value, 2); 1090 | $ok = (count($parts) === 2) && is_numeric($parts[0]) && is_numeric($parts[1]) && ($parts[0] >= 0) && ($parts[1] > 0); 1091 | if ($ok) { 1092 | $ltiOutcome->setValue($parts[0] / $parts[1]); 1093 | $ltiOutcome->type = self::EXT_TYPE_DECIMAL; 1094 | } 1095 | // Convert letter_af to letter_af_plus or text 1096 | } else if ($type === self::EXT_TYPE_LETTER_AF) { 1097 | if (in_array(self::EXT_TYPE_LETTER_AF_PLUS, $supportedTypes)) { 1098 | $ok = true; 1099 | $ltiOutcome->type = self::EXT_TYPE_LETTER_AF_PLUS; 1100 | } else if (in_array(self::EXT_TYPE_TEXT, $supportedTypes)) { 1101 | $ok = true; 1102 | $ltiOutcome->type = self::EXT_TYPE_TEXT; 1103 | } 1104 | // Convert letter_af_plus to letter_af or text 1105 | } else if ($type === self::EXT_TYPE_LETTER_AF_PLUS) { 1106 | if (in_array(self::EXT_TYPE_LETTER_AF, $supportedTypes) && (strlen($value) === 1)) { 1107 | $ok = true; 1108 | $ltiOutcome->type = self::EXT_TYPE_LETTER_AF; 1109 | } else if (in_array(self::EXT_TYPE_TEXT, $supportedTypes)) { 1110 | $ok = true; 1111 | $ltiOutcome->type = self::EXT_TYPE_TEXT; 1112 | } 1113 | // Convert text to decimal 1114 | } else if ($type === self::EXT_TYPE_TEXT) { 1115 | $ok = is_numeric($value) && ($value >= 0) && ($value <=1); 1116 | if ($ok) { 1117 | $ltiOutcome->type = self::EXT_TYPE_DECIMAL; 1118 | } else if (substr($value, -1) === '%') { 1119 | $value = substr($value, 0, -1); 1120 | $ok = is_numeric($value) && ($value >= 0) && ($value <=100); 1121 | if ($ok) { 1122 | if (in_array(self::EXT_TYPE_PERCENTAGE, $supportedTypes)) { 1123 | $ltiOutcome->type = self::EXT_TYPE_PERCENTAGE; 1124 | } else { 1125 | $ltiOutcome->setValue($value / 100); 1126 | $ltiOutcome->type = self::EXT_TYPE_DECIMAL; 1127 | } 1128 | } 1129 | } 1130 | } 1131 | } 1132 | 1133 | return $ok; 1134 | 1135 | } 1136 | 1137 | /** 1138 | * Send a service request to the tool consumer. 1139 | * 1140 | * @param string $type Message type value 1141 | * @param string $url URL to send request to 1142 | * @param array $params Associative array of parameter values to be passed 1143 | * 1144 | * @return boolean True if the request successfully obtained a response 1145 | */ 1146 | private function doService($type, $url, $params) 1147 | { 1148 | 1149 | $ok = false; 1150 | $this->extRequest = null; 1151 | $this->extRequestHeaders = ''; 1152 | $this->extResponse = null; 1153 | $this->extResponseHeaders = ''; 1154 | if (!empty($url)) { 1155 | $params = $this->getConsumer()->signParameters($url, $type, $this->getConsumer()->ltiVersion, $params); 1156 | // Connect to tool consumer 1157 | $http = new HTTPMessage($url, 'POST', $params); 1158 | // Parse XML response 1159 | if ($http->send()) { 1160 | $this->extResponse = $http->response; 1161 | $this->extResponseHeaders = $http->responseHeaders; 1162 | try { 1163 | $this->extDoc = new DOMDocument(); 1164 | $this->extDoc->loadXML($http->response); 1165 | $this->extNodes = $this->domnodeToArray($this->extDoc->documentElement); 1166 | if (isset($this->extNodes['statusinfo']['codemajor']) && ($this->extNodes['statusinfo']['codemajor'] === 'Success')) { 1167 | $ok = true; 1168 | } 1169 | } catch (\Exception $e) { 1170 | } 1171 | } 1172 | $this->extRequest = $http->request; 1173 | $this->extRequestHeaders = $http->requestHeaders; 1174 | } 1175 | 1176 | return $ok; 1177 | 1178 | } 1179 | 1180 | /** 1181 | * Send a service request to the tool consumer. 1182 | * 1183 | * @param string $type Message type value 1184 | * @param string $url URL to send request to 1185 | * @param string $xml XML of message request 1186 | * 1187 | * @return boolean True if the request successfully obtained a response 1188 | */ 1189 | private function doLTI11Service($type, $url, $xml) 1190 | { 1191 | 1192 | $ok = false; 1193 | $this->extRequest = null; 1194 | $this->extRequestHeaders = ''; 1195 | $this->extResponse = null; 1196 | $this->extResponseHeaders = ''; 1197 | if (!empty($url)) { 1198 | $id = uniqid(); 1199 | $xmlRequest = <<< EOD 1200 | 1201 | 1202 | 1203 | 1204 | V1.0 1205 | {$id} 1206 | 1207 | 1208 | 1209 | <{$type}Request> 1210 | {$xml} 1211 | 1212 | 1213 | 1214 | EOD; 1215 | // Calculate body hash 1216 | $hash = base64_encode(sha1($xmlRequest, true)); 1217 | $params = array('oauth_body_hash' => $hash); 1218 | 1219 | // Add OAuth signature 1220 | $hmacMethod = new OAuth\OAuthSignatureMethod_HMAC_SHA1(); 1221 | $consumer = new OAuth\OAuthConsumer($this->getConsumer()->getKey(), $this->getConsumer()->secret, null); 1222 | $req = OAuth\OAuthRequest::from_consumer_and_token($consumer, null, 'POST', $url, $params); 1223 | $req->sign_request($hmacMethod, $consumer, null); 1224 | $params = $req->get_parameters(); 1225 | $header = $req->to_header(); 1226 | $header .= "\nContent-Type: application/xml"; 1227 | // Connect to tool consumer 1228 | $http = new HTTPMessage($url, 'POST', $xmlRequest, $header); 1229 | // Parse XML response 1230 | if ($http->send()) { 1231 | $this->extResponse = $http->response; 1232 | $this->extResponseHeaders = $http->responseHeaders; 1233 | try { 1234 | $this->extDoc = new DOMDocument(); 1235 | $this->extDoc->loadXML($http->response); 1236 | $this->extNodes = $this->domnodeToArray($this->extDoc->documentElement); 1237 | if (isset($this->extNodes['imsx_POXHeader']['imsx_POXResponseHeaderInfo']['imsx_statusInfo']['imsx_codeMajor']) && 1238 | ($this->extNodes['imsx_POXHeader']['imsx_POXResponseHeaderInfo']['imsx_statusInfo']['imsx_codeMajor'] === 'success')) { 1239 | $ok = true; 1240 | } 1241 | } catch (\Exception $e) { 1242 | } 1243 | } 1244 | $this->extRequest = $http->request; 1245 | $this->extRequestHeaders = $http->requestHeaders; 1246 | } 1247 | 1248 | return $ok; 1249 | 1250 | } 1251 | 1252 | /** 1253 | * Convert DOM nodes to array. 1254 | * 1255 | * @param DOMElement $node XML element 1256 | * 1257 | * @return array Array of XML document elements 1258 | */ 1259 | private function domnodeToArray($node) 1260 | { 1261 | 1262 | $output = ''; 1263 | switch ($node->nodeType) { 1264 | case XML_CDATA_SECTION_NODE: 1265 | case XML_TEXT_NODE: 1266 | $output = trim($node->textContent); 1267 | break; 1268 | case XML_ELEMENT_NODE: 1269 | for ($i = 0; $i < $node->childNodes->length; $i++) { 1270 | $child = $node->childNodes->item($i); 1271 | $v = $this->domnodeToArray($child); 1272 | if (isset($child->tagName)) { 1273 | $t = $child->tagName; 1274 | if (!isset($output[$t])) { 1275 | $output[$t] = array(); 1276 | } 1277 | $output[$t][] = $v; 1278 | } else { 1279 | $s = (string) $v; 1280 | if (strlen($s) > 0) { 1281 | $output = $s; 1282 | } 1283 | } 1284 | } 1285 | if (is_array($output)) { 1286 | if ($node->attributes->length) { 1287 | $a = array(); 1288 | foreach ($node->attributes as $attrName => $attrNode) { 1289 | $a[$attrName] = (string) $attrNode->value; 1290 | } 1291 | $output['@attributes'] = $a; 1292 | } 1293 | foreach ($output as $t => $v) { 1294 | if (is_array($v) && count($v)==1 && $t!='@attributes') { 1295 | $output[$t] = $v[0]; 1296 | } 1297 | } 1298 | } 1299 | break; 1300 | } 1301 | 1302 | return $output; 1303 | 1304 | } 1305 | 1306 | } 1307 | -------------------------------------------------------------------------------- /src/ToolProvider/ResourceLinkShare.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright IMS Global Learning Consortium Inc 10 | * @date 2016 11 | * @version 3.0.0 12 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 13 | */ 14 | class ResourceLinkShare 15 | { 16 | 17 | /** 18 | * Consumer key value. 19 | * 20 | * @var string $consumerKey 21 | */ 22 | public $consumerKey = null; 23 | /** 24 | * Resource link ID value. 25 | * 26 | * @var string $resourceLinkId 27 | */ 28 | public $resourceLinkId = null; 29 | /** 30 | * Title of sharing context. 31 | * 32 | * @var string $title 33 | */ 34 | public $title = null; 35 | /** 36 | * Whether sharing request is to be automatically approved on first use. 37 | * 38 | * @var boolean $approved 39 | */ 40 | public $approved = null; 41 | 42 | /** 43 | * Class constructor. 44 | */ 45 | public function __construct() 46 | { 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/ToolProvider/ResourceLinkShareKey.php: -------------------------------------------------------------------------------- 1 | 11 | * @copyright IMS Global Learning Consortium Inc 12 | * @date 2016 13 | * @version 3.0.2 14 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 15 | */ 16 | class ResourceLinkShareKey 17 | { 18 | 19 | /** 20 | * Maximum permitted life for a share key value. 21 | */ 22 | const MAX_SHARE_KEY_LIFE = 168; // in hours (1 week) 23 | /** 24 | * Default life for a share key value. 25 | */ 26 | const DEFAULT_SHARE_KEY_LIFE = 24; // in hours 27 | /** 28 | * Minimum length for a share key value. 29 | */ 30 | const MIN_SHARE_KEY_LENGTH = 5; 31 | /** 32 | * Maximum length for a share key value. 33 | */ 34 | const MAX_SHARE_KEY_LENGTH = 32; 35 | 36 | /** 37 | * ID for resource link being shared. 38 | * 39 | * @var string $resourceLinkId 40 | */ 41 | public $resourceLinkId = null; 42 | /** 43 | * Length of share key. 44 | * 45 | * @var int $length 46 | */ 47 | public $length = null; 48 | /** 49 | * Life of share key. 50 | * 51 | * @var int $life 52 | */ 53 | public $life = null; // in hours 54 | /** 55 | * Whether the sharing arrangement should be automatically approved when first used. 56 | * 57 | * @var boolean $autoApprove 58 | */ 59 | public $autoApprove = false; 60 | /** 61 | * Date/time when the share key expires. 62 | * 63 | * @var int $expires 64 | */ 65 | public $expires = null; 66 | 67 | /** 68 | * Share key value. 69 | * 70 | * @var string $id 71 | */ 72 | private $id = null; 73 | /** 74 | * Data connector. 75 | * 76 | * @var DataConnector $dataConnector 77 | */ 78 | private $dataConnector = null; 79 | 80 | /** 81 | * Class constructor. 82 | * 83 | * @param ResourceLink $resourceLink Resource_Link object 84 | * @param string $id Value of share key (optional, default is null) 85 | */ 86 | public function __construct($resourceLink, $id = null) 87 | { 88 | 89 | $this->initialize(); 90 | $this->dataConnector = $resourceLink->getDataConnector(); 91 | $this->resourceLinkId = $resourceLink->getRecordId(); 92 | $this->id = $id; 93 | if (!empty($id)) { 94 | $this->load(); 95 | } 96 | 97 | } 98 | 99 | /** 100 | * Initialise the resource link share key. 101 | */ 102 | public function initialize() 103 | { 104 | 105 | $this->length = null; 106 | $this->life = null; 107 | $this->autoApprove = false; 108 | $this->expires = null; 109 | 110 | } 111 | 112 | /** 113 | * Initialise the resource link share key. 114 | * 115 | * Pseudonym for initialize(). 116 | */ 117 | public function initialise() 118 | { 119 | 120 | $this->initialize(); 121 | 122 | } 123 | 124 | /** 125 | * Save the resource link share key to the database. 126 | * 127 | * @return boolean True if the share key was successfully saved 128 | */ 129 | public function save() 130 | { 131 | 132 | if (empty($this->life)) { 133 | $this->life = self::DEFAULT_SHARE_KEY_LIFE; 134 | } else { 135 | $this->life = max(min($this->life, self::MAX_SHARE_KEY_LIFE), 0); 136 | } 137 | $this->expires = time() + ($this->life * 60 * 60); 138 | if (empty($this->id)) { 139 | if (empty($this->length) || !is_numeric($this->length)) { 140 | $this->length = self::MAX_SHARE_KEY_LENGTH; 141 | } else { 142 | $this->length = max(min($this->length, self::MAX_SHARE_KEY_LENGTH), self::MIN_SHARE_KEY_LENGTH); 143 | } 144 | $this->id = DataConnector::getRandomString($this->length); 145 | } 146 | 147 | return $this->dataConnector->saveResourceLinkShareKey($this); 148 | 149 | } 150 | 151 | /** 152 | * Delete the resource link share key from the database. 153 | * 154 | * @return boolean True if the share key was successfully deleted 155 | */ 156 | public function delete() 157 | { 158 | 159 | return $this->dataConnector->deleteResourceLinkShareKey($this); 160 | 161 | } 162 | 163 | /** 164 | * Get share key value. 165 | * 166 | * @return string Share key value 167 | */ 168 | public function getId() 169 | { 170 | 171 | return $this->id; 172 | 173 | } 174 | 175 | ### 176 | ### PRIVATE METHOD 177 | ### 178 | 179 | /** 180 | * Load the resource link share key from the database. 181 | */ 182 | private function load() 183 | { 184 | 185 | $this->initialize(); 186 | $this->dataConnector->loadResourceLinkShareKey($this); 187 | if (!is_null($this->id)) { 188 | $this->length = strlen($this->id); 189 | } 190 | if (!is_null($this->expires)) { 191 | $this->life = ($this->expires - time()) / 60 / 60; 192 | } 193 | 194 | } 195 | 196 | } 197 | -------------------------------------------------------------------------------- /src/ToolProvider/Service/Membership.php: -------------------------------------------------------------------------------- 1 | 11 | * @copyright IMS Global Learning Consortium Inc 12 | * @date 2016 13 | * @version 3.0.0 14 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 15 | */ 16 | class Membership extends Service 17 | { 18 | 19 | /** 20 | * The object to which the settings apply (ResourceLink, Context or ToolConsumer). 21 | * 22 | * @var object $source 23 | */ 24 | private $source; 25 | 26 | /** 27 | * Class constructor. 28 | * 29 | * @param object $source The object to which the memberships apply (ResourceLink or Context) 30 | * @param string $endpoint Service endpoint 31 | */ 32 | public function __construct($source, $endpoint) 33 | { 34 | 35 | $consumer = $source->getConsumer(); 36 | parent::__construct($consumer, $endpoint, 'application/vnd.ims.lis.v2.membershipcontainer+json'); 37 | $this->source = $source; 38 | 39 | } 40 | 41 | /** 42 | * Get the memberships. 43 | * 44 | * @param string $role Role for which memberships are to be requested (optional, default is all roles) 45 | * @param int $limit Limit on the number of memberships to be returned (optional, default is all) 46 | * 47 | * @return mixed The array of User objects if successful, otherwise false 48 | */ 49 | public function get($role = null, $limit = 0) { 50 | 51 | $isLink = is_a($this->source, 'IMSGlobal\LTI\ToolProvider\ResourceLink'); 52 | $parameters = array(); 53 | if (!empty($role)) { 54 | $parameters['role'] = $role; 55 | } 56 | if ($limit > 0) { 57 | $parameters['limit'] = strval($limit); 58 | } 59 | if ($isLink) { 60 | $parameters['rlid'] = $this->source->getId(); 61 | } 62 | $http = $this->send('GET', $parameters); 63 | if (!$http->ok) { 64 | $users = false; 65 | } else { 66 | $users = array(); 67 | if ($isLink) { 68 | $oldUsers = $this->source->getUserResultSourcedIDs(true, ToolProvider\ToolProvider::ID_SCOPE_RESOURCE); 69 | } 70 | foreach ($http->responseJson->pageOf->membershipSubject->membership as $membership) { 71 | $member = $membership->member; 72 | if ($isLink) { 73 | $user = ToolProvider\User::fromResourceLink($this->source, $member->userId); 74 | } else { 75 | $user = new ToolProvider\User(); 76 | $user->ltiUserId = $member->userId; 77 | } 78 | 79 | // Set the user name 80 | $firstname = (isset($member->givenName)) ? $member->givenName : ''; 81 | $lastname = (isset($member->familyName)) ? $member->familyName : ''; 82 | $fullname = (isset($member->name)) ? $member->name : ''; 83 | $user->setNames($firstname, $lastname, $fullname); 84 | 85 | // Set the user email 86 | $email = (isset($member->email)) ? $member->email : ''; 87 | $user->setEmail($email, $this->source->getConsumer()->defaultEmail); 88 | 89 | // Set the user roles 90 | if (isset($membership->role)) { 91 | $user->roles = ToolProvider\ToolProvider::parseRoles($membership->role); 92 | } 93 | 94 | // If a result sourcedid is provided save the user 95 | if ($isLink) { 96 | if (isset($member->message)) { 97 | foreach ($member->message as $message) { 98 | if (isset($message->message_type) && ($message->message_type === 'basic-lti-launch-request')) { 99 | if (isset($message->lis_result_sourcedid)) { 100 | $user->ltiResultSourcedId = $message->lis_result_sourcedid; 101 | $user->save(); 102 | } 103 | break; 104 | } 105 | } 106 | } 107 | } 108 | $users[] = $user; 109 | 110 | // Remove old user (if it exists) 111 | if ($isLink) { 112 | unset($oldUsers[$user->getId(ToolProvider\ToolProvider::ID_SCOPE_RESOURCE)]); 113 | } 114 | } 115 | 116 | // Delete any old users which were not in the latest list from the tool consumer 117 | if ($isLink) { 118 | foreach ($oldUsers as $id => $user) { 119 | $user->delete(); 120 | } 121 | } 122 | } 123 | 124 | return $users; 125 | 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /src/ToolProvider/Service/Service.php: -------------------------------------------------------------------------------- 1 | 12 | * @copyright IMS Global Learning Consortium Inc 13 | * @date 2016 14 | * @version 3.0.0 15 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 16 | */ 17 | class Service 18 | { 19 | 20 | /** 21 | * Whether service request should be sent unsigned. 22 | * 23 | * @var boolean $unsigned 24 | */ 25 | public $unsigned = false; 26 | 27 | /** 28 | * Service endpoint. 29 | * 30 | * @var string $endpoint 31 | */ 32 | protected $endpoint; 33 | /** 34 | * Tool Consumer for this service request. 35 | * 36 | * @var ToolConsumer $consumer 37 | */ 38 | private $consumer; 39 | /** 40 | * Media type of message body. 41 | * 42 | * @var string $mediaType 43 | */ 44 | private $mediaType; 45 | 46 | /** 47 | * Class constructor. 48 | * 49 | * @param ToolConsumer $consumer Tool consumer object for this service request 50 | * @param string $endpoint Service endpoint 51 | * @param string $mediaType Media type of message body 52 | */ 53 | public function __construct($consumer, $endpoint, $mediaType) 54 | { 55 | 56 | $this->consumer = $consumer; 57 | $this->endpoint = $endpoint; 58 | $this->mediaType = $mediaType; 59 | 60 | } 61 | 62 | /** 63 | * Send a service request. 64 | * 65 | * @param string $method The action type constant (optional, default is GET) 66 | * @param array $parameters Query parameters to add to endpoint (optional, default is none) 67 | * @param string $body Body of request (optional, default is null) 68 | * 69 | * @return HTTPMessage HTTP object containing request and response details 70 | */ 71 | public function send($method, $parameters = array(), $body = null) 72 | { 73 | 74 | $url = $this->endpoint; 75 | if (!empty($parameters)) { 76 | if (strpos($url, '?') === false) { 77 | $sep = '?'; 78 | } else { 79 | $sep = '&'; 80 | } 81 | foreach ($parameters as $name => $value) { 82 | $url .= $sep . urlencode($name) . '=' . urlencode($value); 83 | $sep = '&'; 84 | } 85 | } 86 | if (!$this->unsigned) { 87 | $header = ToolProvider\ToolConsumer::addSignature($url, $this->consumer->getKey(), $this->consumer->secret, $body, $method, $this->mediaType); 88 | } else { 89 | $header = null; 90 | } 91 | 92 | // Connect to tool consumer 93 | $http = new HTTPMessage($url, $method, $body, $header); 94 | // Parse JSON response 95 | if ($http->send() && !empty($http->response)) { 96 | $http->responseJson = json_decode($http->response); 97 | $http->ok = !is_null($http->responseJson); 98 | } 99 | 100 | return $http; 101 | 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/ToolProvider/Service/ToolSettings.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright IMS Global Learning Consortium Inc 10 | * @date 2016 11 | * @version 3.0.0 12 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 13 | */ 14 | class ToolSettings extends Service 15 | { 16 | 17 | /** 18 | * Settings at current level mode. 19 | */ 20 | const MODE_CURRENT_LEVEL = 1; 21 | /** 22 | * Settings at all levels mode. 23 | */ 24 | const MODE_ALL_LEVELS = 2; 25 | /** 26 | * Settings with distinct names at all levels mode. 27 | */ 28 | const MODE_DISTINCT_NAMES = 3; 29 | 30 | /** 31 | * Names of LTI parameters to be retained in the consumer settings property. 32 | * 33 | * @var array $LEVEL_NAMES 34 | */ 35 | private static $LEVEL_NAMES = array('ToolProxy' => 'system', 36 | 'ToolProxyBinding' => 'context', 37 | 'LtiLink' => 'link'); 38 | 39 | /** 40 | * The object to which the settings apply (ResourceLink, Context or ToolConsumer). 41 | * 42 | * @var object $source 43 | */ 44 | private $source; 45 | /** 46 | * Whether to use the simple JSON format. 47 | * 48 | * @var boolean $simple 49 | */ 50 | private $simple; 51 | 52 | /** 53 | * Class constructor. 54 | * 55 | * @param object $source The object to which the settings apply (ResourceLink, Context or ToolConsumer) 56 | * @param string $endpoint Service endpoint 57 | * @param boolean $simple True if the simple media type is to be used (optional, default is true) 58 | */ 59 | public function __construct($source, $endpoint, $simple = true) 60 | { 61 | 62 | if (is_a($source, 'IMSGlobal\LTI\ToolProvider\ToolConsumer')) { 63 | $consumer = $source; 64 | } else { 65 | $consumer = $source->getConsumer(); 66 | } 67 | if ($simple) { 68 | $mediaType = 'application/vnd.ims.lti.v2.toolsettings.simple+json'; 69 | } else { 70 | $mediaType = 'application/vnd.ims.lti.v2.toolsettings+json'; 71 | } 72 | parent::__construct($consumer, $endpoint, $mediaType); 73 | $this->source = $source; 74 | $this->simple = $simple; 75 | 76 | } 77 | 78 | /** 79 | * Get the tool settings. 80 | * 81 | * @param int $mode Mode for request (optional, default is current level only) 82 | * 83 | * @return mixed The array of settings if successful, otherwise false 84 | */ 85 | public function get($mode = self::MODE_CURRENT_LEVEL) { 86 | 87 | $parameter = array(); 88 | if ($mode === self::MODE_ALL_LEVELS) { 89 | $parameter['bubble'] = 'all'; 90 | } else if ($mode === self::MODE_DISTINCT_NAMES) { 91 | $parameter['bubble'] = 'distinct'; 92 | } 93 | $http = $this->send('GET', $parameter); 94 | if (!$http->ok) { 95 | $response = false; 96 | } else if ($this->simple) { 97 | $response = json_decode($http->response, true); 98 | } else if (isset($http->responseJson->{'@graph'})) { 99 | $response = array(); 100 | foreach ($http->responseJson->{'@graph'} as $level) { 101 | $settings = json_decode(json_encode($level->custom), true); 102 | unset($settings['@id']); 103 | $response[self::$LEVEL_NAMES[$level->{'@type'}]] = $settings; 104 | } 105 | } 106 | 107 | return $response; 108 | 109 | } 110 | 111 | /** 112 | * Set the tool settings. 113 | * 114 | * @param array $settings An associative array of settings (optional, default is null) 115 | * 116 | * @return HTTPMessage HTTP object containing request and response details 117 | */ 118 | public function set($settings) { 119 | 120 | if (!$this->simple) { 121 | if (is_a($this->source, 'ToolConsumer')) { 122 | $type = 'ToolProxy'; 123 | } else if (is_a($this->source, 'ToolConsumer')) { 124 | $type = 'ToolProxyBinding'; 125 | } else { 126 | $type = 'LtiLink'; 127 | } 128 | $obj = new \stdClass(); 129 | $obj->{'@context'} = 'http://purl.imsglobal.org/ctx/lti/v2/ToolSettings'; 130 | $obj->{'@graph'} = array(); 131 | $level = new \stdClass(); 132 | $level->{'@type'} = $type; 133 | $level->{'@id'} = $this->endpoint; 134 | $level->{'custom'} = $settings; 135 | $obj->{'@graph'}[] = $level; 136 | $body = json_encode($obj); 137 | } else { 138 | $body = json_encode($settings); 139 | } 140 | 141 | $response = parent::send('PUT', null, $body); 142 | 143 | return $response->ok; 144 | 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /src/ToolProvider/ToolConsumer.php: -------------------------------------------------------------------------------- 1 | 14 | * @copyright IMS Global Learning Consortium Inc 15 | * @date 2016 16 | * @version 3.0.2 17 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 18 | */ 19 | class ToolConsumer 20 | { 21 | 22 | /** 23 | * Local name of tool consumer. 24 | * 25 | * @var string $name 26 | */ 27 | public $name = null; 28 | /** 29 | * Shared secret. 30 | * 31 | * @var string $secret 32 | */ 33 | public $secret = null; 34 | /** 35 | * LTI version (as reported by last tool consumer connection). 36 | * 37 | * @var string $ltiVersion 38 | */ 39 | public $ltiVersion = null; 40 | /** 41 | * Name of tool consumer (as reported by last tool consumer connection). 42 | * 43 | * @var string $consumerName 44 | */ 45 | public $consumerName = null; 46 | /** 47 | * Tool consumer version (as reported by last tool consumer connection). 48 | * 49 | * @var string $consumerVersion 50 | */ 51 | public $consumerVersion = null; 52 | /** 53 | * Tool consumer GUID (as reported by first tool consumer connection). 54 | * 55 | * @var string $consumerGuid 56 | */ 57 | public $consumerGuid = null; 58 | /** 59 | * Optional CSS path (as reported by last tool consumer connection). 60 | * 61 | * @var string $cssPath 62 | */ 63 | public $cssPath = null; 64 | /** 65 | * Whether the tool consumer instance is protected by matching the consumer_guid value in incoming requests. 66 | * 67 | * @var boolean $protected 68 | */ 69 | public $protected = false; 70 | /** 71 | * Whether the tool consumer instance is enabled to accept incoming connection requests. 72 | * 73 | * @var boolean $enabled 74 | */ 75 | public $enabled = false; 76 | /** 77 | * Date/time from which the the tool consumer instance is enabled to accept incoming connection requests. 78 | * 79 | * @var int $enableFrom 80 | */ 81 | public $enableFrom = null; 82 | /** 83 | * Date/time until which the tool consumer instance is enabled to accept incoming connection requests. 84 | * 85 | * @var int $enableUntil 86 | */ 87 | public $enableUntil = null; 88 | /** 89 | * Date of last connection from this tool consumer. 90 | * 91 | * @var int $lastAccess 92 | */ 93 | public $lastAccess = null; 94 | /** 95 | * Default scope to use when generating an Id value for a user. 96 | * 97 | * @var int $idScope 98 | */ 99 | public $idScope = ToolProvider::ID_SCOPE_ID_ONLY; 100 | /** 101 | * Default email address (or email domain) to use when no email address is provided for a user. 102 | * 103 | * @var string $defaultEmail 104 | */ 105 | public $defaultEmail = ''; 106 | /** 107 | * Setting values (LTI parameters, custom parameters and local parameters). 108 | * 109 | * @var array $settings 110 | */ 111 | public $settings = null; 112 | /** 113 | * Date/time when the object was created. 114 | * 115 | * @var int $created 116 | */ 117 | public $created = null; 118 | /** 119 | * Date/time when the object was last updated. 120 | * 121 | * @var int $updated 122 | */ 123 | public $updated = null; 124 | 125 | /** 126 | * Consumer ID value. 127 | * 128 | * @var int $id 129 | */ 130 | private $id = null; 131 | /** 132 | * Consumer key value. 133 | * 134 | * @var string $key 135 | */ 136 | private $key = null; 137 | /** 138 | * Whether the settings value have changed since last saved. 139 | * 140 | * @var boolean $settingsChanged 141 | */ 142 | private $settingsChanged = false; 143 | /** 144 | * Data connector object or string. 145 | * 146 | * @var mixed $dataConnector 147 | */ 148 | private $dataConnector = null; 149 | 150 | /** 151 | * Class constructor. 152 | * 153 | * @param string $key Consumer key 154 | * @param DataConnector $dataConnector A data connector object 155 | * @param boolean $autoEnable true if the tool consumers is to be enabled automatically (optional, default is false) 156 | */ 157 | public function __construct($key = null, $dataConnector = null, $autoEnable = false) 158 | { 159 | 160 | $this->initialize(); 161 | if (empty($dataConnector)) { 162 | $dataConnector = DataConnector::getDataConnector(); 163 | } 164 | $this->dataConnector = $dataConnector; 165 | if (!empty($key)) { 166 | $this->load($key, $autoEnable); 167 | } else { 168 | $this->secret = DataConnector::getRandomString(32); 169 | } 170 | 171 | } 172 | 173 | /** 174 | * Initialise the tool consumer. 175 | */ 176 | public function initialize() 177 | { 178 | 179 | $this->id = null; 180 | $this->key = null; 181 | $this->name = null; 182 | $this->secret = null; 183 | $this->ltiVersion = null; 184 | $this->consumerName = null; 185 | $this->consumerVersion = null; 186 | $this->consumerGuid = null; 187 | $this->profile = null; 188 | $this->toolProxy = null; 189 | $this->settings = array(); 190 | $this->protected = false; 191 | $this->enabled = false; 192 | $this->enableFrom = null; 193 | $this->enableUntil = null; 194 | $this->lastAccess = null; 195 | $this->idScope = ToolProvider::ID_SCOPE_ID_ONLY; 196 | $this->defaultEmail = ''; 197 | $this->created = null; 198 | $this->updated = null; 199 | 200 | } 201 | 202 | /** 203 | * Initialise the tool consumer. 204 | * 205 | * Pseudonym for initialize(). 206 | */ 207 | public function initialise() 208 | { 209 | 210 | $this->initialize(); 211 | 212 | } 213 | 214 | /** 215 | * Save the tool consumer to the database. 216 | * 217 | * @return boolean True if the object was successfully saved 218 | */ 219 | public function save() 220 | { 221 | 222 | $ok = $this->dataConnector->saveToolConsumer($this); 223 | if ($ok) { 224 | $this->settingsChanged = false; 225 | } 226 | 227 | return $ok; 228 | 229 | } 230 | 231 | /** 232 | * Delete the tool consumer from the database. 233 | * 234 | * @return boolean True if the object was successfully deleted 235 | */ 236 | public function delete() 237 | { 238 | 239 | return $this->dataConnector->deleteToolConsumer($this); 240 | 241 | } 242 | 243 | /** 244 | * Get the tool consumer record ID. 245 | * 246 | * @return int Consumer record ID value 247 | */ 248 | public function getRecordId() 249 | { 250 | 251 | return $this->id; 252 | 253 | } 254 | 255 | /** 256 | * Sets the tool consumer record ID. 257 | * 258 | * @param int $id Consumer record ID value 259 | */ 260 | public function setRecordId($id) 261 | { 262 | 263 | $this->id = $id; 264 | 265 | } 266 | 267 | /** 268 | * Get the tool consumer key. 269 | * 270 | * @return string Consumer key value 271 | */ 272 | public function getKey() 273 | { 274 | 275 | return $this->key; 276 | 277 | } 278 | 279 | /** 280 | * Set the tool consumer key. 281 | * 282 | * @param string $key Consumer key value 283 | */ 284 | public function setKey($key) 285 | { 286 | 287 | $this->key = $key; 288 | 289 | } 290 | 291 | /** 292 | * Get the data connector. 293 | * 294 | * @return mixed Data connector object or string 295 | */ 296 | public function getDataConnector() 297 | { 298 | 299 | return $this->dataConnector; 300 | 301 | } 302 | 303 | /** 304 | * Is the consumer key available to accept launch requests? 305 | * 306 | * @return boolean True if the consumer key is enabled and within any date constraints 307 | */ 308 | public function getIsAvailable() 309 | { 310 | 311 | $ok = $this->enabled; 312 | 313 | $now = time(); 314 | if ($ok && !is_null($this->enableFrom)) { 315 | $ok = $this->enableFrom <= $now; 316 | } 317 | if ($ok && !is_null($this->enableUntil)) { 318 | $ok = $this->enableUntil > $now; 319 | } 320 | 321 | return $ok; 322 | 323 | } 324 | 325 | /** 326 | * Get a setting value. 327 | * 328 | * @param string $name Name of setting 329 | * @param string $default Value to return if the setting does not exist (optional, default is an empty string) 330 | * 331 | * @return string Setting value 332 | */ 333 | public function getSetting($name, $default = '') 334 | { 335 | 336 | if (array_key_exists($name, $this->settings)) { 337 | $value = $this->settings[$name]; 338 | } else { 339 | $value = $default; 340 | } 341 | 342 | return $value; 343 | 344 | } 345 | 346 | /** 347 | * Set a setting value. 348 | * 349 | * @param string $name Name of setting 350 | * @param string $value Value to set, use an empty value to delete a setting (optional, default is null) 351 | */ 352 | public function setSetting($name, $value = null) 353 | { 354 | 355 | $old_value = $this->getSetting($name); 356 | if ($value !== $old_value) { 357 | if (!empty($value)) { 358 | $this->settings[$name] = $value; 359 | } else { 360 | unset($this->settings[$name]); 361 | } 362 | $this->settingsChanged = true; 363 | } 364 | 365 | } 366 | 367 | /** 368 | * Get an array of all setting values. 369 | * 370 | * @return array Associative array of setting values 371 | */ 372 | public function getSettings() 373 | { 374 | 375 | return $this->settings; 376 | 377 | } 378 | 379 | /** 380 | * Set an array of all setting values. 381 | * 382 | * @param array $settings Associative array of setting values 383 | */ 384 | public function setSettings($settings) 385 | { 386 | 387 | $this->settings = $settings; 388 | 389 | } 390 | 391 | /** 392 | * Save setting values. 393 | * 394 | * @return boolean True if the settings were successfully saved 395 | */ 396 | public function saveSettings() 397 | { 398 | 399 | if ($this->settingsChanged) { 400 | $ok = $this->save(); 401 | } else { 402 | $ok = true; 403 | } 404 | 405 | return $ok; 406 | 407 | } 408 | 409 | /** 410 | * Check if the Tool Settings service is supported. 411 | * 412 | * @return boolean True if this tool consumer supports the Tool Settings service 413 | */ 414 | public function hasToolSettingsService() 415 | { 416 | 417 | $url = $this->getSetting('custom_system_setting_url'); 418 | 419 | return !empty($url); 420 | 421 | } 422 | 423 | /** 424 | * Get Tool Settings. 425 | * 426 | * @param boolean $simple True if all the simple media type is to be used (optional, default is true) 427 | * 428 | * @return mixed The array of settings if successful, otherwise false 429 | */ 430 | public function getToolSettings($simple = true) 431 | { 432 | 433 | $url = $this->getSetting('custom_system_setting_url'); 434 | $service = new Service\ToolSettings($this, $url, $simple); 435 | $response = $service->get(); 436 | 437 | return $response; 438 | 439 | } 440 | 441 | /** 442 | * Perform a Tool Settings service request. 443 | * 444 | * @param array $settings An associative array of settings (optional, default is none) 445 | * 446 | * @return boolean True if action was successful, otherwise false 447 | */ 448 | public function setToolSettings($settings = array()) 449 | { 450 | 451 | $url = $this->getSetting('custom_system_setting_url'); 452 | $service = new Service\ToolSettings($this, $url); 453 | $response = $service->set($settings); 454 | 455 | return $response; 456 | 457 | } 458 | 459 | /** 460 | * Add the OAuth signature to an LTI message. 461 | * 462 | * @param string $url URL for message request 463 | * @param string $type LTI message type 464 | * @param string $version LTI version 465 | * @param array $params Message parameters 466 | * 467 | * @return array Array of signed message parameters 468 | */ 469 | public function signParameters($url, $type, $version, $params) 470 | { 471 | 472 | if (!empty($url)) { 473 | // Check for query parameters which need to be included in the signature 474 | $queryParams = array(); 475 | $queryString = parse_url($url, PHP_URL_QUERY); 476 | if (!is_null($queryString)) { 477 | $queryItems = explode('&', $queryString); 478 | foreach ($queryItems as $item) { 479 | if (strpos($item, '=') !== false) { 480 | list($name, $value) = explode('=', $item); 481 | $queryParams[urldecode($name)] = urldecode($value); 482 | } else { 483 | $queryParams[urldecode($item)] = ''; 484 | } 485 | } 486 | } 487 | $params = $params + $queryParams; 488 | // Add standard parameters 489 | $params['lti_version'] = $version; 490 | $params['lti_message_type'] = $type; 491 | $params['oauth_callback'] = 'about:blank'; 492 | // Add OAuth signature 493 | $hmacMethod = new OAuth\OAuthSignatureMethod_HMAC_SHA1(); 494 | $consumer = new OAuth\OAuthConsumer($this->getKey(), $this->secret, null); 495 | $req = OAuth\OAuthRequest::from_consumer_and_token($consumer, null, 'POST', $url, $params); 496 | $req->sign_request($hmacMethod, $consumer, null); 497 | $params = $req->get_parameters(); 498 | // Remove parameters being passed on the query string 499 | foreach (array_keys($queryParams) as $name) { 500 | unset($params[$name]); 501 | } 502 | } 503 | 504 | return $params; 505 | 506 | } 507 | 508 | /** 509 | * Add the OAuth signature to an array of message parameters or to a header string. 510 | * 511 | * @return mixed Array of signed message parameters or header string 512 | */ 513 | public static function addSignature($endpoint, $consumerKey, $consumerSecret, $data, $method = 'POST', $type = null) 514 | { 515 | 516 | $params = array(); 517 | if (is_array($data)) { 518 | $params = $data; 519 | } 520 | // Check for query parameters which need to be included in the signature 521 | $queryParams = array(); 522 | $queryString = parse_url($endpoint, PHP_URL_QUERY); 523 | if (!is_null($queryString)) { 524 | $queryItems = explode('&', $queryString); 525 | foreach ($queryItems as $item) { 526 | if (strpos($item, '=') !== false) { 527 | list($name, $value) = explode('=', $item); 528 | $queryParams[urldecode($name)] = urldecode($value); 529 | } else { 530 | $queryParams[urldecode($item)] = ''; 531 | } 532 | } 533 | $params = $params + $queryParams; 534 | } 535 | 536 | if (!is_array($data)) { 537 | // Calculate body hash 538 | $hash = base64_encode(sha1($data, true)); 539 | $params['oauth_body_hash'] = $hash; 540 | } 541 | 542 | // Add OAuth signature 543 | $hmacMethod = new OAuth\OAuthSignatureMethod_HMAC_SHA1(); 544 | $oauthConsumer = new OAuth\OAuthConsumer($consumerKey, $consumerSecret, null); 545 | $oauthReq = OAuth\OAuthRequest::from_consumer_and_token($oauthConsumer, null, $method, $endpoint, $params); 546 | $oauthReq->sign_request($hmacMethod, $oauthConsumer, null); 547 | $params = $oauthReq->get_parameters(); 548 | // Remove parameters being passed on the query string 549 | foreach (array_keys($queryParams) as $name) { 550 | unset($params[$name]); 551 | } 552 | 553 | if (!is_array($data)) { 554 | $header = $oauthReq->to_header(); 555 | if (empty($data)) { 556 | if (!empty($type)) { 557 | $header .= "\nAccept: {$type}"; 558 | } 559 | } else if (isset($type)) { 560 | $header .= "\nContent-Type: {$type}"; 561 | $header .= "\nContent-Length: " . strlen($data); 562 | } 563 | return $header; 564 | } else { 565 | return $params; 566 | } 567 | 568 | } 569 | 570 | /** 571 | * Perform a service request 572 | * 573 | * @param object $service Service object to be executed 574 | * @param string $method HTTP action 575 | * @param string $format Media type 576 | * @param mixed $data Array of parameters or body string 577 | * 578 | * @return HTTPMessage HTTP object containing request and response details 579 | */ 580 | public function doServiceRequest($service, $method, $format, $data) 581 | { 582 | 583 | $header = ToolConsumer::addSignature($service->endpoint, $this->getKey(), $this->secret, $data, $method, $format); 584 | 585 | // Connect to tool consumer 586 | $http = new HTTPMessage($service->endpoint, $method, $data, $header); 587 | // Parse JSON response 588 | if ($http->send() && !empty($http->response)) { 589 | $http->responseJson = json_decode($http->response); 590 | $http->ok = !is_null($http->responseJson); 591 | } 592 | 593 | return $http; 594 | 595 | } 596 | 597 | /** 598 | * Load the tool consumer from the database by its record ID. 599 | * 600 | * @param string $id The consumer key record ID 601 | * @param DataConnector $dataConnector Database connection object 602 | * 603 | * @return object ToolConsumer The tool consumer object 604 | */ 605 | public static function fromRecordId($id, $dataConnector) 606 | { 607 | 608 | $toolConsumer = new ToolConsumer(null, $dataConnector); 609 | 610 | $toolConsumer->initialize(); 611 | $toolConsumer->setRecordId($id); 612 | if (!$dataConnector->loadToolConsumer($toolConsumer)) { 613 | $toolConsumer->initialize(); 614 | } 615 | 616 | return $toolConsumer; 617 | 618 | } 619 | 620 | 621 | ### 622 | ### PRIVATE METHOD 623 | ### 624 | 625 | /** 626 | * Load the tool consumer from the database. 627 | * 628 | * @param string $key The consumer key value 629 | * @param boolean $autoEnable True if the consumer should be enabled (optional, default if false) 630 | * 631 | * @return boolean True if the consumer was successfully loaded 632 | */ 633 | private function load($key, $autoEnable = false) 634 | { 635 | 636 | $this->key = $key; 637 | $ok = $this->dataConnector->loadToolConsumer($this); 638 | if (!$ok) { 639 | $this->enabled = $autoEnable; 640 | } 641 | 642 | return $ok; 643 | 644 | } 645 | 646 | } 647 | -------------------------------------------------------------------------------- /src/ToolProvider/ToolProxy.php: -------------------------------------------------------------------------------- 1 | 12 | * @copyright IMS Global Learning Consortium Inc 13 | * @date 2016 14 | * @version 3.0.2 15 | * @license GNU Lesser General Public License, version 3 () 16 | */ 17 | class ToolProxy 18 | { 19 | 20 | /** 21 | * Local id of tool consumer. 22 | * 23 | * @var string $id 24 | */ 25 | public $id = null; 26 | 27 | /** 28 | * Tool Consumer for this tool proxy. 29 | * 30 | * @var ToolConsumer $consumer 31 | */ 32 | private $consumer = null; 33 | /** 34 | * Tool Consumer ID for this tool proxy. 35 | * 36 | * @var int $consumerId 37 | */ 38 | private $consumerId = null; 39 | /** 40 | * Consumer ID value. 41 | * 42 | * @var int $id 43 | */ 44 | private $recordId = null; 45 | /** 46 | * Data connector object. 47 | * 48 | * @var DataConnector $dataConnector 49 | */ 50 | private $dataConnector = null; 51 | /** 52 | * Tool Proxy document. 53 | * 54 | * @var MediaType\ToolProxy $toolProxy 55 | */ 56 | private $toolProxy = null; 57 | 58 | /** 59 | * Class constructor. 60 | * 61 | * @param DataConnector $dataConnector Data connector 62 | * @param string $id Tool Proxy ID (optional, default is null) 63 | */ 64 | public function __construct($dataConnector, $id = null) 65 | { 66 | 67 | $this->initialize(); 68 | $this->dataConnector = $dataConnector; 69 | if (!empty($id)) { 70 | $this->load($id); 71 | } else { 72 | $this->recordId = DataConnector::getRandomString(32); 73 | } 74 | 75 | } 76 | 77 | /** 78 | * Initialise the tool proxy. 79 | */ 80 | public function initialize() 81 | { 82 | 83 | $this->id = null; 84 | $this->recordId = null; 85 | $this->toolProxy = null; 86 | $this->created = null; 87 | $this->updated = null; 88 | 89 | } 90 | 91 | /** 92 | * Initialise the tool proxy. 93 | * 94 | * Pseudonym for initialize(). 95 | */ 96 | public function initialise() 97 | { 98 | 99 | $this->initialize(); 100 | 101 | } 102 | 103 | /** 104 | * Get the tool proxy record ID. 105 | * 106 | * @return int Tool Proxy record ID value 107 | */ 108 | public function getRecordId() 109 | { 110 | 111 | return $this->recordId; 112 | 113 | } 114 | 115 | /** 116 | * Sets the tool proxy record ID. 117 | * 118 | * @param int $recordId Tool Proxy record ID value 119 | */ 120 | public function setRecordId($recordId) 121 | { 122 | 123 | $this->recordId = $recordId; 124 | 125 | } 126 | 127 | /** 128 | * Get tool consumer. 129 | * 130 | * @return ToolConsumer Tool consumer object for this context. 131 | */ 132 | public function getConsumer() 133 | { 134 | 135 | if (is_null($this->consumer)) { 136 | $this->consumer = ToolConsumer::fromRecordId($this->consumerId, $this->getDataConnector()); 137 | } 138 | 139 | return $this->consumer; 140 | 141 | } 142 | 143 | /** 144 | * Set tool consumer ID. 145 | * 146 | * @param int $consumerId Tool Consumer ID for this resource link. 147 | */ 148 | public function setConsumerId($consumerId) 149 | { 150 | 151 | $this->consumer = null; 152 | $this->consumerId = $consumerId; 153 | 154 | } 155 | 156 | /** 157 | * Get the data connector. 158 | * 159 | * @return DataConnector Data connector object 160 | */ 161 | public function getDataConnector() 162 | { 163 | 164 | return $this->dataConnector; 165 | 166 | } 167 | 168 | 169 | ### 170 | ### PRIVATE METHOD 171 | ### 172 | 173 | /** 174 | * Load the tool proxy from the database. 175 | * 176 | * @param string $id The tool proxy id value 177 | * 178 | * @return boolean True if the tool proxy was successfully loaded 179 | */ 180 | private function load($id) 181 | { 182 | 183 | $this->initialize(); 184 | $this->id = $id; 185 | $ok = $this->dataConnector->loadToolProxy($this); 186 | if (!$ok) { 187 | $this->enabled = $autoEnable; 188 | } 189 | 190 | return $ok; 191 | 192 | } 193 | 194 | } 195 | -------------------------------------------------------------------------------- /src/ToolProvider/User.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright IMS Global Learning Consortium Inc 11 | * @date 2016 12 | * @version 3.0.2 13 | * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 14 | */ 15 | class User 16 | { 17 | 18 | /** 19 | * User's first name. 20 | * 21 | * @var string $firstname 22 | */ 23 | public $firstname = ''; 24 | /** 25 | * User's last name (surname or family name). 26 | * 27 | * @var string $lastname 28 | */ 29 | public $lastname = ''; 30 | /** 31 | * User's fullname. 32 | * 33 | * @var string $fullname 34 | */ 35 | public $fullname = ''; 36 | /** 37 | * User's email address. 38 | * 39 | * @var string $email 40 | */ 41 | public $email = ''; 42 | /** 43 | * User's image URI. 44 | * 45 | * @var string $image 46 | */ 47 | public $image = ''; 48 | /** 49 | * Roles for user. 50 | * 51 | * @var array $roles 52 | */ 53 | public $roles = array(); 54 | /** 55 | * Groups for user. 56 | * 57 | * @var array $groups 58 | */ 59 | public $groups = array(); 60 | /** 61 | * User's result sourcedid. 62 | * 63 | * @var string $ltiResultSourcedId 64 | */ 65 | public $ltiResultSourcedId = null; 66 | /** 67 | * Date/time the record was created. 68 | * 69 | * @var object $created 70 | */ 71 | public $created = null; 72 | /** 73 | * Date/time the record was last updated. 74 | * 75 | * @var object $updated 76 | */ 77 | public $updated = null; 78 | 79 | /** 80 | * Resource link object. 81 | * 82 | * @var ResourceLink $resourceLink 83 | */ 84 | private $resourceLink = null; 85 | /** 86 | * Resource link record ID. 87 | * 88 | * @var int $resourceLinkId 89 | */ 90 | private $resourceLinkId = null; 91 | /** 92 | * User record ID value. 93 | * 94 | * @var string $id 95 | */ 96 | private $id = null; 97 | /** 98 | * user ID as supplied in the last connection request. 99 | * 100 | * @var string $ltiUserId 101 | */ 102 | public $ltiUserId = null; 103 | /** 104 | * Data connector object or string. 105 | * 106 | * @var mixed $dataConnector 107 | */ 108 | private $dataConnector = null; 109 | 110 | /** 111 | * Class constructor. 112 | */ 113 | public function __construct() 114 | { 115 | 116 | $this->initialize(); 117 | 118 | } 119 | 120 | /** 121 | * Initialise the user. 122 | */ 123 | public function initialize() 124 | { 125 | 126 | $this->firstname = ''; 127 | $this->lastname = ''; 128 | $this->fullname = ''; 129 | $this->email = ''; 130 | $this->image = ''; 131 | $this->roles = array(); 132 | $this->groups = array(); 133 | $this->ltiResultSourcedId = null; 134 | $this->created = null; 135 | $this->updated = null; 136 | 137 | } 138 | 139 | /** 140 | * Initialise the user. 141 | * 142 | * Pseudonym for initialize(). 143 | */ 144 | public function initialise() 145 | { 146 | 147 | $this->initialize(); 148 | 149 | } 150 | 151 | /** 152 | * Save the user to the database. 153 | * 154 | * @return boolean True if the user object was successfully saved 155 | */ 156 | public function save() 157 | { 158 | 159 | if (!empty($this->ltiResultSourcedId) && !is_null($this->resourceLinkId)) { 160 | $ok = $this->getDataConnector()->saveUser($this); 161 | } else { 162 | $ok = true; 163 | } 164 | 165 | return $ok; 166 | 167 | } 168 | 169 | /** 170 | * Delete the user from the database. 171 | * 172 | * @return boolean True if the user object was successfully deleted 173 | */ 174 | public function delete() 175 | { 176 | 177 | $ok = $this->getDataConnector()->deleteUser($this); 178 | 179 | return $ok; 180 | 181 | } 182 | 183 | /** 184 | * Get resource link. 185 | * 186 | * @return ResourceLink Resource link object 187 | */ 188 | public function getResourceLink() 189 | { 190 | 191 | if (is_null($this->resourceLink) && !is_null($this->resourceLinkId)) { 192 | $this->resourceLink = ResourceLink::fromRecordId($this->resourceLinkId, $this->getDataConnector()); 193 | } 194 | 195 | return $this->resourceLink; 196 | 197 | } 198 | 199 | /** 200 | * Get record ID of user. 201 | * 202 | * @return int Record ID of user 203 | */ 204 | public function getRecordId() 205 | { 206 | 207 | return $this->id; 208 | 209 | } 210 | 211 | /** 212 | * Set record ID of user. 213 | * 214 | * @param int $id Record ID of user 215 | */ 216 | public function setRecordId($id) 217 | { 218 | 219 | $this->id = $id; 220 | 221 | } 222 | 223 | /** 224 | * Set resource link ID of user. 225 | * 226 | * @param int $resourceLinkId Resource link ID of user 227 | */ 228 | public function setResourceLinkId($resourceLinkId) 229 | { 230 | 231 | $this->resourceLinkId = $resourceLinkId; 232 | 233 | } 234 | 235 | /** 236 | * Get the data connector. 237 | * 238 | * @return mixed Data connector object or string 239 | */ 240 | public function getDataConnector() 241 | { 242 | 243 | return $this->dataConnector; 244 | 245 | } 246 | 247 | /** 248 | * Get the user ID (which may be a compound of the tool consumer and resource link IDs). 249 | * 250 | * @param int $idScope Scope to use for user ID (optional, default is null for consumer default setting) 251 | * 252 | * @return string User ID value 253 | */ 254 | public function getId($idScope = null) 255 | { 256 | 257 | if (empty($idScope)) { 258 | if (!is_null($this->resourceLink)) { 259 | $idScope = $this->resourceLink->getConsumer()->idScope; 260 | } else { 261 | $idScope = ToolProvider::ID_SCOPE_ID_ONLY; 262 | } 263 | } 264 | switch ($idScope) { 265 | case ToolProvider::ID_SCOPE_GLOBAL: 266 | $id = $this->getResourceLink()->getKey() . ToolProvider::ID_SCOPE_SEPARATOR . $this->ltiUserId; 267 | break; 268 | case ToolProvider::ID_SCOPE_CONTEXT: 269 | $id = $this->getResourceLink()->getKey(); 270 | if ($this->resourceLink->ltiContextId) { 271 | $id .= ToolProvider::ID_SCOPE_SEPARATOR . $this->resourceLink->ltiContextId; 272 | } 273 | $id .= ToolProvider::ID_SCOPE_SEPARATOR . $this->ltiUserId; 274 | break; 275 | case ToolProvider::ID_SCOPE_RESOURCE: 276 | $id = $this->getResourceLink()->getKey(); 277 | if ($this->resourceLink->ltiResourceLinkId) { 278 | $id .= ToolProvider::ID_SCOPE_SEPARATOR . $this->resourceLink->ltiResourceLinkId; 279 | } 280 | $id .= ToolProvider::ID_SCOPE_SEPARATOR . $this->ltiUserId; 281 | break; 282 | default: 283 | $id = $this->ltiUserId; 284 | break; 285 | } 286 | 287 | return $id; 288 | 289 | } 290 | 291 | /** 292 | * Set the user's name. 293 | * 294 | * @param string $firstname User's first name. 295 | * @param string $lastname User's last name. 296 | * @param string $fullname User's full name. 297 | */ 298 | public function setNames($firstname, $lastname, $fullname) 299 | { 300 | 301 | $names = array(0 => '', 1 => ''); 302 | if (!empty($fullname)) { 303 | $this->fullname = trim($fullname); 304 | $names = preg_split("/[\s]+/", $this->fullname, 2); 305 | } 306 | if (!empty($firstname)) { 307 | $this->firstname = trim($firstname); 308 | $names[0] = $this->firstname; 309 | } else if (!empty($names[0])) { 310 | $this->firstname = $names[0]; 311 | } else { 312 | $this->firstname = 'User'; 313 | } 314 | if (!empty($lastname)) { 315 | $this->lastname = trim($lastname); 316 | $names[1] = $this->lastname; 317 | } else if (!empty($names[1])) { 318 | $this->lastname = $names[1]; 319 | } else { 320 | $this->lastname = $this->ltiUserId; 321 | } 322 | if (empty($this->fullname)) { 323 | $this->fullname = "{$this->firstname} {$this->lastname}"; 324 | } 325 | 326 | } 327 | 328 | /** 329 | * Set the user's email address. 330 | * 331 | * @param string $email Email address value 332 | * @param string $defaultEmail Value to use if no email is provided (optional, default is none) 333 | */ 334 | public function setEmail($email, $defaultEmail = null) 335 | { 336 | 337 | if (!empty($email)) { 338 | $this->email = $email; 339 | } else if (!empty($defaultEmail)) { 340 | $this->email = $defaultEmail; 341 | if (substr($this->email, 0, 1) === '@') { 342 | $this->email = $this->getId() . $this->email; 343 | } 344 | } else { 345 | $this->email = ''; 346 | } 347 | 348 | } 349 | 350 | /** 351 | * Check if the user is an administrator (at any of the system, institution or context levels). 352 | * 353 | * @return boolean True if the user has a role of administrator 354 | */ 355 | public function isAdmin() 356 | { 357 | 358 | return $this->hasRole('Administrator') || $this->hasRole('urn:lti:sysrole:ims/lis/SysAdmin') || 359 | $this->hasRole('urn:lti:sysrole:ims/lis/Administrator') || $this->hasRole('urn:lti:instrole:ims/lis/Administrator'); 360 | 361 | } 362 | 363 | /** 364 | * Check if the user is staff. 365 | * 366 | * @return boolean True if the user has a role of instructor, contentdeveloper or teachingassistant 367 | */ 368 | public function isStaff() 369 | { 370 | 371 | return ($this->hasRole('Instructor') || $this->hasRole('ContentDeveloper') || $this->hasRole('TeachingAssistant')); 372 | 373 | } 374 | 375 | /** 376 | * Check if the user is a learner. 377 | * 378 | * @return boolean True if the user has a role of learner 379 | */ 380 | public function isLearner() 381 | { 382 | 383 | return $this->hasRole('Learner'); 384 | 385 | } 386 | 387 | /** 388 | * Load the user from the database. 389 | * 390 | * @param int $id Record ID of user 391 | * @param DataConnector $dataConnector Database connection object 392 | * 393 | * @return User User object 394 | */ 395 | public static function fromRecordId($id, $dataConnector) 396 | { 397 | 398 | $user = new User(); 399 | $user->dataConnector = $dataConnector; 400 | $user->load($id); 401 | 402 | return $user; 403 | 404 | } 405 | 406 | /** 407 | * Class constructor from resource link. 408 | * 409 | * @param ResourceLink $resourceLink Resource_Link object 410 | * @param string $ltiUserId User ID value 411 | * @return User 412 | */ 413 | public static function fromResourceLink($resourceLink, $ltiUserId) 414 | { 415 | 416 | $user = new User(); 417 | $user->resourceLink = $resourceLink; 418 | if (!is_null($resourceLink)) { 419 | $user->resourceLinkId = $resourceLink->getRecordId(); 420 | $user->dataConnector = $resourceLink->getDataConnector(); 421 | } 422 | $user->ltiUserId = $ltiUserId; 423 | if (!empty($ltiUserId)) { 424 | $user->load(); 425 | } 426 | 427 | return $user; 428 | 429 | } 430 | 431 | ### 432 | ### PRIVATE METHODS 433 | ### 434 | 435 | /** 436 | * Check whether the user has a specified role name. 437 | * 438 | * @param string $role Name of role 439 | * 440 | * @return boolean True if the user has the specified role 441 | */ 442 | private function hasRole($role) { 443 | 444 | if (substr($role, 0, 4) !== 'urn:') 445 | { 446 | $role = 'urn:lti:role:ims/lis/' . $role; 447 | } 448 | 449 | return in_array($role, $this->roles); 450 | 451 | } 452 | 453 | /** 454 | * Load the user from the database. 455 | * 456 | * @param int $id Record ID of user (optional, default is null) 457 | * 458 | * @return boolean True if the user object was successfully loaded 459 | */ 460 | private function load($id = null) 461 | { 462 | 463 | $this->initialize(); 464 | $this->id = $id; 465 | $dataConnector = $this->getDataConnector(); 466 | if (!is_null($dataConnector)) { 467 | return $dataConnector->loadUser($this); 468 | } 469 | 470 | return false; 471 | } 472 | 473 | } 474 | --------------------------------------------------------------------------------