Search moodle.org's
Developer Documentation

See Release Notes

  • 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 311 and 401] [Versions 311 and 402] [Versions 311 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   * Extends the IMS Tool provider library data connector for moodle.
  19   *
  20   * @package    enrol_lti
  21   * @copyright  2016 John Okely <john@moodle.com>
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace enrol_lti;
  26  
  27  defined('MOODLE_INTERNAL') || die;
  28  
  29  use IMSGlobal\LTI\ToolProvider;
  30  use IMSGlobal\LTI\ToolProvider\ConsumerNonce;
  31  use IMSGlobal\LTI\ToolProvider\Context;
  32  use IMSGlobal\LTI\ToolProvider\DataConnector\DataConnector;
  33  use IMSGlobal\LTI\ToolProvider\ResourceLink;
  34  use IMSGlobal\LTI\ToolProvider\ResourceLinkShare;
  35  use IMSGlobal\LTI\ToolProvider\ResourceLinkShareKey;
  36  use IMSGlobal\LTI\ToolProvider\ToolConsumer;
  37  use IMSGlobal\LTI\ToolProvider\ToolProxy;
  38  use IMSGlobal\LTI\ToolProvider\User;
  39  use stdClass;
  40  
  41  /**
  42   * Extends the IMS Tool provider library data connector for moodle.
  43   *
  44   * @package    enrol_lti
  45   * @copyright  2016 John Okely <john@moodle.com>
  46   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  47   */
  48  class data_connector extends DataConnector {
  49  
  50      /** @var string Tool consumer table name. */
  51      protected $consumertable;
  52      /** @var string Context table name. */
  53      protected $contexttable;
  54      /** @var string Consumer nonce table name. */
  55      protected $noncetable;
  56      /** @var string Resource link table name. */
  57      protected $resourcelinktable;
  58      /** @var string Resource link share key table name. */
  59      protected $sharekeytable;
  60      /** @var string Tool proxy table name. */
  61      protected $toolproxytable;
  62      /** @var string User result table name. */
  63      protected $userresulttable;
  64  
  65      /**
  66       * data_connector constructor.
  67       */
  68      public function __construct() {
  69          parent::__construct(null, 'enrol_lti_');
  70  
  71          // Set up table names.
  72          $this->consumertable = $this->dbTableNamePrefix . DataConnector::CONSUMER_TABLE_NAME;
  73          $this->contexttable = $this->dbTableNamePrefix . DataConnector::CONTEXT_TABLE_NAME;
  74          $this->noncetable = $this->dbTableNamePrefix . DataConnector::NONCE_TABLE_NAME;
  75          $this->resourcelinktable = $this->dbTableNamePrefix . DataConnector::RESOURCE_LINK_TABLE_NAME;
  76          $this->sharekeytable = $this->dbTableNamePrefix . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME;
  77          $this->toolproxytable = $this->dbTableNamePrefix . DataConnector::TOOL_PROXY_TABLE_NAME;
  78          $this->userresulttable = $this->dbTableNamePrefix . DataConnector::USER_RESULT_TABLE_NAME;
  79      }
  80  
  81      /**
  82       * Load tool consumer object.
  83       *
  84       * @param ToolConsumer $consumer ToolConsumer object
  85       * @return boolean True if the tool consumer object was successfully loaded
  86       */
  87      public function loadToolConsumer($consumer) {
  88          global $DB;
  89  
  90          $id = $consumer->getRecordId();
  91  
  92          if (!empty($id)) {
  93              $result = $DB->get_record($this->consumertable, ['id' => $id]);
  94          } else {
  95              $key256 = DataConnector::getConsumerKey($consumer->getKey());
  96              $result = $DB->get_record($this->consumertable, ['consumerkey256' => $key256]);
  97          }
  98  
  99          if ($result) {
 100              if (empty($key256) || empty($result->consumerkey) || ($consumer->getKey() === $result->consumerkey)) {
 101                  $this->build_tool_consumer_object($result, $consumer);
 102                  return true;
 103              }
 104          }
 105  
 106          return false;
 107      }
 108  
 109      /**
 110       * Save tool consumer object.
 111       *
 112       * @param ToolConsumer $consumer Consumer object
 113       * @return boolean True if the tool consumer object was successfully saved
 114       */
 115      public function saveToolConsumer($consumer) {
 116          global $DB;
 117  
 118          $key = $consumer->getKey();
 119          $key256 = DataConnector::getConsumerKey($key);
 120          if ($key === $key256) {
 121              $key = null;
 122          }
 123          $protected = ($consumer->protected) ? 1 : 0;
 124          $enabled = ($consumer->enabled) ? 1 : 0;
 125          $profile = (!empty($consumer->profile)) ? json_encode($consumer->profile) : null;
 126          $settingsvalue = serialize($consumer->getSettings());
 127          $now = time();
 128          $consumer->updated = $now;
 129          $data = [
 130              'consumerkey256' => $key256,
 131              'consumerkey' => $key,
 132              'name' => $consumer->name,
 133              'secret' => $consumer->secret,
 134              'ltiversion' => $consumer->ltiVersion,
 135              'consumername' => $consumer->consumerName,
 136              'consumerversion' => $consumer->consumerVersion,
 137              'consumerguid' => $consumer->consumerGuid,
 138              'profile' => $profile,
 139              'toolproxy' => $consumer->toolProxy,
 140              'settings' => $settingsvalue,
 141              'protected' => $protected,
 142              'enabled' => $enabled,
 143              'enablefrom' => $consumer->enableFrom,
 144              'enableuntil' => $consumer->enableUntil,
 145              'lastaccess' => $consumer->lastAccess,
 146              'updated' => $consumer->updated,
 147          ];
 148  
 149          $id = $consumer->getRecordId();
 150  
 151          if (empty($id)) {
 152              $consumer->created = $now;
 153              $data['created'] = $consumer->created;
 154              $id = $DB->insert_record($this->consumertable, (object) $data);
 155              if ($id) {
 156                  $consumer->setRecordId($id);
 157                  return true;
 158              }
 159          } else {
 160              $data['id'] = $id;
 161              return $DB->update_record($this->consumertable, (object) $data);
 162          }
 163  
 164          return false;
 165      }
 166  
 167      /**
 168       * Delete tool consumer object and related records.
 169       *
 170       * @param ToolConsumer $consumer Consumer object
 171       * @return boolean True if the tool consumer object was successfully deleted
 172       */
 173      public function deleteToolConsumer($consumer) {
 174          global $DB;
 175  
 176          $consumerpk = $consumer->getRecordId();
 177          $deletecondition = ['consumerid' => $consumerpk];
 178  
 179          // Delete any nonce values for this consumer.
 180          $DB->delete_records($this->noncetable, $deletecondition);
 181  
 182          // Delete any outstanding share keys for resource links for this consumer.
 183          $where = "resourcelinkid IN (
 184                        SELECT rl.id
 185                          FROM {{$this->resourcelinktable}} rl
 186                         WHERE rl.consumerid = :consumerid
 187                    )";
 188          $DB->delete_records_select($this->sharekeytable, $where, $deletecondition);
 189  
 190          // Delete any outstanding share keys for resource links for contexts in this consumer.
 191          $where = "resourcelinkid IN (
 192                        SELECT rl.id
 193                          FROM {{$this->resourcelinktable}} rl
 194                    INNER JOIN {{$this->contexttable}} c
 195                            ON rl.contextid = c.id
 196                         WHERE c.consumerid = :consumerid
 197                  )";
 198          $DB->delete_records_select($this->sharekeytable, $where, $deletecondition);
 199  
 200          // Delete any users in resource links for this consumer.
 201          $where = "resourcelinkid IN (
 202                      SELECT rl.id
 203                        FROM {{$this->resourcelinktable}} rl
 204                       WHERE rl.consumerid = :consumerid
 205                  )";
 206          $DB->delete_records_select($this->userresulttable, $where, $deletecondition);
 207  
 208          // Delete any users in resource links for contexts in this consumer.
 209          $where = "resourcelinkid IN (
 210                           SELECT rl.id
 211                             FROM {{$this->resourcelinktable}} rl
 212                       INNER JOIN {{$this->contexttable}} c
 213                               ON rl.contextid = c.id
 214                            WHERE c.consumerid = :consumerid
 215                  )";
 216          $DB->delete_records_select($this->userresulttable, $where, $deletecondition);
 217  
 218          // Update any resource links for which this consumer is acting as a primary resource link.
 219          $where = "primaryresourcelinkid IN (
 220                      SELECT rl.id
 221                        FROM {{$this->resourcelinktable}} rl
 222                       WHERE rl.consumerid = :consumerid
 223                  )";
 224          $updaterecords = $DB->get_records_select($this->resourcelinktable, $where, $deletecondition);
 225          foreach ($updaterecords as $record) {
 226              $record->primaryresourcelinkid = null;
 227              $record->shareapproved = null;
 228              $DB->update_record($this->resourcelinktable, $record);
 229          }
 230  
 231          // Update any resource links for contexts in which this consumer is acting as a primary resource link.
 232          $where = "primaryresourcelinkid IN (
 233                          SELECT rl.id
 234                            FROM {{$this->resourcelinktable}} rl
 235                      INNER JOIN {{$this->contexttable}} c
 236                              ON rl.contextid = c.id
 237                           WHERE c.consumerid = :consumerid
 238                  )";
 239          $updaterecords = $DB->get_records_select($this->resourcelinktable, $where, $deletecondition);
 240          foreach ($updaterecords as $record) {
 241              $record->primaryresourcelinkid = null;
 242              $record->shareapproved = null;
 243              $DB->update_record($this->resourcelinktable, $record);
 244          }
 245  
 246          // Delete any resource links for contexts in this consumer.
 247          $where = "contextid IN (
 248                        SELECT c.id
 249                          FROM {{$this->contexttable}} c
 250                         WHERE c.consumerid = :consumerid
 251                  )";
 252          $DB->delete_records_select($this->resourcelinktable, $where, $deletecondition);
 253  
 254          // Delete any resource links for this consumer.
 255          $DB->delete_records($this->resourcelinktable, $deletecondition);
 256  
 257          // Delete any contexts for this consumer.
 258          $DB->delete_records($this->contexttable, $deletecondition);
 259  
 260          // Delete consumer.
 261          $DB->delete_records($this->consumertable, ['id' => $consumerpk]);
 262  
 263          $consumer->initialize();
 264  
 265          return true;
 266      }
 267  
 268      /**
 269       * Load all tool consumers from the database.
 270       * @return array
 271       */
 272      public function getToolConsumers() {
 273          global $DB;
 274          $consumers = [];
 275  
 276          $rsconsumers = $DB->get_recordset($this->consumertable, null, 'name');
 277          foreach ($rsconsumers as $row) {
 278              $consumer = new ToolProvider\ToolConsumer($row->consumerkey, $this);
 279              $this->build_tool_consumer_object($row, $consumer);
 280              $consumers[] = $consumer;
 281          }
 282          $rsconsumers->close();
 283  
 284          return $consumers;
 285      }
 286  
 287      /*
 288       * ToolProxy methods.
 289       */
 290  
 291      /**
 292       * Load the tool proxy from the database.
 293       *
 294       * @param ToolProxy $toolproxy
 295       * @return bool
 296       */
 297      public function loadToolProxy($toolproxy) {
 298          return false;
 299      }
 300  
 301      /**
 302       * Save the tool proxy to the database.
 303       *
 304       * @param ToolProxy $toolproxy
 305       * @return bool
 306       */
 307      public function saveToolProxy($toolproxy) {
 308          return false;
 309      }
 310  
 311      /**
 312       * Delete the tool proxy from the database.
 313       *
 314       * @param ToolProxy $toolproxy
 315       * @return bool
 316       */
 317      public function deleteToolProxy($toolproxy) {
 318          return false;
 319      }
 320  
 321      /*
 322       * Context methods.
 323       */
 324  
 325      /**
 326       * Load context object.
 327       *
 328       * @param Context $context Context object
 329       * @return boolean True if the context object was successfully loaded
 330       */
 331      public function loadContext($context) {
 332          global $DB;
 333  
 334          if (!empty($context->getRecordId())) {
 335              $params = ['id' => $context->getRecordId()];
 336          } else {
 337              $params = [
 338                  'consumerid' => $context->getConsumer()->getRecordId(),
 339                  'lticontextkey' => $context->ltiContextId
 340              ];
 341          }
 342          if ($row = $DB->get_record($this->contexttable, $params)) {
 343              $context->setRecordId($row->id);
 344              $context->setConsumerId($row->consumerid);
 345              $context->ltiContextId = $row->lticontextkey;
 346              $context->type = $row->type;
 347              $settings = unserialize($row->settings);
 348              if (!is_array($settings)) {
 349                  $settings = array();
 350              }
 351              $context->setSettings($settings);
 352              $context->created = $row->created;
 353              $context->updated = $row->updated;
 354              return true;
 355          }
 356  
 357          return false;
 358      }
 359  
 360      /**
 361       * Save context object.
 362       *
 363       * @param Context $context Context object
 364       * @return boolean True if the context object was successfully saved
 365       */
 366      public function saveContext($context) {
 367          global $DB;
 368          $now = time();
 369          $context->updated = $now;
 370          $settingsvalue = serialize($context->getSettings());
 371          $id = $context->getRecordId();
 372          $consumerpk = $context->getConsumer()->getRecordId();
 373  
 374          $isinsert = empty($id);
 375          if ($isinsert) {
 376              $context->created = $now;
 377              $params = [
 378                  'consumerid' => $consumerpk,
 379                  'lticontextkey' => $context->ltiContextId,
 380                  'type' => $context->type,
 381                  'settings' => $settingsvalue,
 382                  'created' => $context->created,
 383                  'updated' => $context->updated,
 384              ];
 385              $id = $DB->insert_record($this->contexttable, (object) $params);
 386              if ($id) {
 387                  $context->setRecordId($id);
 388                  return true;
 389              }
 390          } else {
 391              $data = (object) [
 392                  'id' => $id,
 393                  'contextid' => $consumerpk,
 394                  'lticontextkey' => $context->ltiContextId,
 395                  'type' => $context->type,
 396                  'settings' => $settingsvalue,
 397                  'updated' => $context->updated,
 398              ];
 399              return $DB->update_record($this->contexttable, $data);
 400          }
 401  
 402          return false;
 403      }
 404  
 405      /**
 406       * Delete context object.
 407       *
 408       * @param Context $context Context object
 409       * @return boolean True if the Context object was successfully deleted
 410       */
 411      public function deleteContext($context) {
 412          global $DB;
 413  
 414          $contextid = $context->getRecordId();
 415  
 416          $params = ['id' => $contextid];
 417  
 418          // Delete any outstanding share keys for resource links for this context.
 419          $where = "resourcelinkid IN (
 420                      SELECT rl.id
 421                        FROM {{$this->resourcelinktable}} rl
 422                       WHERE rl.contextid = :id
 423                 )";
 424          $DB->delete_records_select($this->sharekeytable, $where, $params);
 425  
 426          // Delete any users in resource links for this context.
 427          $DB->delete_records_select($this->userresulttable, $where, $params);
 428  
 429          // Update any resource links for which this consumer is acting as a primary resource link.
 430          $where = "primaryresourcelinkid IN (
 431                      SELECT rl.id
 432                        FROM {{$this->resourcelinktable}} rl
 433                       WHERE rl.contextid = :id
 434                 )";
 435          $updaterecords = $DB->get_records_select($this->resourcelinktable, $where, $params);
 436          foreach ($updaterecords as $record) {
 437              $record->primaryresourcelinkid = null;
 438              $record->shareapproved = null;
 439              $DB->update_record($this->resourcelinktable, $record);
 440          }
 441  
 442          // Delete any resource links for this context.
 443          $DB->delete_records($this->resourcelinktable, ['contextid' => $contextid]);
 444  
 445          // Delete context.
 446          $DB->delete_records($this->contexttable, $params);
 447  
 448          $context->initialize();
 449  
 450          return true;
 451      }
 452  
 453      /*
 454       * ResourceLink methods
 455       */
 456  
 457      /**
 458       * Load resource link object.
 459       *
 460       * @param ResourceLink $resourcelink ResourceLink object
 461       * @return boolean True if the resource link object was successfully loaded
 462       */
 463      public function loadResourceLink($resourcelink) {
 464          global $DB;
 465  
 466          $resourceid = $resourcelink->getRecordId();
 467          if (!empty($resourceid)) {
 468              $params = ['id' => $resourceid];
 469              $row = $DB->get_record($this->resourcelinktable, $params);
 470          } else if (!empty($resourcelink->getContext())) {
 471              $params = [
 472                  'contextid' => $resourcelink->getContext()->getRecordId(),
 473                  'ltiresourcelinkkey' => $resourcelink->getId()
 474              ];
 475              $row = $DB->get_record($this->resourcelinktable, $params);
 476          } else {
 477              $sql = "SELECT r.*
 478                        FROM {{$this->resourcelinktable}} r
 479             LEFT OUTER JOIN {{$this->contexttable}} c
 480                          ON r.contextid = c.id
 481                       WHERE (r.consumerid = ? OR c.consumerid = ?)
 482                             AND ltiresourcelinkkey = ?";
 483              $params = [
 484                  $resourcelink->getConsumer()->getRecordId(),
 485                  $resourcelink->getConsumer()->getRecordId(),
 486                  $resourcelink->getId()
 487              ];
 488              $row = $DB->get_record_sql($sql, $params);
 489          }
 490          if ($row) {
 491              $resourcelink->setRecordId($row->id);
 492              if (!is_null($row->contextid)) {
 493                  $resourcelink->setContextId($row->contextid);
 494              } else {
 495                  $resourcelink->setContextId(null);
 496              }
 497              if (!is_null($row->consumerid)) {
 498                  $resourcelink->setConsumerId($row->consumerid);
 499              } else {
 500                  $resourcelink->setConsumerId(null);
 501              }
 502              $resourcelink->ltiResourceLinkId = $row->ltiresourcelinkkey;
 503              $settings = unserialize($row->settings);
 504              if (!is_array($settings)) {
 505                  $settings = array();
 506              }
 507              $resourcelink->setSettings($settings);
 508              if (!is_null($row->primaryresourcelinkid)) {
 509                  $resourcelink->primaryResourceLinkId = $row->primaryresourcelinkid;
 510              } else {
 511                  $resourcelink->primaryResourceLinkId = null;
 512              }
 513              $resourcelink->shareApproved = (is_null($row->shareapproved)) ? null : ($row->shareapproved == 1);
 514              $resourcelink->created = $row->created;
 515              $resourcelink->updated = $row->updated;
 516              return true;
 517          }
 518  
 519          return false;
 520      }
 521  
 522      /**
 523       * Save resource link object.
 524       *
 525       * @param ResourceLink $resourcelink Resource_Link object
 526       * @return boolean True if the resource link object was successfully saved
 527       */
 528      public function saveResourceLink($resourcelink) {
 529          global $DB;
 530  
 531          if (is_null($resourcelink->shareApproved)) {
 532              $approved = null;
 533          } else if ($resourcelink->shareApproved) {
 534              $approved = 1;
 535          } else {
 536              $approved = 0;
 537          }
 538          if (empty($resourcelink->primaryResourceLinkId)) {
 539              $primaryresourcelinkid = null;
 540          } else {
 541              $primaryresourcelinkid = $resourcelink->primaryResourceLinkId;
 542          }
 543          $now = time();
 544          $resourcelink->updated = $now;
 545          $settingsvalue = serialize($resourcelink->getSettings());
 546          if (!empty($resourcelink->getContext())) {
 547              $consumerid = null;
 548              $contextid = $resourcelink->getContext()->getRecordId();
 549          } else if (!empty($resourcelink->getContextId())) {
 550              $consumerid = null;
 551              $contextid = $resourcelink->getContextId();
 552          } else {
 553              $consumerid = $resourcelink->getConsumer()->getRecordId();
 554              $contextid = null;
 555          }
 556          $id = $resourcelink->getRecordId();
 557  
 558          $data = [
 559              'consumerid' => $consumerid,
 560              'contextid' => $contextid,
 561              'ltiresourcelinkkey' => $resourcelink->getId(),
 562              'settings' => $settingsvalue,
 563              'primaryresourcelinkid' => $primaryresourcelinkid,
 564              'shareapproved' => $approved,
 565              'updated' => $resourcelink->updated,
 566          ];
 567  
 568          $returnid = null;
 569  
 570          if (empty($id)) {
 571              $resourcelink->created = $now;
 572              $data['created'] = $resourcelink->created;
 573              $id = $DB->insert_record($this->resourcelinktable, (object) $data);
 574              if ($id) {
 575                  $resourcelink->setRecordId($id);
 576                  return true;
 577              }
 578  
 579          } else {
 580              $data['id'] = $id;
 581              return $DB->update_record($this->resourcelinktable, (object) $data);
 582          }
 583  
 584          return false;
 585      }
 586  
 587      /**
 588       * Delete resource link object.
 589       *
 590       * @param ResourceLink $resourcelink ResourceLink object
 591       * @return boolean True if the resource link object and its related records were successfully deleted.
 592       *                 Otherwise, a DML exception is thrown.
 593       */
 594      public function deleteResourceLink($resourcelink) {
 595          global $DB;
 596  
 597          $resourcelinkid = $resourcelink->getRecordId();
 598  
 599          // Delete any outstanding share keys for resource links for this consumer.
 600          $DB->delete_records($this->sharekeytable, ['resourcelinkid' => $resourcelinkid]);
 601  
 602          // Delete users.
 603          $DB->delete_records($this->userresulttable, ['resourcelinkid' => $resourcelinkid]);
 604  
 605          // Update any resource links for which this is the primary resource link.
 606          $records = $DB->get_records($this->resourcelinktable, ['primaryresourcelinkid' => $resourcelinkid]);
 607          foreach ($records as $record) {
 608              $record->primaryresourcelinkid = null;
 609              $DB->update_record($this->resourcelinktable, $record);
 610          }
 611  
 612          // Delete resource link.
 613          $DB->delete_records($this->resourcelinktable, ['id' => $resourcelinkid]);
 614  
 615          $resourcelink->initialize();
 616  
 617          return true;
 618      }
 619  
 620      /**
 621       * Get array of user objects.
 622       *
 623       * Obtain an array of User objects for users with a result sourcedId.  The array may include users from other
 624       * resource links which are sharing this resource link.  It may also be optionally indexed by the user ID of a specified scope.
 625       *
 626       * @param ResourceLink $resourcelink Resource link object
 627       * @param boolean $localonly True if only users within the resource link are to be returned
 628       *                           (excluding users sharing this resource link)
 629       * @param int $idscope Scope value to use for user IDs
 630       * @return array Array of User objects
 631       */
 632      public function getUserResultSourcedIDsResourceLink($resourcelink, $localonly, $idscope) {
 633          global $DB;
 634  
 635          $users = [];
 636  
 637          $params = ['resourcelinkid' => $resourcelink->getRecordId()];
 638  
 639          // Where clause for the subquery.
 640          $subwhere = "(id = :resourcelinkid AND primaryresourcelinkid IS NULL)";
 641          if (!$localonly) {
 642              $subwhere .= " OR (primaryresourcelinkid = :resourcelinkid2 AND shareapproved = 1)";
 643              $params['resourcelinkid2'] = $resourcelink->getRecordId();
 644          }
 645  
 646          // The subquery.
 647          $subsql = "SELECT id
 648                       FROM {{$this->resourcelinktable}}
 649                      WHERE {$subwhere}";
 650  
 651          // Our main where clause.
 652          $where = "resourcelinkid IN ($subsql)";
 653  
 654          // Fields to be queried.
 655          $fields = 'id, ltiresultsourcedid, ltiuserkey, created, updated';
 656  
 657          // Fetch records.
 658          $rs = $DB->get_recordset_select($this->userresulttable, $where, $params, '', $fields);
 659          foreach ($rs as $row) {
 660              $user = User::fromResourceLink($resourcelink, $row->ltiuserkey);
 661              $user->setRecordId($row->id);
 662              $user->ltiResultSourcedId = $row->ltiresultsourcedid;
 663              $user->created = $row->created;
 664              $user->updated = $row->updated;
 665              if (is_null($idscope)) {
 666                  $users[] = $user;
 667              } else {
 668                  $users[$user->getId($idscope)] = $user;
 669              }
 670          }
 671          $rs->close();
 672  
 673          return $users;
 674      }
 675  
 676      /**
 677       * Get array of shares defined for this resource link.
 678       *
 679       * @param ResourceLink $resourcelink ResourceLink object
 680       * @return array Array of ResourceLinkShare objects
 681       */
 682      public function getSharesResourceLink($resourcelink) {
 683          global $DB;
 684  
 685          $shares = [];
 686  
 687          $params = ['primaryresourcelinkid' => $resourcelink->getRecordId()];
 688          $fields = 'id, shareapproved, consumerid';
 689          $records = $DB->get_records($this->resourcelinktable, $params, 'consumerid', $fields);
 690          foreach ($records as $record) {
 691              $share = new ResourceLinkShare();
 692              $share->resourceLinkId = $record->id;
 693              $share->approved = $record->shareapproved == 1;
 694              $shares[] = $share;
 695          }
 696  
 697          return $shares;
 698      }
 699  
 700      /*
 701       * ConsumerNonce methods
 702       */
 703  
 704      /**
 705       * Load nonce object.
 706       *
 707       * @param ConsumerNonce $nonce Nonce object
 708       * @return boolean True if the nonce object was successfully loaded
 709       */
 710      public function loadConsumerNonce($nonce) {
 711          global $DB;
 712  
 713          // Delete any expired nonce values.
 714          $now = time();
 715          $DB->delete_records_select($this->noncetable, "expires <= ?", [$now]);
 716  
 717          // Load the nonce.
 718          $params = [
 719              'consumerid' => $nonce->getConsumer()->getRecordId(),
 720              'value' => $nonce->getValue()
 721          ];
 722          $result = $DB->get_field($this->noncetable, 'value', $params);
 723  
 724          return !empty($result);
 725      }
 726  
 727      /**
 728       * Save nonce object.
 729       *
 730       * @param ConsumerNonce $nonce Nonce object
 731       * @return boolean True if the nonce object was successfully saved
 732       */
 733      public function saveConsumerNonce($nonce) {
 734          global $DB;
 735  
 736          $data = [
 737              'consumerid' => $nonce->getConsumer()->getRecordId(),
 738              'value' => $nonce->getValue(),
 739              'expires' => $nonce->expires
 740          ];
 741  
 742          return $DB->insert_record($this->noncetable, (object) $data, false);
 743      }
 744  
 745      /*
 746       * ResourceLinkShareKey methods.
 747       */
 748  
 749      /**
 750       * Load resource link share key object.
 751       *
 752       * @param ResourceLinkShareKey $sharekey ResourceLink share key object
 753       * @return boolean True if the resource link share key object was successfully loaded
 754       */
 755      public function loadResourceLinkShareKey($sharekey) {
 756          global $DB;
 757  
 758          // Clear expired share keys.
 759          $now = time();
 760          $where = "expires <= :expires";
 761  
 762          $DB->delete_records_select($this->sharekeytable, $where, ['expires' => $now]);
 763  
 764          // Load share key.
 765          $fields = 'resourcelinkid, autoapprove, expires';
 766          if ($sharekeyrecord = $DB->get_record($this->sharekeytable, ['sharekey' => $sharekey->getId()], $fields)) {
 767              if ($sharekeyrecord->resourcelinkid == $sharekey->resourceLinkId) {
 768                  $sharekey->autoApprove = $sharekeyrecord->autoapprove == 1;
 769                  $sharekey->expires = $sharekeyrecord->expires;
 770                  return true;
 771              }
 772          }
 773  
 774          return false;
 775      }
 776  
 777      /**
 778       * Save resource link share key object.
 779       *
 780       * @param ResourceLinkShareKey $sharekey Resource link share key object
 781       * @return boolean True if the resource link share key object was successfully saved
 782       */
 783      public function saveResourceLinkShareKey($sharekey) {
 784          global $DB;
 785  
 786          if ($sharekey->autoApprove) {
 787              $approve = 1;
 788          } else {
 789              $approve = 0;
 790          }
 791  
 792          $expires = $sharekey->expires;
 793  
 794          $params = [
 795              'sharekey' => $sharekey->getId(),
 796              'resourcelinkid' => $sharekey->resourceLinkId,
 797              'autoapprove' => $approve,
 798              'expires' => $expires
 799          ];
 800  
 801          return $DB->insert_record($this->sharekeytable, (object) $params, false);
 802      }
 803  
 804      /**
 805       * Delete resource link share key object.
 806       *
 807       * @param ResourceLinkShareKey $sharekey Resource link share key object
 808       * @return boolean True if the resource link share key object was successfully deleted
 809       */
 810      public function deleteResourceLinkShareKey($sharekey) {
 811          global $DB;
 812  
 813          $DB->delete_records($this->sharekeytable, ['sharekey' => $sharekey->getId()]);
 814          $sharekey->initialize();
 815  
 816          return true;
 817      }
 818  
 819      /*
 820       * User methods
 821       */
 822  
 823      /**
 824       * Load user object.
 825       *
 826       * @param User $user User object
 827       * @return boolean True if the user object was successfully loaded
 828       */
 829      public function loadUser($user) {
 830          global $DB;
 831  
 832          $userid = $user->getRecordId();
 833          $fields = 'id, resourcelinkid, ltiuserkey, ltiresultsourcedid, created, updated';
 834          if (!empty($userid)) {
 835              $row = $DB->get_record($this->userresulttable, ['id' => $userid], $fields);
 836          } else {
 837              $resourcelinkid = $user->getResourceLink()->getRecordId();
 838              $userid = $user->getId(ToolProvider\ToolProvider::ID_SCOPE_ID_ONLY);
 839              $row = $DB->get_record_select(
 840                  $this->userresulttable,
 841                  "resourcelinkid = ? AND ltiuserkey = ?",
 842                  [$resourcelinkid, $userid],
 843                  $fields
 844              );
 845          }
 846          if ($row) {
 847              $user->setRecordId($row->id);
 848              $user->setResourceLinkId($row->resourcelinkid);
 849              $user->ltiUserId = $row->ltiuserkey;
 850              $user->ltiResultSourcedId = $row->ltiresultsourcedid;
 851              $user->created = $row->created;
 852              $user->updated = $row->updated;
 853              return true;
 854          }
 855  
 856          return false;
 857      }
 858  
 859      /**
 860       * Save user object.
 861       *
 862       * @param User $user User object
 863       * @return boolean True if the user object was successfully saved
 864       */
 865      public function saveUser($user) {
 866          global $DB;
 867  
 868          $now = time();
 869          $isinsert = is_null($user->created);
 870          $user->updated = $now;
 871  
 872          $params = [
 873              'ltiresultsourcedid' => $user->ltiResultSourcedId,
 874              'updated' => $user->updated
 875          ];
 876  
 877          if ($isinsert) {
 878              $params['resourcelinkid'] = $user->getResourceLink()->getRecordId();
 879              $params['ltiuserkey'] = $user->getId(ToolProvider\ToolProvider::ID_SCOPE_ID_ONLY);
 880              $user->created = $now;
 881              $params['created'] = $user->created;
 882              $id = $DB->insert_record($this->userresulttable, (object) $params);
 883              if ($id) {
 884                  $user->setRecordId($id);
 885                  return true;
 886              }
 887  
 888          } else {
 889              $params['id'] = $user->getRecordId();
 890              return $DB->update_record($this->userresulttable, (object) $params);
 891          }
 892  
 893          return false;
 894      }
 895  
 896      /**
 897       * Delete user object.
 898       *
 899       * @param User $user User object
 900       * @return boolean True if the user object was successfully deleted
 901       */
 902      public function deleteUser($user) {
 903          global $DB;
 904  
 905          $DB->delete_records($this->userresulttable, ['id' => $user->getRecordId()]);
 906          $user->initialize();
 907  
 908          return true;
 909      }
 910  
 911      /**
 912       * Fetches the list of Context objects that are linked to a ToolConsumer.
 913       *
 914       * @param ToolConsumer $consumer
 915       * @return Context[]
 916       */
 917      public function get_contexts_from_consumer(ToolConsumer $consumer) {
 918          global $DB;
 919  
 920          $contexts = [];
 921          $contextrecords = $DB->get_records($this->contexttable, ['consumerid' => $consumer->getRecordId()], '', 'lticontextkey');
 922          foreach ($contextrecords as $record) {
 923              $context = Context::fromConsumer($consumer, $record->lticontextkey);
 924              $contexts[] = $context;
 925          }
 926  
 927          return $contexts;
 928      }
 929  
 930      /**
 931       * Fetches a resource link record that is associated with a ToolConsumer.
 932       *
 933       * @param ToolConsumer $consumer
 934       * @return ResourceLink
 935       */
 936      public function get_resourcelink_from_consumer(ToolConsumer $consumer) {
 937          global $DB;
 938  
 939          $resourcelink = null;
 940          if ($resourcelinkrecord = $DB->get_record($this->resourcelinktable, ['consumerid' => $consumer->getRecordId()],
 941              'ltiresourcelinkkey')) {
 942              $resourcelink = ResourceLink::fromConsumer($consumer, $resourcelinkrecord->ltiresourcelinkkey);
 943          }
 944  
 945          return $resourcelink;
 946      }
 947  
 948      /**
 949       * Fetches a resource link record that is associated with a Context object.
 950       *
 951       * @param Context $context
 952       * @return ResourceLink
 953       */
 954      public function get_resourcelink_from_context(Context $context) {
 955          global $DB;
 956  
 957          $resourcelink = null;
 958          if ($resourcelinkrecord = $DB->get_record($this->resourcelinktable, ['contextid' => $context->getRecordId()],
 959              'ltiresourcelinkkey')) {
 960              $resourcelink = ResourceLink::fromContext($context, $resourcelinkrecord->ltiresourcelinkkey);
 961          }
 962  
 963          return $resourcelink;
 964      }
 965  
 966  
 967      /**
 968       * Fetches the list of ToolConsumer objects that are linked to a tool.
 969       *
 970       * @param int $toolid
 971       * @return ToolConsumer[]
 972       */
 973      public function get_consumers_mapped_to_tool($toolid) {
 974          global $DB;
 975  
 976          $consumers = [];
 977          $consumerrecords = $DB->get_records('enrol_lti_tool_consumer_map', ['toolid' => $toolid], '', 'consumerid');
 978          foreach ($consumerrecords as $record) {
 979              $consumers[] = ToolConsumer::fromRecordId($record->consumerid, $this);
 980          }
 981          return $consumers;
 982      }
 983  
 984      /**
 985       * Builds a ToolConsumer object from a record object from the DB.
 986       *
 987       * @param stdClass $record The DB record object.
 988       * @param ToolConsumer $consumer
 989       */
 990      protected function build_tool_consumer_object($record, ToolConsumer $consumer) {
 991          $consumer->setRecordId($record->id);
 992          $consumer->name = $record->name;
 993          $key = empty($record->consumerkey) ? $record->consumerkey256 : $record->consumerkey;
 994          $consumer->setKey($key);
 995          $consumer->secret = $record->secret;
 996          $consumer->ltiVersion = $record->ltiversion;
 997          $consumer->consumerName = $record->consumername;
 998          $consumer->consumerVersion = $record->consumerversion;
 999          $consumer->consumerGuid = $record->consumerguid;
1000          $consumer->profile = json_decode($record->profile);
1001          $consumer->toolProxy = $record->toolproxy;
1002          $settings = unserialize($record->settings);
1003          if (!is_array($settings)) {
1004              $settings = array();
1005          }
1006          $consumer->setSettings($settings);
1007          $consumer->protected = $record->protected == 1;
1008          $consumer->enabled = $record->enabled == 1;
1009          $consumer->enableFrom = null;
1010          if (!is_null($record->enablefrom)) {
1011              $consumer->enableFrom = $record->enablefrom;
1012          }
1013          $consumer->enableUntil = null;
1014          if (!is_null($record->enableuntil)) {
1015              $consumer->enableUntil = $record->enableuntil;
1016          }
1017          $consumer->lastAccess = null;
1018          if (!is_null($record->lastaccess)) {
1019              $consumer->lastAccess = $record->lastaccess;
1020          }
1021          $consumer->created = $record->created;
1022          $consumer->updated = $record->updated;
1023      }
1024  }