MDL-61574 mod_lti: removed usage of PHP7 reserved word in namespace
[moodle.git] / mod / lti / service / toolproxy / classes / local / resources / toolproxy.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * This file contains a class definition for the Tool Proxy resource
19  *
20  * @package    ltiservice_toolproxy
21  * @copyright  2014 Vital Source Technologies http://vitalsource.com
22  * @author     Stephen Vickers
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
27 namespace ltiservice_toolproxy\local\resources;
29 defined('MOODLE_INTERNAL') || die();
31 require_once($CFG->dirroot . '/mod/lti/OAuth.php');
32 require_once($CFG->dirroot . '/mod/lti/TrivialStore.php');
34 // TODO: Switch to core oauthlib once implemented - MDL-30149.
35 use moodle\mod\lti as lti;
37 /**
38  * A resource implementing the Tool Proxy.
39  *
40  * @package    ltiservice_toolproxy
41  * @since      Moodle 2.8
42  * @copyright  2014 Vital Source Technologies http://vitalsource.com
43  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
44  */
45 class toolproxy extends \mod_lti\local\ltiservice\resource_base {
47     /**
48      * Class constructor.
49      *
50      * @param ltiservice_toolproxy\local\resources\toolproxy $service Service instance
51      */
52     public function __construct($service) {
54         parent::__construct($service);
55         $this->id = 'ToolProxy.collection';
56         $this->template = '/toolproxy';
57         $this->formats[] = 'application/vnd.ims.lti.v2.toolproxy+json';
58         $this->methods[] = 'POST';
60     }
62     /**
63      * Execute the request for this resource.
64      *
65      * @param mod_lti\local\ltiservice\response $response  Response object for this request.
66      */
67     public function execute($response) {
69         $ok = $this->check_tool_proxy(null, $response->get_request_data());
70         if ($ok) {
71             $toolproxy = $this->get_service()->get_tool_proxy();
72         } else {
73             $toolproxy = null;
74             $response->set_code(401);
75         }
76         $tools = array();
78         // Ensure all required elements are present in the Tool Proxy.
79         if ($ok) {
80             $toolproxyjson = json_decode($response->get_request_data());
81             $ok = !empty($toolproxyjson);
82             if (!$ok) {
83                 debugging('Tool proxy is not properly formed JSON');
84             } else {
85                 $ok = isset($toolproxyjson->tool_profile->product_instance->product_info->product_family->vendor->code);
86                 $ok = $ok && isset($toolproxyjson->security_contract->shared_secret);
87                 $ok = $ok && isset($toolproxyjson->tool_profile->resource_handler);
88                 if (!$ok) {
89                     debugging('One or more missing elements from tool proxy: vendor code, shared secret or resource handlers');
90                 }
91             }
92         }
94         // Check all capabilities requested were offered.
95         if ($ok) {
96             $offeredcapabilities = explode("\n", $toolproxy->capabilityoffered);
97             $resources = $toolproxyjson->tool_profile->resource_handler;
98             $errors = array();
99             foreach ($resources as $resource) {
100                 if (isset($resource->message)) {
101                     foreach ($resource->message as $message) {
102                         if (!in_array($message->message_type, $offeredcapabilities)) {
103                             $errors[] = $message->message_type;
104                         } else if (isset($resource->parameter)) {
105                             foreach ($message->parameter as $parameter) {
106                                 if (isset($parameter->variable) && !in_array($parameter->variable, $offeredcapabilities)) {
107                                     $errors[] = $parameter->variable;
108                                 }
109                             }
110                         }
111                     }
112                 }
113             }
114             if (count($errors) > 0) {
115                 $ok = false;
116                 debugging('Tool proxy contains capabilities which were not offered: ' . implode(', ', $errors));
117             }
118         }
120         // Check all services requested were offered (only tool services currently supported).
121         if ($ok && isset($toolproxyjson->security_contract->tool_service)) {
122             $contexts = lti_get_contexts($toolproxyjson);
123             $profileservice = lti_get_service_by_name('profile');
124             $profileservice->set_tool_proxy($toolproxy);
125             $context = $profileservice->get_service_path() . $profileservice->get_resources()[0]->get_path() . '#';
126             $offeredservices = explode("\n", $toolproxy->serviceoffered);
127             $services = lti_get_services();
128             $tpservices = $toolproxyjson->security_contract->tool_service;
129             $errors = array();
130             foreach ($tpservices as $service) {
131                 $fqid = lti_get_fqid($contexts, $service->service);
132                 if (substr($fqid, 0, strlen($context)) !== $context) {
133                     $errors[] = $service->service;
134                 } else {
135                     $id = explode('#', $fqid, 2);
136                     $aservice = lti_get_service_by_resource_id($services, $id[1]);
137                     $classname = explode('\\', get_class($aservice));
138                     if (empty($aservice) || !in_array($classname[count($classname) - 1], $offeredservices)) {
139                         $errors[] = $service->service;
140                     }
141                 }
142             }
143             if (count($errors) > 0) {
144                 $ok = false;
145                 debugging('Tool proxy contains services which were not offered: ' . implode(', ', $errors));
146             }
147         }
149         // Extract all launchable tools from the resource handlers.
150         if ($ok) {
151             $resources = $toolproxyjson->tool_profile->resource_handler;
152             $messagetypes = [
153                 'basic-lti-launch-request',
154                 'ContentItemSelectionRequest',
155             ];
156             foreach ($resources as $resource) {
157                 $launchable = false;
158                 $messages = array();
159                 $tool = new \stdClass();
161                 $iconinfo = null;
162                 if (is_array($resource->icon_info)) {
163                     $iconinfo = $resource->icon_info[0];
164                 } else {
165                     $iconinfo = $resource->icon_info;
166                 }
167                 if (isset($iconinfo) && isset($iconinfo->default_location) && isset($iconinfo->default_location->path)) {
168                     $tool->iconpath = $iconinfo->default_location->path;
169                 }
171                 foreach ($resource->message as $message) {
172                     if (in_array($message->message_type, $messagetypes)) {
173                         $launchable = $launchable || ($message->message_type === 'basic-lti-launch-request');
174                         $messages[$message->message_type] = $message;
175                     }
176                 }
177                 if (!$launchable) {
178                     continue;
179                 }
180                 $tool->name = $resource->resource_name->default_value;
181                 $tool->messages = $messages;
182                 $tools[] = $tool;
183             }
184             $ok = count($tools) > 0;
185             if (!$ok) {
186                 debugging('No launchable messages found in tool proxy');
187             }
188         }
190         // Add tools and custom parameters.
191         if ($ok) {
192             $baseurl = '';
193             if (isset($toolproxyjson->tool_profile->base_url_choice[0]->default_base_url)) {
194                 $baseurl = $toolproxyjson->tool_profile->base_url_choice[0]->default_base_url;
195             }
196             $securebaseurl = '';
197             if (isset($toolproxyjson->tool_profile->base_url_choice[0]->secure_base_url)) {
198                 $securebaseurl = $toolproxyjson->tool_profile->base_url_choice[0]->secure_base_url;
199             }
200             foreach ($tools as $tool) {
201                 $messages = $tool->messages;
202                 $launchrequest = $messages['basic-lti-launch-request'];
203                 $config = new \stdClass();
204                 $config->lti_toolurl = "{$baseurl}{$launchrequest->path}";
205                 $config->lti_typename = $tool->name;
206                 $config->lti_coursevisible = 1;
207                 $config->lti_forcessl = 0;
208                 if (isset($messages['ContentItemSelectionRequest'])) {
209                     $contentitemrequest = $messages['ContentItemSelectionRequest'];
210                     $config->lti_contentitem = 1;
211                     if ($launchrequest->path !== $contentitemrequest->path) {
212                         $config->lti_toolurl_ContentItemSelectionRequest = $baseurl . $contentitemrequest->path;
213                     }
214                     $contentitemcapabilities = implode("\n", $contentitemrequest->enabled_capability);
215                     $config->lti_enabledcapability_ContentItemSelectionRequest = $contentitemcapabilities;
216                     $contentitemparams = self::lti_extract_parameters($contentitemrequest->parameter);
217                     $config->lti_parameter_ContentItemSelectionRequest = $contentitemparams;
218                 }
220                 $type = new \stdClass();
221                 $type->state = LTI_TOOL_STATE_PENDING;
222                 $type->toolproxyid = $toolproxy->id;
223                 $type->enabledcapability = implode("\n", $launchrequest->enabled_capability);
224                 $type->parameter = self::lti_extract_parameters($launchrequest->parameter);
226                 if (!empty($tool->iconpath)) {
227                     $type->icon = "{$baseurl}{$tool->iconpath}";
228                     if (!empty($securebaseurl)) {
229                         $type->secureicon = "{$securebaseurl}{$tool->iconpath}";
230                     }
231                 }
233                 $ok = $ok && (lti_add_type($type, $config) !== false);
234             }
235             if (isset($toolproxyjson->custom)) {
236                 lti_set_tool_settings($toolproxyjson->custom, $toolproxy->id);
237             }
238         }
240         if (!empty($toolproxy)) {
241             if ($ok) {
242                 // If all went OK accept the tool proxy.
243                 $toolproxy->state = LTI_TOOL_PROXY_STATE_ACCEPTED;
244                 $toolproxy->toolproxy = $response->get_request_data();
245                 $toolproxy->secret = $toolproxyjson->security_contract->shared_secret;
246                 $toolproxy->vendorcode = $toolproxyjson->tool_profile->product_instance->product_info->product_family->vendor->code;
248                 $url = $this->get_endpoint();
249                 $body = <<< EOD
251   "@context" : "http://purl.imsglobal.org/ctx/lti/v2/ToolProxyId",
252   "@type" : "ToolProxy",
253   "@id" : "{$url}",
254   "tool_proxy_guid" : "{$toolproxy->guid}"
256 EOD;
257                 $response->set_code(201);
258                 $response->set_content_type('application/vnd.ims.lti.v2.toolproxy.id+json');
259                 $response->set_body($body);
260             } else {
261                 // Otherwise reject the tool proxy.
262                 $toolproxy->state = LTI_TOOL_PROXY_STATE_REJECTED;
263                 $response->set_code(400);
264             }
265             lti_update_tool_proxy($toolproxy);
266         } else {
267             $response->set_code(400);
268         }
269     }
271     /**
272      * Extracts the message parameters from the tool proxy entry
273      *
274      * @param array $parameters     Parameter section of a message
275      *
276      * @return String  containing parameters
277      */
278     private static function lti_extract_parameters($parameters) {
280         $params = array();
281         foreach ($parameters as $parameter) {
282             if (isset($parameter->variable)) {
283                 $value = '$' . $parameter->variable;
284             } else {
285                 $value = $parameter->fixed;
286                 if (strlen($value) > 0) {
287                     $first = substr($value, 0, 1);
288                     if (($first == '$') || ($first == '\\')) {
289                         $value = '\\' . $value;
290                     }
291                 }
292             }
293             $params[] = "{$parameter->name}={$value}";
294         }
296         return implode("\n", $params);
298     }