Vendor: XAPI: Add php-xapi dependencies for LRS - refs BT#16742

pull/3680/head
Angel Fernando Quiroz Campos 5 years ago
parent fe334b013b
commit e48dc568ac
  1. 2
      plugin/xapi/php-xapi/lrs-bundle/LICENSE
  2. 0
      plugin/xapi/php-xapi/lrs-bundle/README.md
  3. 51
      plugin/xapi/php-xapi/lrs-bundle/composer.json
  4. 4
      plugin/xapi/php-xapi/lrs-bundle/phpspec.yml.dist
  5. 197
      plugin/xapi/php-xapi/lrs-bundle/spec/Controller/StatementGetControllerSpec.php
  6. 106
      plugin/xapi/php-xapi/lrs-bundle/spec/Controller/StatementPutControllerSpec.php
  7. 18
      plugin/xapi/php-xapi/lrs-bundle/spec/DependencyInjection/XApiLrsExtensionSpec.php
  8. 160
      plugin/xapi/php-xapi/lrs-bundle/spec/EventListener/AlternateRequestSyntaxListenerSpec.php
  9. 9
      plugin/xapi/php-xapi/lrs-bundle/spec/EventListener/ExceptionListenerSpec.php
  10. 59
      plugin/xapi/php-xapi/lrs-bundle/spec/EventListener/SerializerListenerSpec.php
  11. 102
      plugin/xapi/php-xapi/lrs-bundle/spec/EventListener/VersionListenerSpec.php
  12. 159
      plugin/xapi/php-xapi/lrs-bundle/spec/Model/StatementsFilterFactorySpec.php
  13. 69
      plugin/xapi/php-xapi/lrs-bundle/spec/Response/AttachmentResponseSpec.php
  14. 31
      plugin/xapi/php-xapi/lrs-bundle/spec/Response/MultipartResponseSpec.php
  15. 13
      plugin/xapi/php-xapi/lrs-bundle/spec/XApiLrsBundleSpec.php
  16. 194
      plugin/xapi/php-xapi/lrs-bundle/src/Controller/StatementGetController.php
  17. 33
      plugin/xapi/php-xapi/lrs-bundle/src/Controller/StatementPostController.php
  18. 63
      plugin/xapi/php-xapi/lrs-bundle/src/Controller/StatementPutController.php
  19. 35
      plugin/xapi/php-xapi/lrs-bundle/src/DependencyInjection/Configuration.php
  20. 60
      plugin/xapi/php-xapi/lrs-bundle/src/DependencyInjection/XApiLrsExtension.php
  21. 73
      plugin/xapi/php-xapi/lrs-bundle/src/EventListener/AlternateRequestSyntaxListener.php
  22. 17
      plugin/xapi/php-xapi/lrs-bundle/src/EventListener/ExceptionListener.php
  23. 40
      plugin/xapi/php-xapi/lrs-bundle/src/EventListener/SerializerListener.php
  24. 66
      plugin/xapi/php-xapi/lrs-bundle/src/EventListener/VersionListener.php
  25. 88
      plugin/xapi/php-xapi/lrs-bundle/src/Model/StatementsFilterFactory.php
  26. 21
      plugin/xapi/php-xapi/lrs-bundle/src/Resources/config/controller.xml
  27. 12
      plugin/xapi/php-xapi/lrs-bundle/src/Resources/config/doctrine.xml
  28. 25
      plugin/xapi/php-xapi/lrs-bundle/src/Resources/config/event_listener.xml
  29. 12
      plugin/xapi/php-xapi/lrs-bundle/src/Resources/config/factory.xml
  30. 18
      plugin/xapi/php-xapi/lrs-bundle/src/Resources/config/orm.xml
  31. 29
      plugin/xapi/php-xapi/lrs-bundle/src/Resources/config/routing.xml
  32. 32
      plugin/xapi/php-xapi/lrs-bundle/src/Resources/config/serializer.xml
  33. 79
      plugin/xapi/php-xapi/lrs-bundle/src/Response/AttachmentResponse.php
  34. 139
      plugin/xapi/php-xapi/lrs-bundle/src/Response/MultipartResponse.php
  35. 26
      plugin/xapi/php-xapi/lrs-bundle/src/XApiLrsBundle.php
  36. 19
      plugin/xapi/php-xapi/repository-doctrine-orm/LICENSE
  37. 0
      plugin/xapi/php-xapi/repository-doctrine-orm/README.md
  38. 41
      plugin/xapi/php-xapi/repository-doctrine-orm/composer.json
  39. 19
      plugin/xapi/php-xapi/repository-doctrine-orm/metadata/Actor.orm.xml
  40. 23
      plugin/xapi/php-xapi/repository-doctrine-orm/metadata/Attachment.orm.xml
  41. 59
      plugin/xapi/php-xapi/repository-doctrine-orm/metadata/Context.orm.xml
  42. 13
      plugin/xapi/php-xapi/repository-doctrine-orm/metadata/Extensions.orm.xml
  43. 28
      plugin/xapi/php-xapi/repository-doctrine-orm/metadata/Result.orm.xml
  44. 62
      plugin/xapi/php-xapi/repository-doctrine-orm/metadata/Statement.orm.xml
  45. 83
      plugin/xapi/php-xapi/repository-doctrine-orm/metadata/StatementObject.orm.xml
  46. 14
      plugin/xapi/php-xapi/repository-doctrine-orm/metadata/Verb.orm.xml
  47. 13
      plugin/xapi/php-xapi/repository-doctrine-orm/phpunit.xml.dist
  48. 50
      plugin/xapi/php-xapi/repository-doctrine-orm/src/StatementRepository.php
  49. 52
      plugin/xapi/php-xapi/repository-doctrine-orm/tests/Functional/StatementRepositoryTest.php
  50. 38
      plugin/xapi/php-xapi/repository-doctrine-orm/tests/Unit/Repository/StatementRepositoryTest.php
  51. 0
      plugin/xapi/php-xapi/repository-doctrine-orm/tests/data/.gitkeep
  52. 0
      plugin/xapi/php-xapi/repository-doctrine-orm/tests/proxies/.gitkeep
  53. 161
      plugin/xapi/src/Lrs/Entity/Actor.php
  54. 163
      plugin/xapi/src/Lrs/Entity/Attachment.php
  55. 268
      plugin/xapi/src/Lrs/Entity/Context.php
  56. 71
      plugin/xapi/src/Lrs/Entity/Extensions.php
  57. 154
      plugin/xapi/src/Lrs/Entity/Result.php
  58. 212
      plugin/xapi/src/Lrs/Entity/Statement.php
  59. 496
      plugin/xapi/src/Lrs/Entity/StatementObject.php
  60. 86
      plugin/xapi/src/Lrs/Entity/Verb.php
  61. 49
      plugin/xapi/src/Lrs/StatementsController.php
  62. 112
      plugin/xapi/src/XApiPlugin.php

@ -1,4 +1,4 @@
Copyright (c) 2014-2017 Christian Flothmann
Copyright (c) 2016-2017 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

@ -0,0 +1,51 @@
{
"name": "php-xapi/lrs-bundle",
"type": "symfony-bundle",
"description": "Experience API (xAPI) Learning Record Store (LRS) based on the Symfony Framework",
"keywords": ["xAPI", "Experience API", "Tin Can API", "LRS", "Learning Record Store", "Symfony", "bundle"],
"homepage": "https://github.com/php-xapi/lrs-bundle",
"license": "MIT",
"authors": [
{
"name": "Christian Flothmann",
"homepage": "https://github.com/xabbuh"
},
{
"name": "Jérôme Parmentier",
"homepage": "https://github.com/Lctrs"
}
],
"require": {
"php": "^7.1",
"php-xapi/exception": "^0.1 || ^0.2",
"php-xapi/model": "^1.1 || ^2.0 || ^3.0",
"php-xapi/repository-api": "^0.3@dev || ^0.4@dev",
"php-xapi/serializer": "^1.0 || ^2.0",
"php-xapi/symfony-serializer": "^1.0 || ^2.0",
"symfony/config": "^3.4 || ^4.3",
"symfony/dependency-injection": "^3.4 || ^4.3",
"symfony/http-foundation": "^3.4 || ^4.3",
"symfony/http-kernel": "^3.4 || ^4.3"
},
"require-dev": {
"phpspec/phpspec": "~2.3",
"php-xapi/json-test-fixtures": "^1.0 || ^2.0",
"php-xapi/test-fixtures": "^1.0.1",
"ramsey/uuid": "^2.9 || ^3.0"
},
"autoload": {
"psr-4": {
"XApi\\LrsBundle\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"spec\\XApi\\LrsBundle\\": "spec/"
}
},
"extra": {
"branch-alias": {
"dev-master": "0.1.x-dev"
}
}
}

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

@ -0,0 +1,197 @@
<?php
namespace spec\XApi\LrsBundle\Controller;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Xabbuh\XApi\Common\Exception\NotFoundException;
use Xabbuh\XApi\DataFixtures\StatementFixtures;
use Xabbuh\XApi\Model\StatementId;
use Xabbuh\XApi\Model\StatementResult;
use Xabbuh\XApi\Model\StatementsFilter;
use Xabbuh\XApi\Serializer\StatementResultSerializerInterface;
use Xabbuh\XApi\Serializer\StatementSerializerInterface;
use XApi\Fixtures\Json\StatementJsonFixtures;
use XApi\Fixtures\Json\StatementResultJsonFixtures;
use XApi\LrsBundle\Model\StatementsFilterFactory;
use XApi\Repository\Api\StatementRepositoryInterface;
class StatementGetControllerSpec extends ObjectBehavior
{
function let(StatementRepositoryInterface $repository, StatementSerializerInterface $statementSerializer, StatementResultSerializerInterface $statementResultSerializer, StatementsFilterFactory $statementsFilterFactory)
{
$statement = StatementFixtures::getAllPropertiesStatement();
$voidedStatement = StatementFixtures::getVoidingStatement()->withStored(new \DateTime());
$statementCollection = StatementFixtures::getStatementCollection();
$statementsFilter = new StatementsFilter();
$statementsFilterFactory->createFromParameterBag(Argument::type('\Symfony\Component\HttpFoundation\ParameterBag'))->willReturn($statementsFilter);
$repository->findStatementById(StatementId::fromString(StatementFixtures::DEFAULT_STATEMENT_ID))->willReturn($statement);
$repository->findVoidedStatementById(StatementId::fromString(StatementFixtures::DEFAULT_STATEMENT_ID))->willReturn($voidedStatement);
$repository->findStatementsBy($statementsFilter)->willReturn($statementCollection);
$statementSerializer->serializeStatement(Argument::type('\Xabbuh\XApi\Model\Statement'))->willReturn(StatementJsonFixtures::getTypicalStatement());
$statementResultSerializer->serializeStatementResult(Argument::type('\Xabbuh\XApi\Model\StatementResult'))->willReturn(StatementResultJsonFixtures::getStatementResult());
$this->beConstructedWith($repository, $statementSerializer, $statementResultSerializer, $statementsFilterFactory);
}
function it_throws_a_badrequesthttpexception_if_the_request_has_given_statement_id_and_voided_statement_id()
{
$request = new Request();
$request->query->set('statementId', StatementFixtures::DEFAULT_STATEMENT_ID);
$request->query->set('voidedStatementId', StatementFixtures::DEFAULT_STATEMENT_ID);
$this
->shouldThrow('\Symfony\Component\HttpKernel\Exception\BadRequestHttpException')
->during('getStatement', array($request));
}
function it_throws_a_badrequesthttpexception_if_the_request_has_statement_id_and_format_and_attachements_and_any_other_parameters()
{
$request = new Request();
$request->query->set('statementId', StatementFixtures::DEFAULT_STATEMENT_ID);
$request->query->set('format', 'ids');
$request->query->set('attachments', false);
$request->query->set('related_agents', false);
$this
->shouldThrow('\Symfony\Component\HttpKernel\Exception\BadRequestHttpException')
->during('getStatement', array($request));
}
function it_throws_a_badrequesthttpexception_if_the_request_has_voided_statement_id_and_format_and_any_other_parameters_except_attachments()
{
$request = new Request();
$request->query->set('voidedStatementId', StatementFixtures::DEFAULT_STATEMENT_ID);
$request->query->set('format', 'ids');
$request->query->set('related_agents', false);
$this
->shouldThrow('\Symfony\Component\HttpKernel\Exception\BadRequestHttpException')
->during('getStatement', array($request));
}
function it_throws_a_badrequesthttpexception_if_the_request_has_statement_id_and_attachments_and_any_other_parameters_except_format()
{
$request = new Request();
$request->query->set('statementId', StatementFixtures::DEFAULT_STATEMENT_ID);
$request->query->set('attachments', false);
$request->query->set('related_agents', false);
$this
->shouldThrow('\Symfony\Component\HttpKernel\Exception\BadRequestHttpException')
->during('getStatement', array($request));
}
function it_throws_a_badrequesthttpexception_if_the_request_has_voided_statement_id_and_any_other_parameters_except_format_and_attachments()
{
$request = new Request();
$request->query->set('voidedStatementId', StatementFixtures::DEFAULT_STATEMENT_ID);
$request->query->set('related_agents', false);
$this
->shouldThrow('\Symfony\Component\HttpKernel\Exception\BadRequestHttpException')
->during('getStatement', array($request));
}
function it_sets_a_X_Experience_API_Consistent_Through_header_to_the_response()
{
$request = new Request();
$request->query->set('statementId', StatementFixtures::DEFAULT_STATEMENT_ID);
$response = $this->getStatement($request);
/** @var ResponseHeaderBag $headers */
$headers = $response->headers;
$headers->has('X-Experience-API-Consistent-Through')->shouldBe(true);
}
function it_includes_a_Last_Modified_Header_if_a_single_statement_is_fetched()
{
$request = new Request();
$request->query->set('statementId', StatementFixtures::DEFAULT_STATEMENT_ID);
$response = $this->getStatement($request);
/** @var ResponseHeaderBag $headers */
$headers = $response->headers;
$headers->has('Last-Modified')->shouldBe(true);
$request = new Request();
$request->query->set('voidedStatementId', StatementFixtures::DEFAULT_STATEMENT_ID);
$response = $this->getStatement($request);
/** @var ResponseHeaderBag $headers */
$headers = $response->headers;
$headers->has('Last-Modified')->shouldBe(true);
}
function it_returns_a_multipart_response_if_attachments_parameter_is_true()
{
$request = new Request();
$request->query->set('attachments', true);
$this->getStatement($request)->shouldReturnAnInstanceOf('XApi\LrsBundle\Response\MultipartResponse');
}
function it_returns_a_jsonresponse_if_attachments_parameter_is_false_or_not_set()
{
$request = new Request();
$this->getStatement($request)->shouldReturnAnInstanceOf('\Symfony\Component\HttpFoundation\JsonResponse');
$request->query->set('attachments', false);
$this->getStatement($request)->shouldReturnAnInstanceOf('\Symfony\Component\HttpFoundation\JsonResponse');
}
function it_should_fetch_a_statement(StatementRepositoryInterface $repository)
{
$request = new Request();
$request->query->set('statementId', StatementFixtures::DEFAULT_STATEMENT_ID);
$repository->findStatementById(StatementId::fromString(StatementFixtures::DEFAULT_STATEMENT_ID))->shouldBeCalled();
$this->getStatement($request);
}
function it_should_fetch_a_voided_statement_id(StatementRepositoryInterface $repository)
{
$request = new Request();
$request->query->set('voidedStatementId', StatementFixtures::DEFAULT_STATEMENT_ID);
$repository->findVoidedStatementById(StatementId::fromString(StatementFixtures::DEFAULT_STATEMENT_ID))->shouldBeCalled();
$this->getStatement($request);
}
function it_should_filter_all_statements_if_no_statement_id_or_voided_statement_id_is_provided(StatementRepositoryInterface $repository)
{
$request = new Request();
$repository->findStatementsBy(Argument::type('\Xabbuh\XApi\Model\StatementsFilter'))->shouldBeCalled();
$this->getStatement($request);
}
function it_should_build_an_empty_statement_result_response_if_no_statement_is_found(StatementRepositoryInterface $repository, StatementResultSerializerInterface $statementResultSerializer)
{
$request = new Request();
$request->query->set('statementId', StatementFixtures::DEFAULT_STATEMENT_ID);
$repository->findStatementById(StatementId::fromString(StatementFixtures::DEFAULT_STATEMENT_ID))->willThrow('\Xabbuh\XApi\Common\Exception\NotFoundException');
$statementResultSerializer->serializeStatementResult(new StatementResult(array()))->shouldBeCalled()->willReturn(StatementResultJsonFixtures::getStatementResult());
$this->getStatement($request);
}
}

@ -0,0 +1,106 @@
<?php
namespace spec\XApi\LrsBundle\Controller;
use PhpSpec\ObjectBehavior;
use Symfony\Component\HttpFoundation\Request;
use Xabbuh\XApi\Common\Exception\NotFoundException;
use Xabbuh\XApi\DataFixtures\StatementFixtures;
use Xabbuh\XApi\Model\StatementId;
use XApi\Repository\Api\StatementRepositoryInterface;
class StatementPutControllerSpec extends ObjectBehavior
{
function let(StatementRepositoryInterface $repository)
{
$this->beConstructedWith($repository);
}
function it_throws_a_badrequesthttpexception_if_a_statement_id_is_not_part_of_a_put_request()
{
$statement = StatementFixtures::getTypicalStatement();
$request = new Request();
$this
->shouldThrow('\Symfony\Component\HttpKernel\Exception\BadRequestHttpException')
->during('putStatement', array($request, $statement));
}
function it_throws_a_badrequesthttpexception_if_the_given_statement_id_as_part_of_a_put_request_is_not_a_valid_uuid()
{
$statement = StatementFixtures::getTypicalStatement();
$request = new Request();
$request->query->set('statementId', 'invalid-uuid');
$this
->shouldThrow('\Symfony\Component\HttpKernel\Exception\BadRequestHttpException')
->during('putStatement', array($request, $statement));
}
function it_stores_a_statement_and_returns_a_204_response_if_the_statement_did_not_exist_before(StatementRepositoryInterface $repository)
{
$statement = StatementFixtures::getTypicalStatement();
$request = new Request();
$request->query->set('statementId', $statement->getId()->getValue());
$repository->findStatementById($statement->getId())->willThrow(new NotFoundException(''));
$repository->storeStatement($statement, true)->shouldBeCalled();
$response = $this->putStatement($request, $statement);
$response->shouldHaveType('Symfony\Component\HttpFoundation\Response');
$response->getStatusCode()->shouldReturn(204);
}
function it_throws_a_conflicthttpexception_if_the_id_parameter_and_the_statement_id_do_not_match_during_a_put_request()
{
$statement = StatementFixtures::getTypicalStatement();
$statementId = StatementId::fromString('39e24cc4-69af-4b01-a824-1fdc6ea8a3af');
$request = new Request();
$request->query->set('statementId', $statementId->getValue());
$this
->shouldThrow('\Symfony\Component\HttpKernel\Exception\ConflictHttpException')
->during('putStatement', array($request, $statement));
}
function it_uses_id_parameter_in_put_request_if_statement_id_is_null(StatementRepositoryInterface $repository)
{
$statement = StatementFixtures::getTypicalStatement();
$statementId = $statement->getId();
$statement = $statement->withId(null);
$request = new Request();
$request->query->set('statementId', $statementId->getValue());
$repository->findStatementById($statementId)->willReturn($statement);
$repository->findStatementById($statementId)->shouldBeCalled();
$this->putStatement($request, $statement);
}
function it_does_not_override_an_existing_statement(StatementRepositoryInterface $repository)
{
$statement = StatementFixtures::getTypicalStatement();
$request = new Request();
$request->query->set('statementId', $statement->getId()->getValue());
$repository->findStatementById($statement->getId())->willReturn($statement);
$repository->storeStatement($statement, true)->shouldNotBeCalled();
$this->putStatement($request, $statement);
}
function it_throws_a_conflicthttpexception_if_an_existing_statement_with_the_same_id_is_not_equal_during_a_put_request(StatementRepositoryInterface $repository)
{
$statement = StatementFixtures::getTypicalStatement();
$existingStatement = StatementFixtures::getAttachmentStatement()->withId($statement->getId());
$request = new Request();
$request->query->set('statementId', $statement->getId()->getValue());
$repository->findStatementById($statement->getId())->willReturn($existingStatement);
$this
->shouldThrow('\Symfony\Component\HttpKernel\Exception\ConflictHttpException')
->during('putStatement', array($request, $statement));
}
}

@ -0,0 +1,18 @@
<?php
namespace spec\XApi\LrsBundle\DependencyInjection;
use PhpSpec\ObjectBehavior;
class XApiLrsExtensionSpec extends ObjectBehavior
{
function it_is_a_di_extension()
{
$this->shouldHaveType('Symfony\Component\DependencyInjection\Extension\ExtensionInterface');
}
function its_alias_is_xapi_lrs()
{
$this->getAlias()->shouldReturn('xapi_lrs');
}
}

@ -0,0 +1,160 @@
<?php
namespace spec\XApi\LrsBundle\EventListener;
use PhpSpec\ObjectBehavior;
use Symfony\Component\HttpFoundation\FileBag;
use Symfony\Component\HttpFoundation\HeaderBag;
use Symfony\Component\HttpFoundation\ParameterBag;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\ServerBag;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
class AlternateRequestSyntaxListenerSpec extends ObjectBehavior
{
function let(GetResponseEvent $event, Request $request, ParameterBag $query, ParameterBag $post, ParameterBag $attributes, HeaderBag $headers)
{
$query->count()->willReturn(1);
$query->get('method')->willReturn('GET');
$post->getIterator()->willReturn(new \ArrayIterator());
$post->get('content')->willReturn(null);
$attributes->has('xapi_lrs.route')->willReturn(true);
$request->query = $query;
$request->request = $post;
$request->attributes = $attributes;
$request->headers = $headers;
$request->getMethod()->willReturn('POST');
$event->isMasterRequest()->willReturn(true);
$event->getRequest()->willReturn($request);
}
function it_returns_null_if_request_is_not_master(GetResponseEvent $event)
{
$event->isMasterRequest()->willReturn(false);
$event->getRequest()->shouldNotBeCalled();
$this->onKernelRequest($event)->shouldReturn(null);
}
function it_returns_null_if_request_has_no_attribute_xapi_lrs_route(GetResponseEvent $event, ParameterBag $attributes)
{
$attributes->has('xapi_lrs.route')->shouldBeCalled()->willReturn(false);
$this->onKernelRequest($event)->shouldReturn(null);
}
function it_returns_null_if_request_method_is_get(GetResponseEvent $event, Request $request, ParameterBag $query)
{
$query->get('method')->shouldNotBeCalled();
$request->getMethod()->willReturn('GET');
$this->onKernelRequest($event)->shouldReturn(null);
}
function it_returns_null_if_request_method_is_put(GetResponseEvent $event, Request $request, ParameterBag $query)
{
$query->get('method')->shouldNotBeCalled();
$request->getMethod()->willReturn('PUT');
$this->onKernelRequest($event)->shouldReturn(null);
}
function it_throws_a_badrequesthttpexception_if_other_query_parameter_than_method_is_set(GetResponseEvent $event, ParameterBag $query)
{
$query->count()->shouldBeCalled()->willReturn(2);
$this
->shouldThrow('\Symfony\Component\HttpKernel\Exception\BadRequestHttpException')
->during('onKernelRequest', array($event));
}
function it_sets_the_request_method_equals_to_method_query_parameter(GetResponseEvent $event, Request $request, ParameterBag $query)
{
$query->remove('method')->shouldBeCalled();
$request->setMethod('GET')->shouldBeCalled();
$this->onKernelRequest($event);
}
function it_sets_defined_post_parameters_as_header(GetResponseEvent $event, Request $request, ParameterBag $query, ParameterBag $post, HeaderBag $headers)
{
$request->setMethod('GET')->shouldBeCalled();
$query->remove('method')->shouldBeCalled();
$headerList = array(
'Authorization' => 'Authorization',
'X-Experience-API-Version' => 'X-Experience-API-Version',
'Content-Type' => 'Content-Type',
'Content-Length' => 'Content-Length',
'If-Match' => 'If-Match',
'If-None-Match' => 'If-None-Match',
);
$post->getIterator()->shouldBeCalled()->willReturn(new \ArrayIterator($headerList));
foreach ($headerList as $key => $value) {
$post->remove($key)->shouldBeCalled();
$headers->set($key, $value)->shouldBeCalled();
}
$this->onKernelRequest($event);
}
function it_sets_other_post_parameters_as_query_parameters(GetResponseEvent $event, Request $request, ParameterBag $query, ParameterBag $post)
{
$request->setMethod('GET')->shouldBeCalled();
$query->remove('method')->shouldBeCalled();
$parameterList = array(
'token' => 'a-token',
'attachments' => true,
);
$post->getIterator()->shouldBeCalled()->willReturn(new \ArrayIterator($parameterList));
foreach ($parameterList as $key => $value) {
$post->remove($key)->shouldBeCalled();
$query->set($key, $value)->shouldBeCalled();
}
$this->onKernelRequest($event);
}
function it_sets_content_from_post_parameters(GetResponseEvent $event, Request $request, ParameterBag $query, ParameterBag $post, ParameterBag $attributes, ParameterBag $cookies, FileBag $files, ServerBag $server)
{
$query->all()->shouldBeCalled()->willReturn(array());
$query->remove('method')->shouldBeCalled();
$post->all()->shouldBeCalled()->willReturn(array());
$post->get('content')->shouldBeCalled()->willReturn('a content');
$post->remove('content')->shouldBeCalled();
$attributes->all()->shouldBeCalled()->willReturn(array());
$cookies->all()->shouldBeCalled()->willReturn(array());
$files->all()->shouldBeCalled()->willReturn(array());
$server->all()->shouldBeCalled()->willReturn(array());
$request->setMethod('GET')->shouldBeCalled();
$request->initialize(
array(),
array(),
array(),
array(),
array(),
array(),
'a content'
)->shouldBeCalled();
$request->cookies = $cookies;
$request->files = $files;
$request->server = $server;
$this->onKernelRequest($event);
}
}

@ -0,0 +1,9 @@
<?php
namespace spec\XApi\LrsBundle\EventListener;
use PhpSpec\ObjectBehavior;
class ExceptionListenerSpec extends ObjectBehavior
{
}

@ -0,0 +1,59 @@
<?php
namespace spec\XApi\LrsBundle\EventListener;
use PhpSpec\ObjectBehavior;
use Symfony\Component\HttpFoundation\ParameterBag;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Xabbuh\XApi\Serializer\StatementSerializerInterface;
use XApi\Fixtures\Json\StatementJsonFixtures;
class SerializerListenerSpec extends ObjectBehavior
{
function let(StatementSerializerInterface $statementSerializer, GetResponseEvent $event, Request $request, ParameterBag $attributes)
{
$attributes->has('xapi_lrs.route')->willReturn(true);
$request->attributes = $attributes;
$event->getRequest()->willReturn($request);
$this->beConstructedWith($statementSerializer);
}
function it_returns_null_if_request_has_no_attribute_xapi_lrs_route(GetResponseEvent $event, ParameterBag $attributes)
{
$attributes->has('xapi_lrs.route')->shouldBeCalled()->willReturn(false);
$attributes->get('xapi_serializer')->shouldNotBeCalled();
$this->onKernelRequest($event)->shouldReturn(null);
}
function it_sets_unserialized_data_as_request_attributes(StatementSerializerInterface $statementSerializer, GetResponseEvent $event, Request $request, ParameterBag $attributes)
{
$jsonString = StatementJsonFixtures::getTypicalStatement();
$statementSerializer->deserializeStatement($jsonString)->shouldBeCalled();
$attributes->get('xapi_serializer')->willReturn('statement');
$attributes->set('statement', null)->shouldBeCalled();
$request->getContent()->shouldBeCalled()->willReturn($jsonString);
$this->onKernelRequest($event);
}
function it_throws_a_badrequesthttpexception_if_the_serializer_fails(StatementSerializerInterface $statementSerializer, GetResponseEvent $event, Request $request, ParameterBag $attributes)
{
$statementSerializer->deserializeStatement(null)->shouldBeCalled()->willThrow('\Symfony\Component\Serializer\Exception\InvalidArgumentException');
$attributes->get('xapi_serializer')->willReturn('statement');
$request->attributes = $attributes;
$this
->shouldThrow('\Symfony\Component\HttpKernel\Exception\BadRequestHttpException')
->during('onKernelRequest', array($event));
}
}

@ -0,0 +1,102 @@
<?php
namespace spec\XApi\LrsBundle\EventListener;
use PhpSpec\ObjectBehavior;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\HttpFoundation\HeaderBag;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
class VersionListenerSpec extends ObjectBehavior
{
function let(GetResponseEvent $getResponseEvent, FilterResponseEvent $filterResponseEvent, Request $request, ParameterBag $attributes, HeaderBag $requestHeaders)
{
$attributes->has('xapi_lrs.route')->willReturn(true);
$request->attributes = $attributes;
$request->headers = $requestHeaders;
$getResponseEvent->isMasterRequest()->willReturn(true);
$getResponseEvent->getRequest()->willReturn($request);
$filterResponseEvent->isMasterRequest()->willReturn(true);
$filterResponseEvent->getRequest()->willReturn($request);
}
function it_returns_null_if_requests_are_not_master(GetResponseEvent $getResponseEvent, FilterResponseEvent $filterResponseEvent)
{
$getResponseEvent->isMasterRequest()->willReturn(false);
$getResponseEvent->getRequest()->shouldNotBeCalled();
$this->onKernelRequest($getResponseEvent)->shouldReturn(null);
$filterResponseEvent->isMasterRequest()->willReturn(false);
$filterResponseEvent->getRequest()->shouldNotBeCalled();
$this->onKernelResponse($filterResponseEvent)->shouldReturn(null);
}
function it_returns_null_if_not_xapi_route(GetResponseEvent $getResponseEvent, FilterResponseEvent $filterResponseEvent, ParameterBag $attributes)
{
$attributes->has('xapi_lrs.route')->shouldBeCalled()->willReturn(false);
$this->onKernelRequest($getResponseEvent)->shouldReturn(null);
$this->onKernelResponse($filterResponseEvent)->shouldReturn(null);
}
function it_throws_a_badrequesthttpexception_if_no_X_Experience_API_Version_header_is_set(GetResponseEvent $getResponseEvent, HeaderBag $requestHeaders)
{
$requestHeaders->get('X-Experience-API-Version')->shouldBeCalled()->willReturn(null);
$this
->shouldThrow(new BadRequestHttpException('Missing required "X-Experience-API-Version" header.'))
->during('onKernelRequest', array($getResponseEvent));
}
function it_throws_a_badrequesthttpexception_if_specified_version_is_not_supported(GetResponseEvent $getResponseEvent, HeaderBag $requestHeaders)
{
$requestHeaders->get('X-Experience-API-Version')->shouldBeCalled()->willReturn('0.9.5');
$this
->shouldThrow(new BadRequestHttpException('xAPI version "0.9.5" is not supported.'))
->during('onKernelRequest', array($getResponseEvent));
$requestHeaders->get('X-Experience-API-Version')->shouldBeCalled()->willReturn('1.1.0');
$this
->shouldThrow(new BadRequestHttpException('xAPI version "1.1.0" is not supported.'))
->during('onKernelRequest', array($getResponseEvent));
}
function it_normalizes_the_X_Experience_API_Version_header(GetResponseEvent $getResponseEvent, HeaderBag $requestHeaders)
{
$requestHeaders->get('X-Experience-API-Version')->shouldBeCalled()->willReturn('1.0');
$requestHeaders->set('X-Experience-API-Version', '1.0.0')->shouldBeCalled();
$this->onKernelRequest($getResponseEvent);
}
function it_returns_null_if_version_is_supported(GetResponseEvent $getResponseEvent, HeaderBag $requestHeaders)
{
$requestHeaders->get('X-Experience-API-Version')->shouldBeCalled()->willReturn('1.0.0');
$this->onKernelRequest($getResponseEvent)->shouldReturn(null);
}
function it_sets_a_X_Experience_API_Version_header_in_response(FilterResponseEvent $filterResponseEvent, Response $response, HeaderBag $responseHeaders)
{
$responseHeaders->has('X-Experience-API-Version')->shouldBeCalled()->willReturn(false);
$responseHeaders->set('X-Experience-API-Version', '1.0.3')->shouldBeCalled();
$response->headers = $responseHeaders;
$filterResponseEvent->getResponse()->shouldBeCalled()->willReturn($response);
$this->onKernelResponse($filterResponseEvent);
}
}

@ -0,0 +1,159 @@
<?php
namespace spec\XApi\LrsBundle\Model;
use PhpSpec\ObjectBehavior;
use Symfony\Component\HttpFoundation\ParameterBag;
use Xabbuh\XApi\DataFixtures\ActorFixtures;
use Xabbuh\XApi\DataFixtures\UuidFixtures;
use Xabbuh\XApi\Model\StatementsFilter;
use Xabbuh\XApi\Serializer\ActorSerializerInterface;
use XApi\Fixtures\Json\ActorJsonFixtures;
class StatementsFilterFactorySpec extends ObjectBehavior
{
function let(ActorSerializerInterface $actorSerializer)
{
$this->beConstructedWith($actorSerializer);
}
function it_sets_default_filter_when_parameters_are_empty()
{
$filter = $this->createFromParameterBag(new ParameterBag())->getFilter();
$filter->shouldNotHaveKey('agent');
$filter->shouldNotHaveKey('verb');
$filter->shouldNotHaveKey('activity');
$filter->shouldNotHaveKey('registration');
$filter->shouldNotHaveKey('since');
$filter->shouldNotHaveKey('until');
$filter->shouldHaveKeyWithValue('related_activities', 'false');
$filter->shouldHaveKeyWithValue('related_agents', 'false');
$filter->shouldHaveKeyWithValue('ascending', 'false');
$filter->shouldHaveKeyWithValue('limit', 0);
}
function it_sets_an_agent_filter(ActorSerializerInterface $actorSerializer)
{
$json = ActorJsonFixtures::getTypicalAgent();
$actor = ActorFixtures::getTypicalAgent();
$actorSerializer->deserializeActor($json)->shouldBeCalled()->willReturn($actor);
$this->beConstructedWith($actorSerializer);
$parameters = new ParameterBag();
$parameters->set('agent', $json);
/** @var StatementsFilter $filter */
$filter = $this->createFromParameterBag($parameters);
$filter->getFilter()->shouldHaveKeyWithValue('agent', $actor);
}
function it_sets_a_verb_filter()
{
$verbId = 'http://tincanapi.com/conformancetest/verbid';
$parameters = new ParameterBag();
$parameters->set('verb', $verbId);
/** @var StatementsFilter $filter */
$filter = $this->createFromParameterBag($parameters);
$filter->getFilter()->shouldHaveKeyWithValue('verb', $verbId);
}
function it_sets_an_activity_filter()
{
$activityId = 'http://tincanapi.com/conformancetest/activityid';
$parameters = new ParameterBag();
$parameters->set('activity', $activityId);
/** @var StatementsFilter $filter */
$filter = $this->createFromParameterBag($parameters);
$filter->getFilter()->shouldHaveKeyWithValue('activity', $activityId);
}
function it_sets_a_registration_filter()
{
$registration = UuidFixtures::getGoodUuid();
$parameters = new ParameterBag();
$parameters->set('registration', $registration);
/** @var StatementsFilter $filter */
$filter = $this->createFromParameterBag($parameters);
$filter->getFilter()->shouldHaveKeyWithValue('registration', $registration);
}
function it_sets_a_related_activities_filter()
{
$parameters = new ParameterBag();
$parameters->set('related_activities', true);
/** @var StatementsFilter $filter */
$filter = $this->createFromParameterBag($parameters);
$filter->getFilter()->shouldHaveKeyWithValue('related_activities', 'true');
}
function it_sets_a_related_agents_filter()
{
$parameters = new ParameterBag();
$parameters->set('related_agents', true);
/** @var StatementsFilter $filter */
$filter = $this->createFromParameterBag($parameters);
$filter->getFilter()->shouldHaveKeyWithValue('related_agents', 'true');
}
function it_sets_a_since_filter()
{
$now = new \DateTime();
$parameters = new ParameterBag();
$parameters->set('since', $now->format(\DateTime::ATOM));
/** @var StatementsFilter $filter */
$filter = $this->createFromParameterBag($parameters);
$filter->getFilter()->shouldHaveKeyWithValue('since', $now->format('c'));
}
function it_sets_an_until_filter()
{
$now = new \DateTime();
$parameters = new ParameterBag();
$parameters->set('until', $now->format(\DateTime::ATOM));
/** @var StatementsFilter $filter */
$filter = $this->createFromParameterBag($parameters);
$filter->getFilter()->shouldHaveKeyWithValue('until', $now->format('c'));
}
function it_sets_an_ascending_filter()
{
$parameters = new ParameterBag();
$parameters->set('ascending', true);
/** @var StatementsFilter $filter */
$filter = $this->createFromParameterBag($parameters);
$filter->getFilter()->shouldHaveKeyWithValue('ascending', 'true');
}
function it_sets_a_limit_filter()
{
$parameters = new ParameterBag();
$parameters->set('limit', 10);
/** @var StatementsFilter $filter */
$filter = $this->createFromParameterBag($parameters);
$filter->getFilter()->shouldHaveKeyWithValue('limit', 10);
}
}

@ -0,0 +1,69 @@
<?php
namespace spec\XApi\LrsBundle\Response;
use PhpSpec\ObjectBehavior;
use Symfony\Component\HttpFoundation\Request;
use Xabbuh\XApi\DataFixtures\AttachmentFixtures;
use Xabbuh\XApi\Model\Attachment;
class AttachmentResponseSpec extends ObjectBehavior
{
/**
* @var Attachment
*/
private $attachment;
function let()
{
$this->attachment = AttachmentFixtures::getTextAttachment();
$this->beConstructedWith($this->attachment);
}
function it_should_throw_a_logicexception_when_sending_content()
{
$this
->shouldThrow('\LogicException')
->during('sendContent');
}
function it_should_throw_a_logicexception_when_setting_content()
{
$this
->shouldThrow('\LogicException')
->during('setContent', array('a custom content'));
}
function it_should_return_content_of_the_attachment()
{
$this->getContent()->shouldBe($this->attachment->getContent());
}
function it_should_set_Content_Type_header_equals_to_ContentType_property_of_attachment()
{
$request = new Request();
$this->prepare($request);
$this->headers->get('Content-Type')->shouldBe($this->attachment->getContentType());
}
function it_should_set_Content_Transfer_Encoding_header_equals_to_binary()
{
$request = new Request();
$this->prepare($request);
$this->headers->get('Content-Transfer-Encoding')->shouldBe('binary');
}
function it_should_set_X_Experience_API_Hash_header_equals_to_sha2_property_of_attachment()
{
$request = new Request();
$this->prepare($request);
$this->headers->get('X-Experience-API-Hash')->shouldBe($this->attachment->getSha2());
}
}

@ -0,0 +1,31 @@
<?php
namespace spec\XApi\LrsBundle\Response;
use PhpSpec\ObjectBehavior;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
class MultipartResponseSpec extends ObjectBehavior
{
function let(JsonResponse $statementResponse)
{
$this->beConstructedWith($statementResponse);
}
function it_should_throw_a_logicexception_when_setting_content()
{
$this
->shouldThrow('\LogicException')
->during('setContent', array('a custom content'));
}
function it_should_set_Content_Type_header_of_a_multipart_response()
{
$request = new Request();
$this->prepare($request);
$this->headers->get('Content-Type')->shouldStartWith('multipart/mixed; boundary=');
}
}

@ -0,0 +1,13 @@
<?php
namespace spec\XApi\LrsBundle;
use PhpSpec\ObjectBehavior;
class XApiLrsBundleSpec extends ObjectBehavior
{
function it_is_a_bundle()
{
$this->shouldHaveType('Symfony\Component\HttpKernel\Bundle\Bundle');
}
}

@ -0,0 +1,194 @@
<?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\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>
*/
final class StatementGetController
{
private static $getParameters = array(
'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,
);
private $repository;
private $statementSerializer;
private $statementResultSerializer;
private $statementsFilterFactory;
public function __construct(StatementRepositoryInterface $repository, StatementSerializerInterface $statementSerializer, StatementResultSerializerInterface $statementResultSerializer, StatementsFilterFactory $statementsFilterFactory)
{
$this->repository = $repository;
$this->statementSerializer = $statementSerializer;
$this->statementResultSerializer = $statementResultSerializer;
$this->statementsFilterFactory = $statementsFilterFactory;
}
/**
* @param Request $request
*
* @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, $includeAttachments);
}
} catch (NotFoundException $e) {
$response = $this->buildMultiStatementsResponse(array());
}
$now = new \DateTime();
$response->headers->set('X-Experience-API-Consistent-Through', $now->format(\DateTime::ATOM));
return $response;
}
/**
* @param Statement $statement
* @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 JsonResponse($json, 200, array(), true);
if ($includeAttachments) {
$response = $this->buildMultipartResponse($response, array($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, $includeAttachments = false)
{
$json = $this->statementResultSerializer->serializeStatementResult(new StatementResult($statements));
$response = new JsonResponse($json, 200, array(), true);
if ($includeAttachments) {
$response = $this->buildMultipartResponse($response, $statements);
}
return $response;
}
/**
* @param JsonResponse $statementResponse
* @param Statement[] $statements
*
* @return MultipartResponse
*/
protected function buildMultipartResponse(JsonResponse $statementResponse, array $statements)
{
$attachmentsParts = array();
foreach ($statements as $statement) {
foreach ((array) $statement->getAttachments() as $attachment) {
$attachmentsParts[] = new AttachmentResponse($attachment);
}
}
return new MultipartResponse($statementResponse, $attachmentsParts);
}
/**
* Validate the parameters.
*
* @param ParameterBag $query
*
* @throws BadRequestHttpException if the parameters does not comply with the xAPI specification
*/
private 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".');
}
}
}

@ -0,0 +1,33 @@
<?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 Xabbuh\XApi\Model\Statement;
/**
* @author Jérôme Parmentier <jerome.parmentier@acensi.fr>
*/
final class StatementPostController
{
public function postStatement(Request $request, Statement $statement)
{
}
/**
* @param Request $request
* @param Statement[] $statements
*/
public function postStatements(Request $request, array $statements)
{
}
}

@ -0,0 +1,63 @@
<?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)
{
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) {
$this->repository->storeStatement($statement, true);
}
return new Response('', 204);
}
}

@ -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'], array('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(array('in_memory', 'mongodb', 'orm'))
->end()
->scalarNode('object_manager_service')->end()
->end()
->end()
;
return $treeBuilder;
}
}

@ -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';
}
}

@ -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, array('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);
}
}
}

@ -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)
{
}
}

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

@ -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');
}
}
}

@ -0,0 +1,88 @@
<?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;
}
/**
* @param ParameterBag $parameters
*
* @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;
}
}

@ -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>

@ -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>

@ -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>

@ -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>

@ -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>

@ -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>

@ -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>

@ -0,0 +1,79 @@
<?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;
/**
* @param Attachment $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 null|string
*/
public function getContent()
{
return $this->attachment->getContent();
}
}

@ -0,0 +1,139 @@
<?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 JsonResponse $statementPart
* @param AttachmentResponse[] $attachmentsParts
* @param int $status
* @param array $headers
* @param null|string $subtype
*/
public function __construct(JsonResponse $statementPart, array $attachmentsParts = array(), $status = 200, array $headers = array(), $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);
}
/**
* @param AttachmentResponse $part
*
* @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 = array($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;
}
}

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

@ -0,0 +1,19 @@
Copyright (c) 2016-2017 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.

@ -0,0 +1,41 @@
{
"name": "php-xapi/repository-doctrine-orm",
"description": "Doctrine based ORM implementations of an Experience API (xAPI) repository",
"keywords": ["xAPI", "Tin Can API", "Experience API", "storage", "database", "repository", "entity", "Doctrine", "ORM"],
"homepage": "https://github.com/php-xapi/repository-orm/",
"license": "MIT",
"authors": [
{
"name": "Christian Flothmann",
"homepage": "https://github.com/xabbuh"
}
],
"require": {
"php": "^5.6 || ^7.0",
"doctrine/orm": "^2.3",
"php-xapi/repository-api": "^0.4",
"php-xapi/repository-doctrine": "^0.4"
},
"require-dev": {
"symfony/phpunit-bridge": "^3.4 || ^4.0"
},
"provide": {
"php-xapi/repository-implementation": "0.3"
},
"autoload": {
"psr-4": {
"XApi\\Repository\\ORM\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"XApi\\Repository\\ORM\\Tests\\": "tests/"
}
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "0.1.x-dev"
}
}
}

@ -0,0 +1,19 @@
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="XApi\Repository\Doctrine\Mapping\Actor" table="xapi_actor">
<id name="identifier" type="integer">
<generator strategy="AUTO" />
</id>
<field name="type" type="string" nullable="true" />
<field name="mbox" type="string" nullable="true" />
<field name="mboxSha1Sum" type="string" nullable="true" />
<field name="openId" type="string" nullable="true" />
<field name="accountName" type="string" nullable="true" />
<field name="accountHomePage" type="string" nullable="true" />
<field name="name" type="string" nullable="true" />
</entity>
</doctrine-mapping>

@ -0,0 +1,23 @@
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="XApi\Repository\Doctrine\Mapping\Attachment" table="xapi_attachment">
<id name="identifier" type="integer">
<generator strategy="AUTO" />
</id>
<field name="usageType" type="string" />
<field name="contentType" type="string" />
<field name="length" type="integer" />
<field name="sha2" type="string" />
<field name="display" type="json_array" />
<field name="hasDescription" type="boolean" />
<field name="description" type="json_array" nullable="true" />
<field name="fileUrl" type="string" nullable="true" />
<field name="content" type="text" nullable="true" />
<many-to-one field="statement" target-entity="XApi\Repository\Doctrine\Mapping\Statement" inversed-by="attachments" />
</entity>
</doctrine-mapping>

@ -0,0 +1,59 @@
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="XApi\Repository\Doctrine\Mapping\Context" table="xapi_context">
<id name="identifier" type="integer">
<generator strategy="AUTO" />
</id>
<field name="registration" type="string" nullable="true" />
<field name="hasContextActivities" type="boolean" nullable="true" />
<field name="revision" type="string" nullable="true" />
<field name="platform" type="string" nullable="true" />
<field name="language" type="string" nullable="true" />
<field name="statement" type="string" nullable="true" />
<one-to-one field="instructor" target-entity="XApi\Repository\Doctrine\Mapping\StatementObject">
<cascade>
<cascade-all />
</cascade>
<join-column referenced-column-name="identifier" />
</one-to-one>
<one-to-one field="team" target-entity="XApi\Repository\Doctrine\Mapping\StatementObject">
<cascade>
<cascade-all />
</cascade>
<join-column referenced-column-name="identifier" />
</one-to-one>
<one-to-one field="extensions" target-entity="XApi\Repository\Doctrine\Mapping\Extensions">
<cascade>
<cascade-all />
</cascade>
<join-column referenced-column-name="identifier" />
</one-to-one>
<!-- context activities -->
<one-to-many field="parentActivities" target-entity="XApi\Repository\Doctrine\Mapping\StatementObject" mapped-by="parentContext">
<cascade>
<cascade-all />
</cascade>
</one-to-many>
<one-to-many field="groupingActivities" target-entity="XApi\Repository\Doctrine\Mapping\StatementObject" mapped-by="groupingContext">
<cascade>
<cascade-all />
</cascade>
</one-to-many>
<one-to-many field="categoryActivities" target-entity="XApi\Repository\Doctrine\Mapping\StatementObject" mapped-by="categoryContext">
<cascade>
<cascade-all />
</cascade>
</one-to-many>
<one-to-many field="otherActivities" target-entity="XApi\Repository\Doctrine\Mapping\StatementObject" mapped-by="otherContext">
<cascade>
<cascade-all />
</cascade>
</one-to-many>
</entity>
</doctrine-mapping>

@ -0,0 +1,13 @@
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="XApi\Repository\Doctrine\Mapping\Extensions" table="xapi_extensions">
<id name="identifier" type="integer">
<generator strategy="AUTO" />
</id>
<field name="extensions" type="json_array" />
</entity>
</doctrine-mapping>

@ -0,0 +1,28 @@
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="XApi\Repository\Doctrine\Mapping\Result" table="xapi_result">
<id name="identifier" type="integer">
<generator strategy="AUTO" />
</id>
<field name="hasScore" type="boolean" />
<field name="scaled" type="float" nullable="true" />
<field name="raw" type="float" nullable="true" />
<field name="min" type="float" nullable="true" />
<field name="max" type="float" nullable="true" />
<field name="success" type="boolean" nullable="true" />
<field name="completion" type="boolean" nullable="true" />
<field name="response" type="string" nullable="true" />
<field name="duration" type="string" nullable="true" />
<one-to-one field="extensions" target-entity="XApi\Repository\Doctrine\Mapping\Extensions">
<cascade>
<cascade-all />
</cascade>
<join-column referenced-column-name="identifier" />
</one-to-one>
</entity>
</doctrine-mapping>

@ -0,0 +1,62 @@
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="XApi\Repository\Doctrine\Mapping\Statement"
repository-class="XApi\Repository\ORM\StatementRepository"
table="xapi_statement">
<id name="id" type="string">
<generator strategy="NONE" />
</id>
<field name="created" type="bigint" nullable="true" />
<field name="stored" type="bigint" nullable="true" />
<field name="hasAttachments" type="boolean" />
<one-to-one field="actor" target-entity="XApi\Repository\Doctrine\Mapping\StatementObject">
<cascade>
<cascade-all />
</cascade>
<join-column referenced-column-name="identifier" />
</one-to-one>
<one-to-one field="verb" target-entity="XApi\Repository\Doctrine\Mapping\Verb">
<cascade>
<cascade-all />
</cascade>
<join-column referenced-column-name="identifier" />
</one-to-one>
<one-to-one field="object" target-entity="XApi\Repository\Doctrine\Mapping\StatementObject">
<cascade>
<cascade-all />
</cascade>
<join-column referenced-column-name="identifier" />
</one-to-one>
<one-to-one field="result" target-entity="XApi\Repository\Doctrine\Mapping\Result">
<cascade>
<cascade-all />
</cascade>
<join-column referenced-column-name="identifier" />
</one-to-one>
<one-to-one field="authority" target-entity="XApi\Repository\Doctrine\Mapping\StatementObject">
<cascade>
<cascade-all />
</cascade>
<join-column referenced-column-name="identifier" />
</one-to-one>
<one-to-one field="context" target-entity="XApi\Repository\Doctrine\Mapping\Context">
<cascade>
<cascade-all />
</cascade>
<join-column referenced-column-name="identifier" />
</one-to-one>
<!-- attachments -->
<one-to-many field="attachments" target-entity="XApi\Repository\Doctrine\Mapping\Attachment" mapped-by="statement">
<cascade>
<cascade-all />
</cascade>
</one-to-many>
</entity>
</doctrine-mapping>

@ -0,0 +1,83 @@
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="XApi\Repository\Doctrine\Mapping\StatementObject" table="xapi_object">
<id name="identifier" type="integer">
<generator strategy="AUTO" />
</id>
<!-- discriminator column -->
<field name="type" type="string" nullable="true" />
<!-- activity -->
<field name="activityId" type="string" nullable="true" />
<field name="hasActivityDefinition" type="boolean" nullable="true" />
<field name="hasActivityName" type="boolean" nullable="true" />
<field name="activityName" type="json_array" nullable="true" />
<field name="hasActivityDescription" type="boolean" nullable="true" />
<field name="activityDescription" type="json_array" nullable="true" />
<field name="activityType" type="string" nullable="true" />
<field name="activityMoreInfo" type="string" nullable="true" />
<!-- actor -->
<field name="mbox" type="string" nullable="true" />
<field name="mboxSha1Sum" type="string" nullable="true" />
<field name="openId" type="string" nullable="true" />
<field name="accountName" type="string" nullable="true" />
<field name="accountHomePage" type="string" nullable="true" />
<field name="name" type="string" nullable="true" />
<!-- statement reference -->
<field name="referencedStatementId" type="string" nullable="true" />
<!-- sub statement -->
<one-to-one field="actor" target-entity="XApi\Repository\Doctrine\Mapping\StatementObject">
<cascade>
<cascade-all />
</cascade>
<join-column referenced-column-name="identifier" />
</one-to-one>
<one-to-one field="verb" target-entity="XApi\Repository\Doctrine\Mapping\Verb">
<cascade>
<cascade-all />
</cascade>
<join-column referenced-column-name="identifier" />
</one-to-one>
<one-to-one field="object" target-entity="XApi\Repository\Doctrine\Mapping\StatementObject">
<cascade>
<cascade-all />
</cascade>
<join-column referenced-column-name="identifier" />
</one-to-one>
<!-- activity extensions -->
<one-to-one field="activityExtensions" target-entity="XApi\Repository\Doctrine\Mapping\Extensions">
<cascade>
<cascade-all />
</cascade>
<join-column referenced-column-name="identifier" />
</one-to-one>
<!-- group members -->
<one-to-many target-entity="XApi\Repository\Doctrine\Mapping\StatementObject" mapped-by="group" field="members" />
<many-to-one target-entity="XApi\Repository\Doctrine\Mapping\StatementObject" field="group" inversed-by="members">
<join-column referenced-column-name="identifier" />
</many-to-one>
<!-- context activities -->
<many-to-one target-entity="XApi\Repository\Doctrine\Mapping\Context" field="parentContext" inversed-by="parentActivities">
<join-column referenced-column-name="identifier" />
</many-to-one>
<many-to-one target-entity="XApi\Repository\Doctrine\Mapping\Context" field="groupingContext" inversed-by="groupingActivities">
<join-column referenced-column-name="identifier" />
</many-to-one>
<many-to-one target-entity="XApi\Repository\Doctrine\Mapping\Context" field="categoryContext" inversed-by="categoryActivities">
<join-column referenced-column-name="identifier" />
</many-to-one>
<many-to-one target-entity="XApi\Repository\Doctrine\Mapping\Context" field="otherContext" inversed-by="otherActivities">
<join-column referenced-column-name="identifier" />
</many-to-one>
</entity>
</doctrine-mapping>

@ -0,0 +1,14 @@
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="XApi\Repository\Doctrine\Mapping\Verb" table="xapi_verb">
<id name="identifier" type="integer">
<generator strategy="AUTO" />
</id>
<field name="id" type="string" />
<field name="display" type="json_array" />
</entity>
</doctrine-mapping>

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true" bootstrap="vendor/autoload.php">
<testsuites>
<testsuite name="Doctrine ORM driver implementation for an xAPI repository">
<directory>./tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./src</directory>
</whitelist>
</filter>
</phpunit>

@ -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 XApi\Repository\ORM;
use Doctrine\ORM\EntityRepository;
use XApi\Repository\Doctrine\Mapping\Statement;
use XApi\Repository\Doctrine\Repository\Mapping\StatementRepository as BaseStatementRepository;
/**
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
final class StatementRepository extends EntityRepository implements BaseStatementRepository
{
/**
* {@inheritdoc}
*/
public function findStatement(array $criteria)
{
return parent::findOneBy($criteria);
}
/**
* {@inheritdoc}
*/
public function findStatements(array $criteria)
{
return parent::findBy($criteria);
}
/**
* {@inheritdoc}
*/
public function storeStatement(Statement $mappedStatement, $flush = true)
{
$this->_em->persist($mappedStatement);
if ($flush) {
$this->_em->flush();
}
}
}

@ -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 XApi\Repository\ORM\Tests\Functional;
use Doctrine\Common\Persistence\Mapping\Driver\SymfonyFileLocator;
use Doctrine\ORM\Configuration;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Mapping\Driver\XmlDriver;
use Doctrine\ORM\Tools\SchemaTool;
use XApi\Repository\Doctrine\Test\Functional\StatementRepositoryTest as BaseStatementRepositoryTest;
class StatementRepositoryTest extends BaseStatementRepositoryTest
{
protected function createObjectManager()
{
$config = new Configuration();
$config->setProxyDir(__DIR__.'/../proxies');
$config->setProxyNamespace('Proxy');
$fileLocator = new SymfonyFileLocator(
array(__DIR__.'/../../metadata' => 'XApi\Repository\Doctrine\Mapping'),
'.orm.xml'
);
$driver = new XmlDriver($fileLocator);
$config->setMetadataDriverImpl($driver);
return EntityManager::create(array('driver' => 'pdo_sqlite', 'path' => __DIR__.'/../data/db.sqlite'), $config);
}
protected function getStatementClassName()
{
return 'XApi\Repository\Doctrine\Mapping\Statement';
}
protected function cleanDatabase()
{
$metadata = $this->objectManager->getMetadataFactory()->getAllMetadata();
$tool = new SchemaTool($this->objectManager);
$tool->dropDatabase();
$tool->createSchema($metadata);
parent::cleanDatabase();
}
}

@ -0,0 +1,38 @@
<?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\Repository\ORM\Tests\Unit\Repository;
use XApi\Repository\Doctrine\Test\Unit\Repository\Mapping\StatementRepositoryTest as BaseStatementRepositoryTest;
use XApi\Repository\ORM\StatementRepository;
class StatementRepositoryTest extends BaseStatementRepositoryTest
{
protected function getObjectManagerClass()
{
return 'Doctrine\ORM\EntityManager';
}
protected function getUnitOfWorkClass()
{
return 'Doctrine\ORM\UnitOfWork';
}
protected function getClassMetadataClass()
{
return 'Doctrine\ORM\Mapping\ClassMetadata';
}
protected function createMappedStatementRepository($objectManager, $unitOfWork, $classMetadata)
{
return new StatementRepository($objectManager, $classMetadata);
}
}

@ -1,161 +0,0 @@
<?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 Chamilo\PluginBundle\Entity\XApi\Lrs;
use Doctrine\ORM\Mapping as ORM;
use Xabbuh\XApi\Model\Account;
use Xabbuh\XApi\Model\Actor as ActorModel;
use Xabbuh\XApi\Model\Agent;
use Xabbuh\XApi\Model\Group;
use Xabbuh\XApi\Model\InverseFunctionalIdentifier;
use Xabbuh\XApi\Model\IRI;
use Xabbuh\XApi\Model\IRL;
/**
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*
* @ORM\Table(name="xapi_actor")
* @ORM\Entity()
*/
class Actor
{
/**
* @var int
*
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
public $identifier;
/**
* @var string
*
* @ORM\Column(type="string", nullable=true)
*/
public $type;
/**
* @var string|null
*
* @ORM\Column(type="string", nullable=true)
*/
public $mbox;
/**
* @var string|null
*
* @ORM\Column(type="string", nullable=true)
*/
public $mboxSha1Sum;
/**
* @var string|null
*
* @ORM\Column(type="string", nullable=true)
*/
public $openId;
/**
* @var string|null
*
* @ORM\Column(type="string", nullable=true)
*/
public $accountName;
/**
* @var string|null
*
* @ORM\Column(type="string", nullable=true)
*/
public $accountHomePage;
/**
* @var string|null
*
* @ORM\Column(type="string", nullable=true)
*/
public $name;
/**
* @var Actor[]|null
*
* @ORM\Column()
*/
public $members;
/**
* @param \Xabbuh\XApi\Model\Actor $model
*
* @return \Chamilo\PluginBundle\Entity\XApi\Lrs\Actor
*/
public static function fromModel(ActorModel $model)
{
$inverseFunctionalIdentifier = $model->getInverseFunctionalIdentifier();
$actor = new self();
$actor->mboxSha1Sum = $inverseFunctionalIdentifier->getMboxSha1Sum();
$actor->openId = $inverseFunctionalIdentifier->getOpenId();
if (null !== $mbox = $inverseFunctionalIdentifier->getMbox()) {
$actor->mbox = $mbox->getValue();
}
if (null !== $account = $inverseFunctionalIdentifier->getAccount()) {
$actor->accountName = $account->getName();
$actor->accountHomePage = $account->getHomePage()->getValue();
}
if ($model instanceof Group) {
$actor->type = 'group';
$actor->members = array();
foreach ($model->getMembers() as $agent) {
$actor->members[] = Actor::fromModel($agent);
}
} else {
$actor->type = 'agent';
}
return $actor;
}
/**
* @return \Xabbuh\XApi\Model\Agent|\Xabbuh\XApi\Model\Group
*/
public function getModel()
{
$inverseFunctionalIdentifier = null;
if (null !== $this->mbox) {
$inverseFunctionalIdentifier = InverseFunctionalIdentifier::withMbox(IRI::fromString($this->mbox));
} elseif (null !== $this->mboxSha1Sum) {
$inverseFunctionalIdentifier = InverseFunctionalIdentifier::withMboxSha1Sum($this->mboxSha1Sum);
} elseif (null !== $this->openId) {
$inverseFunctionalIdentifier = InverseFunctionalIdentifier::withOpenId($this->openId);
} elseif (null !== $this->accountName && null !== $this->accountHomePage) {
$inverseFunctionalIdentifier = InverseFunctionalIdentifier::withAccount(new Account($this->accountName, IRL::fromString($this->accountHomePage)));
}
if ('group' === $this->type) {
$members = array();
foreach ($this->members as $agent) {
$members[] = $agent->getModel();
}
return new Group($inverseFunctionalIdentifier, $this->name, $members);
}
return new Agent($inverseFunctionalIdentifier, $this->name);
}
}

@ -1,163 +0,0 @@
<?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 Chamilo\PluginBundle\Entity\XApi\Lrs;
use Doctrine\ORM\Mapping as ORM;
use Xabbuh\XApi\Model\Attachment as AttachmentModel;
use Xabbuh\XApi\Model\IRI;
use Xabbuh\XApi\Model\IRL;
use Xabbuh\XApi\Model\LanguageMap;
/**
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*
* @ORM\Table(name="xapi_attachment")
* @ORM\Entity()
*/
class Attachment
{
/**
* @var int
*
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
public $identifier;
/**
* @ORM\ManyToOne(targetEntity="Chamilo\PluginBundle\Entity\XApi\Lrs\Statement", inversedBy="attachments")
*/
public $statement;
/**
* @var string
*
* @ORM\Column(type="string")
*/
public $usageType;
/**
* @var string
*
* @ORM\Column(type="string")
*/
public $contentType;
/**
* @var int
*
* @ORM\Column(type="integer")
*/
public $length;
/**
* @var string
*
* @ORM\Column(type="string")
*/
public $sha2;
/**
* @var array
*
* @ORM\Column(type="json")
*/
public $display;
/**
* @var bool
*
* @ORM\Column(type="boolean")
*/
public $hasDescription;
/**
* @var array|null
*
* @ORM\Column(type="json", nullable=true)
*/
public $description;
/**
* @var string|null
*
* @ORM\Column(type="string", nullable=true)
*/
public $fileUrl;
/**
* @var string|null
*
* @ORM\Column(type="text", nullable=true)
*/
public $content;
/**
* @param \Xabbuh\XApi\Model\Attachment $model
*
* @return \Chamilo\PluginBundle\Entity\XApi\Lrs\Attachment
*/
public static function fromModel(AttachmentModel $model)
{
$attachment = new self();
$attachment->usageType = $model->getUsageType()->getValue();
$attachment->contentType = $model->getContentType();
$attachment->length = $model->getLength();
$attachment->sha2 = $model->getSha2();
$attachment->display = array();
if (null !== $model->getFileUrl()) {
$attachment->fileUrl = $model->getFileUrl()->getValue();
}
$attachment->content = $model->getContent();
$display = $model->getDisplay();
foreach ($display->languageTags() as $languageTag) {
$attachment->display[$languageTag] = $display[$languageTag];
}
if (null !== $description = $model->getDescription()) {
$attachment->hasDescription = true;
$attachment->description = array();
foreach ($description->languageTags() as $languageTag) {
$attachment->description[$languageTag] = $description[$languageTag];
}
} else {
$attachment->hasDescription = false;
}
return $attachment;
}
/**
* @return \Xabbuh\XApi\Model\Attachment
*/
public function getModel()
{
$description = null;
$fileUrl = null;
if ($this->hasDescription) {
$description = LanguageMap::create($this->description);
}
if (null !== $this->fileUrl) {
$fileUrl = IRL::fromString($this->fileUrl);
}
return new AttachmentModel(IRI::fromString($this->usageType), $this->contentType, $this->length, $this->sha2, LanguageMap::create($this->display), $description, $fileUrl, $this->content);
}
}

@ -1,268 +0,0 @@
<?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 Chamilo\PluginBundle\Entity\XApi\Lrs;
use Doctrine\ORM\Mapping as ORM;
use Xabbuh\XApi\Model\Context as ContextModel;
use Xabbuh\XApi\Model\ContextActivities;
use Xabbuh\XApi\Model\StatementId;
use Xabbuh\XApi\Model\StatementReference;
/**
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*
* @ORM\Table(name="xapi_context")
* @ORM\Entity()
*/
class Context
{
/**
* @var int
*
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
public $identifier;
/**
* @var string|null
*
* @ORM\Column(type="string", nullable=true)
*/
public $registration;
/**
* @var StatementObject|null
*
* @ORM\OneToOne(targetEntity="Chamilo\PluginBundle\Entity\XApi\Lrs\StatementObject", cascade={"ALL"})
* @ORM\JoinColumn(referencedColumnName="identifier")
*/
public $instructor;
/**
* @var StatementObject|null
*
* @ORM\OneToOne(targetEntity="Chamilo\PluginBundle\Entity\XApi\Lrs\StatementObject", cascade={"ALL"})
* @ORM\JoinColumn(referencedColumnName="identifier")
*/
public $team;
/**
* @var bool|null
*
* @ORM\Column(name="hasContextActivities", type="boolean", nullable=true)
*/
public $hasContextActivities;
/**
* @var StatementObject[]|null
*
* @ORM\OneToMany(targetEntity="Chamilo\PluginBundle\Entity\XApi\Lrs\StatementObject", mappedBy="parentContext", cascade={"ALL"})
*/
public $parentActivities;
/**
* @var StatementObject[]|null
*
* @ORM\OneToMany(targetEntity="Chamilo\PluginBundle\Entity\XApi\Lrs\StatementObject", mappedBy="groupingContext", cascade={"ALL"})
*/
public $groupingActivities;
/**
* @var StatementObject[]|null
*
* @ORM\OneToMany(targetEntity="Chamilo\PluginBundle\Entity\XApi\Lrs\StatementObject", mappedBy="categoryContext", cascade={"ALL"})
*/
public $categoryActivities;
/**
* @var StatementObject[]|null
*
* @ORM\OneToMany(targetEntity="Chamilo\PluginBundle\Entity\XApi\Lrs\StatementObject", mappedBy="otherContext", cascade={"ALL"})
*/
public $otherActivities;
/**
* @var string|null
*
* @ORM\Column(type="string", nullable=true)
*/
public $revision;
/**
* @var string|null
*
* @ORM\Column(type="string", nullable=true)
*/
public $platform;
/**
* @var string|null
*
* @ORM\Column(type="string", nullable=true)
*/
public $language;
/**
* @var string|null
*
* @ORM\Column(type="string", nullable=true)
*/
public $statement;
/**
* @var Extensions|null
*
* @ORM\OneToOne(targetEntity="Chamilo\PluginBundle\Entity\XApi\Lrs\Extensions", cascade={"ALL"})
* @ORM\JoinColumn(referencedColumnName="identifier")
*/
public $extensions;
/**
* @param \Xabbuh\XApi\Model\Context $model
*
* @return \Chamilo\PluginBundle\Entity\XApi\Lrs\Context
*/
public static function fromModel(ContextModel $model)
{
$context = new self();
$context->registration = $model->getRegistration();
$context->revision = $model->getRevision();
$context->platform = $model->getPlatform();
$context->language = $model->getLanguage();
if (null !== $instructor = $model->getInstructor()) {
$context->instructor = StatementObject::fromModel($instructor);
}
if (null !== $team = $model->getTeam()) {
$context->team = StatementObject::fromModel($team);
}
if (null !== $contextActivities = $model->getContextActivities()) {
$context->hasContextActivities = true;
if (null !== $parentActivities = $contextActivities->getParentActivities()) {
$context->parentActivities = array();
foreach ($parentActivities as $parentActivity) {
$activity = StatementObject::fromModel($parentActivity);
$activity->parentContext = $context;
$context->parentActivities[] = $activity;
}
}
if (null !== $groupingActivities = $contextActivities->getGroupingActivities()) {
$context->groupingActivities = array();
foreach ($groupingActivities as $groupingActivity) {
$activity = StatementObject::fromModel($groupingActivity);
$activity->groupingContext = $context;
$context->groupingActivities[] = $activity;
}
}
if (null !== $categoryActivities = $contextActivities->getCategoryActivities()) {
$context->categoryActivities = array();
foreach ($categoryActivities as $categoryActivity) {
$activity = StatementObject::fromModel($categoryActivity);
$activity->categoryContext = $context;
$context->categoryActivities[] = $activity;
}
}
if (null !== $otherActivities = $contextActivities->getOtherActivities()) {
$context->otherActivities = array();
foreach ($otherActivities as $otherActivity) {
$activity = StatementObject::fromModel($otherActivity);
$activity->otherContext = $context;
$context->otherActivities[] = $activity;
}
}
} else {
$context->hasContextActivities = false;
}
if (null !== $statementReference = $model->getStatement()) {
$context->statement = $statementReference->getStatementId()->getValue();
}
if (null !== $contextExtensions = $model->getExtensions()) {
$context->extensions = Extensions::fromModel($contextExtensions);
}
return $context;
}
/**
* @return \Xabbuh\XApi\Model\Context
*/
public function getModel()
{
$context = new ContextModel();
$context = $context->withRegistration($this->registration);
$context = $context->withRevision($this->revision);
$context = $context->withPlatform($this->platform);
$context = $context->withLanguage($this->language);
if (null !== $this->instructor) {
$context = $context->withInstructor($this->instructor->getModel());
}
if (null !== $this->team) {
$context = $context->withTeam($this->team->getModel());
}
if ($this->hasContextActivities) {
$contextActivities = new ContextActivities();
if (null !== $this->parentActivities) {
foreach ($this->parentActivities as $contextParentActivity) {
$contextActivities = $contextActivities->withAddedParentActivity($contextParentActivity->getModel());
}
}
if (null !== $this->groupingActivities) {
foreach ($this->groupingActivities as $contextGroupingActivity) {
$contextActivities = $contextActivities->withAddedGroupingActivity($contextGroupingActivity->getModel());
}
}
if (null !== $this->categoryActivities) {
foreach ($this->categoryActivities as $contextCategoryActivity) {
$contextActivities = $contextActivities->withAddedCategoryActivity($contextCategoryActivity->getModel());
}
}
if (null !== $this->otherActivities) {
foreach ($this->otherActivities as $contextOtherActivity) {
$contextActivities = $contextActivities->withAddedOtherActivity($contextOtherActivity->getModel());
}
}
$context = $context->withContextActivities($contextActivities);
}
if (null !== $this->statement) {
$context = $context->withStatement(new StatementReference(StatementId::fromString($this->statement)));
}
if (null !== $this->extensions) {
$context = $context->withExtensions($this->extensions->getModel());
}
return $context;
}
}

@ -1,71 +0,0 @@
<?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 Chamilo\PluginBundle\Entity\XApi\Lrs;
use Doctrine\ORM\Mapping as ORM;
use Xabbuh\XApi\Model\Extensions as ExtensionsModel;
use Xabbuh\XApi\Model\IRI;
/**
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*
* @ORM\Table(name="xapi_extensions")
* @ORM\Entity()
*/
class Extensions
{
/**
* @var int
*
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
public $identifier;
/**
* @var array
*
* @ORM\Column(type="json")
*/
public $extensions;
/**
* @param \Xabbuh\XApi\Model\Extensions $model
*
* @return \Chamilo\PluginBundle\Entity\XApi\Lrs\Extensions
*/
public static function fromModel(ExtensionsModel $model)
{
$extensions = new self();
$extensions->extensions = array();
foreach ($model->getExtensions() as $key) {
$extensions->extensions[$key->getValue()] = $model[$key];
}
return $extensions;
}
/**
* @return \Xabbuh\XApi\Model\Extensions
*/
public function getModel()
{
$extensions = new \SplObjectStorage();
foreach ($this->extensions as $key => $extension) {
$extensions->attach(IRI::fromString($key), $extension);
}
return new ExtensionsModel($extensions);
}
}

@ -1,154 +0,0 @@
<?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 Chamilo\PluginBundle\Entity\XApi\Lrs;
use Doctrine\ORM\Mapping as ORM;
use Xabbuh\XApi\Model\Result as ResultModel;
use Xabbuh\XApi\Model\Score;
/**
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*
* @ORM\Table(name="xapi_result")
* @ORM\Entity()
*/
class Result
{
/**
* @var int
*
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
public $identifier;
/**
* @var bool
*
* @ORM\Column(type="boolean")
*/
public $hasScore;
/**
* @var float|null
*
* @ORM\Column(type="float", nullable=true)
*/
public $scaled;
/**
* @var float|null
*
* @ORM\Column(type="float", nullable=true)
*/
public $raw;
/**
* @var float|null
*
* @ORM\Column(type="float", nullable=true)
*/
public $min;
/**
* @var float|null
*
* @ORM\Column(type="float", nullable=true)
*/
public $max;
/**
* @var bool|null
*
* @ORM\Column(type="boolean", nullable=true)
*/
public $success;
/**
* @var bool|null
*
* @ORM\Column(type="boolean", nullable=true)
*/
public $completion;
/**
* @var string|null
*
* @ORM\Column(type="string", nullable=true)
*/
public $response;
/**
* @var string|null
*
* @ORM\Column(type="string", nullable=true)
*/
public $duration;
/**
* @var Extensions|null
*
* @ORM\OneToOne(targetEntity="Chamilo\PluginBundle\Entity\XApi\Lrs\Extensions", cascade={"ALL"})
* @ORM\JoinColumn(referencedColumnName="identifier")
*/
public $extensions;
/**
* @param \Xabbuh\XApi\Model\Result $model
*
* @return \Chamilo\PluginBundle\Entity\XApi\Lrs\Result
*/
public static function fromModel(ResultModel $model)
{
$result = new self();
$result->success = $model->getSuccess();
$result->completion = $model->getCompletion();
$result->response = $model->getResponse();
$result->duration = $model->getDuration();
if (null !== $score = $model->getScore()) {
$result->hasScore = true;
$result->scaled = $score->getScaled();
$result->raw = $score->getRaw();
$result->min = $score->getMin();
$result->max = $score->getMax();
} else {
$result->hasScore = false;
}
if (null !== $extensions = $model->getExtensions()) {
$result->extensions = Extensions::fromModel($extensions);
}
return $result;
}
/**
* @return \Xabbuh\XApi\Model\Result
*/
public function getModel()
{
$score = null;
$extensions = null;
if ($this->hasScore) {
$score = new Score($this->scaled, $this->raw, $this->min, $this->max);
}
if (null !== $this->extensions) {
$extensions = $this->extensions->getModel();
}
return new ResultModel($score, $this->success, $this->completion, $this->response, $this->duration, $extensions);
}
}

@ -1,212 +0,0 @@
<?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 Chamilo\PluginBundle\Entity\XApi\Lrs;
use Doctrine\ORM\Mapping as ORM;
use Xabbuh\XApi\Model\Statement as StatementModel;
use Xabbuh\XApi\Model\StatementId;
/**
* A {@link Statement} mapped to a storage backend.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*
* @ORM\Table(name="xapi_statement")
* @ORM\Entity()
*/
class Statement
{
/**
* @var string
*
* @ORM\Id()
* @ORM\GeneratedValue(strategy="NONE")
* @ORM\Column(type="string")
*/
public $id;
/**
* @var StatementObject
*
* @ORM\OneToOne(targetEntity="Chamilo\PluginBundle\Entity\XApi\Lrs\StatementObject", cascade={"ALL"})
* @ORM\JoinColumn(referencedColumnName="identifier")
*/
public $actor;
/**
* @var Verb
*
* @ORM\OneToOne(targetEntity="Chamilo\PluginBundle\Entity\XApi\Lrs\Verb", cascade={"ALL"})
* @ORM\JoinColumn(referencedColumnName="identifier")
*/
public $verb;
/**
* @var StatementObject
*
* @ORM\OneToOne(targetEntity="Chamilo\PluginBundle\Entity\XApi\Lrs\StatementObject", cascade={"ALL"})
* @ORM\JoinColumn(referencedColumnName="identifier")
*/
public $object;
/**
* @var Result
*
* @ORM\OneToOne(targetEntity="Chamilo\PluginBundle\Entity\XApi\Lrs\Result", cascade={"ALL"})
* @ORM\JoinColumn(referencedColumnName="identifier")
*/
public $result;
/**
* @var StatementObject
*
* @ORM\OneToOne(targetEntity="Chamilo\PluginBundle\Entity\XApi\Lrs\StatementObject", cascade={"ALL"})
* @ORM\JoinColumn(referencedColumnName="identifier")
*/
public $authority;
/**
* @var int
*
* @ORM\Column(type="bigint", nullable=true)
*/
public $created;
/**
* @var int
*
* @ORM\Column(type="bigint", nullable=true)
*/
public $stored;
/**
* @var Context
*
* @ORM\OneToOne(targetEntity="Chamilo\PluginBundle\Entity\XApi\Lrs\Context", cascade={"ALL"})
* @ORM\JoinColumn(referencedColumnName="identifier")
*/
public $context;
/**
* @var bool
*
* @ORM\Column(type="boolean", nullable=true)
*/
public $hasAttachments;
/**
* @var Attachment[]|null
*
* @ORM\OneToMany(targetEntity="Chamilo\PluginBundle\Entity\XApi\Lrs\Attachment", mappedBy="statement", cascade={"ALL"})
*/
public $attachments;
/**
* @param \Xabbuh\XApi\Model\Statement $model
*
* @return \Chamilo\PluginBundle\Entity\XApi\Lrs\Statement
*/
public static function fromModel(StatementModel $model)
{
$statement = new self();
$statement->id = $model->getId()->getValue();
$statement->actor = StatementObject::fromModel($model->getActor());
$statement->verb = Verb::fromModel($model->getVerb());
$statement->object = StatementObject::fromModel($model->getObject());
if (null !== $model->getCreated()) {
$statement->created = $model->getCreated()->getTimestamp();
}
if (null !== $result = $model->getResult()) {
$statement->result = Result::fromModel($result);
}
if (null !== $authority = $model->getAuthority()) {
$statement->authority = StatementObject::fromModel($authority);
}
if (null !== $context = $model->getContext()) {
$statement->context = Context::fromModel($context);
}
if (null !== $attachments = $model->getAttachments()) {
$statement->hasAttachments = true;
$statement->attachments = array();
foreach ($attachments as $attachment) {
$mappedAttachment = Attachment::fromModel($attachment);
$mappedAttachment->statement = $statement;
$statement->attachments[] = $mappedAttachment;
}
} else {
$statement->hasAttachments = false;
}
return $statement;
}
/**
* @throws \Exception
* @return \Xabbuh\XApi\Model\Statement
*/
public function getModel()
{
$result = null;
$authority = null;
$created = null;
$stored = null;
$context = null;
$attachments = null;
if (null !== $this->result) {
$result = $this->result->getModel();
}
if (null !== $this->authority) {
$authority = $this->authority->getModel();
}
if (null !== $this->created) {
$created = new \DateTime('@'.$this->created);
}
if (null !== $this->stored) {
$stored = new \DateTime('@'.$this->stored);
}
if (null !== $this->context) {
$context = $this->context->getModel();
}
if ($this->hasAttachments) {
$attachments = array();
foreach ($this->attachments as $attachment) {
$attachments[] = $attachment->getModel();
}
}
return new StatementModel(
StatementId::fromString($this->id),
$this->actor->getModel(),
$this->verb->getModel(),
$this->object->getModel(),
$result,
$authority,
$created,
$stored,
$context,
$attachments
);
}
}

@ -1,496 +0,0 @@
<?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 Chamilo\PluginBundle\Entity\XApi\Lrs;
use Doctrine\ORM\Mapping as ORM;
use Xabbuh\XApi\Model\Account;
use Xabbuh\XApi\Model\Activity;
use Xabbuh\XApi\Model\Actor as ActorModel;
use Xabbuh\XApi\Model\Agent;
use Xabbuh\XApi\Model\Definition;
use Xabbuh\XApi\Model\Group;
use Xabbuh\XApi\Model\InverseFunctionalIdentifier;
use Xabbuh\XApi\Model\IRI;
use Xabbuh\XApi\Model\IRL;
use Xabbuh\XApi\Model\LanguageMap;
use Xabbuh\XApi\Model\Object as ObjectModel;
use Xabbuh\XApi\Model\StatementObject as StatementObjectModel;
use Xabbuh\XApi\Model\StatementId;
use Xabbuh\XApi\Model\StatementReference;
use Xabbuh\XApi\Model\SubStatement;
/**
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*
* @ORM\Table(name="xapi_object")
* @ORM\Entity()
*/
class StatementObject
{
const TYPE_ACTIVITY = 'activity';
const TYPE_AGENT = 'agent';
const TYPE_GROUP = 'group';
const TYPE_STATEMENT_REFERENCE = 'statement_reference';
const TYPE_SUB_STATEMENT = 'sub_statement';
/**
* @var int
*
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
public $identifier;
/**
* @var string
*
* @ORM\Column(type="string", nullable=true)
*/
public $type;
/**
* @var string|null
*
* @ORM\Column(type="string", nullable=true)
*/
public $activityId;
/**
* @var bool|null
*
* @ORM\Column(type="boolean", nullable=true)
*/
public $hasActivityDefinition;
/**
* @var bool|null
*
* @ORM\Column(type="boolean", nullable=true)
*/
public $hasActivityName;
/**
* @var array|null
*
* @ORM\Column(type="json", nullable=true)
*/
public $activityName;
/**
* @var bool|null
*
* @ORM\Column(type="boolean", nullable=true)
*/
public $hasActivityDescription;
/**
* @var array|null
*
* @ORM\Column(type="json", nullable=true)
*/
public $activityDescription;
/**
* @var string|null
*
* @ORM\Column(type="string", nullable=true)
*/
public $activityType;
/**
* @var string|null
*
* @ORM\Column(type="string", nullable=true)
*/
public $activityMoreInfo;
/**
* @var Extensions|null
*
* @ORM\OneToOne(targetEntity="Chamilo\PluginBundle\Entity\XApi\Lrs\Extensions", cascade={"all"})
* @ORM\JoinColumn(referencedColumnName="identifier")
*/
public $activityExtensions;
/**
* @var string|null
*
* @ORM\Column(type="string", nullable=true)
*/
public $mbox;
/**
* @var string|null
*
* @ORM\Column(type="string", nullable=true)
*/
public $mboxSha1Sum;
/**
* @var string|null
*
* @ORM\Column(type="string", nullable=true)
*/
public $openId;
/**
* @var string|null
*
* @ORM\Column(type="string", nullable=true)
*/
public $accountName;
/**
* @var string|null
*
* @ORM\Column(type="string", nullable=true)
*/
public $accountHomePage;
/**
* @var string|null
*
* @ORM\Column(type="string", nullable=true)
*/
public $name;
/**
* @var StatementObject[]|null
*
* @ORM\OneToMany(targetEntity="Chamilo\PluginBundle\Entity\XApi\Lrs\StatementObject", mappedBy="group")
*/
public $members;
/**
* @var StatementObject|null
*
* @ORM\ManyToOne(targetEntity="Chamilo\PluginBundle\Entity\XApi\Lrs\StatementObject", inversedBy="members")
* @ORM\JoinColumn(referencedColumnName="identifier")
*/
public $group;
/**
* @var string|null
*
* @ORM\Column(name="referenced_statement_id", type="string", nullable=true)
*/
public $referencedStatementId;
/**
* @var StatementObject|null
*
* @ORM\OneToOne(targetEntity="Chamilo\PluginBundle\Entity\XApi\Lrs\StatementObject", cascade={"ALL"})
* @ORM\JoinColumn(referencedColumnName="identifier")
*/
public $actor;
/**
* @var Verb|null
*
* @ORM\OneToOne(targetEntity="Chamilo\PluginBundle\Entity\XApi\Lrs\Verb", cascade={"ALL"})
* @ORM\JoinColumn(referencedColumnName="identifier")
*/
public $verb;
/**
* @var StatementObject|null
*
* @ORM\OneToOne(targetEntity="Chamilo\PluginBundle\Entity\XApi\Lrs\StatementObject", cascade={"ALL"})
* @ORM\JoinColumn(referencedColumnName="identifier")
*/
public $object;
/**
* @var Result|null
*/
public $result;
/**
* @var Context|null
*/
public $context;
/**
* @var Statement|null
*
* @ORM\ManyToOne(targetEntity="Chamilo\PluginBundle\Entity\XApi\Lrs\Context", inversedBy="parentActivities")
* @ORM\JoinColumn(referencedColumnName="identifier")
*/
public $parentContext;
/**
* @var Statement|null
*
* @ORM\ManyToOne(targetEntity="Chamilo\PluginBundle\Entity\XApi\Lrs\Context", inversedBy="groupingActivities")
* @ORM\JoinColumn(referencedColumnName="identifier")
*/
public $groupingContext;
/**
* @var Statement|null
*
* @ORM\ManyToOne(targetEntity="Chamilo\PluginBundle\Entity\XApi\Lrs\Context", inversedBy="categoryActivities")
* @ORM\JoinColumn(referencedColumnName="identifier")
*/
public $categoryContext;
/**
* @var Statement|null
*
* @ORM\ManyToOne(targetEntity="Chamilo\PluginBundle\Entity\XApi\Lrs\Context", inversedBy="otherActivities")
* @ORM\JoinColumn(referencedColumnName="identifier")
*/
public $otherContext;
/**
* @param $model
*
* @return \Chamilo\PluginBundle\Entity\XApi\Lrs\StatementObject
*/
public static function fromModel($model)
{
if (!$model instanceof ObjectModel && !$model instanceof StatementObjectModel) {
throw new \InvalidArgumentException(sprintf('Expected a statement object but got %s', is_object($model) ? get_class($model) : gettype($model)));
}
if ($model instanceof ActorModel) {
return self::fromActor($model);
}
if ($model instanceof StatementReference) {
$object = new self();
$object->type = self::TYPE_STATEMENT_REFERENCE;
$object->referencedStatementId = $model->getStatementId()->getValue();
return $object;
}
if ($model instanceof SubStatement) {
return self::fromSubStatement($model);
}
return self::fromActivity($model);
}
/**
* @return \Xabbuh\XApi\Model\StatementObject
*/
public function getModel()
{
if (self::TYPE_AGENT === $this->type || self::TYPE_GROUP === $this->type) {
return $this->getActorModel();
}
if (self::TYPE_STATEMENT_REFERENCE === $this->type) {
return new StatementReference(StatementId::fromString($this->referencedStatementId));
}
if (self::TYPE_SUB_STATEMENT === $this->type) {
return $this->getSubStatementModel();
}
return $this->getActivityModel();
}
/**
* @param \Xabbuh\XApi\Model\Activity $model
*
* @return \Chamilo\PluginBundle\Entity\XApi\Lrs\StatementObject
*/
private static function fromActivity(Activity $model)
{
$object = new self();
$object->activityId = $model->getId()->getValue();
if (null !== $definition = $model->getDefinition()) {
$object->hasActivityDefinition = true;
if (null !== $name = $definition->getName()) {
$object->hasActivityName = true;
$object->activityName = array();
foreach ($name->languageTags() as $languageTag) {
$object->activityName[$languageTag] = $name[$languageTag];
}
} else {
$object->hasActivityName = false;
}
if (null !== $description = $definition->getDescription()) {
$object->hasActivityDescription = true;
$object->activityDescription = array();
foreach ($description->languageTags() as $languageTag) {
$object->activityDescription[$languageTag] = $description[$languageTag];
}
} else {
$object->hasActivityDescription = false;
}
if (null !== $type = $definition->getType()) {
$object->activityType = $type->getValue();
}
if (null !== $moreInfo = $definition->getMoreInfo()) {
$object->activityMoreInfo = $moreInfo->getValue();
}
if (null !== $extensions = $definition->getExtensions()) {
$object->activityExtensions = Extensions::fromModel($extensions);
}
} else {
$object->hasActivityDefinition = false;
}
return $object;
}
/**
* @param \Xabbuh\XApi\Model\Actor $model
*
* @return \Chamilo\PluginBundle\Entity\XApi\Lrs\StatementObject
*/
private static function fromActor(ActorModel $model)
{
$inverseFunctionalIdentifier = $model->getInverseFunctionalIdentifier();
$object = new self();
$object->mboxSha1Sum = $inverseFunctionalIdentifier->getMboxSha1Sum();
$object->openId = $inverseFunctionalIdentifier->getOpenId();
if (null !== $mbox = $inverseFunctionalIdentifier->getMbox()) {
$object->mbox = $mbox->getValue();
}
if (null !== $account = $inverseFunctionalIdentifier->getAccount()) {
$object->accountName = $account->getName();
$object->accountHomePage = $account->getHomePage()->getValue();
}
if ($model instanceof Group) {
$object->type = self::TYPE_GROUP;
$object->members = array();
foreach ($model->getMembers() as $agent) {
$object->members[] = self::fromActor($agent);
}
} else {
$object->type = self::TYPE_AGENT;
}
return $object;
}
/**
* @param \Xabbuh\XApi\Model\SubStatement $model
*
* @return \Chamilo\PluginBundle\Entity\XApi\Lrs\StatementObject
*/
private static function fromSubStatement(SubStatement $model)
{
$object = new self();
$object->type = self::TYPE_SUB_STATEMENT;
$object->actor = StatementObject::fromModel($model->getActor());
$object->verb = Verb::fromModel($model->getVerb());
$object->object = StatementObject::fromModel($model->getObject());
return $object;
}
/**
* @return \Xabbuh\XApi\Model\Activity
*/
private function getActivityModel()
{
$definition = null;
$type = null;
$moreInfo = null;
if ($this->hasActivityDefinition) {
$name = null;
$description = null;
$extensions = null;
if ($this->hasActivityName) {
$name = LanguageMap::create($this->activityName);
}
if ($this->hasActivityDescription) {
$description = LanguageMap::create($this->activityDescription);
}
if (null !== $this->activityType) {
$type = IRI::fromString($this->activityType);
}
if (null !== $this->activityMoreInfo) {
$moreInfo = IRL::fromString($this->activityMoreInfo);
}
if (null !== $this->activityExtensions) {
$extensions = $this->activityExtensions->getModel();
}
$definition = new Definition($name, $description, $type, $moreInfo, $extensions);
}
return new Activity(IRI::fromString($this->activityId), $definition);
}
/**
* @return \Xabbuh\XApi\Model\Agent|\Xabbuh\XApi\Model\Group
*/
private function getActorModel()
{
$inverseFunctionalIdentifier = null;
if (null !== $this->mbox) {
$inverseFunctionalIdentifier = InverseFunctionalIdentifier::withMbox(IRI::fromString($this->mbox));
} elseif (null !== $this->mboxSha1Sum) {
$inverseFunctionalIdentifier = InverseFunctionalIdentifier::withMboxSha1Sum($this->mboxSha1Sum);
} elseif (null !== $this->openId) {
$inverseFunctionalIdentifier = InverseFunctionalIdentifier::withOpenId($this->openId);
} elseif (null !== $this->accountName && null !== $this->accountHomePage) {
$inverseFunctionalIdentifier = InverseFunctionalIdentifier::withAccount(new Account($this->accountName, IRL::fromString($this->accountHomePage)));
}
if (self::TYPE_GROUP === $this->type) {
$members = array();
foreach ($this->members as $agent) {
$members[] = $agent->getModel();
}
return new Group($inverseFunctionalIdentifier, $this->name, $members);
}
return new Agent($inverseFunctionalIdentifier, $this->name);
}
/**
* @return \Xabbuh\XApi\Model\SubStatement
*/
private function getSubStatementModel()
{
$result = null;
$context = null;
return new SubStatement(
$this->actor->getModel(),
$this->verb->getModel(),
$this->object->getModel(),
$result,
$context
);
}
}

@ -1,86 +0,0 @@
<?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 Chamilo\PluginBundle\Entity\XApi\Lrs;
use Doctrine\ORM\Mapping as ORM;
use Xabbuh\XApi\Model\IRI;
use Xabbuh\XApi\Model\LanguageMap;
use Xabbuh\XApi\Model\Verb as VerbModel;
/**
* A {@link Verb} mapped to a storage backend.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*
* @ORM\Table(name="xapi_verb")
* @ORM\Entity()
*/
class Verb
{
/**
* @var int
*
* @ORM\Column(type="integer")
* @ORM\Id()
* @ORM\GeneratedValue()
*/
public $identifier;
/**
* @var string
*
* @ORM\Column(type="string")
*/
public $id;
/**
* @var array|null
*
* @ORM\Column(type="json")
*/
public $display;
/**
* @return \Xabbuh\XApi\Model\Verb
*/
public function getModel()
{
$display = null;
if (null !== $this->display) {
$display = LanguageMap::create($this->display);
}
return new VerbModel(IRI::fromString($this->id), $display);
}
/**
* @param \Xabbuh\XApi\Model\Verb $model
*
* @return \Chamilo\PluginBundle\Entity\XApi\Lrs\Verb
*/
public static function fromModel(VerbModel $model)
{
$verb = new self();
$verb->id = $model->getId()->getValue();
if (null !== $display = $model->getDisplay()) {
$verb->display = array();
foreach ($display->languageTags() as $languageTag) {
$verb->display[$languageTag] = $display[$languageTag];
}
}
return $verb;
}
}

@ -4,14 +4,12 @@
namespace Chamilo\PluginBundle\XApi\Lrs;
use Chamilo\PluginBundle\Entity\XApi\Lrs\Statement as StatementEntity;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
use Xabbuh\XApi\Model\Statement;
use Xabbuh\XApi\Model\StatementId;
use Xabbuh\XApi\Serializer\Symfony\Serializer;
use XApi\LrsBundle\Controller\StatementPutController;
use XApi\Repository\Doctrine\Mapping\Statement as StatementEntity;
use XApi\Repository\Doctrine\Repository\StatementRepository;
use XApiPlugin;
/**
* Class StatementsController.
@ -21,48 +19,23 @@ use Xabbuh\XApi\Serializer\Symfony\Serializer;
class StatementsController extends BaseController
{
/**
* @throws \Doctrine\ORM\ORMException
* @throws \Doctrine\ORM\OptimisticLockException
* @throws \Doctrine\ORM\TransactionRequiredException
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function put()
{
$request = $this->httpRequest;
if (null === $request->query->get('statementId')) {
throw new BadRequestHttpException('Required statementId parameter is missing.');
}
$pluginEm = XApiPlugin::getEntityManager();
$statementId = $request->query->get('statementId');
$id = StatementId::fromString($statementId);
$putStatementController = new StatementPutController(
new StatementRepository(
$pluginEm->getRepository(StatementEntity::class)
)
);
$statement = $this->deserializeStatement(
$this->httpRequest->getContent()
);
if (null !== $statement->getId() && !$id->equals($statement->getId())) {
throw new ConflictHttpException(
"Id parameter ({$id->getValue()}) and statement id ({$statement->getId()->getValue()}) do not match."
);
}
$em = \Database::getManager();
$existingStatement = $em->find(StatementEntity::class, $id->getValue());
if ($existingStatement && !$existingStatement->equals($statement)) {
throw new ConflictHttpException('The new statement is not equal to an existing statement with the same id.');
}
$em->persist(StatementEntity::fromModel($statement));
$em->flush();
return JsonResponse::create(
null,
Response::HTTP_NO_CONTENT
);
return $putStatementController->putStatement($this->httpRequest, $statement);
}
/**

@ -2,17 +2,12 @@
/* For licensing terms, see /license.txt */
use Chamilo\PluginBundle\Entity\XApi\Lrs\Actor as ActorEntity;
use Chamilo\PluginBundle\Entity\XApi\Lrs\Attachment as AttachmentEntity;
use Chamilo\PluginBundle\Entity\XApi\Cmi5Item;
use Chamilo\PluginBundle\Entity\XApi\Lrs\Context as ContextEntity;
use Chamilo\PluginBundle\Entity\XApi\Lrs\Extensions as ExtensionsEntity;
use Chamilo\PluginBundle\Entity\XApi\Lrs\Result as ResultEntity;
use Chamilo\PluginBundle\Entity\XApi\SharedStatement;
use Chamilo\PluginBundle\Entity\XApi\Lrs\Statement as StatementEntity;
use Chamilo\PluginBundle\Entity\XApi\Lrs\StatementObject as StatementObjectEntity;
use Chamilo\PluginBundle\Entity\XApi\ToolLaunch;
use Chamilo\PluginBundle\Entity\XApi\Lrs\Verb as VerbEntity;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Mapping\Driver\SimplifiedXmlDriver;
use Doctrine\ORM\ORMException;
use Doctrine\ORM\Tools\SchemaTool;
use GuzzleHttp\RequestOptions;
use Http\Adapter\Guzzle6\Client;
@ -139,21 +134,27 @@ class XApiPlugin extends Plugin implements HookPluginInterface
private function installPluginDbTables()
{
$em = Database::getManager();
$pluginEm = self::getEntityManager();
$schemaTool = new SchemaTool($em);
$schemaTool->createSchema(
[
$em->getClassMetadata(SharedStatement::class),
$em->getClassMetadata(ToolLaunch::class),
]
);
$em->getClassMetadata(AttachmentEntity::class),
$em->getClassMetadata(StatementObjectEntity::class),
$em->getClassMetadata(ResultEntity::class),
$em->getClassMetadata(VerbEntity::class),
$em->getClassMetadata(ExtensionsEntity::class),
$em->getClassMetadata(ContextEntity::class),
$em->getClassMetadata(ActorEntity::class),
$em->getClassMetadata(StatementEntity::class),
$pluginSchemaTool = new SchemaTool($pluginEm);
$pluginSchemaTool->createSchema(
[
$pluginEm->getClassMetadata(\XApi\Repository\Doctrine\Mapping\Attachment::class),
$pluginEm->getClassMetadata(\XApi\Repository\Doctrine\Mapping\StatementObject::class),
$pluginEm->getClassMetadata(\XApi\Repository\Doctrine\Mapping\Result::class),
$pluginEm->getClassMetadata(\XApi\Repository\Doctrine\Mapping\Verb::class),
$pluginEm->getClassMetadata(\XApi\Repository\Doctrine\Mapping\Extensions::class),
$pluginEm->getClassMetadata(\XApi\Repository\Doctrine\Mapping\Context::class),
$pluginEm->getClassMetadata(\XApi\Repository\Doctrine\Mapping\Actor::class),
$pluginEm->getClassMetadata(\XApi\Repository\Doctrine\Mapping\Statement::class),
]
);
}
@ -216,21 +217,27 @@ class XApiPlugin extends Plugin implements HookPluginInterface
public function uninstallPluginDbTables()
{
$em = Database::getManager();
$pluginEm = self::getEntityManager();
$schemaTool = new SchemaTool($em);
$schemaTool->dropSchema(
[
$em->getClassMetadata(SharedStatement::class),
$em->getClassMetadata(ToolLaunch::class),
]
);
$em->getClassMetadata(AttachmentEntity::class),
$em->getClassMetadata(StatementObjectEntity::class),
$em->getClassMetadata(ResultEntity::class),
$em->getClassMetadata(VerbEntity::class),
$em->getClassMetadata(ExtensionsEntity::class),
$em->getClassMetadata(ContextEntity::class),
$em->getClassMetadata(ActorEntity::class),
$em->getClassMetadata(StatementEntity::class),
$pluginSchemaTool = new SchemaTool($pluginEm);
$pluginSchemaTool->dropSchema(
[
$pluginEm->getClassMetadata(\XApi\Repository\Doctrine\Mapping\Attachment::class),
$pluginEm->getClassMetadata(\XApi\Repository\Doctrine\Mapping\StatementObject::class),
$pluginEm->getClassMetadata(\XApi\Repository\Doctrine\Mapping\Result::class),
$pluginEm->getClassMetadata(\XApi\Repository\Doctrine\Mapping\Verb::class),
$pluginEm->getClassMetadata(\XApi\Repository\Doctrine\Mapping\Extensions::class),
$pluginEm->getClassMetadata(\XApi\Repository\Doctrine\Mapping\Context::class),
$pluginEm->getClassMetadata(\XApi\Repository\Doctrine\Mapping\Actor::class),
$pluginEm->getClassMetadata(\XApi\Repository\Doctrine\Mapping\Statement::class),
]
);
}
@ -453,4 +460,61 @@ class XApiPlugin extends Plugin implements HookPluginInterface
return 'en';
}
public function generateLaunchUrl(
$type,
$launchUrl,
$activityId,
Agent $actor,
$attemptId,
$customLrsUrl = null,
$customLrsUsername = null,
$customLrsPassword = null
) {
$lrsUrl = $customLrsUrl ?: $this->get(self::SETTING_LRS_URL);
$lrsAuthUsername = $customLrsUsername ?: $this->get(self::SETTING_LRS_AUTH_USERNAME);
$lrsAuthPassword = $customLrsPassword ?: $this->get(self::SETTING_LRS_AUTH_PASSWORD);
$queryData = [
'endpoint' => trim($lrsUrl, "/ \t\n\r\0\x0B"),
'actor' => Serializer::createSerializer()->serialize($actor, 'json'),
'registration' => $attemptId,
];
if ('tincan' === $type) {
$queryData['auth'] = 'Basic '.base64_encode(trim($lrsAuthUsername).':'.trim($lrsAuthPassword));
$queryData['activity_id'] = $activityId;
} elseif ('cmi5' === $type) {
$queryData['fetch'] = api_get_path(WEB_PLUGIN_PATH).'xapi/cmi5/token.php';
$queryData['activityId'] = $activityId;
}
return $launchUrl.'?'.http_build_query($queryData, null, '&', PHP_QUERY_RFC3986);
}
/**
* @return \Doctrine\ORM\EntityManager|null
*/
public static function getEntityManager()
{
$em = Database::getManager();
$prefixes = [
__DIR__.'/../php-xapi/repository-doctrine-orm/metadata' => 'XApi\Repository\Doctrine\Mapping'
];
$driver = new SimplifiedXmlDriver($prefixes);
$driver->setGlobalBasename('global');
$config = Database::getDoctrineConfig(api_get_configuration_value('root_sys'));
$config->setMetadataDriverImpl($driver);
try {
return EntityManager::create($em->getConnection()->getParams(), $config);
} catch (ORMException $e) {
api_not_allowed(true, $e->getMessage());
}
return null;
}
}

Loading…
Cancel
Save