Actualización

This commit is contained in:
Xes
2025-04-10 12:36:07 +02:00
parent 1da7c3f3b9
commit 4aff98e77b
3147 changed files with 320647 additions and 0 deletions

View File

@@ -0,0 +1,209 @@
<?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 XApi\LrsBundle\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\ParameterBag;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Xabbuh\XApi\Common\Exception\NotFoundException;
use Xabbuh\XApi\Model\IRL;
use Xabbuh\XApi\Model\Statement;
use Xabbuh\XApi\Model\StatementId;
use Xabbuh\XApi\Model\StatementResult;
use Xabbuh\XApi\Serializer\StatementResultSerializerInterface;
use Xabbuh\XApi\Serializer\StatementSerializerInterface;
use XApi\LrsBundle\Model\StatementsFilterFactory;
use XApi\LrsBundle\Response\AttachmentResponse;
use XApi\LrsBundle\Response\MultipartResponse;
use XApi\Repository\Api\StatementRepositoryInterface;
/**
* @author Jérôme Parmentier <jerome.parmentier@acensi.fr>
*/
class StatementGetController
{
protected static $getParameters = [
'statementId' => true,
'voidedStatementId' => true,
'agent' => true,
'verb' => true,
'activity' => true,
'registration' => true,
'related_activities' => true,
'related_agents' => true,
'since' => true,
'until' => true,
'limit' => true,
'format' => true,
'attachments' => true,
'ascending' => true,
'cursor' => true,
];
protected $repository;
protected $statementSerializer;
protected $statementResultSerializer;
protected $statementsFilterFactory;
public function __construct(StatementRepositoryInterface $repository, StatementSerializerInterface $statementSerializer, StatementResultSerializerInterface $statementResultSerializer, StatementsFilterFactory $statementsFilterFactory)
{
$this->repository = $repository;
$this->statementSerializer = $statementSerializer;
$this->statementResultSerializer = $statementResultSerializer;
$this->statementsFilterFactory = $statementsFilterFactory;
}
/**
* @throws BadRequestHttpException if the query parameters does not comply with xAPI specification
*
* @return Response
*/
public function getStatement(Request $request)
{
$query = new ParameterBag(\array_intersect_key($request->query->all(), self::$getParameters));
$this->validate($query);
$includeAttachments = $query->filter('attachments', false, FILTER_VALIDATE_BOOLEAN);
try {
if (($statementId = $query->get('statementId')) !== null) {
$statement = $this->repository->findStatementById(StatementId::fromString($statementId));
$response = $this->buildSingleStatementResponse($statement, $includeAttachments);
} elseif (($voidedStatementId = $query->get('voidedStatementId')) !== null) {
$statement = $this->repository->findVoidedStatementById(StatementId::fromString($voidedStatementId));
$response = $this->buildSingleStatementResponse($statement, $includeAttachments);
} else {
$statements = $this->repository->findStatementsBy($this->statementsFilterFactory->createFromParameterBag($query));
$response = $this->buildMultiStatementsResponse($statements, $query, $includeAttachments);
}
} catch (NotFoundException $e) {
$response = $this->buildMultiStatementsResponse([], $query)
->setStatusCode(Response::HTTP_NOT_FOUND)
->setContent('');
} catch (\Exception $exception) {
$response = Response::create('', Response::HTTP_BAD_REQUEST);
}
$now = new \DateTime();
$response->headers->set('X-Experience-API-Consistent-Through', $now->format(\DateTime::ATOM));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
/**
* @param bool $includeAttachments true to include the attachments in the response, false otherwise
*
* @return JsonResponse|MultipartResponse
*/
protected function buildSingleStatementResponse(Statement $statement, $includeAttachments = false)
{
$json = $this->statementSerializer->serializeStatement($statement);
$response = new Response($json, 200);
if ($includeAttachments) {
$response = $this->buildMultipartResponse($response, [$statement]);
}
$response->setLastModified($statement->getStored());
return $response;
}
/**
* @param Statement[] $statements
* @param bool $includeAttachments true to include the attachments in the response, false otherwise
*
* @return JsonResponse|MultipartResponse
*/
protected function buildMultiStatementsResponse(array $statements, ParameterBag $query, $includeAttachments = false)
{
$moreUrlPath = $statements ? $this->generateMoreIrl($query) : null;
$json = $this->statementResultSerializer->serializeStatementResult(
new StatementResult($statements, $moreUrlPath)
);
$response = new Response($json, 200);
if ($includeAttachments) {
$response = $this->buildMultipartResponse($response, $statements);
}
return $response;
}
/**
* @param Statement[] $statements
*
* @return MultipartResponse
*/
protected function buildMultipartResponse(JsonResponse $statementResponse, array $statements)
{
$attachmentsParts = [];
foreach ($statements as $statement) {
foreach ((array) $statement->getAttachments() as $attachment) {
$attachmentsParts[] = new AttachmentResponse($attachment);
}
}
return new MultipartResponse($statementResponse, $attachmentsParts);
}
/**
* Validate the parameters.
*
* @throws BadRequestHttpException if the parameters does not comply with the xAPI specification
*/
protected function validate(ParameterBag $query)
{
$hasStatementId = $query->has('statementId');
$hasVoidedStatementId = $query->has('voidedStatementId');
if ($hasStatementId && $hasVoidedStatementId) {
throw new BadRequestHttpException('Request must not have both statementId and voidedStatementId parameters at the same time.');
}
$hasAttachments = $query->has('attachments');
$hasFormat = $query->has('format');
$queryCount = $query->count();
if (($hasStatementId || $hasVoidedStatementId) && $hasAttachments && $hasFormat && $queryCount > 3) {
throw new BadRequestHttpException('Request must not contain statementId or voidedStatementId parameters, and also any other parameter besides "attachments" or "format".');
}
if (($hasStatementId || $hasVoidedStatementId) && ($hasAttachments || $hasFormat) && $queryCount > 2) {
throw new BadRequestHttpException('Request must not contain statementId or voidedStatementId parameters, and also any other parameter besides "attachments" or "format".');
}
if (($hasStatementId || $hasVoidedStatementId) && $queryCount > 1) {
throw new BadRequestHttpException('Request must not contain statementId or voidedStatementId parameters, and also any other parameter besides "attachments" or "format".');
}
}
protected function generateMoreIrl(ParameterBag $query): IRL
{
$params = $query->all();
$params['cursor'] = empty($params['cursor']) ? 1 : $params['cursor'] + 1;
return IRL::fromString(
'/plugin/xapi/lrs.php/statements?'.http_build_query($params)
);
}
}

View File

@@ -0,0 +1,27 @@
<?php
/*
* This file is part of the xAPI package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace XApi\LrsBundle\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
class StatementHeadController extends StatementGetController
{
/**
* @throws BadRequestHttpException if the query parameters does not comply with xAPI specification
*
* @return Response
*/
public function getStatement(Request $request)
{
return parent::getStatement($request)->setContent('');
}
}

View File

@@ -0,0 +1,67 @@
<?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 XApi\LrsBundle\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
use Xabbuh\XApi\Common\Exception\NotFoundException;
use Xabbuh\XApi\Model\Statement;
use XApi\Repository\Api\StatementRepositoryInterface;
/**
* @author Jérôme Parmentier <jerome.parmentier@acensi.fr>
*/
final class StatementPostController
{
/**
* @var StatementRepositoryInterface
*/
private $repository;
public function __construct(StatementRepositoryInterface $repository)
{
$this->repository = $repository;
}
public function postStatements(Request $request, array $statements): JsonResponse
{
$statementsToStore = [];
/** @var Statement $statement */
foreach ($statements as $statement) {
if (null === $statementId = $statement->getId()) {
$statementsToStore[] = $statement;
continue;
}
try {
$existingStatement = $this->repository->findStatementById($statement->getId());
if (!$existingStatement->equals($statement)) {
throw new ConflictHttpException('The new statement is not equal to an existing statement with the same id.');
}
} catch (NotFoundException $e) {
$statementsToStore[] = $statement;
}
}
$uuids = [];
foreach ($statementsToStore as $statement) {
$uuids[] = $this->repository->storeStatement($statement, true)->getValue();
}
return new JsonResponse($uuids);
}
}

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 XApi\LrsBundle\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
use Xabbuh\XApi\Common\Exception\NotFoundException;
use Xabbuh\XApi\Model\Statement;
use Xabbuh\XApi\Model\StatementId;
use XApi\Repository\Api\StatementRepositoryInterface;
/**
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
final class StatementPutController
{
private $repository;
public function __construct(StatementRepositoryInterface $repository)
{
$this->repository = $repository;
}
public function putStatement(Request $request, Statement $statement): Response
{
if (null === $statementId = $request->query->get('statementId')) {
throw new BadRequestHttpException('Required statementId parameter is missing.');
}
try {
$id = StatementId::fromString($statementId);
} catch (\InvalidArgumentException $e) {
throw new BadRequestHttpException(sprintf('Parameter statementId ("%s") is not a valid UUID.', $statementId), $e);
}
if (null !== $statement->getId() && !$id->equals($statement->getId())) {
throw new ConflictHttpException(sprintf('Id parameter ("%s") and statement id ("%s") do not match.', $id->getValue(), $statement->getId()->getValue()));
}
try {
$existingStatement = $this->repository->findStatementById($id);
if (!$existingStatement->equals($statement)) {
throw new ConflictHttpException('The new statement is not equal to an existing statement with the same id.');
}
} catch (NotFoundException $e) {
$statement = $statement->withId($id);
$this->repository->storeStatement($statement, true);
}
return new Response('', 204);
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace XApi\LrsBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
/**
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
final class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$treeBuilder
->root('xapi_lrs')
->beforeNormalization()
->ifTrue(function ($v) { return isset($v['type']) && in_array($v['type'], ['mongodb', 'orm']) && !isset($v['object_manager_service']); })
->thenInvalid('You need to configure the object manager service when the repository type is "mongodb" or orm".')
->end()
->children()
->enumNode('type')
->isRequired()
->values(['in_memory', 'mongodb', 'orm'])
->end()
->scalarNode('object_manager_service')->end()
->end()
->end()
;
return $treeBuilder;
}
}

View File

@@ -0,0 +1,60 @@
<?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 XApi\LrsBundle\DependencyInjection;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
/**
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
final class XApiLrsExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('controller.xml');
$loader->load('event_listener.xml');
$loader->load('factory.xml');
$loader->load('serializer.xml');
switch ($config['type']) {
case 'in_memory':
break;
case 'mongodb':
$loader->load('doctrine.xml');
$loader->load('mongodb.xml');
$container->setAlias('xapi_lrs.doctrine.object_manager', $config['object_manager_service']);
$container->setAlias('xapi_lrs.repository.statement', 'xapi_lrs.repository.statement.doctrine');
break;
case 'orm':
$loader->load('doctrine.xml');
$loader->load('orm.xml');
$container->setAlias('xapi_lrs.doctrine.object_manager', $config['object_manager_service']);
$container->setAlias('xapi_lrs.repository.statement', 'xapi_lrs.repository.statement.doctrine');
break;
}
}
public function getAlias()
{
return 'xapi_lrs';
}
}

View File

@@ -0,0 +1,73 @@
<?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 XApi\LrsBundle\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
/**
* @author Jérôme Parmentier <jerome.parmentier@acensi.fr>
*/
class AlternateRequestSyntaxListener
{
public function onKernelRequest(GetResponseEvent $event)
{
if (!$event->isMasterRequest()) {
return;
}
$request = $event->getRequest();
if (!$request->attributes->has('xapi_lrs.route')) {
return;
}
if ('POST' !== $request->getMethod()) {
return;
}
if (null === $method = $request->query->get('method')) {
return;
}
if ($request->query->count() > 1) {
throw new BadRequestHttpException('Including other query parameters than "method" is not allowed. You have to send them as POST parameters inside the request body.');
}
$request->setMethod($method);
$request->query->remove('method');
if (null !== $content = $request->request->get('content')) {
$request->request->remove('content');
$request->initialize(
$request->query->all(),
$request->request->all(),
$request->attributes->all(),
$request->cookies->all(),
$request->files->all(),
$request->server->all(),
$content
);
}
foreach ($request->request as $key => $value) {
if (in_array($key, ['Authorization', 'X-Experience-API-Version', 'Content-Type', 'Content-Length', 'If-Match', 'If-None-Match'], true)) {
$request->headers->set($key, $value);
} else {
$request->query->set($key, $value);
}
$request->request->remove($key);
}
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace XApi\LrsBundle\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
/**
* Converts Experience API specific domain exceptions into proper HTTP responses.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
class ExceptionListener
{
public function onKernelException(GetResponseForExceptionEvent $event)
{
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace XApi\LrsBundle\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Serializer\Exception\ExceptionInterface as BaseSerializerException;
use Xabbuh\XApi\Serializer\StatementSerializerInterface;
/**
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
class SerializerListener
{
private $statementSerializer;
public function __construct(StatementSerializerInterface $statementSerializer)
{
$this->statementSerializer = $statementSerializer;
}
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
if (!$request->attributes->has('xapi_lrs.route')) {
return;
}
try {
switch ($request->attributes->get('xapi_serializer')) {
case 'statement':
$request->attributes->set('statement', $this->statementSerializer->deserializeStatement($request->getContent()));
break;
}
} catch (BaseSerializerException $e) {
throw new BadRequestHttpException(sprintf('The content of the request cannot be deserialized into a valid xAPI %s.', $request->attributes->get('xapi_serializer')), $e);
}
}
}

View File

@@ -0,0 +1,66 @@
<?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 XApi\LrsBundle\EventListener;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
/**
* @author Jérôme Parmentier <jerome.parmentier@acensi.fr>
*/
class VersionListener
{
public function onKernelRequest(GetResponseEvent $event)
{
if (!$event->isMasterRequest()) {
return;
}
$request = $event->getRequest();
if (!$request->attributes->has('xapi_lrs.route')) {
return;
}
if (null === $version = $request->headers->get('X-Experience-API-Version')) {
throw new BadRequestHttpException('Missing required "X-Experience-API-Version" header.');
}
if (preg_match('/^1\.0(?:\.\d+)?$/', $version)) {
if ('1.0' === $version) {
$request->headers->set('X-Experience-API-Version', '1.0.0');
}
return;
}
throw new BadRequestHttpException(sprintf('xAPI version "%s" is not supported.', $version));
}
public function onKernelResponse(FilterResponseEvent $event)
{
if (!$event->isMasterRequest()) {
return;
}
if (!$event->getRequest()->attributes->has('xapi_lrs.route')) {
return;
}
$headers = $event->getResponse()->headers;
if (!$headers->has('X-Experience-API-Version')) {
$headers->set('X-Experience-API-Version', '1.0.3');
}
}
}

View File

@@ -0,0 +1,86 @@
<?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 XApi\LrsBundle\Model;
use Symfony\Component\HttpFoundation\ParameterBag;
use Xabbuh\XApi\Model\Activity;
use Xabbuh\XApi\Model\IRI;
use Xabbuh\XApi\Model\StatementsFilter;
use Xabbuh\XApi\Model\Verb;
use Xabbuh\XApi\Serializer\ActorSerializerInterface;
/**
* @author Jérôme Parmentier <jerome.parmentier@acensi.fr>
*/
class StatementsFilterFactory
{
private $actorSerializer;
public function __construct(ActorSerializerInterface $actorSerializer)
{
$this->actorSerializer = $actorSerializer;
}
/**
* @return StatementsFilter
*/
public function createFromParameterBag(ParameterBag $parameters)
{
$filter = new StatementsFilter();
if (($actor = $parameters->get('agent')) !== null) {
$filter->byActor($this->actorSerializer->deserializeActor($actor));
}
if (($verbId = $parameters->get('verb')) !== null) {
$filter->byVerb(new Verb(IRI::fromString($verbId)));
}
if (($activityId = $parameters->get('activity')) !== null) {
$filter->byActivity(new Activity(IRI::fromString($activityId)));
}
if (($registration = $parameters->get('registration')) !== null) {
$filter->byRegistration($registration);
}
if ($parameters->filter('related_activities', false, FILTER_VALIDATE_BOOLEAN)) {
$filter->enableRelatedActivityFilter();
} else {
$filter->disableRelatedActivityFilter();
}
if ($parameters->filter('related_agents', false, FILTER_VALIDATE_BOOLEAN)) {
$filter->enableRelatedAgentFilter();
} else {
$filter->disableRelatedAgentFilter();
}
if (($since = $parameters->get('since')) !== null) {
$filter->since(\DateTime::createFromFormat(\DateTime::ATOM, $since));
}
if (($until = $parameters->get('until')) !== null) {
$filter->until(\DateTime::createFromFormat(\DateTime::ATOM, $until));
}
if ($parameters->filter('ascending', false, FILTER_VALIDATE_BOOLEAN)) {
$filter->ascending();
} else {
$filter->descending();
}
$filter->limit($parameters->getInt('limit'));
return $filter;
}
}

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="xapi_lrs.controller.statement.get" class="XApi\LrsBundle\Controller\StatementGetController">
<argument type="service" id="xapi_lrs.repository.statement"/>
<argument type="service" id="xapi_lrs.statement.serializer"/>
<argument type="service" id="xapi_lrs.statement_result.serializer"/>
<argument type="service" id="xapi_lrs.factory.statements_filter"/>
</service>
<service id="xapi_lrs.controller.statement.post" class="XApi\LrsBundle\Controller\StatementPostController"/>
<service id="xapi_lrs.controller.statement.put" class="XApi\LrsBundle\Controller\StatementPutController">
<argument type="service" id="xapi_lrs.repository.statement"/>
</service>
</services>
</container>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="xapi_lrs.repository.statement.doctrine" class="XApi\Repository\Doctrine\Repository\StatementRepository" public="false">
<argument type="service" id="xapi_lrs.repository.mapped_statement" />
</service>
</services>
</container>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="xapi_lrs.event_listener.alternate_request_syntax" class="XApi\LrsBundle\EventListener\AlternateRequestSyntaxListener">
<tag name="kernel.event_listener" event="kernel.request" />
</service>
<service id="xapi_lrs.event_listener.exception" class="XApi\LrsBundle\EventListener\ExceptionListener">
</service>
<service id="xapi_lrs.event_listener.serializer" class="XApi\LrsBundle\EventListener\SerializerListener">
<argument type="service" id="xapi_lrs.statement.serializer" />
<tag name="kernel.event_listener" event="kernel.request" />
</service>
<service id="xapi_lrs.event_listener.version" class="XApi\LrsBundle\EventListener\VersionListener">
<tag name="kernel.event_listener" event="kernel.request" />
<tag name="kernel.event_listener" event="kernel.response" />
</service>
</services>
</container>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="xapi_lrs.factory.statements_filter" class="XApi\LrsBundle\Model\StatementsFilterFactory">
<argument type="service" id="xapi_lrs.actor.serializer"/>
</service>
</services>
</container>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="xapi_lrs.doctrine.class_metadata" class="Doctrine\ORM\Mapping\ClassMetadata" public="false">
<argument>XApi\Repository\Api\Mapping\MappedStatement</argument>
<factory service="xapi_lrs.doctrine.object_manager" method="getClassMetadata" />
</service>
<service id="xapi_lrs.repository.mapped_statement" class="XApi\Repository\ORM\MappedStatementRepository" public="false">
<argument type="service" id="xapi_lrs.doctrine.object_manager" />
<argument type="service" id="xapi_lrs.doctrine.class_metadata" />
</service>
</services>
</container>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing
http://symfony.com/schema/routing/routing-1.0.xsd">
<route id="xapi_lrs.statement.put" path="/statements" methods="PUT">
<default key="_controller">xapi_lrs.controller.statement.put:putStatement</default>
<default key="xapi_serializer">statement</default>
<default key="xapi_lrs.route">
<bool>true</bool>
</default>
</route>
<route id="xapi_lrs.statement.post" path="/statements" methods="POST">
<default key="_controller">xapi_lrs.controller.statement.post:postStatement</default>
<default key="xapi_serializer">statement</default>
<default key="xapi_lrs.route">
<bool>true</bool>
</default>
</route>
<route id="xapi_lrs.statement.get" path="/statements" methods="GET">
<default key="_controller">xapi_lrs.controller.statement.get:getStatement</default>
<default key="xapi_lrs.route">
<bool>true</bool>
</default>
</route>
</routes>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="xapi_lrs.statement.serializer" class="Xabbuh\XApi\Serializer\StatementSerializerInterface" public="false">
<factory service="xapi_lrs.serializer.factory" method="createStatementSerializer"/>
</service>
<service id="xapi_lrs.statement_result.serializer" class="Xabbuh\XApi\Serializer\StatementResultSerializerInterface" public="false">
<factory service="xapi_lrs.serializer.factory" method="createStatementResultSerializer"/>
</service>
<service id="xapi_lrs.actor.serializer" class="Xabbuh\XApi\Serializer\ActorSerializerInterface" public="false">
<factory service="xapi_lrs.serializer.factory" method="createActorSerializer"/>
</service>
<service id="xapi_lrs.document_data.serializer" class="Xabbuh\XApi\Serializer\DocumentDataSerializerInterface" public="false">
<factory service="xapi_lrs.serializer.factory" method="createDocumentDataSerializer"/>
</service>
<service id="xapi_lrs.serializer_factory" class="Xabbuh\XApi\Serializer\Symfony\SerializerFactory" public="false">
<argument type="service" id="xapi_lrs.serializer"/>
</service>
<service id="xapi_lrs.serializer" class="Symfony\Component\Serializer\SerializerInterface" public="false">
<factory class="Xabbuh\XApi\Serializer\Symfony\Serializer" method="createSerializer"/>
</service>
</services>
</container>

View File

@@ -0,0 +1,76 @@
<?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 XApi\LrsBundle\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Xabbuh\XApi\Model\Attachment;
/**
* @author Jérôme Parmentier <jerome.parmentier@acensi.fr>
*/
class AttachmentResponse extends Response
{
protected $attachment;
public function __construct(Attachment $attachment)
{
parent::__construct(null);
$this->attachment = $attachment;
}
/**
* {@inheritdoc}
*/
public function prepare(Request $request)
{
if (!$this->headers->has('Content-Type')) {
$this->headers->set('Content-Type', $this->attachment->getContentType());
}
$this->headers->set('Content-Transfer-Encoding', 'binary');
$this->headers->set('X-Experience-API-Hash', $this->attachment->getSha2());
}
/**
* {@inheritdoc}
*
* @throws \LogicException
*/
public function sendContent()
{
throw new \LogicException('An AttachmentResponse is only meant to be part of a multipart Response.');
}
/**
* {@inheritdoc}
*
* @throws \LogicException when the content is not null
*/
public function setContent($content)
{
if (null !== $content) {
throw new \LogicException('The content cannot be set on an AttachmentResponse instance.');
}
}
/**
* {@inheritdoc}
*
* @return string|null
*/
public function getContent()
{
return $this->attachment->getContent();
}
}

View File

@@ -0,0 +1,135 @@
<?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 XApi\LrsBundle\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* @author Jérôme Parmentier <jerome.parmentier@acensi.fr>
*/
class MultipartResponse extends Response
{
protected $subtype;
protected $boundary;
protected $statementPart;
/**
* @var Response[]
*/
protected $parts;
/**
* @param AttachmentResponse[] $attachmentsParts
* @param int $status
* @param string|null $subtype
*/
public function __construct(JsonResponse $statementPart, array $attachmentsParts = [], $status = 200, array $headers = [], $subtype = null)
{
parent::__construct(null, $status, $headers);
if (null === $subtype) {
$subtype = 'mixed';
}
$this->subtype = $subtype;
$this->boundary = uniqid('', true);
$this->statementPart = $statementPart;
$this->setAttachmentsParts($attachmentsParts);
}
/**
* @return $this
*/
public function addAttachmentPart(AttachmentResponse $part)
{
if ($part->getContent() !== null) {
$this->parts[] = $part;
}
return $this;
}
/**
* @param AttachmentResponse[] $attachmentsParts
*
* @return $this
*/
public function setAttachmentsParts(array $attachmentsParts)
{
$this->parts = [$this->statementPart];
foreach ($attachmentsParts as $part) {
$this->addAttachmentPart($part);
}
return $this;
}
/**
* {@inheritdoc}
*/
public function prepare(Request $request)
{
foreach ($this->parts as $part) {
$part->prepare($request);
}
$this->headers->set('Content-Type', sprintf('multipart/%s; boundary="%s"', $this->subtype, $this->boundary));
$this->headers->set('Transfer-Encoding', 'chunked');
return parent::prepare($request);
}
/**
* {@inheritdoc}
*/
public function sendContent()
{
$content = '';
foreach ($this->parts as $part) {
$content .= sprintf('--%s', $this->boundary)."\r\n";
$content .= $part->headers."\r\n";
$content .= $part->getContent();
$content .= "\r\n";
}
$content .= sprintf('--%s--', $this->boundary)."\r\n";
echo $content;
return $this;
}
/**
* {@inheritdoc}
*
* @throws \LogicException when the content is not null
*/
public function setContent($content)
{
if (null !== $content) {
throw new \LogicException('The content cannot be set on a MultipartResponse instance.');
}
}
/**
* {@inheritdoc}
*
* @return false
*/
public function getContent()
{
return false;
}
}

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 XApi\LrsBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use XApi\LrsBundle\DependencyInjection\XApiLrsExtension;
/**
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
class XApiLrsBundle extends Bundle
{
public function getContainerExtension()
{
return new XApiLrsExtension();
}
}