. namespace core_xapi; use core_xapi\local\state; /** * The state store manager. * * @package core_xapi * @since Moodle 4.2 * @copyright 2022 Ferran Recio * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class state_store { /** @var string component name in frankenstyle. */ protected $component; /** * Constructor for a xAPI handler base class. * * @param string $component the component name */ public function __construct(string $component) { $this->component = $component; } /** * Delete any extra state data stored in the database. * * This method will be called only if the state is accepted by validate_state. * * Plugins may override this method add extra clean up tasks to the deletion. * * @param state $state * @return bool if the state is removed */ public function delete(state $state): bool { global $DB; $data = [ 'component' => $this->component, 'userid' => $state->get_user()->id, 'itemid' => $state->get_activity_id(), 'stateid' => $state->get_state_id(), 'registration' => $state->get_registration(), ]; return $DB->delete_records('xapi_states', $data); } /** * Get a state object from the database. * * This method will be called only if the state is accepted by validate_state. * * Plugins may override this method if they store some data in different tables. * * @param state $state * @return state|null the state */ public function get(state $state): ?state { global $DB; $data = [ 'component' => $this->component, 'userid' => $state->get_user()->id, 'itemid' => $state->get_activity_id(), 'stateid' => $state->get_state_id(), 'registration' => $state->get_registration(), ]; $record = $DB->get_record('xapi_states', $data); if ($record) { $statedata = null; if ($record->statedata !== null) { $statedata = json_decode($record->statedata, null, 512, JSON_THROW_ON_ERROR); } $state->set_state_data($statedata); return $state; } return null; } /** * Inserts an state object into the database. * * This method will be called only if the state is accepted by validate_state. * * Plugins may override this method if they store some data in different tables. * * @param state $state * @return bool if the state is inserted/updated */ public function put(state $state): bool { global $DB; $data = [ 'component' => $this->component, 'userid' => $state->get_user()->id, 'itemid' => $state->get_activity_id(), 'stateid' => $state->get_state_id(), 'registration' => $state->get_registration(), ]; $record = $DB->get_record('xapi_states', $data) ?: (object) $data; if (isset($record->id)) { $record->statedata = json_encode($state->jsonSerialize()); $record->timemodified = time(); $result = $DB->update_record('xapi_states', $record); } else { $data['statedata'] = json_encode($state->jsonSerialize()); $data['timecreated'] = time(); $data['timemodified'] = $data['timecreated']; $result = $DB->insert_record('xapi_states', $data); } return $result ? true : false; } /** * Reset all states from the component. * The given parameters are filters to decide the states to reset. If no parameters are defined, the only filter applied * will be the component. * * Plugins may override this method if they store some data in different tables. * * @param string|null $itemid * @param int|null $userid * @param string|null $stateid * @param string|null $registration */ public function reset( ?string $itemid = null, ?int $userid = null, ?string $stateid = null, ?string $registration = null ): void { global $DB; $data = [ 'component' => $this->component, ]; if ($itemid) { $data['itemid'] = $itemid; } if ($userid) { $data['userid'] = $userid; } if ($stateid) { $data['stateid'] = $stateid; } if ($registration) { $data['registration'] = $registration; } $DB->set_field('xapi_states', 'statedata', null, $data); } /** * Remove all states from the component * The given parameters are filters to decide the states to wipe. If no parameters are defined, the only filter applied * will be the component. * * Plugins may override this method if they store some data in different tables. * * @param string|null $itemid * @param int|null $userid * @param string|null $stateid * @param string|null $registration */ public function wipe( ?string $itemid = null, ?int $userid = null, ?string $stateid = null, ?string $registration = null ): void { global $DB; $data = [ 'component' => $this->component, ]; if ($itemid) { $data['itemid'] = $itemid; } if ($userid) { $data['userid'] = $userid; } if ($stateid) { $data['stateid'] = $stateid; } if ($registration) { $data['registration'] = $registration; } $DB->delete_records('xapi_states', $data); } /** * Get all state ids from a specific activity and agent. * * Plugins may override this method if they store some data in different tables. * * @param string|null $itemid * @param int|null $userid * @param string|null $registration * @param int|null $since filter ids updated since a specific timestamp * @return string[] the state ids values */ public function get_state_ids( ?string $itemid = null, ?int $userid = null, ?string $registration = null, ?int $since = null, ): array { global $DB; $select = 'component = :component'; $params = [ 'component' => $this->component, ]; if ($itemid) { $select .= ' AND itemid = :itemid'; $params['itemid'] = $itemid; } if ($userid) { $select .= ' AND userid = :userid'; $params['userid'] = $userid; } if ($registration) { $select .= ' AND registration = :registration'; $params['registration'] = $registration; } if ($since) { $select .= ' AND timemodified > :since'; $params['since'] = $since; } return $DB->get_fieldset_select('xapi_states', 'stateid', $select, $params, ''); } /** * Execute a state store clean up. * * Plugins can override this methos to provide an alternative clean up logic. */ public function cleanup(): void { global $DB; $xapicleanupperiod = get_config('core', 'xapicleanupperiod'); if (empty($xapicleanupperiod)) { return; } $todelete = time() - $xapicleanupperiod; $DB->delete_records_select( 'xapi_states', 'component = :component AND timemodified < :todelete', ['component' => $this->component, 'todelete' => $todelete] ); } }