This commit is contained in:
Xes
2025-08-14 22:41:49 +02:00
parent 2de81ccc46
commit 8ce45119b6
39774 changed files with 4309466 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
name: 'CI'
on:
- 'push'
- 'pull_request'
jobs:
tests:
name: 'Tests'
runs-on: 'ubuntu-latest'
strategy:
matrix:
include:
- php-version: '7.1'
composer-options: '--prefer-stable'
- php-version: '7.1'
composer-options: '--prefer-lowest --prefer-stable'
- php-version: '7.2'
composer-options: '--prefer-stable'
- php-version: '7.3'
composer-options: '--prefer-stable'
steps:
- name: 'Check out'
uses: 'actions/checkout@v2'
- name: 'Set up PHP'
uses: 'shivammathur/setup-php@v2'
with:
php-version: '${{ matrix.php-version }}'
coverage: 'none'
- name: 'Get Composer cache directory'
id: 'composer-cache'
run: 'echo "::set-output name=cache-dir::$(composer config cache-files-dir)"'
- name: 'Cache dependencies'
uses: 'actions/cache@v2'
with:
path: '${{ steps.composer-cache.outputs.cache-dir }}'
key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}"
restore-keys: 'php-${{ matrix.php-version }}-composer-locked-'
- name: 'Install dependencies'
run: 'composer update --no-progress $COMPOSER_OPTIONS'
- name: 'Install PHPUnit'
run: 'vendor/bin/simple-phpunit install'
- name: 'Run PhpSpec'
run: 'vendor/bin/phpspec run'
- name: 'Run PHPUnit'
run: 'SYMFONY_DEPRECATIONS_HELPER=weak vendor/bin/simple-phpunit'

4
vendor/php-xapi/client/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/.phpunit.result.cache
/composer.lock
/phpunit.xml
/vendor

166
vendor/php-xapi/client/CHANGELOG.md vendored Normal file
View File

@@ -0,0 +1,166 @@
CHANGELOG
=========
0.7.0
-----
* dropped support for PHP < 7.1
0.6.0
-----
* added compatibility with HTTPlug 2
* dropped the support for HHVM and for PHP < 5.6
* updated the `X-Experience-API-Version` header to default to the latest patch
version (`1.0.3`)
* allow `2.x` releases of the `php-xapi/model` package too
* allow `3.x` releases of the `php-xapi/model` package for PHP 7.2 compatibility
0.5.0
-----
* **CAUTION**: This release drops support for PHP 5.3 due to the introduced
dependency on `php-http/httplug` (see below).
* The client now depends on the [HTTPlug library](http://httplug.io/) to
perform HTTP requests. This means that the package now depends the virtual
`php-http/client-implementation`. To satisfy this dependency you have to
pick [an implementation](https://packagist.org/providers/php-http/client-implementation)
and install it together with `php-xapi/client`.
For example, if you prefer to use [Guzzle 6](http://docs.guzzlephp.org/en/latest/)
you would do the following:
```bash
$ composer require --no-update php-http/guzzle6-adapter
$ composer require php-xapi/client
```
* The `setHttpClient()` and `setRequestFactory()` method have been added
to the `XApiClientBuilderInterface` and must be used to configure the
`HttpClient` and `RequestFactory` instances you intend to use.
To use [Guzzle 6](http://docs.guzzlephp.org/en/latest/), for example,
this will look like this:
```php
use Http\Adapter\Guzzle6\Client;
use Http\Message\MessageFactory\GuzzleMessageFactory;
use Xabbuh\XApi\Client\XApiClientBuilder;
$builder = new XApiClientBuilder();
$client = $builder->setHttpClient(new Client())
->setRequestFactory(new GuzzleMessageFactory())
->setBaseUrl('http://example.com/xapi/')
->build();
```
You can avoid calling `setHttpClient()` and `setRequestFactory` by installing
the [HTTP discovery](http://php-http.org/en/latest/discovery.html) package.
* The `xabbuh/oauth1-authentication` package now must be installed if you want
to use OAuth1 authentication.
* Bumped the required versions of all `php-xapi` packages to the `1.x` release
series.
* Include the raw attachment content wrapped in a `multipart/mixed` encoded
request when raw content is part of a statement's attachment.
* Added the possibility to decide whether or not to include attachments when
requesting statements from an LRS. A second optional `$attachments` argument
(defaulting to `true`) has been added for this purpose to the `getStatement()`,
`getVoidedStatement()`, and `getStatements()` methods of the `StatementsApiClient`
class and the `StatementsApiClientInterface`.
* An optional fifth `$headers` parameter has been added to the `createRequest()`
method of the `HandlerInterface` and the `Handler` class which allows to pass
custom headers when performing HTTP requests.
0.4.0
-----
* The `XApiClientBuilder` class now makes use of the `SerializerFactoryInterface`
introduced in release `0.4.0` of the `php-xapi/serializer` package. By
default, it will fall back to the `SerializerFactory` implemented provided
by the `php-xapi/symfony-serializer` to maintain backwards-compatibility
with the previous release. However, you are now able to inject arbitrary
implementations of the `SerializerFactoryInterface` into the constructor
of the `XApiClientBuilder` to use whatever alternative implementation
(packages providing such an implementation should provide the virtual
`php-xapi/serializer-implementation` package).
0.3.0
-----
* Do not send authentication headers when no credentials have been configured.
* Fixed treating HTTP methods case insensitive. Rejecting uppercased HTTP
method names contradicts the HTTP specification. Lowercased method names
will still be supported to keep backwards compatibility though.
* Fixed creating `XApiClient` instances in an invalid state. The `XApiClientBuilder`
now throws a `\LogicException` when the `build()` method is called before
a base URI was configured.
* Removed the `ApiClient` class. The `$requestHandler` and `$version` attributes
have been moved to the former child classes of the `ApiClient` class and
their visibility has been changed to `private`.
* The visibility of the `$documentDataSerializer` property of the `ActivityProfileApiClient`,
`AgentProfileApiClient`, `DocumentApiClient`, and `StateApiClient` classes
has been changed to `private`.
* Removed the `getRequestHandler()` method from the API classes:
* `ActivityProfileApiClient::getRequestHandler()`
* `AgentProfileApiClient::getRequestHandler()`
* `ApiClient::getRequestHandler()`
* `DocumentApiClient::getRequestHandler()`
* `StateApiClient::getRequestHandler()`
* `StatementsApiClient::getRequestHandler()`
* Removed the `getVersion()` method from the API interfaces:
* `ActivityProfileApiClientInterface::getVersion()`
* `AgentProfileApiClientInterface::getVersion()`
* `StateApiClientInterface::getVersion()`
* `StatementsApiClientInterface::getVersion()`
* Removed the `getVersion()` method from the API classes:
* `ActivityProfileApiClient::getVersion()`
* `AgentProfileApiClient::getVersion()`
* `ApiClient::getVersion()`
* `DocumentApiClient::getVersion()`
* `StateApiClient::getVersion()`
* `StatementsApiClient::getVersion()`
* `XApiClient::getVersion()`
* Removed the `getUsername()` and `getPassword()` methods from the `HandlerInterface`
and the `Handler` class.
* Removed the `getHttpClient()` method from the `Handler` class.
* Removed the `getSerializerRegistry()` method from the `XApiClient` class.
* Made all classes final.
0.2.0
-----
* made the client compatible with version 0.5 of the `php-xapi/model` package
* made the client compatible with version 0.3 of the `php-xapi/serializer` package
0.1.0
-----
First release of an Experience API client based on the Guzzle HTTP library.
This package replaces the `xabbuh/xapi-client` package which is now deprecated
and should no longer be used.

19
vendor/php-xapi/client/LICENSE vendored Normal file
View File

@@ -0,0 +1,19 @@
Copyright (c) 2014-2019 Christian Flothmann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

43
vendor/php-xapi/client/README.md vendored Normal file
View File

@@ -0,0 +1,43 @@
PHP xApi (Experience API) Client
================================
[![Build Status](https://travis-ci.org/php-xapi/client.svg?branch=master)](https://travis-ci.org/php-xapi/client)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/php-xapi/client/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/php-xapi/client/?branch=master)
[![Code Coverage](https://scrutinizer-ci.com/g/php-xapi/client/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/php-xapi/client/?branch=master)
Client side PHP implementation of the
[Experience API](https://github.com/adlnet/xAPI-Spec/blob/master/xAPI.md).
Installation
------------
The recommended way to install the xAPI client is using
[Composer](http://getcomposer.org/):
1. Add ``php-xapi/client`` as a dependency to your project:
```bash
$ composer require php-xapi/client
```
1. Require Composer's autoloader:
``` php
require __DIR__.'/vendor/autoload.php';
```
Usage
-----
Read the [documentation](doc/index.md) to find out how to use the library.
Issues
------
Report issues in the [issue tracker of this package](https://github.com/php-xapi/client/issues).
License
-------
This package is under the MIT license. See the complete license in the
[LICENSE](LICENSE) file.

110
vendor/php-xapi/client/UPGRADE.md vendored Normal file
View File

@@ -0,0 +1,110 @@
UPGRADE
=======
Upgrading from 0.4 to 0.5
-------------------------
* **CAUTION**: This release drops support for PHP 5.3 due to the introduced
dependency on `php-http/httplug` (see below).
* The client now depends on the [HTTPlug library](http://httplug.io/) to
perform HTTP requests. This means that the package now depends the virtual
`php-http/client-implementation`. To satisfy this dependency you have to
pick [an implementation](https://packagist.org/providers/php-http/client-implementation)
and install it together with `php-xapi/client`.
For example, if you prefer to use [Guzzle 6](http://docs.guzzlephp.org/en/latest/)
you would do the following:
```bash
$ composer require --no-update php-http/guzzle6-adapter
$ composer require php-xapi/client
```
* The `setHttpClient()` and `setRequestFactory()` method have been added
to the `XApiClientBuilderInterface` and must be used to configure the
`HttpClient` and `RequestFactory` instances you intend to use.
To use [Guzzle 6](http://docs.guzzlephp.org/en/latest/), for example,
this will look like this:
```php
use Http\Adapter\Guzzle6\Client;
use Http\Message\MessageFactory\GuzzleMessageFactory;
use Xabbuh\XApi\Client\XApiClientBuilder;
$builder = new XApiClientBuilder();
$client = $builder->setHttpClient(new Client())
->setRequestFactory(new GuzzleMessageFactory())
->setBaseUrl('http://example.com/xapi/')
->build();
```
You can avoid calling `setHttpClient()` and `setRequestFactory` by installing
the [HTTP discovery](http://php-http.org/en/latest/discovery.html) package.
* The `xabbuh/oauth1-authentication` package now must be installed if you want
to use OAuth1 authentication.
* A second optional `$attachments` argument (defaulting to `true`) has been added
to the `getStatement()`, `getVoidedStatement()`, and `getStatements()` methods
of the `StatementsApiClient` class and the `StatementsApiClientInterface`.
* An optional fifth `$headers` parameter has been added to the `createRequest()`
method of the `HandlerInterface` and the `Handler` class which allows to pass
custom headers when performing HTTP requests.
Upgrading from 0.2 to 0.3
-------------------------
* Removed the `ApiClient` class. The `$requestHandler` and `$version` attributes
have been moved to the former child classes of the `ApiClient` class and
their visibility has been changed to `private`.
* The visibility of the `$documentDataSerializer` property of the `ActivityProfileApiClient`,
`AgentProfileApiClient`, `DocumentApiClient`, and `StateApiClient` classes
has been changed to `private`.
* Removed the `getRequestHandler()` method from the API classes:
* `ActivityProfileApiClient::getRequestHandler()`
* `AgentProfileApiClient::getRequestHandler()`
* `ApiClient::getRequestHandler()`
* `DocumentApiClient::getRequestHandler()`
* `StateApiClient::getRequestHandler()`
* `StatementsApiClient::getRequestHandler()`
* `XApiClient::getRequestHandler()`
* Removed the `getVersion()` method from the API interfaces:
* `ActivityProfileApiClientInterface::getVersion()`
* `AgentProfileApiClientInterface::getVersion()`
* `StateApiClientInterface::getVersion()`
* `StatementsApiClientInterface::getVersion()`
* Removed the `getVersion()` method from the API classes:
* `ActivityProfileApiClient::getVersion()`
* `AgentProfileApiClient::getVersion()`
* `ApiClient::getVersion()`
* `DocumentApiClient::getVersion()`
* `StateApiClient::getVersion()`
* `StatementsApiClient::getVersion()`
* `XApiClient::getVersion()`
* Removed the `getUsername()` and `getPassword()` methods from the `HandlerInterface`
and the `Handler` class.
* Removed the `getHttpClient()` method from the `Handler` class.
* Removed the `getSerializerRegistry()` method from the `XApiClient` class.
* All classes are final now which means that you can now longer extend them.
Consider using composition/decoration instead if you need to build functionality
on top of the built-in classes.
Upgrading from 0.1 to 0.2
-------------------------
* Statement identifiers must be passed as `StatementId` objects instead of
strings.

58
vendor/php-xapi/client/composer.json vendored Normal file
View File

@@ -0,0 +1,58 @@
{
"name": "php-xapi/client",
"type": "library",
"description": "client library for the Experience API (xAPI)",
"keywords": ["xAPI", "Experience API", "Tin Can API", "client"],
"homepage": "https://github.com/php-xapi/client",
"license": "MIT",
"authors": [
{
"name": "Christian Flothmann",
"homepage": "https://github.com/xabbuh"
}
],
"require": {
"php": "^7.1",
"php-http/client-common": "^1.0 || ^2.0",
"php-http/client-implementation": "^1.0",
"php-http/httplug": "^1.0 || ^2.0",
"php-http/message": "^1.0",
"php-http/message-factory": "^1.0",
"php-xapi/exception": "^0.1 || ^0.2",
"php-xapi/model": "^1.0 || ^2.0 || ^3.0",
"php-xapi/serializer": "^2.0",
"php-xapi/serializer-implementation": "^2.0",
"php-xapi/symfony-serializer": "^2.0",
"psr/http-message": "^1.0"
},
"require-dev": {
"phpspec/phpspec": "^2.4",
"php-http/mock-client": "^1.2",
"php-xapi/test-fixtures": "^1.0",
"symfony/phpunit-bridge": "^5.2"
},
"minimum-stability": "dev",
"suggest": {
"php-http/discovery": "For automatic discovery of HTTP clients and request factories",
"xabbuh/oauth1-authentication": "For OAuth1 authentication support"
},
"conflict": {
"xabbuh/xapi-client": "*"
},
"autoload": {
"psr-4": {
"Xabbuh\\XApi\\Client\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"spec\\Xabbuh\\XApi\\Client\\": "spec/",
"Xabbuh\\XApi\\Client\\Tests\\": "tests/"
}
},
"extra": {
"branch-alias": {
"dev-master": "0.7.x-dev"
}
}
}

View File

@@ -0,0 +1,89 @@
The Activity Profile API
========================
Activity Profiles
-----------------
A LMS can use the xAPI to store documents associated to a certain activity using
activity profiles. An activity profile is dedicated to an activity and a profile
id:
```php
use Xabbuh\XApi\Model\ActivityProfile;
// ...
$profile = new ActivityProfile();
$profile->setActivity($activity);
$profile->setProfileId($profileId);
```
Documents
---------
Documents are simple collections of key-value pairs and can be accessed like arrays:
```php
use Xabbuh\XApi\Model\ActivityProfileDocument;
// ...
$document = new ActivityProfileDocument();
$document->setActivityProfile($profile);
$document['x'] = 'foo';
$document['y'] = 'bar';
```
Obtaining the Activity Profile API Client
-----------------------------------------
After you have [built the global xAPI client](client.md), you can obtain an activity
profile API client by calling its ``getActivityProfileApiClient()`` method:
```php
$activityProfileApiClient = $xApiClient->getActivityProfileApiClient();
```
Storing Activity Profile Documents
----------------------------------
You can simply store an ``ActivityProfileDocument`` passing it to the
``createOrUpdateActivityProfileDocument()`` method of the xAPI client:
```php
$document = ...; // the activity profile document
$activityProfileApiClient->createOrUpdateActivityProfileDocument($document);
```
If a document already exists for this activity profile, the existing document will
be updated. This means that new fields will be updated, existing fields that are
included in the new document will be overwritten and existing fields that are
not included in the new document will be kept as they are.
If you want to replace a document, use the ``createOrReplaceActivityProfileDocument()``
method instead:
```php
$document = ...; // the activity profile document
$activityProfileApiClient->createOrReplaceActivityProfileDocument($document);
```
Deleting Activity Profile Documents
-----------------------------------
An ``ActivityProfileDocument`` is deleted by passing the particular ``ActivityProfile``
to the ``deleteActivityProfileDocument()`` method:
```php
$profile = ...; // the activity profile the document should be deleted from
$activityProfileApiClient->deleteActivityProfileDocument($profile);
```
Retrieving Activity Profile Documents
-------------------------------------
Similarly, you receive a document for a particular activity profile by passing
the profile to the ``getActivityProfileDocument()`` method:
```php
$profile = ...; // the activity profile the document should be retrieved from
$document = $activityProfileApiClient->getActivityProfileDocument($profile);
```

View File

@@ -0,0 +1,88 @@
The Agent Profile API
=====================
Agent Profiles
--------------
A LMS can use the xAPI to store documents associated to a certain agent using
agent profiles. An agent profile is dedicated to an agent and a profile id:
```php
use Xabbuh\XApi\Model\AgentProfile;
// ...
$profile = new AgentProfile();
$profile->setAgent($agent);
$profile->setProfileId($profileId);
```
Documents
---------
Documents are simple collections of key-value pairs and can be accessed like arrays:
```php
use Xabbuh\XApi\Model\AgentProfileDocument;
// ...
$document = new AgentProfileDocument();
$document->setAgentProfile($profile);
$document['x'] = 'foo';
$document['y'] = 'bar';
```
Obtaining the Agent Profile API Client
--------------------------------------
After you have [built the global xAPI client](client.md), you can obtain an agent
profile API client by calling its ``getAgentProfileApiClient()`` method:
```php
$agentProfileApiClient = $xApiClient->getAgentProfileApiClient();
```
Storing Agent Profile Documents
-------------------------------
You can simply store an ``AgentProfileDocument`` passing it to the
``createOrUpdateAgentProfileDocument()`` method of the xAPI client:
```php
$document = ...; // the agent profile document
$agentProfileApiClient->createOrUpdateAgentProfileDocument($document);
```
If a document already exists for this agent profile, the existing document will
be updated. This means that new fields will be updated, existing fields that are
included in the new document will be overwritten and existing fields that are
not included in the new document will be kept as they are.
If you want to replace a document, use the ``createOrReplaceAgentProfileDocument()``
method instead:
```php
$document = ...; // the agent profile document
$agentProfileApiClient->createOrReplaceAgentProfileDocument($document);
```
Deleting Agent Profile Documents
--------------------------------
An ``AgentProfileDocument`` is deleted by passing the particular ``AgentProfile``
to the ``deleteAgentProfileDocument()`` method:
```php
$profile = ...; // the agent profile the document should be deleted from
$agentProfileApiClient->deleteAgentProfileDocument($profile);
```
Retrieving Agent Profile Documents
----------------------------------
Similarly, you receive a document for a particular agent profile by passing the
profile to the ``getAgentProfileDocument()`` method:
```php
$profile = ...; // the agent profile the document should be retrieved from
$document = $agentProfileApiClient->getAgentProfileDocument($profile);
```

70
vendor/php-xapi/client/doc/client.md vendored Normal file
View File

@@ -0,0 +1,70 @@
Building an xAPI Client
=======================
The xAPI client library ships with a builder class which eases the process of
creating an instance of an ``XApiClient`` class:
```php
use Xabbuh\XApi\Client\XApiClientBuilder;
$builder = new XApiClientBuilder();
$xApiClient = $builder->setBaseUrl('http://example.com/lrs/api')
->setVersion('1.0.0')
->build();
```
The builder creates a client for the 1.0.1 API version if you don't set a version.
HTTP Basic Authentication
-------------------------
Use the ``setAuth()`` method if access to the LRS resources is protected with
HTTP Basic authentication:
```php
use Xabbuh\XApi\Client\XApiClientBuilder;
$builder = new XApiClientBuilder();
$xApiClient = $builder->setBaseUrl('http://example.com/lrs/api')
->setAuth('username', 'password')
->build();
```
OAuth1 Authentication
---------------------
Using the ``setOAuthCredentials()`` method, you can configure the client to
access OAuth1 protected resources:
```php
use Xabbuh\XApi\Client\XApiClientBuilder;
$builder = new XApiClientBuilder();
$xApiClient = $builder->setBaseUrl('http://example.com/lrs/api')
->setOAuthCredentials('consumer-key', 'consumer-secret', 'token', 'token-secret')
->build();
```
Using the APIs
--------------
The Experience API consists of four sub APIs: the statements API, the state API,
the activity profile API and the agent profile API. A client for each of these
APIs can be obtained from the global ``XApiClient`` instance:
```php
$statementsApiClient = $xApiClient->getStatementsApiClient();
$stateApiClient = $xApiClient->getStateApiClient();
$activityProfileApiClient = $xApiClient->getActivityProfileApiClient();
$agentProfileApiClient = $xApiClient->getAgentProfileApiClient();
```
Read the dedicated chapters of the sub APIs to learn how to make use of them:
1. [The Statements API](statements.md)
1. [The State API](state.md)
1. [The Activity Profile API](activity_profile.md)
1. [The Agent profile API](agent_profile.md)

12
vendor/php-xapi/client/doc/index.md vendored Normal file
View File

@@ -0,0 +1,12 @@
Documentation
=============
1. [Obtaining an xAPI client](client.md)
1. [The Statements API](statements.md)
1. [The State API](state.md)
1. [The Activity Profile API](activity_profile.md)
1. [The Agent profile API](agent_profile.md)

90
vendor/php-xapi/client/doc/state.md vendored Normal file
View File

@@ -0,0 +1,90 @@
The State API
=============
States
------
A LMS can use the xAPI to store documents associated to a certain state. A state
is dedicated to an activity, an actor, a state id and an optional registration
id (for example a user id):
```php
use Xabbuh\XApi\Model\State;
// ...
$state = new State();
$state->setActivity($activity);
$state->setActor($actor);
$state->setStateId($stateId);
```
Documents
---------
Documents are simple collections of key-value pairs and can be accessed like arrays:
```php
use Xabbuh\XApi\Model\StateDocument;
// ...
$document = new StateDocument();
$document->setState($state);
$document['x'] = 'foo';
$document['y'] = 'bar';
```
Obtaining the State API Client
------------------------------
After you have [built the global xAPI client](client.md), you can obtain a state
API client by calling its ``getStateApiClient()`` method:
```php
$stateApiClient = $xApiClient->getStateApiClient();
```
Storing State Documents
-----------------------
You can simply store a ``StateDocument`` passing it to the ``createOrUpdateStateDocument()``
method of the xAPI client:
```php
$document = ...; // the state document
$stateApiClient->createOrUpdateStateDocument($document);
```
If a document already exists for this state, the existing document will be updated.
This means that new fields will be updated, existing fields that are included in
the new document will be overwritten and existing fields that are not included in
the new document will be kept as they are.
If you want to replace a document, use the ``createOrReplaceStateDocument()`` method
instead:
```php
$document = ...; // the state document
$stateApiClient->createOrReplaceStateDocument($document);
```
Deleting State Documents
------------------------
A ``StateDocument`` is deleted by passing the particular ``State`` to the ``deleteStateDocument()``
method:
```php
$state = ...; // the state the document should be deleted from
$stateApiClient->deleteStateDocument($state);
```
Retrieving State Documents
--------------------------
Similarly, you receive a document for a particular state by passing the state to
the ``getStateDocument()`` method:
```php
$state = ...; // the state the document should be retrieved from
$document = $stateApiClient->getStateDocument($state);
```

123
vendor/php-xapi/client/doc/statements.md vendored Normal file
View File

@@ -0,0 +1,123 @@
The Statements API
==================
Obtaining the Agent Profile API Client
--------------------------------------
After you have [built the global xAPI client](client.md), you can obtain a statements
API client by calling its ``getStatementsApiClient()`` method:
```php
$statementsApiClient = $xApiClient->getStatementsApiClient();
```
Storing Statements
------------------
The ``storeStatement()`` and ``storeStatements()`` methods can be used to store
a single Statement or a collection of Statements. Both method return the stored
Statement(s) each having a unique id created by the remote LRS.
```php
use Xabbuh\XApi\Model\Statement;
$statement = new Statement();
// ...
// store a single Statement
$statementsApiClient->storeStatement($statement);
$statement2 = new Statement();
// ...
// store a collection of clients
$statementsApiClient->storeStatements(array($statement, $statement2));
```
Retrieving Statements
---------------------
Use the ``getStatement()`` method to obtain a certain Statement given its id:
```php
// ...
// get a single Statement
$statement = $statementsApiClient->getStatement($statementId);
```
``getStatements()`` returns a collection of Statements encapsulated in a
StatementResult instance:
```php
// ...
// returns all accessible Statements
$result = $statementsApiClient->getStatements();
```
You can even filter Statements using a StatementFilter:
```php
use Xabbuh\XApi\Model\StatementsFilter;
// ...
$filter = new StatementsFilter();
$filter
->byActor($actor) // filter by Actor
->byVerb($verb) // filter by Verb
->byActivity($activity) // filter by Activity
->byRegistration(...) // filter for Statements matching the given
// registration id
->enableRelatedActivityFilter() // apply the Activity filter to Sub-Statements
->disableRelatedActivityFilter() // apply the Activity filter to Sub-Statements
->enableRelatedAgentFilter() // apply the Agent filter to Sub-Statements
->disableRelatedAgentFilter() // apply the Agent filter to Sub-Statements
->since(new \DateTime(...)) // filter for Statements stored since
// the given timestamp
->until(new \DateTime(...)) // filter for Statements stored before
// the given timestamp
->limit(5) // limit the number of Statements returned
->format(...) // the result format (one of "ids", "exact",
// "canonical")
->includeAttachments() // return Statements with attachments included
->excludeAttachments() // return Statements without attachments
->ascending() // ascending order of stored time
->descending(); // ascending order of stored time
$result = $statementsApiClient->getStatements($filter->getFilter());
```
If you limited the number of returned results, you can get the next Statements
by calling the ``getNextStatements()`` method passing the ``StatementResult``
of the previous request to it:
```php
// ....
$filter = new StatementsFilter();
$filter->limit(3);
$firstStatementResult = $statementsApiClient->getStatements($filter);
// get the next Statements
$nextStatementResult = $statementsApiClient->getNextStatements($firstStatementResult);
```
The Experience API doesn't allow to delete Statements. You have to mark them as
voided instead:
```php
// ...
$statement = ...; // The Statement being voided
$actor = ...; // The Actor voiding the Statement
$statementsApiClient->voidStatement($statement, $actor);
```
Voided Statements won't be returned when requesting either a single Statement or
a collection of Statements. Though, you can retrieve a single voided Statement
using the ``getVoidedStatement()`` method:
```php
// ...
$voidedStatement = $statementsApiClient->getVoidedStatement($statementId);
```

View File

@@ -0,0 +1,4 @@
suites:
default:
namespace: Xabbuh\XApi\Client
psr4_prefix: Xabbuh\XApi\Client

13
vendor/php-xapi/client/phpunit.xml.dist vendored Normal file
View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true" bootstrap="vendor/autoload.php">
<testsuites>
<testsuite name="client side implementation of the Experience API">
<directory>./tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./src</directory>
</whitelist>
</filter>
</phpunit>

View File

@@ -0,0 +1,124 @@
<?php
namespace spec\Xabbuh\XApi\Client\Request;
use Http\Client\HttpClient;
use Http\Message\RequestFactory;
use PhpSpec\ObjectBehavior;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Xabbuh\XApi\Common\Exception\AccessDeniedException;
use Xabbuh\XApi\Common\Exception\ConflictException;
use Xabbuh\XApi\Common\Exception\NotFoundException;
use Xabbuh\XApi\Common\Exception\XApiException;
class HandlerSpec extends ObjectBehavior
{
function let(HttpClient $client, RequestFactory $requestFactory)
{
$this->beConstructedWith($client, $requestFactory, 'http://example.com/xapi/', '1.0.1');
}
function it_throws_an_exception_if_a_request_is_created_with_an_invalid_method()
{
$this->shouldThrow('\InvalidArgumentException')->during('createRequest', array('options', '/xapi/statements'));
}
function it_returns_get_request_created_by_the_http_client(RequestFactory $requestFactory, RequestInterface $request)
{
$requestFactory->createRequest('GET', 'http://example.com/xapi/statements', array(
'X-Experience-API-Version' => '1.0.1',
'Content-Type' => 'application/json',
), null)->willReturn($request);
$this->createRequest('get', '/statements')->shouldReturn($request);
$this->createRequest('GET', '/statements')->shouldReturn($request);
}
function it_returns_post_request_created_by_the_http_client(RequestFactory $requestFactory, RequestInterface $request)
{
$requestFactory->createRequest('POST', 'http://example.com/xapi/statements', array(
'X-Experience-API-Version' => '1.0.1',
'Content-Type' => 'application/json',
), 'body')->willReturn($request);
$this->createRequest('post', '/statements', array(), 'body')->shouldReturn($request);
$this->createRequest('POST', '/statements', array(), 'body')->shouldReturn($request);
}
function it_returns_put_request_created_by_the_http_client(RequestFactory $requestFactory, RequestInterface $request)
{
$requestFactory->createRequest('PUT', 'http://example.com/xapi/statements', array(
'X-Experience-API-Version' => '1.0.1',
'Content-Type' => 'application/json',
), 'body')->willReturn($request);
$this->createRequest('put', '/statements', array(), 'body')->shouldReturn($request);
$this->createRequest('PUT', '/statements', array(), 'body')->shouldReturn($request);
}
function it_returns_delete_request_created_by_the_http_client(RequestFactory $requestFactory, RequestInterface $request)
{
$requestFactory->createRequest('DELETE', 'http://example.com/xapi/statements', array(
'X-Experience-API-Version' => '1.0.1',
'Content-Type' => 'application/json',
), null)->willReturn($request);
$this->createRequest('delete', '/statements')->shouldReturn($request);
$this->createRequest('DELETE', '/statements')->shouldReturn($request);
}
function it_throws_an_access_denied_exception_when_a_401_status_code_is_returned(HttpClient $client, RequestInterface $request, ResponseInterface $response)
{
$client->sendRequest($request)->willReturn($response);
$response->getStatusCode()->willReturn(401);
$response->getBody()->willReturn('body');
$this->shouldThrow(AccessDeniedException::class)->during('executeRequest', array($request, array(200)));
}
function it_throws_an_access_denied_exception_when_a_403_status_code_is_returned(HttpClient $client, RequestInterface $request, ResponseInterface $response)
{
$client->sendRequest($request)->willReturn($response);
$response->getStatusCode()->willReturn(403);
$response->getBody()->willReturn('body');
$this->shouldThrow(AccessDeniedException::class)->during('executeRequest', array($request, array(200)));
}
function it_throws_a_not_found_exception_when_a_404_status_code_is_returned(HttpClient $client, RequestInterface $request, ResponseInterface $response)
{
$client->sendRequest($request)->willReturn($response);
$response->getStatusCode()->willReturn(404);
$response->getBody()->willReturn('body');
$this->shouldThrow(NotFoundException::class)->during('executeRequest', array($request, array(200)));
}
function it_throws_a_conflict_exception_when_a_409_status_code_is_returned(HttpClient $client, RequestInterface $request, ResponseInterface $response)
{
$client->sendRequest($request)->willReturn($response);
$response->getStatusCode()->willReturn(409);
$response->getBody()->willReturn('body');
$this->shouldThrow(ConflictException::class)->during('executeRequest', array($request, array(200)));
}
function it_throws_an_xapi_exception_when_an_unexpected_status_code_is_returned(HttpClient $client, RequestInterface $request, ResponseInterface $response)
{
$client->sendRequest($request)->willReturn($response);
$response->getStatusCode()->willReturn(204);
$response->getBody()->willReturn('body');
$this->shouldThrow(XApiException::class)->during('executeRequest', array($request, array(200)));
}
function it_returns_the_response_on_success(HttpClient $client, RequestInterface $request, ResponseInterface $response)
{
$client->sendRequest($request)->willReturn($response);
$response->getStatusCode()->willReturn(200);
$response->getBody()->willReturn('body');
$this->executeRequest($request, array(200))->shouldReturn($response);
}
}

View File

@@ -0,0 +1,139 @@
<?php
namespace spec\Xabbuh\XApi\Client;
use Http\Client\HttpClient;
use Http\Discovery\HttpClientDiscovery;
use Http\Discovery\MessageFactoryDiscovery;
use Http\Message\RequestFactory;
use PhpSpec\Exception\Example\SkippingException;
use PhpSpec\ObjectBehavior;
use Xabbuh\Http\Authentication\OAuth1;
use Xabbuh\XApi\Client\XApiClientBuilderInterface;
use Xabbuh\XApi\Client\XApiClientInterface;
class XApiClientBuilderSpec extends ObjectBehavior
{
function it_is_an_xapi_client_builder()
{
$this->shouldHaveType(XApiClientBuilderInterface::class);
}
function it_creates_an_xapi_client(HttpClient $httpClient, RequestFactory $requestFactory)
{
$this->setHttpClient($httpClient);
$this->setRequestFactory($requestFactory);
$this->setBaseUrl('http://example.com/xapi/');
$this->build()->shouldHaveType(XApiClientInterface::class);
}
function its_methods_can_be_chained(HttpClient $httpClient, RequestFactory $requestFactory)
{
$this->setHttpClient($httpClient)->shouldReturn($this);
$this->setRequestFactory($requestFactory)->shouldReturn($this);
$this->setBaseUrl('http://example.com/xapi/')->shouldReturn($this);
$this->setVersion('1.0.0')->shouldReturn($this);
$this->setAuth('foo', 'bar')->shouldReturn($this);
$this->setOAuthCredentials('consumer key', 'consumer secret', 'token', 'token secret')->shouldReturn($this);
}
function it_throws_an_exception_if_the_http_client_is_not_configured(RequestFactory $requestFactory)
{
if ($this->isAbleToDiscoverHttpClient()) {
throw new SkippingException('The builder does not throw an exception if it can automatically discover an HTTP client.');
}
$this->setRequestFactory($requestFactory);
$this->setBaseUrl('http://example.com/xapi/');
$this->shouldThrow('\LogicException')->during('build');
}
function it_throws_an_exception_if_the_request_factory_is_not_configured(HttpClient $httpClient)
{
if ($this->isAbleToDiscoverRequestFactory()) {
throw new SkippingException('The builder does not throw an exception if it can automatically discover a request factory.');
}
$this->setHttpClient($httpClient);
$this->setBaseUrl('http://example.com/xapi/');
$this->shouldThrow('\LogicException')->during('build');
}
function it_can_build_the_client_when_it_is_able_to_discover_the_http_client_and_the_request_factory_without_configuring_them_explicitly()
{
if (!class_exists(HttpClientDiscovery::class)) {
throw new SkippingException(sprintf('The "%s" class is required to let the builder auto discover the HTTP client and request factory.', HttpClientDiscovery::class));
}
if (!$this->isAbleToDiscoverHttpClient()) {
throw new SkippingException('Unable to discover an HTTP client.');
}
if (!$this->isAbleToDiscoverRequestFactory()) {
throw new SkippingException('Unable to discover a request factory.');
}
$this->setBaseUrl('http://example.com/xapi/');
$this->build()->shouldReturnAnInstanceOf(XApiClientInterface::class);
}
function it_throws_an_exception_if_the_base_uri_is_not_configured(HttpClient $httpClient, RequestFactory $requestFactory)
{
$this->setHttpClient($httpClient);
$this->setRequestFactory($requestFactory);
$this->shouldThrow('\LogicException')->during('build');
}
function it_throws_an_exception_when_oauth_credentials_are_configured_but_the_auth_package_is_missing(HttpClient $httpClient, RequestFactory $requestFactory)
{
if (class_exists(OAuth1::class)) {
throw new SkippingException('OAuth1 credentials can be used when the "xabbuh/oauth1-authentication" package is present.');
}
$this->setHttpClient($httpClient);
$this->setRequestFactory($requestFactory);
$this->setBaseUrl('http://example.com/xapi/');
$this->setOAuthCredentials('consumer_key', 'consumer_secret', 'access_token', 'token_secret');
$this->shouldThrow(new \LogicException('The "xabbuh/oauth1-authentication package is needed to use OAuth1 authorization.'))->during('build');
}
function it_accepts_oauth_credentials_when_the_auth_package_is_present(HttpClient $httpClient, RequestFactory $requestFactory)
{
if (!class_exists(OAuth1::class)) {
throw new SkippingException('OAuth1 credentials cannot be used when the "xabbuh/oauth1-authentication" package is missing.');
}
$this->setHttpClient($httpClient);
$this->setRequestFactory($requestFactory);
$this->setBaseUrl('http://example.com/xapi/');
$this->setOAuthCredentials('consumer_key', 'consumer_secret', 'access_token', 'token_secret');
$this->build();
}
private function isAbleToDiscoverHttpClient()
{
try {
HttpClientDiscovery::find();
return true;
} catch (\Exception $e) {
return false;
}
}
private function isAbleToDiscoverRequestFactory()
{
try {
MessageFactoryDiscovery::find();
return true;
} catch (\Exception $e) {
return false;
}
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace spec\Xabbuh\XApi\Client;
use PhpSpec\ObjectBehavior;
use Xabbuh\XApi\Client\Api\ActivityProfileApiClientInterface;
use Xabbuh\XApi\Client\Api\AgentProfileApiClientInterface;
use Xabbuh\XApi\Client\Api\StateApiClientInterface;
use Xabbuh\XApi\Client\Api\StatementsApiClientInterface;
use Xabbuh\XApi\Client\Request\HandlerInterface;
use Xabbuh\XApi\Serializer\ActorSerializerInterface;
use Xabbuh\XApi\Serializer\DocumentDataSerializerInterface;
use Xabbuh\XApi\Serializer\SerializerRegistryInterface;
use Xabbuh\XApi\Serializer\StatementResultSerializerInterface;
use Xabbuh\XApi\Serializer\StatementSerializerInterface;
class XApiClientSpec extends ObjectBehavior
{
function let(
HandlerInterface $requestHandler,
SerializerRegistryInterface $serializerRegistry,
ActorSerializerInterface $actorSerializer,
DocumentDataSerializerInterface $documentDataSerializer,
StatementSerializerInterface $statementSerializer,
StatementResultSerializerInterface $statementResultSerializer
) {
$serializerRegistry->getActorSerializer()->willReturn($actorSerializer);
$serializerRegistry->getDocumentDataSerializer()->willReturn($documentDataSerializer);
$serializerRegistry->getStatementSerializer()->willReturn($statementSerializer);
$serializerRegistry->getStatementResultSerializer()->willReturn($statementResultSerializer);
$this->beConstructedWith($requestHandler, $serializerRegistry, '1.0.1');
}
function it_returns_a_statements_api_client_instance()
{
$this->getStatementsApiClient()->shouldBeAnInstanceOf(StatementsApiClientInterface::class);
}
function it_returns_an_activity_profile_api_client_instance()
{
$this->getActivityProfileApiClient()->shouldBeAnInstanceOf(ActivityProfileApiClientInterface::class);
}
function it_returns_an_agent_profile_api_client_instance()
{
$this->getAgentProfileApiClient()->shouldBeAnInstanceOf(AgentProfileApiClientInterface::class);
}
function it_returns_a_state_api_client_instance()
{
$this->getStateApiClient()->shouldBeAnInstanceOf(StateApiClientInterface::class);
}
}

View File

@@ -0,0 +1,85 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Xabbuh\XApi\Client\Api;
use Xabbuh\XApi\Model\ActivityProfile;
use Xabbuh\XApi\Model\ActivityProfileDocument;
/**
* Client to access the activity profile API of an xAPI based learning record
* store.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
final class ActivityProfileApiClient extends DocumentApiClient implements ActivityProfileApiClientInterface
{
/**
* {@inheritDoc}
*/
public function createOrUpdateDocument(ActivityProfileDocument $document)
{
$this->doStoreActivityProfileDocument('post', $document);
}
/**
* {@inheritDoc}
*/
public function createOrReplaceDocument(ActivityProfileDocument $document)
{
$this->doStoreActivityProfileDocument('put', $document);
}
/**
* {@inheritDoc}
*/
public function deleteDocument(ActivityProfile $profile)
{
$this->doDeleteDocument('activities/profile', array(
'activityId' => $profile->getActivity()->getId()->getValue(),
'profileId' => $profile->getProfileId(),
));
}
/**
* {@inheritDoc}
*/
public function getDocument(ActivityProfile $profile)
{
/** @var \Xabbuh\XApi\Model\DocumentData $documentData */
$documentData = $this->doGetDocument('activities/profile', array(
'activityId' => $profile->getActivity()->getId()->getValue(),
'profileId' => $profile->getProfileId(),
));
return new ActivityProfileDocument($profile, $documentData);
}
/**
* Stores a state document.
*
* @param string $method HTTP method to use
* @param ActivityProfileDocument $document The document to store
*/
private function doStoreActivityProfileDocument($method, ActivityProfileDocument $document)
{
$profile = $document->getActivityProfile();
$this->doStoreDocument(
$method,
'activities/profile',
array(
'activityId' => $profile->getActivity()->getId()->getValue(),
'profileId' => $profile->getProfileId(),
),
$document
);
}
}

View File

@@ -0,0 +1,57 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Xabbuh\XApi\Client\Api;
use Xabbuh\XApi\Model\ActivityProfile;
use Xabbuh\XApi\Model\ActivityProfileDocument;
/**
* Client to access the activity profile API of an xAPI based learning record
* store.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
interface ActivityProfileApiClientInterface
{
/**
* Stores a document for an activity profile. Updates an existing document
* for this activity profile if one exists.
*
* @param ActivityProfileDocument $document The document to store
*/
public function createOrUpdateDocument(ActivityProfileDocument $document);
/**
* Stores a document for an activity profile. Replaces any existing document
* for this activity profile.
*
* @param ActivityProfileDocument $document The document to store
*/
public function createOrReplaceDocument(ActivityProfileDocument $document);
/**
* Deletes a document stored for the given activity profile.
*
* @param ActivityProfile $profile The activity profile
*/
public function deleteDocument(ActivityProfile $profile);
/**
* Returns the document for an activity profile.
*
* @param ActivityProfile $profile The activity profile to request the
* document for
*
* @return ActivityProfileDocument The document
*/
public function getDocument(ActivityProfile $profile);
}

View File

@@ -0,0 +1,98 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Xabbuh\XApi\Client\Api;
use Xabbuh\XApi\Client\Request\HandlerInterface;
use Xabbuh\XApi\Serializer\ActorSerializerInterface;
use Xabbuh\XApi\Serializer\DocumentDataSerializerInterface;
use Xabbuh\XApi\Model\AgentProfile;
use Xabbuh\XApi\Model\AgentProfileDocument;
/**
* Client to access the agent profile API of an xAPI based learning record
* store.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
final class AgentProfileApiClient extends DocumentApiClient implements AgentProfileApiClientInterface
{
/**
* @var ActorSerializerInterface
*/
private $actorSerializer;
/**
* @param HandlerInterface $requestHandler The HTTP request handler
* @param string $version The xAPI version
* @param DocumentDataSerializerInterface $documentDataSerializer The document data serializer
* @param ActorSerializerInterface $actorSerializer The actor serializer
*/
public function __construct(
HandlerInterface $requestHandler,
$version,
DocumentDataSerializerInterface $documentDataSerializer,
ActorSerializerInterface $actorSerializer
) {
parent::__construct($requestHandler, $version, $documentDataSerializer);
$this->actorSerializer = $actorSerializer;
}
/**
* {@inheritDoc}
*/
public function createOrUpdateDocument(AgentProfileDocument $document)
{
$profile = $document->getAgentProfile();
$this->doStoreDocument('post', 'agents/profile', array(
'agent' => $this->actorSerializer->serializeActor($profile->getAgent()),
'profileId' => $profile->getProfileId(),
), $document);
}
/**
* {@inheritDoc}
*/
public function createOrReplaceDocument(AgentProfileDocument $document)
{
$profile = $document->getAgentProfile();
$this->doStoreDocument('put', 'agents/profile', array(
'agent' => $this->actorSerializer->serializeActor($profile->getAgent()),
'profileId' => $profile->getProfileId(),
), $document);
}
/**
* {@inheritDoc}
*/
public function deleteDocument(AgentProfile $profile)
{
$this->doDeleteDocument('agents/profile', array(
'agent' => $this->actorSerializer->serializeActor($profile->getAgent()),
'profileId' => $profile->getProfileId(),
));
}
/**
* {@inheritDoc}
*/
public function getDocument(AgentProfile $profile)
{
/** @var \Xabbuh\XApi\Model\DocumentData $documentData */
$documentData = $this->doGetDocument('agents/profile', array(
'agent' => $this->actorSerializer->serializeActor($profile->getAgent()),
'profileId' => $profile->getProfileId(),
));
return new AgentProfileDocument($profile, $documentData);
}
}

View File

@@ -0,0 +1,56 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Xabbuh\XApi\Client\Api;
use Xabbuh\XApi\Model\AgentProfile;
use Xabbuh\XApi\Model\AgentProfileDocument;
/**
* Client to access the agent profile API of an xAPI based learning record
* store.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
interface AgentProfileApiClientInterface
{
/**
* Stores a document for an agent profile. Updates an existing document for
* this agent profile if one exists.
*
* @param AgentProfileDocument $document The document to store
*/
public function createOrUpdateDocument(AgentProfileDocument $document);
/**
* Stores a document for an agent profile. Replaces any existing document
* for this agent profile.
*
* @param AgentProfileDocument $document The document to store
*/
public function createOrReplaceDocument(AgentProfileDocument $document);
/**
* Deletes a document stored for the given agent profile.
*
* @param AgentProfile $profile The agent profile
*/
public function deleteDocument(AgentProfile $profile);
/**
* Returns the document for an agent profile.
*
* @param AgentProfile $profile The agent profile to request the document for
*
* @return AgentProfileDocument The document
*/
public function getDocument(AgentProfile $profile);
}

View File

@@ -0,0 +1,101 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Xabbuh\XApi\Client\Api;
use Xabbuh\XApi\Client\Request\HandlerInterface;
use Xabbuh\XApi\Model\Document;
use Xabbuh\XApi\Model\DocumentData;
use Xabbuh\XApi\Serializer\DocumentDataSerializerInterface;
/**
* Base class for the document API classes.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
abstract class DocumentApiClient
{
private $requestHandler;
private $version;
private $documentDataSerializer;
/**
* @param HandlerInterface $requestHandler The HTTP request handler
* @param string $version The xAPI version
* @param DocumentDataSerializerInterface $documentDataSerializer The document data serializer
*/
public function __construct(HandlerInterface $requestHandler, $version, DocumentDataSerializerInterface $documentDataSerializer)
{
$this->requestHandler = $requestHandler;
$this->version = $version;
$this->documentDataSerializer = $documentDataSerializer;
}
/**
* Stores a document.
*
* @param string $method HTTP method to use
* @param string $uri Endpoint URI
* @param array $urlParameters URL parameters
* @param Document $document The document to store
*/
protected function doStoreDocument($method, $uri, $urlParameters, Document $document)
{
$request = $this->requestHandler->createRequest(
$method,
$uri,
$urlParameters,
$this->documentDataSerializer->serializeDocumentData($document->getData())
);
$this->requestHandler->executeRequest($request, array(204));
}
/**
* Deletes a document.
*
* @param string $uri The endpoint URI
* @param array $urlParameters The URL parameters
*/
protected function doDeleteDocument($uri, array $urlParameters)
{
$request = $this->requestHandler->createRequest('delete', $uri, $urlParameters);
$this->requestHandler->executeRequest($request, array(204));
}
/**
* Returns a document.
*
* @param string $uri The endpoint URI
* @param array $urlParameters The URL parameters
*
* @return Document The document
*/
protected function doGetDocument($uri, array $urlParameters)
{
$request = $this->requestHandler->createRequest('get', $uri, $urlParameters);
$response = $this->requestHandler->executeRequest($request, array(200));
$document = $this->deserializeDocument((string) $response->getBody());
return $document;
}
/**
* Deserializes the data of a document.
*
* @param string $data The serialized document data
*
* @return DocumentData The parsed document data
*/
protected function deserializeDocument($data)
{
return $this->documentDataSerializer->deserializeDocumentData($data);
}
}

View File

@@ -0,0 +1,112 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Xabbuh\XApi\Client\Api;
use Xabbuh\XApi\Client\Request\HandlerInterface;
use Xabbuh\XApi\Serializer\ActorSerializerInterface;
use Xabbuh\XApi\Serializer\DocumentDataSerializerInterface;
use Xabbuh\XApi\Model\StateDocument;
use Xabbuh\XApi\Model\State;
/**
* Client to access the state API of an xAPI based learning record store.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
final class StateApiClient extends DocumentApiClient implements StateApiClientInterface
{
/**
* @var ActorSerializerInterface
*/
private $actorSerializer;
/**
* @param HandlerInterface $requestHandler The HTTP request handler
* @param string $version The xAPI version
* @param DocumentDataSerializerInterface $documentDataSerializer The document data serializer
* @param ActorSerializerInterface $actorSerializer The actor serializer
*/
public function __construct(
HandlerInterface $requestHandler,
$version,
DocumentDataSerializerInterface $documentDataSerializer,
ActorSerializerInterface $actorSerializer
) {
parent::__construct($requestHandler, $version, $documentDataSerializer);
$this->actorSerializer = $actorSerializer;
}
/**
* {@inheritDoc}
*/
public function createOrUpdateDocument(StateDocument $document)
{
$this->doStoreStateDocument('post', $document);
}
/**
* {@inheritDoc}
*/
public function createOrReplaceDocument(StateDocument $document)
{
$this->doStoreStateDocument('put', $document);
}
/**
* {@inheritDoc}
*/
public function deleteDocument(State $state)
{
$this->doDeleteDocument('activities/state', array(
'activityId' => $state->getActivity()->getId()->getValue(),
'agent' => $this->actorSerializer->serializeActor($state->getActor()),
'stateId' => $state->getStateId(),
));
}
/**
* {@inheritDoc}
*/
public function getDocument(State $state)
{
/** @var \Xabbuh\XApi\Model\DocumentData $documentData */
$documentData = $this->doGetDocument('activities/state', array(
'activityId' => $state->getActivity()->getId()->getValue(),
'agent' => $this->actorSerializer->serializeActor($state->getActor()),
'stateId' => $state->getStateId(),
));
return new StateDocument($state, $documentData);
}
/**
* Stores a state document.
*
* @param string $method HTTP method to use
* @param StateDocument $document The document to store
*/
private function doStoreStateDocument($method, StateDocument $document)
{
$state = $document->getState();
$this->doStoreDocument(
$method,
'activities/state',
array(
'activityId' => $state->getActivity()->getId()->getValue(),
'agent' => $this->actorSerializer->serializeActor($state->getActor()),
'stateId' => $state->getStateId(),
),
$document
);
}
}

View File

@@ -0,0 +1,55 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Xabbuh\XApi\Client\Api;
use Xabbuh\XApi\Model\StateDocument;
use Xabbuh\XApi\Model\State;
/**
* Client to access the state API of an xAPI based learning record store.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
interface StateApiClientInterface
{
/**
* Stores a document for a state. Updates an existing document for this
* state if one exists.
*
* @param StateDocument $document The document to store
*/
public function createOrUpdateDocument(StateDocument $document);
/**
* Stores a document for a state. Replaces any existing document for this
* state.
*
* @param StateDocument $document The document to store
*/
public function createOrReplaceDocument(StateDocument $document);
/**
* Deletes a document stored for the given state.
*
* @param State $state The state
*/
public function deleteDocument(State $state);
/**
* Returns the document for a state.
*
* @param State $state The state to request the document for
*
* @return StateDocument The document
*/
public function getDocument(State $state);
}

View File

@@ -0,0 +1,309 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Xabbuh\XApi\Client\Api;
use Xabbuh\XApi\Client\Http\MultipartStatementBody;
use Xabbuh\XApi\Client\Request\HandlerInterface;
use Xabbuh\XApi\Model\StatementId;
use Xabbuh\XApi\Serializer\ActorSerializerInterface;
use Xabbuh\XApi\Serializer\StatementResultSerializerInterface;
use Xabbuh\XApi\Serializer\StatementSerializerInterface;
use Xabbuh\XApi\Model\Actor;
use Xabbuh\XApi\Model\Statement;
use Xabbuh\XApi\Model\StatementResult;
use Xabbuh\XApi\Model\StatementsFilter;
/**
* Client to access the statements API of an xAPI based learning record store.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
final class StatementsApiClient implements StatementsApiClientInterface
{
private $requestHandler;
private $version;
private $statementSerializer;
private $statementResultSerializer;
private $actorSerializer;
/**
* @param HandlerInterface $requestHandler The HTTP request handler
* @param string $version The xAPI version
* @param StatementSerializerInterface $statementSerializer The statement serializer
* @param StatementResultSerializerInterface $statementResultSerializer The statement result serializer
* @param ActorSerializerInterface $actorSerializer The actor serializer
*/
public function __construct(
HandlerInterface $requestHandler,
$version,
StatementSerializerInterface $statementSerializer,
StatementResultSerializerInterface $statementResultSerializer,
ActorSerializerInterface $actorSerializer
) {
$this->requestHandler = $requestHandler;
$this->version = $version;
$this->statementSerializer = $statementSerializer;
$this->statementResultSerializer = $statementResultSerializer;
$this->actorSerializer = $actorSerializer;
}
/**
* {@inheritDoc}
*/
public function storeStatement(Statement $statement)
{
if (null !== $statement->getId()) {
return $this->doStoreStatements(
$statement,
'put',
array('statementId' => $statement->getId()->getValue()),
204
);
} else {
return $this->doStoreStatements($statement);
}
}
/**
* {@inheritDoc}
*/
public function storeStatements(array $statements)
{
// check that only Statements without ids will be sent to the LRS
foreach ($statements as $statement) {
/** @var Statement $statement */
$isStatement = is_object($statement) && $statement instanceof Statement;
if (!$isStatement || null !== $statement->getId()) {
throw new \InvalidArgumentException('API can only handle statements without ids');
}
}
return $this->doStoreStatements($statements);
}
/**
* {@inheritDoc}
*/
public function voidStatement(Statement $statement, Actor $actor)
{
return $this->storeStatement($statement->getVoidStatement($actor));
}
/**
* {@inheritDoc}
*/
public function getStatement(StatementId $statementId, $attachments = true)
{
return $this->doGetStatements('statements', array(
'statementId' => $statementId->getValue(),
'attachments' => $attachments ? 'true' : 'false',
));
}
/**
* {@inheritDoc}
*/
public function getVoidedStatement(StatementId $statementId, $attachments = true)
{
return $this->doGetStatements('statements', array(
'voidedStatementId' => $statementId->getValue(),
'attachments' => $attachments ? 'true' : 'false',
));
}
/**
* {@inheritDoc}
*/
public function getStatements(StatementsFilter $filter = null, $attachments = true)
{
$urlParameters = array();
if (null !== $filter) {
$urlParameters = $filter->getFilter();
}
// the Agent must be JSON encoded
if (isset($urlParameters['agent'])) {
$urlParameters['agent'] = $this->actorSerializer->serializeActor($urlParameters['agent']);
}
return $this->doGetStatements('statements', $urlParameters);
}
/**
* {@inheritDoc}
*/
public function getNextStatements(StatementResult $statementResult)
{
return $this->doGetStatements($statementResult->getMoreUrlPath()->getValue());
}
/**
* @param Statement|Statement[] $statements
* @param string $method
* @param string[] $parameters
* @param int $validStatusCode
*
* @return Statement|Statement[] The created statement(s)
*/
private function doStoreStatements($statements, $method = 'post', $parameters = array(), $validStatusCode = 200)
{
$attachments = array();
if (is_array($statements)) {
foreach ($statements as $statement) {
if (null !== $statement->getAttachments()) {
foreach ($statement->getAttachments() as $attachment) {
if ($attachment->getContent()) {
$attachments[] = $attachment;
}
}
}
}
$serializedStatements = $this->statementSerializer->serializeStatements($statements);
} else {
if (null !== $statements->getAttachments()) {
foreach ($statements->getAttachments() as $attachment) {
if ($attachment->getContent()) {
$attachments[] = $attachment;
}
}
}
$serializedStatements = $this->statementSerializer->serializeStatement($statements);
}
$headers = array();
if (!empty($attachments)) {
$builder = new MultipartStatementBody($serializedStatements, $attachments);
$headers = array(
'Content-Type' => 'multipart/mixed; boundary='.$builder->getBoundary(),
);
$body = $builder->build();
} else {
$body = $serializedStatements;
}
$request = $this->requestHandler->createRequest(
$method,
'statements',
$parameters,
$body,
$headers
);
$response = $this->requestHandler->executeRequest($request, array($validStatusCode));
$statementIds = json_decode((string) $response->getBody());
if (is_array($statements)) {
/** @var Statement[] $statements */
$createdStatements = array();
foreach ($statements as $index => $statement) {
$createdStatements[] = $statement->withId(StatementId::fromString($statementIds[$index]));
}
return $createdStatements;
} else {
/** @var Statement $statements */
if (200 === $validStatusCode) {
return $statements->withId(StatementId::fromString($statementIds[0]));
} else {
return $statements;
}
}
}
/**
* Fetch one or more Statements.
*
* @param string $url URL to request
* @param array $urlParameters URL parameters
*
* @return Statement|StatementResult
*/
private function doGetStatements($url, array $urlParameters = array())
{
$request = $this->requestHandler->createRequest('get', $url, $urlParameters);
$response = $this->requestHandler->executeRequest($request, array(200));
$contentType = $response->getHeader('Content-Type')[0];
$body = (string) $response->getBody();
$attachments = array();
if (false !== strpos($contentType, 'application/json')) {
$serializedStatement = $body;
} else {
$boundary = substr($contentType, strpos($contentType, '=') + 1);
$parts = $this->parseMultipartResponseBody($body, $boundary);
$serializedStatement = $parts[0]['content'];
unset($parts[0]);
foreach ($parts as $part) {
$attachments[$part['headers']['X-Experience-API-Hash'][0]] = array(
'type' => $part['headers']['Content-Type'][0],
'content' => $part['content'],
);
}
}
if (isset($urlParameters['statementId']) || isset($urlParameters['voidedStatementId'])) {
return $this->statementSerializer->deserializeStatement($serializedStatement, $attachments);
} else {
return $this->statementResultSerializer->deserializeStatementResult($serializedStatement, $attachments);
}
}
private function parseMultipartResponseBody($body, $boundary)
{
$parts = array();
$lines = explode("\r\n", $body);
$currentPart = null;
$isHeaderLine = true;
foreach ($lines as $line) {
if (false !== strpos($line, '--'.$boundary)) {
if (null !== $currentPart) {
$parts[] = $currentPart;
}
$currentPart = array(
'headers' => array(),
'content' => '',
);
$isBoundaryLine = true;
$isHeaderLine = true;
} else {
$isBoundaryLine = false;
}
if ('' === $line) {
$isHeaderLine = false;
continue;
}
if (!$isBoundaryLine && !$isHeaderLine) {
$currentPart['content'] .= $line;
} elseif (!$isBoundaryLine && $isHeaderLine) {
list($name, $value) = explode(':', $line, 2);
$currentPart['headers'][$name][] = $value;
}
}
return $parts;
}
}

View File

@@ -0,0 +1,121 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Xabbuh\XApi\Client\Api;
use Xabbuh\XApi\Common\Exception\ConflictException;
use Xabbuh\XApi\Common\Exception\NotFoundException;
use Xabbuh\XApi\Common\Exception\XApiException;
use Xabbuh\XApi\Model\Actor;
use Xabbuh\XApi\Model\Statement;
use Xabbuh\XApi\Model\StatementId;
use Xabbuh\XApi\Model\StatementResult;
use Xabbuh\XApi\Model\StatementsFilter;
/**
* Client to access the statements API of an xAPI based learning record store.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
interface StatementsApiClientInterface
{
/**
* Stores a single {@link Statement}.
*
* @param Statement $statement The Statement to store
*
* @return Statement The Statement as it has been stored in the remote LRS,
* this is not necessarily the same object that was
* passed to storeStatement()
*
* @throws ConflictException if a Statement with the given id already exists
* and the given Statement does not match the
* stored Statement
* @throws XApiException for all other xAPI related problems
*/
public function storeStatement(Statement $statement);
/**
* Stores a collection of {@link Statement Statements}.
*
* @param Statement[] $statements The statements to store
*
* @return Statement[] The stored Statements
*
* @throws \InvalidArgumentException if a given object is no Statement or
* if one of the Statements has an id
* @throws XApiException for all other xAPI related problems
*/
public function storeStatements(array $statements);
/**
* Marks a {@link Statement} as voided.
*
* @param Statement $statement The Statement to void
* @param Actor $actor The Actor voiding the given Statement
*
* @return Statement The Statement sent to the remote LRS to void the
* given Statement
*
* @throws XApiException for all other xAPI related problems
*/
public function voidStatement(Statement $statement, Actor $actor);
/**
* Retrieves a single {@link Statement Statement}.
*
* @param StatementId $statementId The Statement id
* @param bool $attachments Whether or not to request raw attachment data
*
* @return Statement The Statement
*
* @throws NotFoundException if no statement with the given id could be found
* @throws XApiException for all other xAPI related problems
*/
public function getStatement(StatementId $statementId, $attachments = true);
/**
* Retrieves a voided {@link Statement Statement}.
*
* @param StatementId $statementId The id of the voided Statement
* @param bool $attachments Whether or not to request raw attachment data
*
* @return Statement The voided Statement
*
* @throws NotFoundException if no statement with the given id could be found
* @throws XApiException for all other xAPI related problems
*/
public function getVoidedStatement(StatementId $statementId, $attachments = true);
/**
* Retrieves a collection of {@link Statement Statements}.
*
* @param StatementsFilter $filter Optional Statements filter
* @param bool $attachments Whether or not to request raw attachment data
*
* @return StatementResult The {@link StatementResult}
*
* @throws XApiException in case of any problems related to the xAPI
*/
public function getStatements(StatementsFilter $filter = null, $attachments = true);
/**
* Returns the next {@link Statement Statements} for a limited Statement
* result.
*
* @param StatementResult $statementResult The former StatementResult
*
* @return StatementResult The {@link StatementResult}
*
* @throws XApiException in case of any problems related to the xAPI
*/
public function getNextStatements(StatementResult $statementResult);
}

View File

@@ -0,0 +1,65 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Xabbuh\XApi\Client\Http;
use Xabbuh\XApi\Model\Attachment;
/**
* HTTP message body containing serialized statements and their attachments.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
final class MultipartStatementBody
{
private $boundary;
private $serializedStatements;
private $attachments;
/**
* @param string $serializedStatements The JSON encoded statement(s)
* @param Attachment[] $attachments The statement attachments that include not only a file URL
*/
public function __construct($serializedStatements, array $attachments)
{
$this->boundary = uniqid();
$this->serializedStatements = $serializedStatements;
$this->attachments = $attachments;
}
public function getBoundary()
{
return $this->boundary;
}
public function build()
{
$body = '--'.$this->boundary."\r\n";
$body .= "Content-Type: application/json\r\n";
$body .= 'Content-Length: '.strlen($this->serializedStatements)."\r\n";
$body .= "\r\n";
$body .= $this->serializedStatements."\r\n";
foreach ($this->attachments as $attachment) {
$body .= '--'.$this->boundary."\r\n";
$body .= 'Content-Type: '.$attachment->getContentType()."\r\n";
$body .= "Content-Transfer-Encoding: binary\r\n";
$body .= 'Content-Length: '.$attachment->getLength()."\r\n";
$body .= 'X-Experience-API-Hash: '.$attachment->getSha2()."\r\n";
$body .= "\r\n";
$body .= $attachment->getContent()."\r\n";
}
$body .= '--'.$this->boundary.'--'."\r\n";
return $body;
}
}

View File

@@ -0,0 +1,104 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Xabbuh\XApi\Client\Request;
use Http\Client\Exception;
use Http\Client\HttpClient;
use Http\Message\RequestFactory;
use Psr\Http\Message\RequestInterface;
use Xabbuh\XApi\Common\Exception\AccessDeniedException;
use Xabbuh\XApi\Common\Exception\ConflictException;
use Xabbuh\XApi\Common\Exception\NotFoundException;
use Xabbuh\XApi\Common\Exception\XApiException;
/**
* Prepares and executes xAPI HTTP requests.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
final class Handler implements HandlerInterface
{
private $httpClient;
private $requestFactory;
private $baseUri;
private $version;
/**
* @param HttpClient $httpClient The HTTP client sending requests to the remote LRS
* @param RequestFactory $requestFactory The factory used to create PSR-7 HTTP requests
* @param string $baseUri The APIs base URI (all end points will be created relatively to this URI)
* @param string $version The xAPI version
*/
public function __construct(HttpClient $httpClient, RequestFactory $requestFactory, $baseUri, $version)
{
$this->httpClient = $httpClient;
$this->requestFactory = $requestFactory;
$this->baseUri = $baseUri;
$this->version = $version;
}
/**
* {@inheritDoc}
*/
public function createRequest($method, $uri, array $urlParameters = array(), $body = null, array $headers = array())
{
if (!in_array(strtoupper($method), array('GET', 'POST', 'PUT', 'DELETE'))) {
throw new \InvalidArgumentException(sprintf('"%s" is no valid HTTP method (expected one of [GET, POST, PUT, DELETE]) in an xAPI context.', $method));
}
$uri = rtrim($this->baseUri, '/').'/'.ltrim($uri, '/');
if (count($urlParameters) > 0) {
$uri .= '?'.http_build_query($urlParameters);
}
if (!isset($headers['X-Experience-API-Version'])) {
$headers['X-Experience-API-Version'] = $this->version;
}
if (!isset($headers['Content-Type'])) {
$headers['Content-Type'] = 'application/json';
}
return $this->requestFactory->createRequest(strtoupper($method), $uri, $headers, $body);
}
/**
* {@inheritDoc}
*/
public function executeRequest(RequestInterface $request, array $validStatusCodes)
{
try {
$response = $this->httpClient->sendRequest($request);
} catch (Exception $e) {
throw new XApiException($e->getMessage(), $e->getCode(), $e);
}
// catch some common errors
if (in_array($response->getStatusCode(), array(401, 403))) {
throw new AccessDeniedException(
(string) $response->getBody(),
$response->getStatusCode()
);
} elseif (404 === $response->getStatusCode()) {
throw new NotFoundException((string) $response->getBody());
} elseif (409 === $response->getStatusCode()) {
throw new ConflictException((string) $response->getBody());
}
if (!in_array($response->getStatusCode(), $validStatusCodes)) {
throw new XApiException((string) $response->getBody(), $response->getStatusCode());
}
return $response;
}
}

View File

@@ -0,0 +1,52 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Xabbuh\XApi\Client\Request;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
use Xabbuh\XApi\Common\Exception\XApiException;
/**
* Prepare and execute xAPI HTTP requests.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
interface HandlerInterface
{
/**
* @param string $method The HTTP method
* @param string $uri The URI to send the request to
* @param array $urlParameters Optional url parameters
* @param string $body An optional request body
* @param array $headers Optional additional HTTP headers
*
* @return RequestInterface The request
*
* @throws \InvalidArgumentException when no valid HTTP method is given
*/
public function createRequest($method, $uri, array $urlParameters = array(), $body = null, array $headers = array());
/**
* Performs the given HTTP request.
*
* @param RequestInterface $request The HTTP request to perform
* @param int[] $validStatusCodes A list of HTTP status codes
* the calling method is able to
* handle
*
* @return ResponseInterface The remote server's response
*
* @throws XApiException when the request fails
*/
public function executeRequest(RequestInterface $request, array $validStatusCodes);
}

View File

@@ -0,0 +1,97 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Xabbuh\XApi\Client;
use Xabbuh\XApi\Client\Api\ActivityProfileApiClient;
use Xabbuh\XApi\Client\Api\AgentProfileApiClient;
use Xabbuh\XApi\Client\Api\ApiClient;
use Xabbuh\XApi\Client\Api\StateApiClient;
use Xabbuh\XApi\Client\Api\StatementsApiClient;
use Xabbuh\XApi\Client\Request\HandlerInterface;
use Xabbuh\XApi\Serializer\SerializerRegistryInterface;
/**
* An Experience API client.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
final class XApiClient implements XApiClientInterface
{
/**
* @var SerializerRegistryInterface
*/
private $serializerRegistry;
/**
* @param HandlerInterface $requestHandler The HTTP request handler
* @param SerializerRegistryInterface $serializerRegistry The serializer registry
* @param string $version The xAPI version
*/
public function __construct(HandlerInterface $requestHandler, SerializerRegistryInterface $serializerRegistry, $version)
{
$this->requestHandler = $requestHandler;
$this->serializerRegistry = $serializerRegistry;
$this->version = $version;
}
/**
* {@inheritDoc}
*/
public function getStatementsApiClient()
{
return new StatementsApiClient(
$this->requestHandler,
$this->version,
$this->serializerRegistry->getStatementSerializer(),
$this->serializerRegistry->getStatementResultSerializer(),
$this->serializerRegistry->getActorSerializer()
);
}
/**
* {@inheritDoc}
*/
public function getStateApiClient()
{
return new StateApiClient(
$this->requestHandler,
$this->version,
$this->serializerRegistry->getDocumentDataSerializer(),
$this->serializerRegistry->getActorSerializer()
);
}
/**
* {@inheritDoc}
*/
public function getActivityProfileApiClient()
{
return new ActivityProfileApiClient(
$this->requestHandler,
$this->version,
$this->serializerRegistry->getDocumentDataSerializer()
);
}
/**
* {@inheritDoc}
*/
public function getAgentProfileApiClient()
{
return new AgentProfileApiClient(
$this->requestHandler,
$this->version,
$this->serializerRegistry->getDocumentDataSerializer(),
$this->serializerRegistry->getActorSerializer()
);
}
}

View File

@@ -0,0 +1,191 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Xabbuh\XApi\Client;
use ApiClients\Tools\Psr7\Oauth1\Definition\AccessToken;
use ApiClients\Tools\Psr7\Oauth1\Definition\ConsumerKey;
use ApiClients\Tools\Psr7\Oauth1\Definition\ConsumerSecret;
use ApiClients\Tools\Psr7\Oauth1\Definition\TokenSecret;
use ApiClients\Tools\Psr7\Oauth1\RequestSigning\RequestSigner;
use Http\Client\Common\Plugin\AuthenticationPlugin;
use Http\Client\Common\PluginClient;
use Http\Client\HttpClient;
use Http\Discovery\HttpClientDiscovery;
use Http\Discovery\MessageFactoryDiscovery;
use Http\Message\Authentication\BasicAuth;
use Http\Message\RequestFactory;
use Xabbuh\Http\Authentication\OAuth1;
use Xabbuh\XApi\Client\Request\Handler;
use Xabbuh\XApi\Serializer\SerializerFactoryInterface;
use Xabbuh\XApi\Serializer\SerializerRegistry;
use Xabbuh\XApi\Serializer\Symfony\SerializerFactory;
/**
* xAPI client builder.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
final class XApiClientBuilder implements XApiClientBuilderInterface
{
private $serializerFactory;
/**
* @var HttpClient|null
*/
private $httpClient;
/**
* @var RequestFactory|null
*/
private $requestFactory;
private $baseUrl;
private $version;
private $username;
private $password;
private $consumerKey;
private $consumerSecret;
private $accessToken;
private $tokenSecret;
public function __construct(SerializerFactoryInterface $serializerFactory = null)
{
$this->serializerFactory = $serializerFactory ?: new SerializerFactory();
}
/**
* {@inheritdoc}
*/
public function setHttpClient(HttpClient $httpClient)
{
$this->httpClient = $httpClient;
return $this;
}
/**
* {@inheritdoc}
*/
public function setRequestFactory(RequestFactory $requestFactory)
{
$this->requestFactory = $requestFactory;
return $this;
}
/**
* {@inheritDoc}
*/
public function setBaseUrl($baseUrl)
{
$this->baseUrl = $baseUrl;
return $this;
}
/**
* {@inheritDoc}
*/
public function setVersion($version)
{
$this->version = $version;
return $this;
}
/**
* {@inheritDoc}
*/
public function setAuth($username, $password)
{
$this->username = $username;
$this->password = $password;
return $this;
}
/**
* {@inheritDoc}
*/
public function setOAuthCredentials($consumerKey, $consumerSecret, $token, $tokenSecret)
{
$this->consumerKey = $consumerKey;
$this->consumerSecret = $consumerSecret;
$this->accessToken = $token;
$this->tokenSecret = $tokenSecret;
return $this;
}
/**
* {@inheritDoc}
*/
public function build()
{
if (null === $this->httpClient && class_exists(HttpClientDiscovery::class)) {
try {
$this->httpClient = HttpClientDiscovery::find();
} catch (\Exception $e) {
}
}
if (null === $httpClient = $this->httpClient) {
throw new \LogicException('No HTTP client was configured.');
}
if (null === $this->requestFactory && class_exists(MessageFactoryDiscovery::class)) {
try {
$this->requestFactory = MessageFactoryDiscovery::find();
} catch (\Exception $e) {
}
}
if (null === $this->requestFactory) {
throw new \LogicException('No request factory was configured.');
}
if (null === $this->baseUrl) {
throw new \LogicException('Base URI value was not configured.');
}
$serializerRegistry = new SerializerRegistry();
$serializerRegistry->setStatementSerializer($this->serializerFactory->createStatementSerializer());
$serializerRegistry->setStatementResultSerializer($this->serializerFactory->createStatementResultSerializer());
$serializerRegistry->setActorSerializer($this->serializerFactory->createActorSerializer());
$serializerRegistry->setDocumentDataSerializer($this->serializerFactory->createDocumentDataSerializer());
$plugins = array();
if (null !== $this->username && null !== $this->password) {
$plugins[] = new AuthenticationPlugin(new BasicAuth($this->username, $this->password));
}
if (null !== $this->consumerKey && null !== $this->consumerSecret && null !== $this->accessToken && null !== $this->tokenSecret) {
if (!class_exists(OAuth1::class)) {
throw new \LogicException('The "xabbuh/oauth1-authentication package is needed to use OAuth1 authorization.');
}
$requestSigner = new RequestSigner(new ConsumerKey($this->consumerKey), new ConsumerSecret($this->consumerSecret));
$oauth = new OAuth1($requestSigner, new AccessToken($this->accessToken), new TokenSecret($this->tokenSecret));
$plugins[] = new AuthenticationPlugin($oauth);
}
if (!empty($plugins)) {
$httpClient = new PluginClient($httpClient, $plugins);
}
$version = null === $this->version ? '1.0.3' : $this->version;
$requestHandler = new Handler($httpClient, $this->requestFactory, $this->baseUrl, $version);
return new XApiClient($requestHandler, $serializerRegistry, $this->version);
}
}

View File

@@ -0,0 +1,90 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Xabbuh\XApi\Client;
use Http\Client\HttpClient;
use Http\Message\RequestFactory;
/**
* xAPI client builder.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
interface XApiClientBuilderInterface
{
/**
* Sets the HTTP client implementation that will be used to issue HTTP requests.
*
* @param HttpClient $httpClient The HTTP client implementation
*
* @return XApiClientBuilderInterface The builder
*/
public function setHttpClient(HttpClient $httpClient);
/**
* Sets the requests factory which creates requests that are then handled by the HTTP client.
*
* @param RequestFactory $requestFactory The request factory
*
* @return XApiClientBuilderInterface The builder
*/
public function setRequestFactory(RequestFactory $requestFactory);
/**
* Sets the LRS base URL.
*
* @param string $baseUrl The base url
*
* @return XApiClientBuilderInterface The builder
*/
public function setBaseUrl($baseUrl);
/**
* Sets the xAPI version.
*
* @param string $version The version to use
*
* @return XApiClientBuilderInterface The builder
*/
public function setVersion($version);
/**
* Sets HTTP authentication credentials.
*
* @param string $username The username
* @param string $password The password
*
* @return XApiClientBuilderInterface The builder
*/
public function setAuth($username, $password);
/**
* Sets OAuth credentials.
*
* @param string $consumerKey The consumer key
* @param string $consumerSecret The consumer secret
* @param string $token The token
* @param string $tokenSecret The secret token
*
* @return XApiClientBuilderInterface The builder
*/
public function setOAuthCredentials($consumerKey, $consumerSecret, $token, $tokenSecret);
/**
* Builds the xAPI client.
*
* @return XApiClientInterface The xAPI client
*
* @throws \LogicException if no base URI was configured
*/
public function build();
}

View File

@@ -0,0 +1,50 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Xabbuh\XApi\Client;
/**
* An Experience API client.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
interface XApiClientInterface
{
/**
* Returns an API client to access the statements API of an xAPI based LRS.
*
* @return \Xabbuh\XApi\Client\Api\StatementsApiClientInterface The API client
*/
public function getStatementsApiClient();
/**
* Returns an API client to access the state API of an xAPI based LRS.
*
* @return \Xabbuh\XApi\Client\Api\StateApiClientInterface The API client
*/
public function getStateApiClient();
/**
* Returns an API client to access the activity profile API of an xAPI based
* LRS.
*
* @return \Xabbuh\XApi\Client\Api\ActivityProfileApiClientInterface The API client
*/
public function getActivityProfileApiClient();
/**
* Returns an API client to access the agent profile API of an xAPI based
* LRS.
*
* @return \Xabbuh\XApi\Client\Api\AgentProfileApiClientInterface The API client
*/
public function getAgentProfileApiClient();
}

View File

@@ -0,0 +1,128 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Xabbuh\XApi\Client\Tests\Api;
use Xabbuh\XApi\Client\Api\ActivityProfileApiClient;
use Xabbuh\XApi\DataFixtures\DocumentFixtures;
use Xabbuh\XApi\Model\Activity;
use Xabbuh\XApi\Model\ActivityProfile;
use Xabbuh\XApi\Model\ActivityProfileDocument;
use Xabbuh\XApi\Model\IRI;
use Xabbuh\XApi\Serializer\Symfony\DocumentDataSerializer;
/**
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
class ActivityProfileApiClientTest extends ApiClientTest
{
/**
* @var ActivityProfileApiClient
*/
private $client;
protected function setUp(): void
{
parent::setUp();
$this->client = new ActivityProfileApiClient(
$this->requestHandler,
'1.0.1',
new DocumentDataSerializer($this->serializer)
);
}
public function testCreateOrUpdateDocument()
{
$document = DocumentFixtures::getActivityProfileDocument();
$this->validateStoreApiCall(
'post',
'activities/profile',
array(
'activityId' => 'activity-id',
'profileId' => 'profile-id',
),
204,
'',
$document->getData()
);
$this->client->createOrUpdateDocument($document);
}
public function testCreateOrReplaceDocument()
{
$document = DocumentFixtures::getActivityProfileDocument();
$this->validateStoreApiCall(
'put',
'activities/profile',
array(
'activityId' => 'activity-id',
'profileId' => 'profile-id',
),
204,
'',
$document->getData()
);
$this->client->createOrReplaceDocument($document);
}
public function testDeleteDocument()
{
$activityProfile = $this->createActivityProfile();
$this->validateRequest(
'delete',
'activities/profile',
array(
'activityId' => 'activity-id',
'profileId' => 'profile-id',
),
''
);
$this->validateSerializer(array());
$this->client->deleteDocument($activityProfile);
}
public function testGetDocument()
{
$document = DocumentFixtures::getActivityProfileDocument();
$activityProfile = $document->getActivityProfile();
$this->validateRetrieveApiCall(
'get',
'activities/profile',
array(
'activityId' => 'activity-id',
'profileId' => 'profile-id',
),
200,
'DocumentData',
$document->getData()
);
$document = $this->client->getDocument($activityProfile);
$this->assertInstanceOf(ActivityProfileDocument::class, $document);
$this->assertEquals($activityProfile, $document->getActivityProfile());
}
private function createActivityProfile()
{
$activity = new Activity(IRI::fromString('activity-id'));
$activityProfile = new ActivityProfile('profile-id', $activity);
return $activityProfile;
}
}

View File

@@ -0,0 +1,138 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Xabbuh\XApi\Client\Tests\Api;
use Xabbuh\XApi\Client\Api\AgentProfileApiClient;
use Xabbuh\XApi\DataFixtures\DocumentFixtures;
use Xabbuh\XApi\Model\Agent;
use Xabbuh\XApi\Model\AgentProfile;
use Xabbuh\XApi\Model\AgentProfileDocument;
use Xabbuh\XApi\Model\InverseFunctionalIdentifier;
use Xabbuh\XApi\Model\IRI;
use Xabbuh\XApi\Serializer\Symfony\ActorSerializer;
use Xabbuh\XApi\Serializer\Symfony\DocumentDataSerializer;
/**
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
class AgentProfileApiClientTest extends ApiClientTest
{
/**
* @var AgentProfileApiClient
*/
private $client;
protected function setUp(): void
{
parent::setUp();
$this->client = new AgentProfileApiClient(
$this->requestHandler,
'1.0.1',
new DocumentDataSerializer($this->serializer),
new ActorSerializer($this->serializer)
);
}
public function testCreateOrUpdateDocument()
{
$document = DocumentFixtures::getAgentProfileDocument();
$profile = $document->getAgentProfile();
$this->validateStoreApiCall(
'post',
'agents/profile',
array(
'agent' => 'agent-as-json',
'profileId' => 'profile-id',
),
204,
'',
$document->getData(),
array(array('data' => $profile->getAgent(), 'result' => 'agent-as-json'))
);
$this->client->createOrUpdateDocument($document);
}
public function testCreateOrReplaceDocument()
{
$document = DocumentFixtures::getAgentProfileDocument();
$profile = $document->getAgentProfile();
$this->validateStoreApiCall(
'put',
'agents/profile',
array(
'agent' => 'agent-as-json',
'profileId' => 'profile-id',
),
204,
'',
$document->getData(),
array(array('data' => $profile->getAgent(), 'result' => 'agent-as-json'))
);
$this->client->createOrReplaceDocument($document);
}
public function testDeleteDocument()
{
$profile = $this->createAgentProfile();
$this->validateRequest(
'delete',
'agents/profile',
array(
'agent' => 'agent-as-json',
'profileId' => 'profile-id',
),
''
);
$this->validateSerializer(array(array('data' => $profile->getAgent(), 'result' => 'agent-as-json')));
$this->client->deleteDocument(
$profile
);
}
public function testGetDocument()
{
$document = DocumentFixtures::getAgentProfileDocument();
$profile = $document->getAgentProfile();
$this->validateRetrieveApiCall(
'get',
'agents/profile',
array(
'agent' => 'agent-as-json',
'profileId' => 'profile-id',
),
200,
'DocumentData',
$document->getData(),
array(array('data' => $profile->getAgent(), 'result' => 'agent-as-json'))
);
$document = $this->client->getDocument($profile);
$this->assertInstanceOf(AgentProfileDocument::class, $document);
$this->assertEquals($profile, $document->getAgentProfile());
}
private function createAgentProfile()
{
$agent = new Agent(InverseFunctionalIdentifier::withMbox(IRI::fromString('mailto:christian@example.com')));
$profile = new AgentProfile('profile-id', $agent);
return $profile;
}
}

View File

@@ -0,0 +1,146 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Xabbuh\XApi\Client\Tests\Api;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\Serializer\SerializerInterface;
use Xabbuh\XApi\Client\Request\HandlerInterface;
use Xabbuh\XApi\Common\Exception\NotFoundException;
use Xabbuh\XApi\Serializer\SerializerRegistry;
use Xabbuh\XApi\Serializer\Symfony\ActorSerializer;
use Xabbuh\XApi\Serializer\Symfony\DocumentDataSerializer;
use Xabbuh\XApi\Serializer\Symfony\StatementResultSerializer;
use Xabbuh\XApi\Serializer\Symfony\StatementSerializer;
/**
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
abstract class ApiClientTest extends TestCase
{
/**
* @var HandlerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $requestHandler;
/**
* @var SerializerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $serializer;
/**
* @var SerializerRegistry
*/
protected $serializerRegistry;
protected function setUp(): void
{
$this->requestHandler = $this->getMockBuilder(HandlerInterface::class)->getMock();
$this->serializer = $this->getMockBuilder(SerializerInterface::class)->getMock();
$this->serializerRegistry = $this->createSerializerRegistry();
}
protected function createSerializerRegistry()
{
$registry = new SerializerRegistry();
$registry->setStatementSerializer(new StatementSerializer($this->serializer));
$registry->setStatementResultSerializer(new StatementResultSerializer($this->serializer));
$registry->setActorSerializer(new ActorSerializer($this->serializer));
$registry->setDocumentDataSerializer(new DocumentDataSerializer($this->serializer));
return $registry;
}
protected function validateSerializer(array $serializerMap)
{
$this
->serializer
->expects($this->any())
->method('serialize')
->willReturnCallback(function ($data) use ($serializerMap) {
foreach ($serializerMap as $entry) {
if ($data == $entry['data']) {
return $entry['result'];
}
}
return '';
});
}
protected function validateRequest($method, $uri, array $urlParameters, $body = null)
{
$request = $this->getMockBuilder(RequestInterface::class)->getMock();
$this
->requestHandler
->expects($this->once())
->method('createRequest')
->with($method, $uri, $urlParameters, $body)
->willReturn($request);
return $request;
}
protected function validateRetrieveApiCall($method, $uri, array $urlParameters, $statusCode, $type, $transformedResult, array $serializerMap = array())
{
$rawResponse = 'the-server-response';
$response = $this->getMockBuilder(ResponseInterface::class)->getMock();
$response->expects($this->any())->method('getStatusCode')->willReturn($statusCode);
$response->expects($this->any())->method('getHeader')->with('Content-Type')->willReturn(array('application/json'));
$response->expects($this->any())->method('getBody')->willReturn($rawResponse);
$request = $this->validateRequest($method, $uri, $urlParameters);
if (404 === $statusCode) {
$this
->requestHandler
->expects($this->once())
->method('executeRequest')
->with($request)
->willThrowException(new NotFoundException('Not found'));
} else {
$this
->requestHandler
->expects($this->once())
->method('executeRequest')
->with($request)
->willReturn($response);
}
$this->validateSerializer($serializerMap);
if ($statusCode < 400) {
$this->serializer
->expects($this->once())
->method('deserialize')
->with($rawResponse, 'Xabbuh\XApi\Model\\'.$type, 'json')
->willReturn($transformedResult);
}
}
protected function validateStoreApiCall($method, $uri, array $urlParameters, $statusCode, $rawResponse, $object, array $serializerMap = array())
{
$rawRequest = 'the-request-body';
$response = $this->getMockBuilder(ResponseInterface::class)->getMock();
$response->expects($this->any())->method('getStatusCode')->willReturn($statusCode);
$response->expects($this->any())->method('getBody')->willReturn($rawResponse);
$request = $this->validateRequest($method, $uri, $urlParameters, $rawRequest);
$this
->requestHandler
->expects($this->once())
->method('executeRequest')
->with($request, array($statusCode))
->willReturn($response);
$serializerMap[] = array('data' => $object, 'result' => $rawRequest);
$this->validateSerializer($serializerMap);
}
}

View File

@@ -0,0 +1,140 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Xabbuh\XApi\Client\Tests\Api;
use Xabbuh\XApi\Client\Api\StateApiClient;
use Xabbuh\XApi\DataFixtures\DocumentFixtures;
use Xabbuh\XApi\Model\Activity;
use Xabbuh\XApi\Model\Agent;
use Xabbuh\XApi\Model\InverseFunctionalIdentifier;
use Xabbuh\XApi\Model\IRI;
use Xabbuh\XApi\Model\State;
use Xabbuh\XApi\Model\StateDocument;
use Xabbuh\XApi\Serializer\Symfony\ActorSerializer;
use Xabbuh\XApi\Serializer\Symfony\DocumentDataSerializer;
/**
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
class StateApiClientTest extends ApiClientTest
{
/**
* @var StateApiClient
*/
private $client;
protected function setUp(): void
{
parent::setUp();
$this->client = new StateApiClient(
$this->requestHandler,
'1.0.1',
new DocumentDataSerializer($this->serializer),
new ActorSerializer($this->serializer)
);
}
public function testCreateOrUpdateDocument()
{
$document = DocumentFixtures::getStateDocument();
$this->validateStoreApiCall(
'post',
'activities/state',
array(
'activityId' => 'activity-id',
'agent' => 'agent-as-json',
'stateId' => 'state-id',
),
204,
'',
$document->getData(),
array(array('data' => $document->getState()->getActor(), 'result' => 'agent-as-json'))
);
$this->client->createOrUpdateDocument($document);
}
public function testCreateOrReplaceDocument()
{
$document = DocumentFixtures::getStateDocument();
$this->validateStoreApiCall(
'put',
'activities/state',
array(
'activityId' => 'activity-id',
'agent' => 'agent-as-json',
'stateId' => 'state-id',
),
204,
'',
$document->getData(),
array(array('data' => $document->getState()->getActor(), 'result' => 'agent-as-json'))
);
$this->client->createOrReplaceDocument($document);
}
public function testDeleteDocument()
{
$state = $this->createState();
$this->validateRequest(
'delete',
'activities/state',
array(
'activityId' => 'activity-id',
'agent' => 'agent-as-json',
'stateId' => 'state-id',
),
''
);
$this->validateSerializer(array(array('data' => $state->getActor(), 'result' => 'agent-as-json')));
$this->client->deleteDocument($state);
}
public function testGetDocument()
{
$document = DocumentFixtures::getStateDocument();
$state = $document->getState();
$this->validateRetrieveApiCall(
'get',
'activities/state',
array(
'activityId' => 'activity-id',
'agent' => 'agent-as-json',
'stateId' => 'state-id',
),
200,
'DocumentData',
$document->getData(),
array(array('data' => $state->getActor(), 'result' => 'agent-as-json'))
);
$document = $this->client->getDocument($state);
$this->assertInstanceOf(StateDocument::class, $document);
$this->assertEquals($state, $document->getState());
}
private function createState()
{
$agent = new Agent(InverseFunctionalIdentifier::withMbox(IRI::fromString('mailto:alice@example.com')));
$activity = new Activity(IRI::fromString('activity-id'));
$state = new State($activity, $agent, 'state-id');
return $state;
}
}

View File

@@ -0,0 +1,369 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Xabbuh\XApi\Client\Tests\Api;
use Xabbuh\XApi\Client\Api\StatementsApiClient;
use Xabbuh\XApi\Common\Exception\NotFoundException;
use Xabbuh\XApi\DataFixtures\StatementFixtures;
use Xabbuh\XApi\Model\Agent;
use Xabbuh\XApi\Model\InverseFunctionalIdentifier;
use Xabbuh\XApi\Model\IRI;
use Xabbuh\XApi\Model\IRL;
use Xabbuh\XApi\Model\Statement;
use Xabbuh\XApi\Model\StatementId;
use Xabbuh\XApi\Model\StatementReference;
use Xabbuh\XApi\Model\StatementResult;
use Xabbuh\XApi\Model\StatementsFilter;
use Xabbuh\XApi\Model\Verb;
use Xabbuh\XApi\Serializer\Symfony\ActorSerializer;
use Xabbuh\XApi\Serializer\Symfony\StatementResultSerializer;
use Xabbuh\XApi\Serializer\Symfony\StatementSerializer;
/**
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
class StatementsApiClientTest extends ApiClientTest
{
/**
* @var StatementsApiClient
*/
private $client;
protected function setUp(): void
{
parent::setUp();
$this->client = new StatementsApiClient(
$this->requestHandler,
'1.0.1',
new StatementSerializer($this->serializer),
new StatementResultSerializer($this->serializer),
new ActorSerializer($this->serializer)
);
}
public function testStoreStatement()
{
$statementId = '12345678-1234-5678-1234-567812345678';
$statement = $this->createStatement();
$this->validateStoreApiCall(
'post',
'statements',
array(),
200,
'["'.$statementId.'"]',
$this->createStatement()
);
$returnedStatement = $this->client->storeStatement($statement);
$expectedStatement = $this->createStatement($statementId);
$this->assertEquals($expectedStatement, $returnedStatement);
}
public function testStoreStatementWithId()
{
$statementId = '12345678-1234-5678-1234-567812345678';
$statement = $this->createStatement($statementId);
$this->validateStoreApiCall(
'put',
'statements',
array('statementId' => $statementId),
204,
'["'.$statementId.'"]',
$statement
);
$this->assertEquals($statement, $this->client->storeStatement($statement));
}
public function testStoreStatementWithIdEnsureThatTheIdIsNotOverwritten()
{
$statementId = '12345678-1234-5678-1234-567812345678';
$statement = $this->createStatement($statementId);
$this->validateStoreApiCall(
'put',
'statements',
array('statementId' => $statementId),
204,
'',
$statement
);
$storedStatement = $this->client->storeStatement($statement);
$this->assertEquals($statementId, $storedStatement->getId()->getValue());
}
public function testStoreStatements()
{
$statementId1 = '12345678-1234-5678-1234-567812345678';
$statementId2 = '12345678-1234-5678-1234-567812345679';
$statement1 = $this->createStatement();
$statement2 = $this->createStatement();
$this->validateStoreApiCall(
'post',
'statements',
array(),
'200',
'["'.$statementId1.'","'.$statementId2.'"]',
array($this->createStatement(), $this->createStatement())
);
$statements = $this->client->storeStatements(array($statement1, $statement2));
$expectedStatement1 = $this->createStatement($statementId1);
$expectedStatement2 = $this->createStatement($statementId2);
$expectedStatements = array($expectedStatement1, $expectedStatement2);
$this->assertNotContains($statements[0], array($statement1, $statement2));
$this->assertNotContains($statements[1], array($statement1, $statement2));
$this->assertEquals($expectedStatements, $statements);
$this->assertEquals($statementId1, $statements[0]->getId()->getValue());
$this->assertEquals($statementId2, $statements[1]->getId()->getValue());
}
public function testStoreStatementsWithNonStatementObject()
{
$this->expectException(\InvalidArgumentException::class);
$statement1 = $this->createStatement();
$statement2 = $this->createStatement();
$this->client->storeStatements(array($statement1, new \stdClass(), $statement2));
}
public function testStoreStatementsWithNonObject()
{
$this->expectException(\InvalidArgumentException::class);
$statement1 = $this->createStatement();
$statement2 = $this->createStatement();
$this->client->storeStatements(array($statement1, 'foo', $statement2));
}
public function testStoreStatementsWithId()
{
$this->expectException(\InvalidArgumentException::class);
$statement1 = $this->createStatement();
$statement2 = $this->createStatement('12345678-1234-5678-1234-567812345679');
$this->client->storeStatements(array($statement1, $statement2));
}
public function testVoidStatement()
{
$voidedStatementId = '12345678-1234-5678-1234-567812345679';
$voidingStatementId = '12345678-1234-5678-1234-567812345678';
$agent = new Agent(InverseFunctionalIdentifier::withMbox(IRI::fromString('mailto:john.doe@example.com')));
$statementReference = new StatementReference(StatementId::fromString($voidedStatementId));
$voidingStatement = new Statement(null, $agent, Verb::createVoidVerb(), $statementReference);
$voidedStatement = $this->createStatement($voidedStatementId);
$this->validateStoreApiCall(
'post',
'statements',
array(),
200,
'["'.$voidingStatementId.'"]',
$voidingStatement
);
$returnedVoidingStatement = $this->client->voidStatement($voidedStatement, $agent);
$expectedVoidingStatement = new Statement(
StatementId::fromString($voidingStatementId),
$agent,
Verb::createVoidVerb(),
$statementReference
);
$this->assertEquals($expectedVoidingStatement, $returnedVoidingStatement);
}
public function testGetStatement()
{
$statementId = '12345678-1234-5678-1234-567812345678';
$statement = $this->createStatement();
$this->validateRetrieveApiCall(
'get',
'statements',
array('statementId' => $statementId, 'attachments' => 'true'),
200,
'Statement',
$statement
);
$this->client->getStatement(StatementId::fromString($statementId));
}
public function testGetStatementWithNotExistingStatement()
{
$this->expectException(NotFoundException::class);
$statementId = '12345678-1234-5678-1234-567812345678';
$this->validateRetrieveApiCall(
'get',
'statements',
array('statementId' => $statementId, 'attachments' => 'true'),
404,
'Statement',
'There is no statement associated with this id'
);
$this->client->getStatement(StatementId::fromString($statementId));
}
public function testGetVoidedStatement()
{
$statementId = '12345678-1234-5678-1234-567812345678';
$statement = $this->createStatement();
$this->validateRetrieveApiCall(
'get',
'statements',
array('voidedStatementId' => $statementId, 'attachments' => 'true'),
200,
'Statement',
$statement
);
$this->client->getVoidedStatement(StatementId::fromString($statementId));
}
public function testGetVoidedStatementWithNotExistingStatement()
{
$this->expectException(NotFoundException::class);
$statementId = '12345678-1234-5678-1234-567812345678';
$this->validateRetrieveApiCall(
'get',
'statements',
array('voidedStatementId' => $statementId, 'attachments' => 'true'),
404,
'Statement',
'There is no statement associated with this id'
);
$this->client->getVoidedStatement(StatementId::fromString($statementId));
}
public function testGetStatements()
{
$statementResult = $this->createStatementResult();
$this->validateRetrieveApiCall(
'get',
'statements',
array(),
200,
'StatementResult',
$statementResult
);
$this->assertEquals($statementResult, $this->client->getStatements());
}
public function testGetStatementsWithStatementsFilter()
{
$filter = new StatementsFilter();
$filter->limit(10)->ascending();
$statementResult = $this->createStatementResult();
$this->validateRetrieveApiCall(
'get',
'statements',
array('limit' => 10, 'ascending' => 'true'),
200,
'StatementResult',
$statementResult
);
$this->assertEquals($statementResult, $this->client->getStatements($filter));
}
public function testGetStatementsWithAgentInStatementsFilter()
{
// {"mbox":"mailto:alice@example.com","objectType":"Agent"}
$filter = new StatementsFilter();
$agent = new Agent(InverseFunctionalIdentifier::withMbox(IRI::fromString('mailto:alice@example.com')));
$filter->byActor($agent);
$statementResult = $this->createStatementResult();
$agentJson = '{"mbox":"mailto:alice@example.com","objectType":"Agent"}';
$this->serializer
->expects($this->once())
->method('serialize')
->with($agent, 'json')
->willReturn($agentJson);
$this->validateRetrieveApiCall(
'get',
'statements',
array('agent' => $agentJson),
200,
'StatementResult',
$statementResult
);
$this->assertEquals($statementResult, $this->client->getStatements($filter));
}
public function testGetStatementsWithVerbInStatementsFilter()
{
$filter = new StatementsFilter();
$verb = new Verb(IRI::fromString('http://adlnet.gov/expapi/verbs/attended'));
$filter->byVerb($verb);
$statementResult = $this->createStatementResult();
$this->validateRetrieveApiCall(
'get',
'statements',
array('verb' => 'http://adlnet.gov/expapi/verbs/attended'),
200,
'StatementResult',
$statementResult
);
$this->assertEquals($statementResult, $this->client->getStatements($filter));
}
public function testGetNextStatements()
{
$moreUrl = '/xapi/statements/more/b381d8eca64a61a42c7b9b4ecc2fabb6';
$previousStatementResult = new StatementResult(array(), IRL::fromString($moreUrl));
$this->validateRetrieveApiCall(
'get',
$moreUrl,
array(),
200,
'StatementResult',
$previousStatementResult
);
$statementResult = $this->client->getNextStatements($previousStatementResult);
$this->assertInstanceOf(StatementResult::class, $statementResult);
}
/**
* @param int $id
*
* @return Statement
*/
private function createStatement($id = null)
{
$statement = StatementFixtures::getMinimalStatement($id);
if (null === $id) {
$statement = $statement->withId(null);
}
return $statement;
}
/**
* @return StatementResult
*/
private function createStatementResult()
{
return new StatementResult(array());
}
}

2
vendor/php-xapi/exception/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
composer.lock
vendor

22
vendor/php-xapi/exception/CHANGELOG.md vendored Normal file
View File

@@ -0,0 +1,22 @@
CHANGELOG
=========
0.2.0
-----
* Added `StatementIdAlreadyExistsException`.
* Added `UnsupportedStatementVersionException`.
0.1.1
-----
Added `conflict` rule to Composer config to prevent having two packages that
provide the same classes.
0.1.0
-----
This is a first release containing common exception classes that can be used by
both xAPI servers and clients.
This package replaces the `xabbuh/xapi-common` package which is now deprecated.

19
vendor/php-xapi/exception/LICENSE vendored Normal file
View File

@@ -0,0 +1,19 @@
Copyright (c) 2014-2018 Christian Flothmann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

9
vendor/php-xapi/exception/README.md vendored Normal file
View File

@@ -0,0 +1,9 @@
Xabbuh xApi Common
==================
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/php-xapi/exception/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/php-xapi/exception/?branch=master)
Package providing common functionality for both server and client side implementations
of the [Experience API](https://github.com/adlnet/xAPI-Spec/blob/master/xAPI.md).
This package replaces the `xabbuh/xapi-common` package.

30
vendor/php-xapi/exception/composer.json vendored Normal file
View File

@@ -0,0 +1,30 @@
{
"name": "php-xapi/exception",
"type": "library",
"description": "common exception classes for Experience API implementations",
"keywords": ["xAPI", "Experience API", "Tin Can API", "exception"],
"homepage": "https://github.com/php-xapi/exception",
"license": "MIT",
"authors": [
{
"name": "Christian Flothmann",
"homepage": "https://github.com/xabbuh"
}
],
"require": {
"php": ">=5.3.0"
},
"conflict": {
"xabbuh/xapi-common": "*"
},
"autoload": {
"psr-4": {
"Xabbuh\\XApi\\Common\\Exception\\": "src"
}
},
"extra": {
"branch-alias": {
"dev-master": "0.2.x-dev"
}
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Xabbuh\XApi\Common\Exception;
/**
* Exception indicating authentication or authorization failures.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
class AccessDeniedException extends XApiException
{
}

View File

@@ -0,0 +1,26 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Xabbuh\XApi\Common\Exception;
/**
* Exception indicating an error due to a conflict with the current state of
* a resource.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
class ConflictException extends XApiException
{
public function __construct($message)
{
parent::__construct($message, 409);
}
}

View File

@@ -0,0 +1,25 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Xabbuh\XApi\Common\Exception;
/**
* More specific xAPI exception indicating that a resource could not be found.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
class NotFoundException extends XApiException
{
public function __construct($message)
{
parent::__construct($message, 404);
}
}

View File

@@ -0,0 +1,25 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Xabbuh\XApi\Common\Exception;
/**
* Statement id already exists exception.
*
* @author Jérôme Parmentier <jerome.parmentier@acensi.fr>
*/
class StatementIdAlreadyExistsException extends XApiException
{
public function __construct($statementId, \Exception $previous = null)
{
parent::__construct(sprintf('A statement with ID "%s" already exists.', $statementId), 0, $previous);
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Xabbuh\XApi\Common\Exception;
/**
* Unsupported operation exception.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
class UnsupportedOperationException extends XApiException
{
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Xabbuh\XApi\Common\Exception;
/**
* Unsupported statement version exception.
*
* @author Jérôme Parmentier <jerome.parmentier@acensi.fr>
*/
class UnsupportedStatementVersionException extends XApiException
{
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* This file is part of the xAPI package.
*
* (c) Christian Flothmann <christian.flothmann@xabbuh.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Xabbuh\XApi\Common\Exception;
/**
* Experience API exceptions.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
class XApiException extends \Exception
{
}

View File

@@ -0,0 +1,2 @@
/composer.lock
/vendor

View File

@@ -0,0 +1,30 @@
sudo: false
language: php
cache:
directories:
- $HOME/.composer/cache/files
php:
- 5.4
- 5.5
- 5.6
- 7.0
- hhvm
matrix:
fast_finish: true
include:
- php: 5.3
env: deps="low"
before_install:
- if [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then phpenv config-rm xdebug.ini; fi
- composer self-update
install:
- if [[ "$deps" = "low" ]]; then composer update --prefer-lowest; else composer install; fi
script:
- php validate.php

View File

@@ -0,0 +1,60 @@
CHANGELOG
=========
2.0.0
-----
* Attachment fixtures are now objects containing up to two keys. `metadata`
contains the JSON encoded attachment while `content`, if present, denotes
the raw attachment content.
1.0.0
-----
This first stable release contains all the official conformance test fixtures
provided by the maintainers of the xAPI spec as JSON encoded files and provides
ways to access them through a PHP API.
This release is equivalent to the 0.2 release series and is considered to
be stable.
0.2.3
-----
* Added a `Statement` fixture defining all properties of a statement.
* Added fixtures for statement attachments.
* Added fixtures for activity definition extensions.
* Added fixtures for activity interaction definitions and interaction components.
* Added missing fixtures for `Account`, `Activity`, `Actor`, `Context`,
`ContextActivities`, `Extensions`, `Result`, `SubStatement`, `StatementReference`
and `Verb`.
* Removed dependency on the `php-xapi/model` package. The classes from that
package are never used.
0.2.2
-----
Synchronized the list of statement fixtures with the test cases of the
`php-xapi/test-fixtures` package.
0.2.1
-----
Do not block the installation of release `0.4` of the `php-xapi/model` package.
0.2.0
-----
Switched to make use of the official conformance test fixtures that are provided
by the maintainers of the xAPI spec.
0.1.0
-----
This is the first release containing JSON test fixtures for actors (agents and
groups), verbs, documents, activities,statements, and statement results.

View File

@@ -0,0 +1,19 @@
Copyright (c) 2014-2016 Christian Flothmann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,4 @@
Experience API JSON Test Fixtures
=================================
Package providing JSON encoded test data fixtures for the xAPI packages.

View File

@@ -0,0 +1,9 @@
UPGRADE
=======
Upgrading from 1.x to 2.0
-------------------------
* Attachment fixtures are now objects containing up to two keys. `metadata`
contains the JSON encoded attachment while `content`, if present, denotes
the raw attachment content.

View File

@@ -0,0 +1,27 @@
{
"name": "php-xapi/json-test-fixtures",
"type": "library",
"description": "common JSON test fixtures for Experience API related packages",
"keywords": ["xAPI", "Experience API", "Tin Can API", "fixtures", "data fixtures", "test fixtures", "JSON"],
"homepage": "https://github.com/php-xapi/json-test-fixtures",
"license": "MIT",
"authors": [
{
"name": "Christian Flothmann",
"homepage": "https://github.com/xabbuh"
}
],
"require": {
"php": ">=5.3.0"
},
"autoload": {
"psr-4": {
"XApi\\Fixtures\\Json\\": "src/"
}
},
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
}
}

View File

@@ -0,0 +1,4 @@
{
"name": "test",
"homePage": "https://tincanapi.com"
}

View File

@@ -0,0 +1,4 @@
{
"homePage": "https://tincanapi.com/OAuth/Token",
"name": "oauth_consumer_x75db"
}

View File

@@ -0,0 +1,4 @@
{
"name": "forQuery",
"homePage": "https://tincanapi.com"
}

View File

@@ -0,0 +1,4 @@
{
"name": "test",
"homePage": "https://tincanapi.com"
}

View File

@@ -0,0 +1,6 @@
{
"id": "http://tincanapi.com/conformancetest/activityid",
"objectType": "Activity",
"definition": {
}
}

View File

@@ -0,0 +1,8 @@
{
"id": "http://tincanapi.com/conformancetest/activityid/forQuery",
"definition": {
"name": {
"en-US": "for query"
}
}
}

View File

@@ -0,0 +1,3 @@
{
"id": "http://tincanapi.com/conformancetest/activityid"
}

View File

@@ -0,0 +1,5 @@
{
"id": "http://tincanapi.com/conformancetest/activityid",
"definition": {
}
}

View File

@@ -0,0 +1,6 @@
{
"id": "http://tincanapi.com/conformancetest/activityid",
"definition": {
},
"objectType": "Activity"
}

View File

@@ -0,0 +1,4 @@
{
"id": "http://tincanapi.com/conformancetest/activityid",
"objectType": "Activity"
}

View File

@@ -0,0 +1,4 @@
{
"id": "http://tincanapi.com/conformancetest/activityid",
"objectType": "Activity"
}

View File

@@ -0,0 +1,6 @@
{
"account": {
"name": "test",
"homePage": "https://tincanapi.com"
}
}

View File

@@ -0,0 +1,7 @@
{
"account": {
"name": "test",
"homePage": "https://tincanapi.com"
},
"objectType": "Agent"
}

View File

@@ -0,0 +1,13 @@
{
"objectType": "Group",
"member": [
{
"mbox": "mailto:conformancetest@tincanapi.com",
"objectType": "Agent"
}
],
"account": {
"name": "test",
"homePage": "https://tincanapi.com"
}
}

View File

@@ -0,0 +1,8 @@
{
"objectType": "Group",
"account": {
"name": "test",
"homePage": "https://tincanapi.com"
},
"name": "test group"
}

View File

@@ -0,0 +1,7 @@
{
"account": {
"name": "test",
"homePage": "https://tincanapi.com"
},
"objectType": "Group"
}

View File

@@ -0,0 +1,14 @@
{
"objectType": "Group",
"mbox": "mailto:conformancetest-group@tincanapi.com",
"name": "test group",
"member": [
{
"account": {
"name": "test",
"homePage": "https://tincanapi.com"
},
"objectType": "Agent"
}
]
}

View File

@@ -0,0 +1,11 @@
{
"objectType": "Group",
"mbox": "mailto:conformancetest-group@tincanapi.com",
"name": "test group",
"member": [
{
"mbox": "mailto:conformancetest@tincanapi.com",
"objectType": "Agent"
}
]
}

View File

@@ -0,0 +1,11 @@
{
"objectType": "Group",
"mbox": "mailto:conformancetest-group@tincanapi.com",
"name": "test group",
"member": [
{
"mbox_sha1sum": "db77b9104b531ecbb0b967f6942549d0ba80fda1",
"objectType": "Agent"
}
]
}

View File

@@ -0,0 +1,11 @@
{
"objectType": "Group",
"mbox": "mailto:conformancetest-group@tincanapi.com",
"name": "test group",
"member": [
{
"openid": "http://openid.tincanapi.com",
"objectType": "Agent"
}
]
}

View File

@@ -0,0 +1,15 @@
{
"objectType": "Group",
"mbox": "mailto:conformancetest-group@tincanapi.com",
"name": "test group",
"member": [
{
"mbox": "mailto:conformancetest@tincanapi.com",
"objectType": "Agent"
},
{
"mbox": "mailto:conformancetest@tincanapi.com",
"objectType": "Agent"
}
]
}

View File

@@ -0,0 +1,11 @@
{
"objectType": "Group",
"mbox": "mailto:conformancetest-group@tincanapi.com",
"name": "test group",
"member": [
{
"mbox": "mailto:conformancetest@tincanapi.com",
"objectType": "Agent"
}
]
}

View File

@@ -0,0 +1,6 @@
{
"account": {
"name": "forQuery",
"homePage": "https://tincanapi.com"
}
}

View File

@@ -0,0 +1,3 @@
{
"mbox": "mailto:conformancetest+query@tincanapi.com"
}

View File

@@ -0,0 +1,3 @@
{
"mbox_sha1sum": "6954e807cfbfc5b375d185de32f05de269f93d6f"
}

View File

@@ -0,0 +1,3 @@
{
"openid": "http://openid.tincanapi.com/query"
}

View File

@@ -0,0 +1,3 @@
{
"mbox": "mailto:conformancetest@tincanapi.com"
}

View File

@@ -0,0 +1,4 @@
{
"mbox": "mailto:conformancetest@tincanapi.com",
"objectType": "Agent"
}

View File

@@ -0,0 +1,10 @@
{
"objectType": "Group",
"mbox": "mailto:conformancetest-group@tincanapi.com",
"member": [
{
"mbox": "mailto:conformancetest@tincanapi.com",
"objectType": "Agent"
}
]
}

View File

@@ -0,0 +1,5 @@
{
"objectType": "Group",
"mbox": "mailto:conformancetest-group@tincanapi.com",
"name": "test group"
}

View File

@@ -0,0 +1,4 @@
{
"mbox": "mailto:conformancetest-group@tincanapi.com",
"objectType": "Group"
}

View File

@@ -0,0 +1,3 @@
{
"mbox_sha1sum": "db77b9104b531ecbb0b967f6942549d0ba80fda1"
}

View File

@@ -0,0 +1,4 @@
{
"mbox_sha1sum": "db77b9104b531ecbb0b967f6942549d0ba80fda1",
"objectType": "Agent"
}

View File

@@ -0,0 +1,10 @@
{
"objectType": "Group",
"member": [
{
"mbox": "mailto:conformancetest@tincanapi.com",
"objectType": "Agent"
}
],
"mbox_sha1sum": "4e271041e78101311fb37284ef1a1d35c3f1db35"
}

View File

@@ -0,0 +1,5 @@
{
"objectType": "Group",
"mbox_sha1sum": "4e271041e78101311fb37284ef1a1d35c3f1db35",
"name": "test group"
}

View File

@@ -0,0 +1,4 @@
{
"mbox_sha1sum": "4e271041e78101311fb37284ef1a1d35c3f1db35",
"objectType": "Group"
}

View File

@@ -0,0 +1,3 @@
{
"openid": "http://openid.tincanapi.com"
}

View File

@@ -0,0 +1,4 @@
{
"openid": "http://openid.tincanapi.com",
"objectType": "Agent"
}

View File

@@ -0,0 +1,10 @@
{
"objectType": "Group",
"member": [
{
"mbox": "mailto:conformancetest@tincanapi.com",
"objectType": "Agent"
}
],
"openid": "http://group.openid.tincanapi.com"
}

View File

@@ -0,0 +1,5 @@
{
"objectType": "Group",
"openid": "http://group.openid.tincanapi.com",
"name": "test group"
}

View File

@@ -0,0 +1,4 @@
{
"openid": "http://group.openid.tincanapi.com",
"objectType": "Group"
}

View File

@@ -0,0 +1,3 @@
{
"mbox": "mailto:conformancetest@tincanapi.com"
}

Some files were not shown because too many files have changed in this diff Show More