first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+69
View File
@@ -0,0 +1,69 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_xapi;
/**
* The xAPI internal API.
*
* @package core_xapi
* @copyright 2023 Ferran Recio
* @since Moodle 4.2
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class api {
/**
* Delete all states from a component.
*
* @param string $component The component name in frankenstyle.
* @return void
*/
public static function remove_states_from_component(string $component): void {
global $DB;
$statestore = null;
$dbman = $DB->get_manager();
try {
$handler = handler::create($component);
$statestore = $handler->get_state_store();
} catch (xapi_exception $exception) {
// If the component is not available but the xapi_states table exists, use the standard one to ensure we clean it.
$table = new \xmldb_table('xapi_states');
if ($dbman->table_exists($table)) {
$statestore = new state_store($component);
}
}
if ($statestore) {
$statestore->wipe();
}
}
/**
* Execute the states clean up for all compatible components.
*
* @return void
*/
public static function execute_state_cleanup(): void {
foreach (\core_component::get_plugin_types() as $ptype => $unused) {
$components = \core_component::get_plugin_list_with_class($ptype, 'xapi\handler');
foreach ($components as $component => $unused) {
$handler = handler::create($component);
$statestore = $handler->get_state_store();
$statestore->cleanup();
}
}
}
}
+114
View File
@@ -0,0 +1,114 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_xapi\external;
use core_xapi\local\state;
use core_xapi\local\statement\item_activity;
use core_xapi\handler;
use core_xapi\xapi_exception;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_value;
use core_xapi\iri;
/**
* This is the external API for generic xAPI state deletion.
*
* @package core_xapi
* @since Moodle 4.2
* @copyright 2023 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class delete_state extends external_api {
use \core_xapi\local\helper\state_trait;
/**
* Parameters for execute.
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'component' => new external_value(PARAM_COMPONENT, 'Component name'),
'activityId' => new external_value(PARAM_URL, 'xAPI activity ID IRI'),
'agent' => new external_value(PARAM_RAW, 'The xAPI agent json'),
'stateId' => new external_value(PARAM_ALPHAEXT, 'The xAPI state ID'),
'registration' => new external_value(PARAM_ALPHANUMEXT, 'The xAPI registration UUID', VALUE_DEFAULT, null),
]);
}
/**
* Process a state delete request.
*
* @param string $component The component name in frankenstyle.
* @param string $activityiri The activity IRI.
* @param string $agent The agent JSON.
* @param string $stateid The xAPI state id.
* @param string|null $registration The xAPI registration UUID.
* @return bool Whether the state has been removed or not.
*/
public static function execute(
string $component,
string $activityiri,
string $agent,
string $stateid,
?string $registration = null
): bool {
$params = self::validate_parameters(self::execute_parameters(), [
'component' => $component,
'activityId' => $activityiri,
'agent' => $agent,
'stateId' => $stateid,
'registration' => $registration,
]);
[
'component' => $component,
'activityId' => $activityiri,
'agent' => $agent,
'stateId' => $stateid,
'registration' => $registration,
] = $params;
static::validate_component($component);
$handler = handler::create($component);
$activityid = iri::extract($activityiri, 'activity');
$state = new state(
self::get_agent_from_json($agent),
item_activity::create_from_id($activityid),
$stateid,
$registration,
null
);
if (!self::check_state_user($state)) {
throw new xapi_exception('State agent is not the current user');
}
return $handler->delete_state($state);
}
/**
* Return for execute.
*/
public static function execute_returns(): external_value {
return new external_value(PARAM_BOOL, 'If the state data is deleted');
}
}
+101
View File
@@ -0,0 +1,101 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_xapi\external;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
use core_xapi\handler;
use core_xapi\iri;
use core_xapi\xapi_exception;
/**
* This is the external API for generic xAPI states deletion.
*
* @package core_xapi
* @since Moodle 4.3
* @copyright 2023 Laurent David <laurent.david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class delete_states extends external_api {
use \core_xapi\local\helper\state_trait;
/**
* Process a state delete request.
*
* @param string $component The component name in frankenstyle.
* @param string $activityiri The activity IRI.
* @param string $agent The agent JSON.
* @param string|null $registration The xAPI registration UUID.
* @return void
*/
public static function execute(
string $component,
string $activityiri,
string $agent,
?string $registration = null,
): void {
global $USER;
[
'component' => $component,
'activityId' => $activityiri,
'agent' => $agent,
'registration' => $registration,
] = self::validate_parameters(self::execute_parameters(), [
'component' => $component,
'activityId' => $activityiri,
'agent' => $agent,
'registration' => $registration,
]);
static::validate_component($component);
$handler = handler::create($component);
$activityid = iri::extract($activityiri, 'activity');
$agent = self::get_agent_from_json($agent);
$user = $agent->get_user();
if ($user->id != $USER->id) {
throw new xapi_exception('State agent is not the current user');
}
$handler->wipe_states($activityid, $user->id, null, $registration);
}
/**
* Parameters for execute.
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'component' => new external_value(PARAM_COMPONENT, 'Component name'),
'activityId' => new external_value(PARAM_URL, 'xAPI activity ID IRI'),
'agent' => new external_value(PARAM_RAW, 'The xAPI agent json'),
'registration' => new external_value(PARAM_ALPHANUMEXT, 'The xAPI registration UUID', VALUE_DEFAULT, null)
]);
}
/**
* Return for execute.
*/
public static function execute_returns() {
return null;
}
}
+119
View File
@@ -0,0 +1,119 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_xapi\external;
use core_xapi\local\state;
use core_xapi\local\statement\item_activity;
use core_xapi\handler;
use core_xapi\xapi_exception;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_value;
use core_xapi\iri;
/**
* This is the external API for generic xAPI state get.
*
* @package core_xapi
* @since Moodle 4.2
* @copyright 2023 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class get_state extends external_api {
use \core_xapi\local\helper\state_trait;
/**
* Parameters for execute
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'component' => new external_value(PARAM_COMPONENT, 'Component name'),
'activityId' => new external_value(PARAM_URL, 'xAPI activity ID IRI'),
'agent' => new external_value(PARAM_RAW, 'The xAPI agent json'),
'stateId' => new external_value(PARAM_ALPHAEXT, 'The xAPI state ID'),
'registration' => new external_value(PARAM_ALPHANUMEXT, 'The xAPI registration UUID', VALUE_DEFAULT, null),
]);
}
/**
* Process a get state request.
*
* @param string $component The component name in frankenstyle.
* @param string $activityiri The activity IRI.
* @param string $agent The agent JSON.
* @param string $stateid The xAPI state id.
* @param string|null $registration The xAPI registration UUID.
* @return string|null
*/
public static function execute(
string $component,
string $activityiri,
string $agent,
string $stateid,
?string $registration = null
): ?string {
$params = self::validate_parameters(self::execute_parameters(), [
'component' => $component,
'activityId' => $activityiri,
'agent' => $agent,
'stateId' => $stateid,
'registration' => $registration,
]);
[
'component' => $component,
'activityId' => $activityiri,
'agent' => $agent,
'stateId' => $stateid,
'registration' => $registration,
] = $params;
static::validate_component($component);
$handler = handler::create($component);
$activityid = iri::extract($activityiri, 'activity');
$state = new state(
self::get_agent_from_json($agent),
item_activity::create_from_id($activityid),
$stateid,
null,
$registration
);
if (!self::check_state_user($state)) {
throw new xapi_exception('State agent is not the current user');
}
$result = $handler->load_state($state);
if ($result !== null) {
return json_encode($result);
}
return $result;
}
/**
* Return for execute.
*/
public static function execute_returns(): external_value {
return new external_value(PARAM_RAW, 'The state data json');
}
}
+147
View File
@@ -0,0 +1,147 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_xapi\external;
use core_xapi\handler;
use core_xapi\xapi_exception;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_multiple_structure;
use core_external\external_value;
use core_xapi\iri;
use core_xapi\local\statement\item_agent;
/**
* This is the external API for generic xAPI get all states ids.
*
* @package core_xapi
* @since Moodle 4.2
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class get_states extends external_api {
use \core_xapi\local\helper\state_trait;
/**
* Parameters for execute
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'component' => new external_value(PARAM_COMPONENT, 'Component name'),
'activityId' => new external_value(PARAM_URL, 'xAPI activity ID IRI'),
'agent' => new external_value(PARAM_RAW, 'The xAPI agent json'),
'registration' => new external_value(PARAM_ALPHANUMEXT, 'The xAPI registration UUID', VALUE_DEFAULT, null),
'since' => new external_value(PARAM_TEXT, 'Filter ids stored since the timestamp (exclusive)', VALUE_DEFAULT, null),
]);
}
/**
* Process a get states request.
*
* @param string $component The component name in frankenstyle.
* @param string $activityiri The activity IRI.
* @param string $agent The agent JSON.
* @param string|null $registration The xAPI registration UUID.
* @param string|null $since A ISO 8601 timestamps or a numeric timestamp.
* @return array the list of the stored state ids
*/
public static function execute(
string $component,
string $activityiri,
string $agent,
?string $registration = null,
?string $since = null
): array {
global $USER;
[
'component' => $component,
'activityId' => $activityiri,
'agent' => $agent,
'registration' => $registration,
'since' => $since,
] = self::validate_parameters(self::execute_parameters(), [
'component' => $component,
'activityId' => $activityiri,
'agent' => $agent,
'registration' => $registration,
'since' => $since,
]);
static::validate_component($component);
$handler = handler::create($component);
$agent = self::get_agent_from_json($agent);
$user = $agent->get_user();
if ($user->id !== $USER->id) {
throw new xapi_exception('State agent is not the current user');
}
$activityid = iri::extract($activityiri, 'activity');
$createdsince = self::convert_since_param_to_timestamp($since);
$store = $handler->get_state_store();
return $store->get_state_ids(
$activityid,
$user->id,
$registration,
$createdsince
);
}
/**
* Convert the xAPI since param into a Moodle integer timestamp.
*
* According to xAPI standard, the "since" param must follow the ISO 8601
* format. However, because Moodle do not use this format, we accept both
* numeric timestamp and ISO 8601.
*
* @param string|null $since A ISO 8601 timestamps or a numeric timestamp.
* @return null|int the resulting timestamp or null if since is null.
*/
private static function convert_since_param_to_timestamp(?string $since): ?int {
if ($since === null) {
return null;
}
if (is_numeric($since)) {
return intval($since);
}
try {
$datetime = new \DateTime($since);
return $datetime->getTimestamp();
} catch (\Exception $exception) {
throw new xapi_exception("Since param '$since' is not in ISO 8601 or a numeric timestamp format");
}
}
/**
* Return for execute.
*
* @return external_multiple_structure
*/
public static function execute_returns(): external_multiple_structure {
return new external_multiple_structure(
new external_value(PARAM_RAW, 'State ID'),
'List of state Ids'
);
}
}
+119
View File
@@ -0,0 +1,119 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_xapi\external;
use core_xapi\local\state;
use core_xapi\local\statement\item_activity;
use core_xapi\handler;
use core_xapi\xapi_exception;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_value;
use core_xapi\iri;
/**
* This is the external API for generic xAPI state post.
*
* @package core_xapi
* @since Moodle 4.2
* @copyright 2023 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class post_state extends external_api {
use \core_xapi\local\helper\state_trait;
/**
* Parameters for execute
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'component' => new external_value(PARAM_COMPONENT, 'Component name'),
'activityId' => new external_value(PARAM_URL, 'xAPI activity ID IRI'),
'agent' => new external_value(PARAM_RAW, 'The xAPI agent json'),
'stateId' => new external_value(PARAM_ALPHAEXT, 'The xAPI state ID'),
'stateData' => new external_value(PARAM_RAW, 'JSON object with the state data'),
'registration' => new external_value(PARAM_ALPHANUMEXT, 'The xAPI registration UUID', VALUE_DEFAULT, null),
]);
}
/**
* Process a state post request.
*
* @param string $component The component name in frankenstyle.
* @param string $activityiri The activity IRI.
* @param string $agent The agent JSON.
* @param string $stateid The xAPI state id.
* @param string $statedata JSON object with the state data
* @param string|null $registration The xAPI registration UUID.
* @return bool
*/
public static function execute(
string $component,
string $activityiri,
string $agent,
string $stateid,
string $statedata,
?string $registration = null
): bool {
$params = self::validate_parameters(self::execute_parameters(), [
'component' => $component,
'activityId' => $activityiri,
'agent' => $agent,
'stateId' => $stateid,
'stateData' => $statedata,
'registration' => $registration,
]);
[
'component' => $component,
'activityId' => $activityiri,
'agent' => $agent,
'stateId' => $stateid,
'stateData' => $statedata,
'registration' => $registration,
] = $params;
static::validate_component($component);
$handler = handler::create($component);
$activityid = iri::extract($activityiri, 'activity');
$state = new state(
self::get_agent_from_json($agent),
item_activity::create_from_id($activityid),
$stateid,
self::get_statedata_from_json($statedata),
$registration
);
if (!self::check_state_user($state)) {
throw new xapi_exception('State agent is not the current user');
}
return $handler->save_state($state);
}
/**
* Return for execute.
*/
public static function execute_returns(): external_value {
return new external_value(PARAM_BOOL, 'If the state is accepted');
}
}
+165
View File
@@ -0,0 +1,165 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_xapi\external;
use core_xapi\local\statement;
use core_xapi\handler;
use core_xapi\xapi_exception;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_multiple_structure;
use core_external\external_value;
use core_component;
/**
* This is the external API for generic xAPI handling.
*
* @package core_xapi
* @since Moodle 3.9
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class post_statement extends external_api {
/**
* Parameters for execute
*
* @return external_function_parameters
*/
public static function execute_parameters() {
return new external_function_parameters(
[
'component' => new external_value(PARAM_COMPONENT, 'Component name', VALUE_REQUIRED),
'requestjson' => new external_value(PARAM_RAW, 'json object with all the statements to post', VALUE_REQUIRED)
]
);
}
/**
* Process a statement post request.
*
* @param string $component component name (frankenstyle)
* @param string $requestjson json object with all the statements to post
* @return bool[] storing acceptance of every statement
*/
public static function execute(string $component, string $requestjson): array {
$params = self::validate_parameters(self::execute_parameters(), array(
'component' => $component,
'requestjson' => $requestjson,
));
$component = $params['component'];
$requestjson = $params['requestjson'];
static::validate_component($component);
$handler = handler::create($component);
$statements = self::get_statements_from_json($requestjson);
if (!self::check_statements_users($statements, $handler)) {
throw new xapi_exception('Statements actor is not the current user');
}
$result = $handler->process_statements($statements);
// In case no statement is processed, an error must be returned.
if (count(array_filter($result)) == 0) {
throw new xapi_exception('No statement can be processed.');
}
return $result;
}
/**
* Return for execute.
*/
public static function execute_returns() {
return new external_multiple_structure(
new external_value(PARAM_BOOL, 'If the statement is accepted'),
'List of statements storing acceptance results'
);
}
/**
* Check component name.
*
* Note: this function is separated mainly for testing purposes to
* be overridden to fake components.
*
* @throws xapi_exception if component is not available
* @param string $component component name
*/
protected static function validate_component(string $component): void {
// Check that $component is a real component name.
$dir = core_component::get_component_directory($component);
if (!$dir) {
throw new xapi_exception("Component $component not available.");
}
}
/**
* Convert mulitple types of statement request into an array of statements.
*
* @throws xapi_exception if JSON cannot be parsed
* @param string $requestjson json encoded statements structure
* @return statement[] array of statements
*/
private static function get_statements_from_json(string $requestjson): array {
$request = json_decode($requestjson);
if ($request === null) {
throw new xapi_exception('JSON error: '.json_last_error_msg());
}
$result = [];
if (is_array($request)) {
foreach ($request as $data) {
$result[] = statement::create_from_data($data);
}
} else {
$result[] = statement::create_from_data($request);
}
if (empty($result)) {
throw new xapi_exception('No statements detected');
}
return $result;
}
/**
* Check that $USER is actor in all statements.
*
* @param statement[] $statements array of statements
* @param handler $handler specific xAPI handler
* @return bool if $USER is actor in all statements
*/
private static function check_statements_users(array $statements, handler $handler): bool {
global $USER;
foreach ($statements as $statement) {
if ($handler->supports_group_actors()) {
$users = $statement->get_all_users();
if (!isset($users[$USER->id])) {
return false;
}
} else {
$user = $statement->get_user();
if ($user->id != $USER->id) {
return false;
}
}
}
return true;
}
}
+224
View File
@@ -0,0 +1,224 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_xapi;
use core_xapi\local\state;
use core_xapi\local\statement;
use core_xapi\xapi_exception;
/**
* Class handler handles basic xAPI statements and states.
*
* @package core_xapi
* @since Moodle 3.9
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class handler {
/** @var string component name in frankenstyle. */
protected $component;
/** @var state_store the state_store instance. */
protected $statestore;
/**
* Constructor for a xAPI handler base class.
*
* @param string $component the component name
*/
final protected function __construct(string $component) {
$this->component = $component;
$this->statestore = $this->get_state_store();
}
/**
* Returns the xAPI handler of a specific component.
*
* @param string $component the component name in frankenstyle.
* @return handler|null a handler object or null if none found.
* @throws xapi_exception
*/
final public static function create(string $component): self {
if (self::supports_xapi($component)) {
$classname = "\\$component\\xapi\\handler";
return new $classname($component);
}
throw new xapi_exception('Unknown handler');
}
/**
* Whether a component supports (and implements) xAPI.
*
* @param string $component the component name in frankenstyle.
* @return bool true if the given component implements xAPI handler; false otherwise.
*/
final public static function supports_xapi(string $component): bool {
$classname = "\\$component\\xapi\\handler";
return class_exists($classname);
}
/**
* Convert a statement object into a Moodle xAPI Event.
*
* If a statement is accepted by validate_statement the component must provide a event
* to handle that statement, otherwise the statement will be rejected.
*
* Note: this method must be overridden by the plugins which want to use xAPI.
*
* @param statement $statement
* @return \core\event\base|null a Moodle event to trigger
*/
abstract public function statement_to_event(statement $statement): ?\core\event\base;
/**
* Return true if group actor is enabled.
*
* Note: this method must be overridden by the plugins which want to
* use groups in statements.
*
* @return bool
*/
public function supports_group_actors(): bool {
return false;
}
/**
* Process a bunch of statements sended to a specific component.
*
* @param statement[] $statements an array with all statement to process.
* @return int[] return an specifying what statements are being stored.
*/
public function process_statements(array $statements): array {
$result = [];
foreach ($statements as $key => $statement) {
try {
// Ask the plugin to convert into an event.
$event = $this->statement_to_event($statement);
if ($event) {
$event->trigger();
$result[$key] = true;
} else {
$result[$key] = false;
}
} catch (\Exception $e) {
$result[$key] = false;
}
}
return $result;
}
/**
* Validate a xAPI state.
*
* Check if the state is valid for this handler.
*
* This method is used also for the state get requests so the validation
* cannot rely on having state data.
*
* Note: this method must be overridden by the plugins which want to use xAPI states.
*
* @param state $state
* @return bool if the state is valid or not
*/
abstract protected function validate_state(state $state): bool;
/**
* Process a state save request.
*
* @param state $state the state object
* @return bool if the state can be saved
*/
public function save_state(state $state): bool {
if (!$this->validate_state($state)) {
throw new xapi_exception('The state is not accepted, so it cannot be saved');
}
return $this->statestore->put($state);
}
/**
* Process a state save request.
*
* @param state $state the state object
* @return state|null the resulting loaded state
*/
public function load_state(state $state): ?state {
if (!$this->validate_state($state)) {
throw new xapi_exception('The state is not accepted, so it cannot be loaded');
}
$state = $this->statestore->get($state);
return $state;
}
/**
* Process a state delete request.
*
* @param state $state the state object
* @return bool if the deletion is successful
*/
public function delete_state(state $state): bool {
if (!$this->validate_state($state)) {
throw new xapi_exception('The state is not accepted, so it cannot be deleted');
}
return $this->statestore->delete($state);
}
/**
* Delete all states from this component.
*
* @param string|null $itemid
* @param int|null $userid
* @param string|null $stateid
* @param string|null $registration
*/
public function wipe_states(
?string $itemid = null,
?int $userid = null,
?string $stateid = null,
?string $registration = null
): void {
$this->statestore->wipe($itemid, $userid, $stateid, $registration);
}
/**
* Reset all states from this component.
*
* @param string|null $itemid
* @param int|null $userid
* @param string|null $stateid
* @param string|null $registration
*/
public function reset_states(
?string $itemid = null,
?int $userid = null,
?string $stateid = null,
?string $registration = null
): void {
$this->statestore->reset($itemid, $userid, $stateid, $registration);
}
/**
* Return a valor state store for this component.
*
* Plugins may override this method is they want to use a different
* state store class.
* @return state_store the store to use to get/put/delete states.
*/
public function get_state_store(): state_store {
return new state_store($this->component);
}
}
+93
View File
@@ -0,0 +1,93 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* xAPI LRS IRI values generator.
*
* @package core_xapi
* @since Moodle 3.9
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_xapi;
defined('MOODLE_INTERNAL') || die();
use stdClass;
use moodle_url;
/**
* Class to translate Moodle objects to xAPI elements.
*
* @copyright 2020 Ferran Recio
* @since Moodle 3.9
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class iri {
/**
* Generate a valid IRI element from a $value and an optional $type.
*
* Verbs and Objects in xAPI are in IRI format. This function could get
* a valid IRI value (and will return without modifiyng it) or a simple
* string and a type and generate a fake IRI valir for any xAPI statement.
*
* @param string $value a valid IRI value or any string
* @param string|null $type if none passed $type will be 'element'
* @return string a valid IRI value
*/
public static function generate(string $value, string $type = null): string {
if (self::check($value)) {
return $value;
}
if (empty($type)) {
$type = 'element';
}
return (new moodle_url("/xapi/$type/$value"))->out(false);
}
/**
* Try to extract the original value from an IRI.
*
* If a real IRI value is passed, it will return it without any change. If a
* fake IRI is passed (generated by iri::generate)
* it will try to extract the original value.
*
* @param string $value the currewnt IRI value.
* @param string|null $type if $value is a fake IRI, the $type must be provided.
* @return string the original value used in iri::generate.
*/
public static function extract(string $value, string $type = null): string {
if (empty($type)) {
$type = 'element';
}
$xapibase = (new moodle_url("/xapi/$type/"))->out(false);
if (strpos($value, $xapibase) === 0) {
return substr($value, strlen($xapibase));
}
return $value;
}
/**
* Check if a $value could be a valid IRI or not.
*
* @param string $value the currewnt IRI value.
* @return bool if the $value could be an IRI.
*/
public static function check(string $value): bool {
$iri = new moodle_url($value);
return in_array($iri->get_scheme(), ['http', 'https']);
}
}
@@ -0,0 +1,99 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_xapi\local\helper;
use core_component;
use core_xapi\local\state;
use core_xapi\local\statement\item_agent;
use core_xapi\xapi_exception;
use JsonException;
use stdClass;
/**
* State trait helper, with common methods.
*
* @package core_xapi
* @since Moodle 4.2
* @copyright 2023 Sara Arjona (sara@moodle.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
trait state_trait {
/**
* Check component name.
*
* Note: this function is separated mainly for testing purposes to
* be overridden to fake components.
*
* @throws xapi_exception if component is not available
* @param string $component component name
*/
protected static function validate_component(string $component): void {
// Check that $component is a real component name.
$dir = core_component::get_component_directory($component);
if (!$dir) {
throw new xapi_exception("Component $component not available.");
}
}
/**
* Convert a JSON agent into a valid item_agent.
*
* @throws xapi_exception if JSON cannot be parsed
* @param string $agentjson JSON encoded agent structure
* @return item_agent the agent
*/
private static function get_agent_from_json(string $agentjson): item_agent {
try {
$agentdata = json_decode($agentjson, null, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
throw new xapi_exception('No agent detected');
}
return item_agent::create_from_data($agentdata);
}
/**
* Check that $USER is actor in state.
*
* @param state $state The state
* @return bool if $USER is actor of the state
*/
private static function check_state_user(state $state): bool {
global $USER;
$user = $state->get_user();
if ($user->id != $USER->id) {
return false;
}
return true;
}
/**
* Convert the state data JSON into valid object.
*
* @throws xapi_exception if JSON cannot be parsed
* @param string $statedatajson JSON encoded structure
* @return stdClass the state data structure
*/
private static function get_statedata_from_json(string $statedatajson): stdClass {
try {
// Force it to be an object, because some statedata might be sent as array instead of JSON.
$statedata = json_decode($statedatajson, false, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
throw new xapi_exception('Invalid state data format');
}
return $statedata;
}
}
+197
View File
@@ -0,0 +1,197 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_xapi\local;
use core_xapi\local\statement\item_agent;
use core_xapi\local\statement\item_activity;
use JsonSerializable;
use stdClass;
/**
* State resource object for xAPI structure checking and validation.
*
* @package core_xapi
* @since Moodle 4.2
* @copyright 2023 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class state implements JsonSerializable {
/** @var item_agent The state agent (user). */
protected $agent = null;
/** @var item_activity The state activity owner (the plugin instance). */
protected $activity = null;
/** @var string The state identifier. */
protected $stateid = null;
/** @var stdClass|null The state data. */
protected $statedata = null;
/** @var string|null The state registration. */
protected $registration = null;
/**
* State constructor.
*
* @param item_agent $agent The state agent (user)
* @param item_activity $activity The state activity owner
* @param string $stateid The state identifier
* @param stdClass|null $statedata The state data
* @param string|null $registration The state registration
*/
public function __construct(
item_agent $agent,
item_activity $activity,
string $stateid,
?stdClass $statedata,
?string $registration
) {
$this->agent = $agent;
$this->activity = $activity;
$this->stateid = $stateid;
$this->statedata = $statedata;
$this->registration = $registration;
}
/**
* Return the data to serialize in case JSON state when needed.
*
* @return stdClass The state data structure. If statedata is null, this method will return an empty class.
*/
public function jsonSerialize(): stdClass {
if ($this->statedata) {
return $this->statedata;
}
return new stdClass();
}
/**
* Return the record data of this state.
*
* @return stdClass the record data structure
*/
public function get_record_data(): stdClass {
$result = (object) [
'userid' => $this->get_user()->id,
'itemid' => $this->get_activity_id(),
'stateid' => $this->stateid,
'statedata' => json_encode($this),
'registration' => $this->registration,
];
return $result;
}
/**
* Returns a minified version of a given state.
*
* The returned structure is suitable to store in the "other" field
* of logstore. xAPI standard specifies a list of attributes that can be calculated
* instead of stored literally. This function get rid of these attributes.
*
* Note: it also converts stdClass to assoc array to make it compatible
* with "other" field in the logstore
*
* @return array the minimal state needed to be stored a part from logstore data
*/
public function minify(): ?array {
$result = [];
$fields = ['activity', 'stateid', 'statedata', 'registration'];
foreach ($fields as $field) {
if (!empty($this->$field)) {
$result[$field] = $this->$field;
}
}
return json_decode(json_encode($result), true);
}
/**
* Set the state data.
*
* @param stdClass|null $statedata the state data
*/
public function set_state_data(?stdClass $statedata): void {
$this->statedata = $statedata;
}
/**
* Returns the state data.
* For getting the JSON representation of this state data, use jsonSerialize().
*
* @return stdClass|null The state data object.
*/
public function get_state_data(): ?stdClass {
return $this->statedata;
}
/**
* Returns the moodle user represented by this state agent.
*
* @return stdClass user record
*/
public function get_user(): stdClass {
return $this->agent->get_user();
}
/**
* Returns the state activity ID.
*
* @return string activity ID
*/
public function get_activity_id(): string {
return $this->activity->get_id();
}
/**
* Return the state agent.
*
* @return item_agent
*/
public function get_agent(): item_agent {
return $this->agent;
}
/**
* Return the state object if it is defined.
*
* @return item_activity|null
*/
public function get_activity(): ?item_activity {
return $this->activity;
}
/**
* Returns the state id.
*
* @return string state identifier
*/
public function get_state_id(): string {
return $this->stateid;
}
/**
* Returns the state registration if any.
*
* @return string|null state registration
*/
public function get_registration(): ?string {
return $this->registration;
}
}
+440
View File
@@ -0,0 +1,440 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Statement base object for xAPI structure checking and validation.
*
* @package core_xapi
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_xapi\local;
use core_xapi\local\statement\item;
use core_xapi\local\statement\item_actor;
use core_xapi\local\statement\item_object;
use core_xapi\local\statement\item_verb;
use core_xapi\local\statement\item_result;
use core_xapi\local\statement\item_attachment;
use core_xapi\local\statement\item_context;
use core_xapi\xapi_exception;
use JsonSerializable;
use stdClass;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for core_xapi implementing null_provider.
*
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class statement implements JsonSerializable {
/** @var item_actor The statement actor. */
protected $actor = null;
/** @var item_verb The statement verb. */
protected $verb = null;
/** @var item_object The statement object. */
protected $object = null;
/** @var item_result The statement result. */
protected $result = null;
/** @var item_context The statement context. */
protected $context = null;
/** @var string The statement timestamp. */
protected $timestamp = null;
/** @var string The statement stored. */
protected $stored = null;
/** @var item_actor The statement authority. */
protected $authority = null;
/** @var string The statement version. */
protected $version = null;
/** @var item_attachment[] The statement attachments. */
protected $attachments = null;
/** @var additionalfields list of additional fields. */
private static $additionalsfields = [
'timestamp', 'stored', 'version'
];
/**
* Function to create a full statement from xAPI statement data.
*
* @param stdClass $data the original xAPI statement
* @return statement statement object
*/
public static function create_from_data(stdClass $data): self {
$result = new self();
$requiredfields = ['actor', 'verb', 'object'];
foreach ($requiredfields as $required) {
if (!isset($data->$required)) {
throw new xapi_exception("Missing '{$required}'");
}
}
$result->set_actor(item_actor::create_from_data($data->actor));
$result->set_verb(item_verb::create_from_data($data->verb));
$result->set_object(item_object::create_from_data($data->object));
if (isset($data->result)) {
$result->set_result(item_result::create_from_data($data->result));
}
if (!empty($data->attachments)) {
if (!is_array($data->attachments)) {
throw new xapi_exception("Attachments must be an array");
}
foreach ($data->attachments as $attachment) {
$result->add_attachment(item_attachment::create_from_data($attachment));
}
}
if (isset($data->context)) {
$result->set_context(item_context::create_from_data($data->context));
}
if (isset($data->authority)) {
$result->set_authority(item_actor::create_from_data($data->authority));
}
// Store other generic xAPI statement fields.
foreach (self::$additionalsfields as $additional) {
if (isset($data->$additional)) {
$method = 'set_'.$additional;
$result->$method($data->$additional);
}
}
return $result;
}
/**
* Return the data to serialize in case JSON statement is needed.
*
* @return stdClass the statement data structure
*/
public function jsonSerialize(): stdClass {
$result = (object) [
'actor' => $this->actor,
'verb' => $this->verb,
'object' => $this->object,
];
if (!empty($this->result)) {
$result->result = $this->result;
}
if (!empty($this->context)) {
$result->context = $this->context;
}
if (!empty($this->authority)) {
$result->authority = $this->authority;
}
if (!empty($this->attachments)) {
$result->attachments = $this->attachments;
}
foreach (self::$additionalsfields as $additional) {
if (!empty($this->$additional)) {
$result->$additional = $this->$additional;
}
}
return $result;
}
/**
* Returns a minified version of a given statement.
*
* The returned structure is suitable to store in the "other" field
* of logstore. xAPI standard specifies a list of attributes that can be calculated
* instead of stored literally. This function get rid of these attributes.
*
* Note: it also converts stdClass to assoc array to make it compatible
* with "other" field in the logstore
*
* @return array the minimal statement needed to be stored a part from logstore data
*/
public function minify(): ?array {
$result = [];
$fields = ['verb', 'object', 'context', 'result', 'authority', 'attachments'];
foreach ($fields as $field) {
if (!empty($this->$field)) {
$result[$field] = $this->$field;
}
}
return json_decode(json_encode($result), true);
}
/**
* Set the statement actor.
*
* @param item_actor $actor actor item
*/
public function set_actor(item_actor $actor): void {
$this->actor = $actor;
}
/**
* Set the statement verb.
*
* @param item_verb $verb verb element
*/
public function set_verb(item_verb $verb): void {
$this->verb = $verb;
}
/**
* Set the statement object.
*
* @param item_object $object compatible object item
*/
public function set_object(item_object $object): void {
$this->object = $object;
}
/**
* Set the statement context.
*
* @param item_context $context context item element
*/
public function set_context(item_context $context): void {
$this->context = $context;
}
/**
* Set the statement result.
*
* @param item_result $result result item element
*/
public function set_result(item_result $result): void {
$this->result = $result;
}
/**
* Set the statement timestamp.
*
* @param string $timestamp timestamp element
*/
public function set_timestamp(string $timestamp): void {
$this->timestamp = $timestamp;
}
/**
* Set the statement stored.
*
* @param string $stored stored element
*/
public function set_stored(string $stored): void {
$this->stored = $stored;
}
/**
* Set the statement authority.
*
* @param item $authority authority item element
*/
public function set_authority(item_actor $authority): void {
$this->authority = $authority;
}
/**
* Set the statement version.
*
* @param string $version version element
*/
public function set_version(string $version): void {
$this->version = $version;
}
/**
* Adds and attachment to the statement.
*
* @param item $attachments attachments item element
*/
public function add_attachment(item_attachment $attachment): void {
if ($this->attachments === null) {
$this->attachments = [];
}
$this->attachments[] = $attachment;
}
/**
* Returns the moodle user represented by this statement actor.
*
* @throws xapi_exception if it's a group statement
* @return stdClass user record
*/
public function get_user(): stdClass {
if (!$this->actor) {
throw new xapi_exception("No actor defined");
}
return $this->actor->get_user();
}
/**
* Return all moodle users represented by this statement actor.
*
* @return array user records
*/
public function get_all_users(): array {
if (!$this->actor) {
throw new xapi_exception("No actor defined");
}
return $this->actor->get_all_users();
}
/**
* Return the moodle group represented by this statement actor.
*
* @throws xapi_exception if it is not a group statement
* @return stdClass a group record
*/
public function get_group(): stdClass {
if (!$this->actor) {
throw new xapi_exception("No actor defined");
}
if (method_exists($this->actor, 'get_group')) {
return $this->actor->get_group();
}
throw new xapi_exception("Method not valid on this actor");
}
/**
* Returns the statement verb ID.
*
* @throws xapi_exception in case the item is no yet defined
* @return string verb ID
*/
public function get_verb_id(): string {
if (!$this->verb) {
throw new xapi_exception("No verb defined");
}
return $this->verb->get_id();
}
/**
* Returns the statement activity ID.
*
* @throws xapi_exception in case the item is no yet defined
* @return string activity ID
*/
public function get_activity_id(): string {
if (!$this->object) {
throw new xapi_exception("No object defined");
}
if (method_exists($this->object, 'get_id')) {
return $this->object->get_id();
}
throw new xapi_exception("Method not valid on this object");
}
/**
* Return the statement actor if it is defined.
*
* @return item_actor|null
*/
public function get_actor(): ?item_actor {
return $this->actor;
}
/**
* Return the statement verb if it is defined.
*
* @return item_verb|null
*/
public function get_verb(): ?item_verb {
return $this->verb;
}
/**
* Return the statement object if it is defined.
*
* @return item_object|null
*/
public function get_object(): ?item_object {
return $this->object;
}
/**
* Return the statement context if it is defined.
*
* @return item|null
*/
public function get_context(): ?item_context {
return $this->context;
}
/**
* Return the statement result if it is defined.
*
* @return item|null
*/
public function get_result(): ?item_result {
return $this->result;
}
/**
* Return the statement timestamp if it is defined.
*
* @return string|null
*/
public function get_timestamp(): ?string {
return $this->timestamp;
}
/**
* Return the statement stored if it is defined.
*
* @return string|null
*/
public function get_stored(): ?string {
return $this->stored;
}
/**
* Return the statement authority if it is defined.
*
* @return item_actor|null
*/
public function get_authority(): ?item_actor {
return $this->authority;
}
/**
* Return the statement version if it is defined.
*
* @return string|null
*/
public function get_version(): ?string {
return $this->version;
}
/**
* Return the statement attachments if it is defined.
*
* @return item_attachment[]|null
*/
public function get_attachments(): ?array {
return $this->attachments;
}
}
+79
View File
@@ -0,0 +1,79 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Statement base object for xAPI structure checking and usage.
*
* @package core_xapi
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_xapi\local\statement;
use stdClass;
use JsonSerializable;
use core_xapi\iri;
defined('MOODLE_INTERNAL') || die();
/**
* Item class used for xAPI statement elements without validation.
*
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class item implements JsonSerializable {
/** @var stdClass the item structure. */
protected $data;
/**
* Item constructor.
*
* @param stdClass $data from the specific xAPI element
*/
protected function __construct(stdClass $data) {
$this->data = $data;
}
/**
* Function to create an item from part of the xAPI statement.
*
* @param stdClass $data the original xAPI element
* @return item the xAPI item generated
*/
public static function create_from_data(stdClass $data): item {
return new self($data);
}
/**
* Return the data to serialize in case JSON statement is needed.
*
* @return stdClass the original data structure
*/
public function jsonSerialize(): stdClass {
return $this->get_data();
}
/**
* Return the original data from this item.
*
* @return stdClass the original data structure
*/
public function get_data(): stdClass {
return $this->data;
}
}
@@ -0,0 +1,129 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Statement activity object for xAPI structure checking and usage.
*
* @package core_xapi
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_xapi\local\statement;
use core_xapi\xapi_exception;
use core_xapi\iri;
use stdClass;
defined('MOODLE_INTERNAL') || die();
/**
* Class that implements a xAPI activity compatible with xAPI object.
*
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class item_activity extends item_object {
/** @var string Activity ID. */
protected $id;
/** @var item_definition Definition object. */
protected $definition;
/**
* Item activity constructor.
*
* An xAPI activity is mainly an IRI ID and an optional definition.
*
* @param stdClass $data from the specific xAPI element
* @param item_definition $definition option definition item
*/
protected function __construct(stdClass $data, item_definition $definition = null) {
parent::__construct($data);
$this->id = iri::extract($data->id, 'activity');
$this->definition = $definition;
}
/**
* Function to create an item from part of the xAPI statement.
*
* @param stdClass $data the original xAPI element
* @return item item_activity xAPI generated
*/
public static function create_from_data(stdClass $data): item {
if (!isset($data->objectType)) {
throw new xapi_exception('Missing activity objectType');
}
if ($data->objectType != 'Activity') {
throw new xapi_exception('Activity objectType must be "Activity"');
}
if (empty($data->id)) {
throw new xapi_exception("Missing Activity id");
}
if (!iri::check($data->id)) {
throw new xapi_exception("Activity id $data->id is not a valid IRI");
}
$definition = null;
if (!empty($data->definition)) {
$definition = item_definition::create_from_data($data->definition);
}
return new self($data, $definition);
}
/**
* Generate a valid item_activity from a simple ID string and an optional definition.
*
* @param string $id any string that will converted into a valid IRI
* @param item_definition|null $definition optional item_definition
* @return item_activity
*/
public static function create_from_id(string $id, item_definition $definition = null): item_activity {
$data = (object) [
'objectType' => 'Activity',
'id' => iri::generate($id, 'activity'),
];
if (!empty($definition)) {
$data->definition = $definition->get_data();
}
return new self($data, $definition);
}
/**
* Return the activity ID.
*
* If the ID was generated by iri::generate this function will return
* the iri:extract value.
*
* @return string the activity ID
*/
public function get_id(): string {
return $this->id;
}
/**
* Returns the item_definition of this item.
*
* @return item_definition|null the item definition if available
*/
public function get_definition(): ?item_definition {
return $this->definition;
}
}
@@ -0,0 +1,79 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Statement actor (user or group) object for xAPI structure checking and usage.
*
* @package core_xapi
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_xapi\local\statement;
use core_xapi\xapi_exception;
use stdClass;
defined('MOODLE_INTERNAL') || die();
/**
* Abstract xAPI actor class.
*
* This class extends from item_object instead of basic item
* because both actors (agent and group) could be used as
* statement actor or object.
*
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class item_actor extends item_object {
/**
* Function to create an actor from part of the xAPI statement.
*
* @param stdClass $data the original xAPI element
* @return item item_agent|item_grou|item_activity xAPI generated
*/
public static function create_from_data(stdClass $data): item {
if (!isset($data->objectType)) {
$data->objectType = 'Agent';
}
switch ($data->objectType) {
case 'Agent':
return item_agent::create_from_data($data);
break;
case 'Group':
return item_group::create_from_data($data);
break;
default:
throw new xapi_exception("Unknown Actor type '{$data->objectType}'");
}
}
/**
* Returns the moodle user represented by this item.
*
* @return stdClass user record
*/
abstract public function get_user(): stdClass;
/**
* Return all moodle users represented by this item.
*
* @return array user records
*/
abstract public function get_all_users(): array;
}
@@ -0,0 +1,142 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Statement agent (user) object for xAPI structure checking and usage.
*
* @package core_xapi
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_xapi\local\statement;
use core_xapi\xapi_exception;
use core_user;
use stdClass;
defined('MOODLE_INTERNAL') || die();
/**
* Agent xAPI statement element representing a Moodle user.
*
* Agents can be used either as actor or object in a statement.
*
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class item_agent extends item_actor {
/** @var stdClass The user record of this actor. */
protected $user;
/**
* Function to create an agent (user) from part of the xAPI statement.
*
* @param stdClass $data the original xAPI element
* @param stdClass $user user record
*/
protected function __construct(stdClass $data, stdClass $user) {
parent::__construct($data);
$this->user = $user;
}
/**
* Function to create an item from part of the xAPI statement.
*
* @param stdClass $data the original xAPI element
* @return item item_agentxAPI generated
*/
public static function create_from_data(stdClass $data): item {
global $CFG;
if (!isset($data->objectType)) {
throw new xapi_exception('Missing agent objectType');
}
if ($data->objectType != 'Agent') {
throw new xapi_exception("Agent objectType must be 'Agent'");
}
if (isset($data->account) && isset($data->mbox)) {
throw new xapi_exception("Agent cannot have more than one identifier");
}
$user = null;
if (!empty($data->account)) {
if ($data->account->homePage != $CFG->wwwroot) {
throw new xapi_exception("Invalid agent homePage '{$data->account->homePage}'");
}
if (!is_numeric($data->account->name)) {
throw new xapi_exception("Agent account name must be integer '{$data->account->name}' found");
}
$user = core_user::get_user($data->account->name);
if (empty($user)) {
throw new xapi_exception("Inexistent agent '{$data->account->name}'");
}
}
if (!empty($data->mbox)) {
$mbox = str_replace('mailto:', '', $data->mbox);
$user = core_user::get_user_by_email($mbox);
if (empty($user)) {
throw new xapi_exception("Inexistent agent '{$data->mbox}'");
}
}
if (empty($user)) {
throw new xapi_exception("Unsupported agent definition");
}
return new self($data, $user);
}
/**
* Create a item_agent from a existing user.
*
* @param stdClass $user A user record.
* @return item_agent
*/
public static function create_from_user(stdClass $user): item_agent {
global $CFG;
if (!isset($user->id)) {
throw new xapi_exception("Missing user id");
}
$data = (object) [
'objectType' => 'Agent',
'account' => (object) [
'homePage' => $CFG->wwwroot,
'name' => $user->id,
],
];
return new self($data, $user);
}
/**
* Returns the moodle user represented by this item.
*
* @return stdClass user record
*/
public function get_user(): stdClass {
return $this->user;
}
/**
* Return all users represented by this item.
*
* In this case the item is an agent so a single element array
* will be returned always.
*
* @return array list of users
*/
public function get_all_users(): array {
return [$this->user->id => $this->user];
}
}
@@ -0,0 +1,73 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Statement attachment for xAPI structure checking and usage.
*
* @package core_xapi
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_xapi\local\statement;
use core_xapi\xapi_exception;
use core_xapi\iri;
use stdClass;
/**
* Abstract xAPI attachment class.
*
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class item_attachment extends item {
/**
* Function to create an item from part of the xAPI statement.
*
* @param stdClass $data the original xAPI element
* @return item item_attachment xAPI generated
*/
public static function create_from_data(stdClass $data): item {
if (empty($data->usageType)) {
throw new xapi_exception("missing attachment usageType");
}
if (!iri::check($data->usageType)) {
throw new xapi_exception("attachment usageType $data->usageType is not a valid IRI");
}
if (empty($data->display)) {
throw new xapi_exception("missing attachment display");
}
if (empty($data->contentType)) {
throw new xapi_exception("missing attachment contentType");
}
if (empty($data->length)) {
throw new xapi_exception("missing attachment length");
}
if (!is_numeric($data->length)) {
throw new xapi_exception("invalid attachment length format");
}
if (empty($data->sha2)) {
throw new xapi_exception("missing attachment sha2");
}
// More required property checks will appear here in the future.
return new self($data);
}
}
@@ -0,0 +1,49 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Statement context for xAPI structure checking and usage.
*
* @package core_xapi
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_xapi\local\statement;
use stdClass;
/**
* Abstract xAPI context class.
*
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class item_context extends item {
/**
* Function to create an item from part of the xAPI statement.
*
* @param stdClass $data the original xAPI element
* @return item item_context xAPI generated
*/
public static function create_from_data(stdClass $data): item {
// Required property checks will appear here in the future.
return new self($data);
}
}
@@ -0,0 +1,93 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Statement definition object for xAPI structure checking and usage.
*
* @package core_xapi
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_xapi\local\statement;
use core_xapi\xapi_exception;
use core_xapi\iri;
use stdClass;
defined('MOODLE_INTERNAL') || die();
/**
* Validation and usage of xAPI definition.
*
* Definition contains extra information about user interaction with
* questions and other activities inside a xAPI statement. For now
* it performs a basic validation on the provided data.
*
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class item_definition extends item {
/** @var string The statement. */
protected $interactiontype;
/**
* Function to create a definition from part of the xAPI statement.
*
* @param stdClass $data the original xAPI element.
*/
protected function __construct(stdClass $data) {
parent::__construct($data);
$this->interactiontype = $data->interactionType ?? null;
}
/**
* Function to create an item from part of the xAPI statement.
*
* @param stdClass $data the original xAPI element
* @return item item_definition xAPI generated
*/
public static function create_from_data(stdClass $data): item {
// Interaction Type is a optopnal param.
if (!empty($data->interactionType)) {
$posiblevalues = [
'choice' => true,
'fill-in' => true,
'long-fill-in' => true,
'true-false' => true,
'matching' => true,
'performance' => true,
'sequencing' => true,
'likert' => true,
'numeric' => true,
'other' => true,
'compound' => true,
];
if (!isset($posiblevalues[$data->interactionType])) {
throw new xapi_exception("Invalid definition \"{$data->interactionType}\"");
}
}
return new self($data);
}
/**
* Return the definition interaction type.
*/
public function get_interactiontype(): ?string {
return $this->interactiontype;
}
}
@@ -0,0 +1,147 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Statement group object for xAPI structure checking and usage.
*
* @package core_xapi
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_xapi\local\statement;
use core_xapi\xapi_exception;
use stdClass;
defined('MOODLE_INTERNAL') || die();
/**
* Group item inside a xAPI statement.
*
* Only named groups are accepted (all groups must be real groups in the
* platform) so anonymous groups will be rejected on creation. Groups can
* be used as actor or as object inside a xAPI statement.
*
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class item_group extends item_actor {
/** @var array */
protected $users;
/** @var stdClass */
protected $group;
/**
* Function to create an group from part of the xAPI statement.
*
* @param stdClass $data the original xAPI element
* @param stdClass $group group record
*/
protected function __construct(stdClass $data, stdClass $group) {
parent::__construct($data);
$this->group = $group;
$this->users = groups_get_members($group->id);
if (!$this->users) {
$this->users = [];
}
}
/**
* Function to create an item from part of the xAPI statement.
*
* @param stdClass $data the original xAPI element
* @return item item_group xAPI item generated
*/
public static function create_from_data(stdClass $data): item {
global $CFG;
if (!isset($data->objectType)) {
throw new xapi_exception('Missing group objectType');
}
if ($data->objectType != 'Group') {
throw new xapi_exception("Group objectType must be 'Group'");
}
if (!isset($data->account)) {
throw new xapi_exception("Missing Group account");
}
if ($data->account->homePage != $CFG->wwwroot) {
throw new xapi_exception("Invalid group homePage '{$data->account->homePage}'");
}
if (!is_numeric($data->account->name)) {
throw new xapi_exception("Agent account name must be integer '{$data->account->name}' found");
}
$group = groups_get_group($data->account->name);
if (empty($group)) {
throw new xapi_exception("Inexistent group '{$data->account->name}'");
}
return new self($data, $group);
}
/**
* Create a item_group from a existing group.
*
* @param stdClass $group A group record.
* @return item_group
*/
public static function create_from_group(stdClass $group): item_group {
global $CFG;
if (!isset($group->id)) {
throw new xapi_exception("Missing group id");
}
$data = (object) [
'objectType' => 'Group',
'account' => (object) [
'homePage' => $CFG->wwwroot,
'name' => $group->id,
],
];
return new self($data, $group);
}
/**
* Returns the moodle user represented by this item.
*
* This is a group item. To avoid security problems this method
* thorws an exception when is called from a item_group class.
*
* @throws xapi_exception get_user must not be called from an item_group
* @return stdClass user record
*/
public function get_user(): stdClass {
throw new xapi_exception("Group statements cannot be used as a individual user");
}
/**
* Return all users from the group represented by this item.
*
* @return array group users
*/
public function get_all_users(): array {
return $this->users;
}
/**
* Return the moodle group represented by this item.
*
* @return stdClass a group record
*/
public function get_group(): stdClass {
return $this->group;
}
}
@@ -0,0 +1,69 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Statement object (activity, user or group) for xAPI structure checking and usage.
*
* @package core_xapi
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_xapi\local\statement;
use core_xapi\xapi_exception;
use core_xapi\iri;
use stdClass;
defined('MOODLE_INTERNAL') || die();
/**
* Abstract object item used in xAPI statements.
*
* Object represents the object in which a xAPI verb is applied. There
* are 3 types of objects supported: agent (user), group (of users) and
* activity (defined by every plugin).
*
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class item_object extends item {
/**
* Create a xAPI object compatible from data (Agent, Group or Activity).
*
* @param stdClass $data data structure from statement object
* @return item item_group|item_agent|item_activity resulting object
*/
public static function create_from_data(stdClass $data): item {
if (!isset($data->objectType)) {
$data->objectType = 'Activity';
}
switch ($data->objectType) {
case 'Agent':
return item_agent::create_from_data($data);
break;
case 'Group':
return item_group::create_from_data($data);
break;
case 'Activity':
return item_activity::create_from_data($data);
break;
default:
throw new xapi_exception("Unknown Object type '{$data->objectType}'");
}
}
}
@@ -0,0 +1,107 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Statement result for xAPI structure checking and usage.
*
* @package core_xapi
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_xapi\local\statement;
use core_xapi\xapi_exception;
use DateInterval;
use Exception;
use stdClass;
/**
* Abstract xAPI result class.
*
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class item_result extends item {
/** @var int The second of duration if present. */
protected $duration;
/** @var item_score the result score if present. */
protected $score;
/**
* Function to create a result from part of the xAPI statement.
*
* @param stdClass $data the original xAPI element
* @param int $duration duration in seconds
* @param item_score $score the provided score
*/
protected function __construct(stdClass $data, int $duration = null, item_score $score = null) {
parent::__construct($data);
$this->duration = $duration;
$this->score = $score;
}
/**
* Function to create an item from part of the xAPI statement.
*
* @param stdClass $data the original xAPI element
* @return item item_result xAPI generated
*/
public static function create_from_data(stdClass $data): item {
$duration = null;
if (!empty($data->duration)) {
try {
// Duration uses ISO 8601 format which is ALMOST compatible with PHP DateInterval.
// Because we are mesuring human time we get rid of milliseconds, which are not
// compatible with DateInterval (More info: https://bugs.php.net/bug.php?id=53831),
// all other fractions like "P1.5Y" will throw an exception.
$value = preg_replace('/[.,][0-9]*S/', 'S', $data->duration);
$interval = new DateInterval($value);
$duration = date_create('@0')->add($interval)->getTimestamp();
} catch (Exception $e) {
throw new xapi_exception('Invalid duration format.');
}
}
$score = null;
if (!empty($data->score)) {
$score = item_score::create_from_data($data->score);
}
return new self($data, $duration, $score);
}
/**
* Returns the duration in seconds (if present).
*
* @return int|null duration in seconds
*/
public function get_duration(): ?int {
return $this->duration;
}
/**
* Returns the score.
*
* @return item_score|null the score item
*/
public function get_score(): ?item_score {
return $this->score;
}
}
@@ -0,0 +1,49 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Statement score for xAPI structure checking and usage.
*
* @package core_xapi
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_xapi\local\statement;
use stdClass;
/**
* Abstract xAPI score class.
*
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class item_score extends item {
/**
* Function to create an item from part of the xAPI statement.
*
* @param stdClass $data the original xAPI element
* @return item item_score xAPI generated
*/
public static function create_from_data(stdClass $data): item {
// Required property checks will appear here in the future.
return new self($data);
}
}
@@ -0,0 +1,103 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Statement verb object for xAPI structure checking and usage.
*
* @package core_xapi
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_xapi\local\statement;
use core_xapi\xapi_exception;
use core_xapi\iri;
use stdClass;
defined('MOODLE_INTERNAL') || die();
/**
* Verb xAPI statement item.
*
* Verbs represent the interaction a user/group made inside a xAPI
* compatible plugin. Internally a xAPI verb must be representad as
* in a valid IRI format (which is a less restrictive version of a
* regular URL so a moodle_url out is completelly fine). To make it
* easy for plugins to generate valid IRI, a simple string van be
* provided and the class will convert into a valid IRI internally.
*
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class item_verb extends item {
/** @var string The statement. */
protected $id;
/**
* An xAPI verb constructor based on xAPI data structure.
*
* @param stdClass $data from the specific xAPI element
*/
protected function __construct(stdClass $data) {
parent::__construct($data);
$this->id = iri::extract($data->id, 'verb');
}
/**
* Function to create an item from part of the xAPI statement.
*
* @param stdClass $data the original xAPI element
* @return item item_verb xAPI generated
*/
public static function create_from_data(stdClass $data): item {
if (empty($data->id)) {
throw new xapi_exception("missing verb id");
}
if (!iri::check($data->id)) {
throw new xapi_exception("verb id $data->id is not a valid IRI");
}
return new self($data);
}
/**
* Create a valid item_verb from a simple verb string.
*
* @param string $id string to convert to a valid IRI (or a valid IRI)
* @return item_verb the resulting item_verb
*/
public static function create_from_id(string $id): item_verb {
$data = new stdClass();
$data->id = iri::generate($id, 'verb');
return new self($data);
}
/**
* Return the id used in this item.
*
* Id will be extracted from the provided IRI. If it's a valid IRI
* it will return all IRI value but if it is generate by the iri helper
* from this library it will extract the original value.
*
* @return string the ID (extracted from IRI value)
*/
public function get_id(): string {
return $this->id;
}
}
+228
View File
@@ -0,0 +1,228 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_xapi\privacy;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\transform;
/**
* Privacy implementation for core xAPI Library.
*
* @package core_xapi
* @category privacy
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
\core_privacy\local\metadata\provider,
\core_privacy\local\request\subsystem\plugin_provider,
\core_privacy\local\request\shared_userlist_provider {
/**
* Return the fields which contain personal data.
*
* @param collection $collection The initialised collection to add items to.
* @return collection A listing of user data stored through this system.
*/
public static function get_metadata(collection $collection): collection {
$collection->add_database_table('xapi_states', [
'component' => 'privacy:metadata:component',
'userid' => 'privacy:metadata:userid',
'itemid' => 'privacy:metadata:itemid',
'stateid' => 'privacy:metadata:stateid',
'statedata' => 'privacy:metadata:statedata',
'registration' => 'privacy:metadata:registration',
'timecreated' => 'privacy:metadata:timecreated',
'timemodified' => 'privacy:metadata:timemodified',
], 'privacy:metadata:xapi_states');
return $collection;
}
/**
* Provide a list of contexts which have xAPI for the user, in the respective area (component/itemtype combination).
*
* This method is to be called by consumers of the xAPI subsystem (plugins), in their get_contexts_for_userid() method,
* to add the contexts for items which may have xAPI data, but would normally not be reported as having user data by the
* plugin responsible for them.
*
* @param \core_privacy\local\request\contextlist $contextlist
* @param int $userid The id of the user in scope.
* @param string $component the frankenstyle component name.
*/
public static function add_contexts_for_userid(
\core_privacy\local\request\contextlist $contextlist,
int $userid,
string $component) {
$sql = "SELECT ctx.id
FROM {xapi_states} xs
JOIN {context} ctx
ON ctx.id = xs.itemid
WHERE xs.userid = :userid
AND xs.component = :component";
$params = ['userid' => $userid, 'component' => $component];
$contextlist->add_from_sql($sql, $params);
}
/**
* Add users to a userlist who have xAPI within the specified context.
*
* @param \core_privacy\local\request\userlist $userlist The userlist to add the users to.
* @return void
*/
public static function add_userids_for_context(\core_privacy\local\request\userlist $userlist) {
if (empty($userlist)) {
return;
}
$params = [
'contextid' => $userlist->get_context()->id,
'component' => $userlist->get_component()
];
$sql = "SELECT xs.userid
FROM {xapi_states} xs
JOIN {context} ctx
ON ctx.id = xs.itemid
WHERE ctx.id = :contextid
AND xs.component = :component";
$userlist->add_from_sql('userid', $sql, $params);
}
/**
* Get xAPI states data for the specified user in the specified component and item ID.
*
* @param int $userid The id of the user in scope.
* @param string $component The component name.
* @param int $itemid The item ID.
* @return array|null
*/
public static function get_xapi_states_for_user(int $userid, string $component, int $itemid) {
global $DB;
$params = [
'userid' => $userid,
'component' => $component,
'itemid' => $itemid,
];
if (!$states = $DB->get_records('xapi_states', $params)) {
return;
}
$result = [];
foreach ($states as $state) {
$result[] = [
'statedata' => $state->statedata,
'timecreated' => transform::datetime($state->timecreated),
'timemodified' => transform::datetime($state->timemodified)
];
}
return $result;
}
/**
* Delete all xAPI states for all users in the specified contexts, and component area.
*
* @param \context $context The context to which deletion is scoped.
* @param string $component The component name.
* @throws \dml_exception if any errors are encountered during deletion.
*/
public static function delete_states_for_all_users(\context $context, string $component) {
global $DB;
$params = [
'component' => $component,
];
$select = "component = :component";
if (!empty($context)) {
$select .= " AND itemid = :itemid";
$params['itemid'] = $context->id;
}
$DB->delete_records_select('xapi_states', $select, $params);
}
/**
* Delete all xAPI states for the specified users in the specified context, component area and item type.
*
* @param \core_privacy\local\request\approved_userlist $userlist The approved contexts and user information
* to delete information for.
* @param int $itemid Optional itemid associated with component.
* @throws \dml_exception if any errors are encountered during deletion.
*/
public static function delete_states_for_userlist(\core_privacy\local\request\approved_userlist $userlist, int $itemid = 0) {
global $DB;
$userids = $userlist->get_userids();
if (empty($userids)) {
return;
}
list($usersql, $userparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
$params = [
'component' => $userlist->get_component(),
];
$params += $userparams;
$select = "component = :component AND userid $usersql";
if (!empty($itemid)) {
$select .= " AND itemid = :itemid";
$params['itemid'] = $itemid;
}
$DB->delete_records_select('xapi_states', $select, $params);
}
/**
* Delete all xAPI states for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
* @param string $component The component name.
* @param int $itemid Optional itemid associated with component.
* @throws \coding_exception
* @throws \dml_exception
*/
public static function delete_states_for_user(approved_contextlist $contextlist, string $component, int $itemid = 0) {
global $DB;
$userid = $contextlist->get_user()->id;
$params = [
'userid' => $userid,
'component' => $component,
];
$select = "userid = :userid AND component = :component";
if (!empty($itemid)) {
$select .= " AND itemid = :itemid";
$params['itemid'] = $itemid;
}
$DB->delete_records_select('xapi_states', $select, $params);
}
}
+278
View File
@@ -0,0 +1,278 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_xapi;
use core_xapi\local\state;
/**
* The state store manager.
*
* @package core_xapi
* @since Moodle 4.2
* @copyright 2022 Ferran Recio <ferran@moodle.com>
* @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;
}
/**
* Convert the xAPI activity ID into an item ID integer.
*
* @throws xapi_exception if the activity id is not numeric.
* @param string $activityid the provided activity ID
* @return int
*/
protected function activity_id_to_item_id(string $activityid): int {
if (!is_numeric($activityid)) {
throw new xapi_exception('The state store can only store numeric activity IDs.');
}
return intval($activityid);
}
/**
* 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' => $this->activity_id_to_item_id($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' => $this->activity_id_to_item_id($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' => $this->activity_id_to_item_id($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'] = $this->activity_id_to_item_id($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'] = $this->activity_id_to_item_id($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'] = $this->activity_id_to_item_id($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]
);
}
}
@@ -0,0 +1,44 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_xapi\task;
/**
* A scheduled task to clear up old xAPI state data.
*
* @package core_xapi
* @since Moodle 4.2
* @copyright 2022 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class state_cleanup_task extends \core\task\scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('xapicleanup', 'xapi');
}
/**
* Run task.
*/
public function execute() {
\core_xapi\api::execute_state_cleanup();
}
}
+37
View File
@@ -0,0 +1,37 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* General xAPI invalid statement exception.
*
* @package core_xapi
* @since Moodle 3.9
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_xapi;
defined('MOODLE_INTERNAL') || die();
/**
* General invalid xAPI exception.
*
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class xapi_exception extends \moodle_exception {
}