Search moodle.org's
Developer Documentation

See Release Notes

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

Differences Between: [Versions 402 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  namespace core_xapi;
  18  
  19  use core_xapi\local\state;
  20  
  21  /**
  22   * The state store manager.
  23   *
  24   * @package    core_xapi
  25   * @since      Moodle 4.2
  26   * @copyright  2022 Ferran Recio <ferran@moodle.com>
  27   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  28   */
  29  class state_store {
  30  
  31      /** @var string component name in frankenstyle. */
  32      protected $component;
  33  
  34      /**
  35       * Constructor for a xAPI handler base class.
  36       *
  37       * @param string $component the component name
  38       */
  39      public function __construct(string $component) {
  40          $this->component = $component;
  41      }
  42  
  43      /**
  44       * Convert the xAPI activity ID into an item ID integer.
  45       *
  46       * @throws xapi_exception if the activity id is not numeric.
  47       * @param string $activityid the provided activity ID
  48       * @return int
  49       */
  50      protected function activity_id_to_item_id(string $activityid): int {
  51          if (!is_numeric($activityid)) {
  52              throw new xapi_exception('The state store can only store numeric activity IDs.');
  53          }
  54          return intval($activityid);
  55      }
  56  
  57      /**
  58       * Delete any extra state data stored in the database.
  59       *
  60       * This method will be called only if the state is accepted by validate_state.
  61       *
  62       * Plugins may override this method add extra clean up tasks to the deletion.
  63       *
  64       * @param state $state
  65       * @return bool if the state is removed
  66       */
  67      public function delete(state $state): bool {
  68          global $DB;
  69          $data = [
  70              'component' => $this->component,
  71              'userid' => $state->get_user()->id,
  72              'itemid' => $this->activity_id_to_item_id($state->get_activity_id()),
  73              'stateid' => $state->get_state_id(),
  74              'registration' => $state->get_registration(),
  75          ];
  76          return $DB->delete_records('xapi_states', $data);
  77      }
  78  
  79      /**
  80       * Get a state object from the database.
  81       *
  82       * This method will be called only if the state is accepted by validate_state.
  83       *
  84       * Plugins may override this method if they store some data in different tables.
  85       *
  86       * @param state $state
  87       * @return state|null the state
  88       */
  89      public function get(state $state): ?state {
  90          global $DB;
  91          $data = [
  92              'component' => $this->component,
  93              'userid' => $state->get_user()->id,
  94              'itemid' => $this->activity_id_to_item_id($state->get_activity_id()),
  95              'stateid' => $state->get_state_id(),
  96              'registration' => $state->get_registration(),
  97          ];
  98          $record = $DB->get_record('xapi_states', $data);
  99          if ($record) {
 100              $statedata = null;
 101              if ($record->statedata !== null) {
 102                  $statedata = json_decode($record->statedata, null, 512, JSON_THROW_ON_ERROR);
 103              }
 104              $state->set_state_data($statedata);
 105              return $state;
 106          }
 107  
 108          return null;
 109      }
 110  
 111      /**
 112       * Inserts an state object into the database.
 113       *
 114       * This method will be called only if the state is accepted by validate_state.
 115       *
 116       * Plugins may override this method if they store some data in different tables.
 117       *
 118       * @param state $state
 119       * @return bool if the state is inserted/updated
 120       */
 121      public function put(state $state): bool {
 122          global $DB;
 123          $data = [
 124              'component' => $this->component,
 125              'userid' => $state->get_user()->id,
 126              'itemid' => $this->activity_id_to_item_id($state->get_activity_id()),
 127              'stateid' => $state->get_state_id(),
 128              'registration' => $state->get_registration(),
 129          ];
 130          $record = $DB->get_record('xapi_states', $data) ?: (object) $data;
 131          if (isset($record->id)) {
 132              $record->statedata = json_encode($state->jsonSerialize());
 133              $record->timemodified = time();
 134              $result = $DB->update_record('xapi_states', $record);
 135          } else {
 136              $data['statedata'] = json_encode($state->jsonSerialize());
 137              $data['timecreated'] = time();
 138              $data['timemodified'] = $data['timecreated'];
 139              $result = $DB->insert_record('xapi_states', $data);
 140          }
 141          return $result ? true : false;
 142      }
 143  
 144      /**
 145       * Reset all states from the component.
 146       * The given parameters are filters to decide the states to reset. If no parameters are defined, the only filter applied
 147       * will be the component.
 148       *
 149       * Plugins may override this method if they store some data in different tables.
 150       *
 151       * @param string|null $itemid
 152       * @param int|null $userid
 153       * @param string|null $stateid
 154       * @param string|null $registration
 155       */
 156      public function reset(
 157          ?string $itemid = null,
 158          ?int $userid = null,
 159          ?string $stateid = null,
 160          ?string $registration = null
 161      ): void {
 162          global $DB;
 163  
 164          $data = [
 165              'component' => $this->component,
 166          ];
 167          if ($itemid) {
 168              $data['itemid'] = $this->activity_id_to_item_id($itemid);
 169          }
 170          if ($userid) {
 171              $data['userid'] = $userid;
 172          }
 173          if ($stateid) {
 174              $data['stateid'] = $stateid;
 175          }
 176          if ($registration) {
 177              $data['registration'] = $registration;
 178          }
 179          $DB->set_field('xapi_states', 'statedata', null, $data);
 180      }
 181  
 182      /**
 183       * Remove all states from the component
 184       * The given parameters are filters to decide the states to wipe. If no parameters are defined, the only filter applied
 185       * will be the component.
 186       *
 187       * Plugins may override this method if they store some data in different tables.
 188       *
 189       * @param string|null $itemid
 190       * @param int|null $userid
 191       * @param string|null $stateid
 192       * @param string|null $registration
 193       */
 194      public function wipe(
 195          ?string $itemid = null,
 196          ?int $userid = null,
 197          ?string $stateid = null,
 198          ?string $registration = null
 199      ): void {
 200          global $DB;
 201          $data = [
 202              'component' => $this->component,
 203          ];
 204          if ($itemid) {
 205              $data['itemid'] = $this->activity_id_to_item_id($itemid);
 206          }
 207          if ($userid) {
 208              $data['userid'] = $userid;
 209          }
 210          if ($stateid) {
 211              $data['stateid'] = $stateid;
 212          }
 213          if ($registration) {
 214              $data['registration'] = $registration;
 215          }
 216          $DB->delete_records('xapi_states', $data);
 217      }
 218  
 219      /**
 220       * Get all state ids from a specific activity and agent.
 221       *
 222       * Plugins may override this method if they store some data in different tables.
 223       *
 224       * @param string|null $itemid
 225       * @param int|null $userid
 226       * @param string|null $registration
 227       * @param int|null $since filter ids updated since a specific timestamp
 228       * @return string[] the state ids values
 229       */
 230      public function get_state_ids(
 231          ?string $itemid = null,
 232          ?int $userid = null,
 233          ?string $registration = null,
 234          ?int $since = null,
 235      ): array {
 236          global $DB;
 237          $select = 'component = :component';
 238          $params = [
 239              'component' => $this->component,
 240          ];
 241          if ($itemid) {
 242              $select .= ' AND itemid = :itemid';
 243              $params['itemid'] = $this->activity_id_to_item_id($itemid);
 244          }
 245          if ($userid) {
 246              $select .= ' AND userid = :userid';
 247              $params['userid'] = $userid;
 248          }
 249          if ($registration) {
 250              $select .= ' AND registration = :registration';
 251              $params['registration'] = $registration;
 252          }
 253          if ($since) {
 254              $select .= ' AND timemodified > :since';
 255              $params['since'] = $since;
 256          }
 257          return $DB->get_fieldset_select('xapi_states', 'stateid', $select, $params, '');
 258      }
 259  
 260      /**
 261       * Execute a state store clean up.
 262       *
 263       * Plugins can override this methos to provide an alternative clean up logic.
 264       */
 265      public function cleanup(): void {
 266          global $DB;
 267          $xapicleanupperiod = get_config('core', 'xapicleanupperiod');
 268          if (empty($xapicleanupperiod)) {
 269              return;
 270          }
 271          $todelete = time() - $xapicleanupperiod;
 272          $DB->delete_records_select(
 273              'xapi_states',
 274              'component = :component AND timemodified < :todelete',
 275              ['component' => $this->component, 'todelete' => $todelete]
 276          );
 277      }
 278  }