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