Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403] [Versions 402 and 403]

   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/>.
  16  
  17  /**
  18   * LTI web service endpoints
  19   *
  20   * @package mod_lti
  21   * @copyright  Copyright (c) 2011 Moodlerooms Inc. (http://www.moodlerooms.com)
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   * @author     Chris Scribner
  24   */
  25  
  26  define('NO_DEBUG_DISPLAY', true);
  27  define('NO_MOODLE_COOKIES', true);
  28  
  29  require_once(__DIR__ . "/../../config.php");
  30  require_once($CFG->dirroot.'/mod/lti/locallib.php');
  31  require_once($CFG->dirroot.'/mod/lti/servicelib.php');
  32  
  33  // TODO: Switch to core oauthlib once implemented - MDL-30149.
  34  use mod_lti\service_exception_handler;
  35  use moodle\mod\lti as lti;
  36  use ltiservice_basicoutcomes\local\service\basicoutcomes;
  37  
  38  $rawbody = file_get_contents("php://input");
  39  
  40  $logrequests  = lti_should_log_request($rawbody);
  41  $errorhandler = new service_exception_handler($logrequests);
  42  
  43  // Register our own error handler so we can always send valid XML response.
  44  set_exception_handler(array($errorhandler, 'handle'));
  45  
  46  if ($logrequests) {
  47      lti_log_request($rawbody);
  48  }
  49  
  50  $ok = true;
  51  $type = null;
  52  $toolproxy = false;
  53  
  54  $consumerkey = lti\get_oauth_key_from_headers(null, array(basicoutcomes::SCOPE_BASIC_OUTCOMES));
  55  if ($consumerkey === false) {
  56      throw new Exception('Missing or invalid consumer key or access token.');
  57  } else if (is_string($consumerkey)) {
  58      $toolproxy = lti_get_tool_proxy_from_guid($consumerkey);
  59      if ($toolproxy !== false) {
  60          $secrets = array($toolproxy->secret);
  61      } else if (!empty($tool)) {
  62          $secrets = array($typeconfig['password']);
  63      } else {
  64          $secrets = lti_get_shared_secrets_by_key($consumerkey);
  65      }
  66      $sharedsecret = lti_verify_message($consumerkey, lti_get_shared_secrets_by_key($consumerkey), $rawbody);
  67      if ($sharedsecret === false) {
  68          throw new Exception('Message signature not valid');
  69      }
  70  }
  71  
  72  // TODO MDL-46023 Replace this code with a call to the new library.
  73  $xml = simplexml_load_string($rawbody);
  74  if (!$xml) {
  75      throw new Exception('Invalid XML content');
  76  }
  77  
  78  $body = $xml->imsx_POXBody;
  79  foreach ($body->children() as $child) {
  80      $messagetype = $child->getName();
  81  }
  82  
  83  // We know more about the message, update error handler to send better errors.
  84  $errorhandler->set_message_id(lti_parse_message_id($xml));
  85  $errorhandler->set_message_type($messagetype);
  86  
  87  switch ($messagetype) {
  88      case 'replaceResultRequest':
  89          $parsed = lti_parse_grade_replace_message($xml);
  90  
  91          $ltiinstance = $DB->get_record('lti', array('id' => $parsed->instanceid));
  92  
  93          if (!lti_accepts_grades($ltiinstance)) {
  94              throw new Exception('Tool does not accept grades');
  95          }
  96  
  97          lti_verify_sourcedid($ltiinstance, $parsed);
  98          lti_set_session_user($parsed->userid);
  99  
 100          $gradestatus = lti_update_grade($ltiinstance, $parsed->userid, $parsed->launchid, $parsed->gradeval);
 101  
 102          if (!$gradestatus) {
 103              throw new Exception('Grade replace response');
 104          }
 105  
 106          $responsexml = lti_get_response_xml(
 107                  'success',
 108                  'Grade replace response',
 109                  $parsed->messageid,
 110                  'replaceResultResponse'
 111          );
 112  
 113          echo $responsexml->asXML();
 114  
 115          break;
 116  
 117      case 'readResultRequest':
 118          $parsed = lti_parse_grade_read_message($xml);
 119  
 120          $ltiinstance = $DB->get_record('lti', array('id' => $parsed->instanceid));
 121  
 122          if (!lti_accepts_grades($ltiinstance)) {
 123              throw new Exception('Tool does not accept grades');
 124          }
 125  
 126          // Getting the grade requires the context is set.
 127          $context = context_course::instance($ltiinstance->course);
 128          $PAGE->set_context($context);
 129  
 130          lti_verify_sourcedid($ltiinstance, $parsed);
 131  
 132          $grade = lti_read_grade($ltiinstance, $parsed->userid);
 133  
 134          $responsexml = lti_get_response_xml(
 135                  'success',  // Empty grade is also 'success'.
 136                  'Result read',
 137                  $parsed->messageid,
 138                  'readResultResponse'
 139          );
 140  
 141          $node = $responsexml->imsx_POXBody->readResultResponse;
 142          $node = $node->addChild('result')->addChild('resultScore');
 143          $node->addChild('language', 'en');
 144          $node->addChild('textString', isset($grade) ? $grade : '');
 145  
 146          echo $responsexml->asXML();
 147  
 148          break;
 149  
 150      case 'deleteResultRequest':
 151          $parsed = lti_parse_grade_delete_message($xml);
 152  
 153          $ltiinstance = $DB->get_record('lti', array('id' => $parsed->instanceid));
 154  
 155          if (!lti_accepts_grades($ltiinstance)) {
 156              throw new Exception('Tool does not accept grades');
 157          }
 158  
 159          lti_verify_sourcedid($ltiinstance, $parsed);
 160          lti_set_session_user($parsed->userid);
 161  
 162          $gradestatus = lti_delete_grade($ltiinstance, $parsed->userid);
 163  
 164          if (!$gradestatus) {
 165              throw new Exception('Grade delete request');
 166          }
 167  
 168          $responsexml = lti_get_response_xml(
 169                  'success',
 170                  'Grade delete request',
 171                  $parsed->messageid,
 172                  'deleteResultResponse'
 173          );
 174  
 175          echo $responsexml->asXML();
 176  
 177          break;
 178  
 179      default:
 180          // Fire an event if we get a web service request which we don't support directly.
 181          // This will allow others to extend the LTI services, which I expect to be a common
 182          // use case, at least until the spec matures.
 183          $data = new stdClass();
 184          $data->body = $rawbody;
 185          $data->xml = $xml;
 186          $data->messageid = lti_parse_message_id($xml);
 187          $data->messagetype = $messagetype;
 188          $data->consumerkey = $consumerkey;
 189          $data->sharedsecret = $sharedsecret;
 190          $eventdata = array();
 191          $eventdata['other'] = array();
 192          $eventdata['other']['messageid'] = $data->messageid;
 193          $eventdata['other']['messagetype'] = $messagetype;
 194          $eventdata['other']['consumerkey'] = $consumerkey;
 195  
 196          // Before firing the event, allow subplugins a chance to handle.
 197          if (lti_extend_lti_services($data)) {
 198              break;
 199          }
 200  
 201          // If an event handler handles the web service, it should set this global to true
 202          // So this code knows whether to send an "operation not supported" or not.
 203          global $ltiwebservicehandled;
 204          $ltiwebservicehandled = false;
 205  
 206          try {
 207              $event = \mod_lti\event\unknown_service_api_called::create($eventdata);
 208              $event->set_message_data($data);
 209              $event->trigger();
 210          } catch (Exception $e) {
 211              $ltiwebservicehandled = false;
 212          }
 213  
 214          if (!$ltiwebservicehandled) {
 215              $responsexml = lti_get_response_xml(
 216                  'unsupported',
 217                  'unsupported',
 218                   lti_parse_message_id($xml),
 219                   $messagetype
 220              );
 221  
 222              echo $responsexml->asXML();
 223          }
 224  
 225          break;
 226  }