Search moodle.org's
Developer Documentation

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.
  • Differences Between: [Versions 37 and 311] [Versions 38 and 311]

       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 container 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 container.
      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 lineitems extends resource_base {
      41  
      42      /**
      43       * Class constructor.
      44       *
      45       * @param \ltiservice_gradebookservices\local\service\gradebookservices $service Service instance
      46       */
      47      public function __construct($service) {
      48  
      49          parent::__construct($service);
      50          $this->id = 'LineItem.collection';
      51          $this->template = '/{context_id}/lineitems';
      52          $this->variables[] = 'LineItems.url';
      53          $this->formats[] = 'application/vnd.ims.lis.v2.lineitemcontainer+json';
      54          $this->formats[] = 'application/vnd.ims.lis.v2.lineitem+json';
      55          $this->methods[] = self::HTTP_GET;
      56          $this->methods[] = self::HTTP_POST;
      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 $DB;
      67  
      68          $params = $this->parse_template();
      69          $contextid = $params['context_id'];
      70          $isget = $response->get_request_method() === self::HTTP_GET;
      71          if ($isget) {
      72              $contenttype = $response->get_accept();
      73          } else {
      74              $contenttype = $response->get_content_type();
      75          }
      76          $container = empty($contenttype) || ($contenttype === $this->formats[0]);
      77          // We will receive typeid when working with LTI 1.x, if not then we are in LTI 2.
      78          $typeid = optional_param('type_id', null, PARAM_INT);
      79  
      80          $scopes = array(gradebookservices::SCOPE_GRADEBOOKSERVICES_LINEITEM);
      81          if ($response->get_request_method() === self::HTTP_GET) {
      82              $scopes[] = gradebookservices::SCOPE_GRADEBOOKSERVICES_LINEITEM_READ;
      83          }
      84  
      85          try {
      86              if (!$this->check_tool($typeid, $response->get_request_data(), $scopes)) {
      87                  throw new \Exception(null, 401);
      88              }
      89              $typeid = $this->get_service()->get_type()->id;
      90              if (empty($contextid) || !($container ^ ($response->get_request_method() === self::HTTP_POST)) ||
      91                      (!empty($contenttype) && !in_array($contenttype, $this->formats))) {
      92                  throw new \Exception('No context or unsupported content type', 400);
      93              }
      94              if (!($course = $DB->get_record('course', array('id' => $contextid), 'id', IGNORE_MISSING))) {
      95                  throw new \Exception("Not Found: Course {$contextid} doesn't exist", 404);
      96              }
      97              if (!$this->get_service()->is_allowed_in_context($typeid, $course->id)) {
      98                  throw new \Exception('Not allowed in context', 403);
      99              }
     100              if ($response->get_request_method() !== self::HTTP_POST) {
     101                  $resourceid = optional_param('resource_id', null, PARAM_TEXT);
     102                  $ltilinkid = optional_param('resource_link_id', null, PARAM_TEXT);
     103                  if (is_null($ltilinkid)) {
     104                      $ltilinkid = optional_param('lti_link_id', null, PARAM_TEXT);
     105                  }
     106                  $tag = optional_param('tag', null, PARAM_TEXT);
     107                  $limitnum = optional_param('limit', 0, PARAM_INT);
     108                  $limitfrom = optional_param('from', 0, PARAM_INT);
     109                  $itemsandcount = $this->get_service()->get_lineitems($contextid, $resourceid, $ltilinkid, $tag, $limitfrom,
     110                          $limitnum, $typeid);
     111                  $items = $itemsandcount[1];
     112                  $totalcount = $itemsandcount[0];
     113                  $json = $this->get_json_for_get_request($items, $resourceid, $ltilinkid, $tag, $limitfrom,
     114                          $limitnum, $totalcount, $typeid, $response);
     115                  $response->set_content_type($this->formats[0]);
     116              } else {
     117                  $json = $this->get_json_for_post_request($response->get_request_data(), $contextid, $typeid);
     118                  $response->set_code(201);
     119                  $response->set_content_type($this->formats[1]);
     120              }
     121              $response->set_body($json);
     122  
     123          } catch (\Exception $e) {
     124              $response->set_code($e->getCode());
     125              $response->set_reason($e->getMessage());
     126          }
     127  
     128      }
     129  
     130      /**
     131       * Generate the JSON for a GET request.
     132       *
     133       * @param array $items Array of lineitems
     134       * @param string $resourceid Resource identifier used for filtering, may be null
     135       * @param string $ltilinkid Resource Link identifier used for filtering, may be null
     136       * @param string $tag Tag identifier used for filtering, may be null
     137       * @param int $limitfrom Offset of the first line item to return
     138       * @param int $limitnum Maximum number of line items to return, ignored if zero or less
     139       * @param int $totalcount Number of total lineitems before filtering for paging
     140       * @param int $typeid Maximum number of line items to return, ignored if zero or less
     141       * @param \mod_lti\local\ltiservice\response $response
     142  
     143       * @return string
     144       */
     145      private function get_json_for_get_request($items, $resourceid, $ltilinkid,
     146              $tag, $limitfrom, $limitnum, $totalcount, $typeid, $response) {
     147  
     148          $firstpage = null;
     149          $nextpage = null;
     150          $prevpage = null;
     151          $lastpage = null;
     152          if (isset($limitnum) && $limitnum > 0) {
     153              if ($limitfrom >= $totalcount || $limitfrom < 0) {
     154                  $outofrange = true;
     155              } else {
     156                  $outofrange = false;
     157              }
     158              $limitprev = $limitfrom - $limitnum >= 0 ? $limitfrom - $limitnum : 0;
     159              $limitcurrent = $limitfrom;
     160              $limitlast = $totalcount - $limitnum + 1 >= 0 ? $totalcount - $limitnum + 1 : 0;
     161              $limitfrom += $limitnum;
     162  
     163              $baseurl = new \moodle_url($this->get_endpoint());
     164              if (isset($resourceid)) {
     165                  $baseurl->param('resource_id', $resourceid);
     166              }
     167              if (isset($ltilinkid)) {
     168                  $baseurl->param('resource_link_id', $ltilinkid);
     169              }
     170              if (isset($tag)) {
     171                  $baseurl->param('tag', $tag);
     172              }
     173  
     174              if (is_null($typeid)) {
     175                  $baseurl->param('limit', $limitnum);
     176                  if (($limitfrom <= $totalcount - 1) && (!$outofrange)) {
     177                      $nextpage = new \moodle_url($baseurl, ['from' => $limitfrom]);
     178                  }
     179                  $firstpage = new \moodle_url($baseurl, ['from' => 0]);
     180                  $canonicalpage = new \moodle_url($baseurl, ['from' => $limitcurrent]);
     181                  $lastpage = new \moodle_url($baseurl, ['from' > $limitlast]);
     182                  if (($limitcurrent > 0) && (!$outofrange)) {
     183                      $prevpage = new \moodle_url($baseurl, ['from' => $limitprev]);
     184                  }
     185              } else {
     186                  $baseurl->params(['type_id' => $typeid, 'limit' => $limitnum]);
     187                  if (($limitfrom <= $totalcount - 1) && (!$outofrange)) {
     188                      $nextpage = new \moodle_url($baseurl, ['from' => $limitfrom]);
     189                  }
     190                  $firstpage = new \moodle_url($baseurl, ['from' => 0]);
     191                  $canonicalpage = new \moodle_url($baseurl, ['from' => $limitcurrent]);
     192                  $lastpage = new \moodle_url($baseurl, ['from' => $limitlast]);
     193                  if (($limitcurrent > 0) && (!$outofrange)) {
     194                      $prevpage = new \moodle_url($baseurl, ['from' => $limitprev]);
     195                  }
     196              }
     197          }
     198  
     199          $jsonitems = [];
     200          $endpoint = parent::get_endpoint();
     201          foreach ($items as $item) {
     202              array_push($jsonitems, gradebookservices::item_for_json($item, $endpoint, $typeid));
     203          }
     204  
     205          if (isset($canonicalpage) && ($canonicalpage)) {
     206              $links = 'Link: <' . $firstpage->out() . '>; rel=“first”';
     207              if (!is_null($prevpage)) {
     208                  $links .= ', <' . $prevpage->out() . '>; rel=“prev”';
     209              }
     210              $links .= ', <' . $canonicalpage->out(). '>; rel=“canonical”';
     211              if (!is_null($nextpage)) {
     212                  $links .= ', <' . $nextpage->out() . '>; rel=“next”';
     213              }
     214              $links .= ', <' . $lastpage->out() . '>; rel=“last”';
     215              $response->add_additional_header($links);
     216          }
     217          return json_encode($jsonitems);
     218      }
     219  
     220      /**
     221       * Generate the JSON for a POST request.
     222       *
     223       * @param string $body POST body
     224       * @param string $contextid Course ID
     225       * @param string $typeid
     226       *
     227       * @return string
     228       * @throws \Exception
     229       */
     230      private function get_json_for_post_request($body, $contextid, $typeid) {
     231          global $CFG, $DB;
     232  
     233          $json = json_decode($body);
     234          if (empty($json) ||
     235                  !isset($json->scoreMaximum) ||
     236                  !isset($json->label)) {
     237              throw new \Exception('No label or Score Maximum', 400);
     238          }
     239          if (is_numeric($json->scoreMaximum)) {
     240              $max = $json->scoreMaximum;
     241          } else {
     242              throw new \Exception(null, 400);
     243          }
     244          require_once($CFG->libdir.'/gradelib.php');
     245          $resourceid = (isset($json->resourceId)) ? $json->resourceId : '';
     246          $ltilinkid = (isset($json->resourceLinkId)) ? $json->resourceLinkId : null;
     247          if ($ltilinkid == null) {
     248              $ltilinkid = (isset($json->ltiLinkId)) ? $json->ltiLinkId : null;
     249          }
     250          if ($ltilinkid != null) {
     251              if (is_null($typeid)) {
     252                  if (!gradebookservices::check_lti_id($ltilinkid, $contextid, $this->get_service()->get_tool_proxy()->id)) {
     253                      throw new \Exception(null, 403);
     254                  }
     255              } else {
     256                  if (!gradebookservices::check_lti_1x_id($ltilinkid, $contextid, $typeid)) {
     257                      throw new \Exception(null, 403);
     258                  }
     259              }
     260          }
     261          $tag = (isset($json->tag)) ? $json->tag : '';
     262          if (is_null($typeid)) {
     263              $toolproxyid = $this->get_service()->get_tool_proxy()->id;
     264              $baseurl = null;
     265          } else {
     266              $toolproxyid = null;
     267              $baseurl = lti_get_type_type_config($typeid)->lti_toolurl;
     268          }
     269          $gradebookservices = new gradebookservices();
     270          $id = $gradebookservices->add_standalone_lineitem($contextid, $json->label,
     271              $max, $baseurl, $ltilinkid, $resourceid, $tag, $typeid, $toolproxyid);
     272          if (is_null($typeid)) {
     273              $json->id = parent::get_endpoint() . "/{$id}/lineitem";
     274          } else {
     275              $json->id = parent::get_endpoint() . "/{$id}/lineitem?type_id={$typeid}";
     276          }
     277          return json_encode($json, JSON_UNESCAPED_SLASHES);
     278      }
     279  
     280      /**
     281       * Parse a value for custom parameter substitution variables.
     282       *
     283       * @param string $value String to be parsed
     284       *
     285       * @return string
     286       */
     287      public function parse_value($value) {
     288          global $COURSE;
     289  
     290          if (strpos($value, '$LineItems.url') !== false) {
     291              $this->params['context_id'] = $COURSE->id;
     292              $query = '';
     293              if (($tool = $this->get_service()->get_type())) {
     294                  $query = "?type_id={$tool->id}";
     295              }
     296              $value = str_replace('$LineItems.url', parent::get_endpoint() . $query, $value);
     297          }
     298  
     299          return $value;
     300  
     301      }
     302  }