Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
moodle
moodle
Commits
1d4c6f53
Commit
1d4c6f53
authored
Jan 21, 2020
by
Ferran Recio Calderó
Browse files
MDL-67734 core_xapi: add xAPI statement support webservice
parent
0a832fa1
Changes
35
Hide whitespace changes
Inline
Side-by-side
lang/en/xapi.php
0 → 100644
View file @
1d4c6f53
<?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/>.
/**
* Strings for xapi library, language 'en'
*
* @package core_xapi
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string
[
'eventxapipost'
]
=
'Post xAPI statement'
;
$string
[
'privacy:metadata'
]
=
'The xAPI library does not store any personal data.'
;
lib/components.json
View file @
1d4c6f53
...
...
@@ -108,6 +108,7 @@
"timezones"
:
null
,
"user"
:
"user"
,
"userkey"
:
"lib
\/
userkey"
,
"webservice"
:
"webservice"
"webservice"
:
"webservice"
,
"xapi"
:
"lib
\/
xapi"
}
}
lib/db/services.php
View file @
1d4c6f53
...
...
@@ -2744,6 +2744,16 @@ $functions = array(
'capabilities'
=>
''
,
'services'
=>
[
MOODLE_OFFICIAL_MOBILE_SERVICE
],
],
'core_xapi_statement_post'
=>
[
'classname'
=>
'core_xapi\external\post_statement'
,
'methodname'
=>
'execute'
,
'classpath'
=>
''
,
'description'
=>
'Post an xAPI statement.'
,
'type'
=>
'write'
,
'ajax'
=>
'true'
,
'capabilities'
=>
''
,
'services'
=>
[
MOODLE_OFFICIAL_MOBILE_SERVICE
],
],
);
$services
=
array
(
...
...
lib/tests/component_test.php
View file @
1d4c6f53
...
...
@@ -36,7 +36,7 @@ class core_component_testcase extends advanced_testcase {
* this is defined here to annoy devs that try to add more without any thinking,
* always verify that it does not collide with any existing add-on modules and subplugins!!!
*/
const
SUBSYSTEMCOUNT
=
69
;
const
SUBSYSTEMCOUNT
=
70
;
public
function
setUp
()
{
$psr0namespaces
=
new
ReflectionProperty
(
'core_component'
,
'psr0namespaces'
);
...
...
lib/xapi/classes/external/post_statement.php
0 → 100644
View file @
1d4c6f53
<?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/>.
/**
* 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
*/
namespace
core_xapi\external
;
use
core_xapi
\
local\statement
;
use
core_xapi
\
handler
;
use
core_xapi\xapi_exception
;
use
external_api
;
use
external_function_parameters
;
use
external_value
;
use
external_single_structure
;
use
external_multiple_structure
;
use
external_warnings
;
use
core_component
;
defined
(
'MOODLE_INTERNAL'
)
||
die
();
require_once
(
$CFG
->
libdir
.
'/externallib.php'
);
/**
* This is the external API for generic xAPI handling.
*
* @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
;
}
}
lib/xapi/classes/handler.php
0 → 100644
View file @
1d4c6f53
<?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/>.
/**
* The core_xapi statement validation and tansformation.
*
* @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
;
use
core_xapi
\
local\statement
;
use
core_xapi\xapi_exception
;
use
stdClass
;
defined
(
'MOODLE_INTERNAL'
)
||
die
();
/**
* Class handler handles basic xapi statements.
*
* @package core_xapi
* @copyright 2020 Ferran Recio
*/
abstract
class
handler
{
/** @var string component name in frankenstyle. */
protected
$component
;
/**
* Constructor for a xAPI handler base class.
*
* @param string $component the component name
*/
final
protected
function
__construct
(
string
$component
)
{
$this
->
component
=
$component
;
}
/**
* 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
{
$classname
=
"
\\
$component
\\
xapi
\\
handler"
;
if
(
class_exists
(
$classname
))
{
return
new
$classname
(
$component
);
}
throw
new
xapi_exception
(
'Unknown handler'
);
}
/**
* 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
;
}
}
lib/xapi/classes/iri.php
0 → 100644
View file @
1d4c6f53
<?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'
]);
}
}
lib/xapi/classes/local/statement.php
0 → 100644
View file @
1d4c6f53
<?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\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 actor The statement actor. */
protected
$actor
=
null
;
/** @var verb The statement verb. */
protected
$verb
=
null
;
/** @var object The statement object. */
protected
$object
=
null
;
/** @var result The statement result. */
protected
$result
=
null
;
/** @var context The statement context. */
protected
$context
=
null
;
/** @var timestamp The statement timestamp. */
protected
$timestamp
=
null
;
/** @var stored The statement stored. */
protected
$stored
=
null
;
/** @var authority The statement authority. */
protected
$authority
=
null
;
/** @var version The statement version. */
protected
$version
=
null
;
/** @var attachments The statement attachments. */
protected
$attachments
=
null
;
/** @var additionalfields list of additional fields. */
private
static
$additionalsfields
=
[
'context'
,
'result'
,
'timestamp'
,
'stored'
,
'authority'
,
'version'
,
'attachments'
];
/**
* 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
));
// Store other generic xAPI statement fields.
foreach
(
self
::
$additionalsfields
as
$additional
)
{
if
(
isset
(
$data
->
$additional
))
{
$method
=
'set_'
.
$additional
;
$result
->
$method
(
item
::
create_from_data
(
$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
,
];
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
;