Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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

   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   * This file contains a class definition for the LineItem resource
  19   *
  20   * @package    ltiservice_gradebookservices
  21   * @copyright  2017 Cengage Learning http://www.cengage.com
  22   * @author     Dirk Singels, Diego del Blanco, Claude Vervoort
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  namespace ltiservice_gradebookservices\local\resources;
  27  
  28  use ltiservice_gradebookservices\local\service\gradebookservices;
  29  use mod_lti\local\ltiservice\resource_base;
  30  
  31  defined('MOODLE_INTERNAL') || die();
  32  
  33  /**
  34   * A resource implementing LineItem.
  35   *
  36   * @package    ltiservice_gradebookservices
  37   * @copyright  2017 Cengage Learning http://www.cengage.com
  38   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  39   */
  40  class lineitem extends resource_base {
  41  
  42      /**
  43       * Class constructor.
  44       *
  45       * @param gradebookservices $service Service instance
  46       */
  47      public function __construct($service) {
  48  
  49          parent::__construct($service);
  50          $this->id = 'LineItem.item';
  51          $this->template = '/{context_id}/lineitems/{item_id}/lineitem';
  52          $this->variables[] = 'LineItem.url';
  53          $this->formats[] = 'application/vnd.ims.lis.v2.lineitem+json';
  54          $this->methods[] = self::HTTP_GET;
  55          $this->methods[] = self::HTTP_PUT;
  56          $this->methods[] = self::HTTP_DELETE;
  57  
  58      }
  59  
  60      /**
  61       * Execute the request for this resource.
  62       *
  63       * @param \mod_lti\local\ltiservice\response $response  Response object for this request.
  64       */
  65      public function execute($response) {
  66          global $CFG, $DB;
  67  
  68          $params = $this->parse_template();
  69          $contextid = $params['context_id'];
  70          $itemid = $params['item_id'];
  71          $isget = $response->get_request_method() === self::HTTP_GET;
  72          // We will receive typeid when working with LTI 1.x, if not then we are in LTI 2.
  73          $typeid = optional_param('type_id', null, PARAM_INT);
  74  
  75          $scopes = array(gradebookservices::SCOPE_GRADEBOOKSERVICES_LINEITEM);
  76          if ($response->get_request_method() === self::HTTP_GET) {
  77              $scopes[] = gradebookservices::SCOPE_GRADEBOOKSERVICES_LINEITEM_READ;
  78          }
  79  
  80          try {
  81              if (!$this->check_tool($typeid, $response->get_request_data(), $scopes)) {
  82                  throw new \Exception(null, 401);
  83              }
  84              $typeid = $this->get_service()->get_type()->id;
  85              if (!($course = $DB->get_record('course', array('id' => $contextid), 'id', IGNORE_MISSING))) {
  86                  throw new \Exception("Not Found: Course {$contextid} doesn't exist", 404);
  87              }
  88              if (!$this->get_service()->is_allowed_in_context($typeid, $course->id)) {
  89                  throw new \Exception('Not allowed in context', 403);
  90              }
  91              if (!$DB->record_exists('grade_items', array('id' => $itemid))) {
  92                  throw new \Exception("Not Found: Grade item {$itemid} doesn't exist", 404);
  93              }
  94              $item = $this->get_service()->get_lineitem($contextid, $itemid, $typeid);
  95              if ($item === false) {
  96                  throw new \Exception('Line item does not exist', 404);
  97              }
  98              require_once($CFG->libdir.'/gradelib.php');
  99              switch ($response->get_request_method()) {
 100                  case self::HTTP_GET:
 101                      $this->get_request($response, $item, $typeid);
 102                      break;
 103                  case self::HTTP_PUT:
 104                      $json = $this->process_put_request($response->get_request_data(), $item, $typeid);
 105                      $response->set_body($json);
 106                      $response->set_code(200);
 107                      break;
 108                  case self::HTTP_DELETE:
 109                      $this->process_delete_request($item);
 110                      $response->set_code(204);
 111                      break;
 112              }
 113          } catch (\Exception $e) {
 114              $response->set_code($e->getCode());
 115              $response->set_reason($e->getMessage());
 116          }
 117      }
 118  
 119      /**
 120       * Process a GET request.
 121       *
 122       * @param \mod_lti\local\ltiservice\response $response Response object for this request.
 123       * @param object $item Grade item instance.
 124       * @param string $typeid Tool Type Id
 125       */
 126      private function get_request($response, $item, $typeid) {
 127  
 128          $response->set_content_type($this->formats[0]);
 129          $lineitem = gradebookservices::item_for_json($item, substr(parent::get_endpoint(),
 130              0, strrpos(parent::get_endpoint(), "/", -10)), $typeid);
 131          $response->set_body(json_encode($lineitem));
 132  
 133      }
 134  
 135      /**
 136       * Process a PUT request.
 137       *
 138       * @param string $body PUT body
 139       * @param \ltiservice_gradebookservices\local\resources\lineitem $olditem Grade item instance
 140       * @param string $typeid Tool Type Id
 141       *
 142       * @return string
 143       * @throws \Exception
 144       */
 145      private function process_put_request($body, $olditem, $typeid) {
 146          global $DB;
 147          $json = json_decode($body);
 148          if (empty($json) ||
 149                  !isset($json->scoreMaximum) ||
 150                  !isset($json->label)) {
 151              throw new \Exception(null, 400);
 152          }
 153          $item = \grade_item::fetch(array('id' => $olditem->id, 'courseid' => $olditem->courseid));
 154          $gbs = gradebookservices::find_ltiservice_gradebookservice_for_lineitem($olditem->id);
 155          $updategradeitem = false;
 156          $rescalegrades = false;
 157          $oldgrademax = grade_floatval($item->grademax);
 158          $upgradegradebookservices = false;
 159          if ($item->itemname !== $json->label) {
 160              $updategradeitem = true;
 161          }
 162          $item->itemname = $json->label;
 163          if (!is_numeric($json->scoreMaximum)) {
 164              throw new \Exception(null, 400);
 165          } else {
 166              if (grade_floats_different($oldgrademax,
 167                      grade_floatval($json->scoreMaximum))) {
 168                  $updategradeitem = true;
 169                  $rescalegrades = true;
 170              }
 171              $item->grademax = grade_floatval($json->scoreMaximum);
 172          }
 173          if ($gbs) {
 174              $resourceid = (isset($json->resourceId)) ? $json->resourceId : '';
 175              $tag = (isset($json->tag)) ? $json->tag : '';
 176              if ($gbs->tag !== $tag || $gbs->resourceid !== $resourceid) {
 177                  $upgradegradebookservices = true;
 178              }
 179              $gbs->tag = $tag;
 180              $gbs->resourceid = $resourceid;
 181              $incomingurl = null;
 182              $incomingparams = null;
 183              if (isset($json->submissionReview)) {
 184                  $incomingurl = $json->submissionReview->url ?? 'DEFAULT';
 185                  if (isset($json->submissionReview->custom)) {
 186                      $incomingparams = params_to_string($json->submissionReview->custom);
 187                  }
 188              }
 189              if ($gbs->subreviewurl ?? null !== $incomingurl || $gbs->subreviewparams ?? null !== $incomingparams) {
 190                  $upgradegradebookservices = true;
 191                  $gbs->subreviewurl = $incomingurl;
 192                  $gbs->subreviewparams = $incomingparams;
 193              }
 194          }
 195          $ltilinkid = null;
 196          if (isset($json->resourceLinkId)) {
 197              if (is_numeric($json->resourceLinkId)) {
 198                  $ltilinkid = $json->resourceLinkId;
 199                  if ($gbs) {
 200                      if (intval($gbs->ltilinkid) !== intval($json->resourceLinkId)) {
 201                          $gbs->ltilinkid = $json->resourceLinkId;
 202                          $upgradegradebookservices = true;
 203                      }
 204                  } else {
 205                      if (intval($item->iteminstance) !== intval($json->resourceLinkId)) {
 206                          $item->iteminstance = intval($json->resourceLinkId);
 207                          $updategradeitem = true;
 208                      }
 209                  }
 210              } else {
 211                  throw new \Exception(null, 400);
 212              }
 213          } else if (isset($json->ltiLinkId)) {
 214              if (is_numeric($json->ltiLinkId)) {
 215                  $ltilinkid = $json->ltiLinkId;
 216                  if ($gbs) {
 217                      if (intval($gbs->ltilinkid) !== intval($json->ltiLinkId)) {
 218                          $gbs->ltilinkid = $json->ltiLinkId;
 219                          $upgradegradebookservices = true;
 220                      }
 221                  } else {
 222                      if (intval($item->iteminstance) !== intval($json->ltiLinkId)) {
 223                          $item->iteminstance = intval($json->ltiLinkId);
 224                          $updategradeitem = true;
 225                      }
 226                  }
 227              } else {
 228                  throw new \Exception(null, 400);
 229              }
 230          }
 231          if ($ltilinkid != null) {
 232              if (is_null($typeid)) {
 233                  if (!gradebookservices::check_lti_id($ltilinkid, $item->courseid,
 234                          $this->get_service()->get_tool_proxy()->id)) {
 235                              throw new \Exception(null, 403);
 236                  }
 237              } else {
 238                  if (!gradebookservices::check_lti_1x_id($ltilinkid, $item->courseid,
 239                          $typeid)) {
 240                              throw new \Exception(null, 403);
 241                  }
 242              }
 243          }
 244          if ($updategradeitem) {
 245              if (!$item->update('mod/ltiservice_gradebookservices')) {
 246                  throw new \Exception(null, 500);
 247              }
 248              if ($rescalegrades) {
 249                  $item->rescale_grades_keep_percentage(0, $oldgrademax, 0, $item->grademax);
 250              }
 251          }
 252  
 253          $lineitem = new lineitem($this->get_service());
 254          $endpoint = $lineitem->get_endpoint();
 255  
 256          if ($upgradegradebookservices) {
 257              if (is_null($typeid)) {
 258                  $toolproxyid = $this->get_service()->get_tool_proxy()->id;
 259                  $baseurl = null;
 260              } else {
 261                  $toolproxyid = null;
 262                  $baseurl = lti_get_type_type_config($typeid)->lti_toolurl;
 263              }
 264              $DB->update_record('ltiservice_gradebookservices', (object)array(
 265                      'id' => $gbs->id,
 266                      'gradeitemid' => $gbs->gradeitemid,
 267                      'courseid' => $gbs->courseid,
 268                      'toolproxyid' => $toolproxyid,
 269                      'typeid' => $typeid,
 270                      'baseurl' => $baseurl,
 271                      'ltilinkid' => $ltilinkid,
 272                      'resourceid' => $resourceid,
 273                      'tag' => $gbs->tag,
 274                      'subreviewurl' => $gbs->subreviewurl,
 275                      'subreviewparams' => $gbs->subreviewparams
 276              ));
 277          }
 278  
 279          if (is_null($typeid)) {
 280              $id = "{$endpoint}";
 281              $json->id = $id;
 282          } else {
 283              $id = "{$endpoint}?type_id={$typeid}";
 284              $json->id = $id;
 285          }
 286          return json_encode($json, JSON_UNESCAPED_SLASHES);
 287  
 288      }
 289  
 290      /**
 291       * Process a DELETE request.
 292       *
 293       * @param \ltiservice_gradebookservices\local\resources\lineitem $item Grade item instance
 294       * @throws \Exception
 295       */
 296      private function process_delete_request($item) {
 297          global $DB;
 298  
 299          $gradeitem = \grade_item::fetch(array('id' => $item->id));
 300          if (($gbs = gradebookservices::find_ltiservice_gradebookservice_for_lineitem($item->id)) == false) {
 301              throw new \Exception(null, 403);
 302          }
 303          if (!$gradeitem->delete('mod/ltiservice_gradebookservices')) {
 304              throw new \Exception(null, 500);
 305          } else {
 306              $sqlparams = array();
 307              $sqlparams['id'] = $gbs->id;
 308              if (!$DB->delete_records('ltiservice_gradebookservices', $sqlparams)) {
 309                  throw new \Exception(null, 500);
 310              }
 311          }
 312      }
 313  
 314      /**
 315       * Parse a value for custom parameter substitution variables.
 316       *
 317       * @param string $value String to be parsed
 318       *
 319       * @return string
 320       */
 321      public function parse_value($value) {
 322          global $COURSE, $CFG;
 323          if (strpos($value, '$LineItem.url') !== false) {
 324              $resolved = '';
 325              require_once($CFG->libdir . '/gradelib.php');
 326  
 327              $this->params['context_id'] = $COURSE->id;
 328              if ($tool = $this->get_service()->get_type()) {
 329                  $this->params['tool_code'] = $tool->id;
 330              }
 331              $id = optional_param('id', 0, PARAM_INT); // Course Module ID.
 332              if (empty($id)) {
 333                  $hint = optional_param('lti_message_hint', "", PARAM_TEXT);
 334                  if ($hint) {
 335                      $hintdec = json_decode($hint);
 336                      if (isset($hintdec->cmid)) {
 337                          $id = $hintdec->cmid;
 338                      }
 339                  }
 340              }
 341              if (!empty($id)) {
 342                  $cm = get_coursemodule_from_id('lti', $id, 0, false, MUST_EXIST);
 343                  $id = $cm->instance;
 344                  $item = grade_get_grades($COURSE->id, 'mod', 'lti', $id);
 345                  if ($item && $item->items) {
 346                      $this->params['item_id'] = $item->items[0]->id;
 347                      $resolved = parent::get_endpoint();
 348                      $resolved .= "?type_id={$tool->id}";
 349                  }
 350              }
 351              $value = str_replace('$LineItem.url', $resolved, $value);
 352          }
 353          return $value;
 354      }
 355  }