Actualización

This commit is contained in:
Xes
2025-04-10 12:24:57 +02:00
parent 8969cc929d
commit 45420b6f0d
39760 changed files with 4303286 additions and 0 deletions

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();
}