diff --git a/plugin/xapi/src/Lrs/Entity/LICENSE b/plugin/xapi/php-xapi/lrs-bundle/LICENSE similarity index 95% rename from plugin/xapi/src/Lrs/Entity/LICENSE rename to plugin/xapi/php-xapi/lrs-bundle/LICENSE index b4a076bf73..de1d823c3d 100644 --- a/plugin/xapi/src/Lrs/Entity/LICENSE +++ b/plugin/xapi/php-xapi/lrs-bundle/LICENSE @@ -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 diff --git a/plugin/xapi/php-xapi/lrs-bundle/README.md b/plugin/xapi/php-xapi/lrs-bundle/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugin/xapi/php-xapi/lrs-bundle/composer.json b/plugin/xapi/php-xapi/lrs-bundle/composer.json new file mode 100644 index 0000000000..874027f020 --- /dev/null +++ b/plugin/xapi/php-xapi/lrs-bundle/composer.json @@ -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" + } + } +} diff --git a/plugin/xapi/php-xapi/lrs-bundle/phpspec.yml.dist b/plugin/xapi/php-xapi/lrs-bundle/phpspec.yml.dist new file mode 100644 index 0000000000..0f9371bee7 --- /dev/null +++ b/plugin/xapi/php-xapi/lrs-bundle/phpspec.yml.dist @@ -0,0 +1,4 @@ +suites: + default: + namespace: XApi\LrsBundle + psr4_prefix: XApi\LrsBundle diff --git a/plugin/xapi/php-xapi/lrs-bundle/spec/Controller/StatementGetControllerSpec.php b/plugin/xapi/php-xapi/lrs-bundle/spec/Controller/StatementGetControllerSpec.php new file mode 100644 index 0000000000..1229050c10 --- /dev/null +++ b/plugin/xapi/php-xapi/lrs-bundle/spec/Controller/StatementGetControllerSpec.php @@ -0,0 +1,197 @@ +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); + } +} diff --git a/plugin/xapi/php-xapi/lrs-bundle/spec/Controller/StatementPutControllerSpec.php b/plugin/xapi/php-xapi/lrs-bundle/spec/Controller/StatementPutControllerSpec.php new file mode 100644 index 0000000000..2eb489190e --- /dev/null +++ b/plugin/xapi/php-xapi/lrs-bundle/spec/Controller/StatementPutControllerSpec.php @@ -0,0 +1,106 @@ +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)); + } +} diff --git a/plugin/xapi/php-xapi/lrs-bundle/spec/DependencyInjection/XApiLrsExtensionSpec.php b/plugin/xapi/php-xapi/lrs-bundle/spec/DependencyInjection/XApiLrsExtensionSpec.php new file mode 100644 index 0000000000..18aec35fe1 --- /dev/null +++ b/plugin/xapi/php-xapi/lrs-bundle/spec/DependencyInjection/XApiLrsExtensionSpec.php @@ -0,0 +1,18 @@ +shouldHaveType('Symfony\Component\DependencyInjection\Extension\ExtensionInterface'); + } + + function its_alias_is_xapi_lrs() + { + $this->getAlias()->shouldReturn('xapi_lrs'); + } +} diff --git a/plugin/xapi/php-xapi/lrs-bundle/spec/EventListener/AlternateRequestSyntaxListenerSpec.php b/plugin/xapi/php-xapi/lrs-bundle/spec/EventListener/AlternateRequestSyntaxListenerSpec.php new file mode 100644 index 0000000000..5f0bfd65fc --- /dev/null +++ b/plugin/xapi/php-xapi/lrs-bundle/spec/EventListener/AlternateRequestSyntaxListenerSpec.php @@ -0,0 +1,160 @@ +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); + } +} diff --git a/plugin/xapi/php-xapi/lrs-bundle/spec/EventListener/ExceptionListenerSpec.php b/plugin/xapi/php-xapi/lrs-bundle/spec/EventListener/ExceptionListenerSpec.php new file mode 100644 index 0000000000..3db7bde984 --- /dev/null +++ b/plugin/xapi/php-xapi/lrs-bundle/spec/EventListener/ExceptionListenerSpec.php @@ -0,0 +1,9 @@ +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)); + } +} diff --git a/plugin/xapi/php-xapi/lrs-bundle/spec/EventListener/VersionListenerSpec.php b/plugin/xapi/php-xapi/lrs-bundle/spec/EventListener/VersionListenerSpec.php new file mode 100644 index 0000000000..2782a6b773 --- /dev/null +++ b/plugin/xapi/php-xapi/lrs-bundle/spec/EventListener/VersionListenerSpec.php @@ -0,0 +1,102 @@ +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); + } +} diff --git a/plugin/xapi/php-xapi/lrs-bundle/spec/Model/StatementsFilterFactorySpec.php b/plugin/xapi/php-xapi/lrs-bundle/spec/Model/StatementsFilterFactorySpec.php new file mode 100644 index 0000000000..9e20291ba2 --- /dev/null +++ b/plugin/xapi/php-xapi/lrs-bundle/spec/Model/StatementsFilterFactorySpec.php @@ -0,0 +1,159 @@ +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); + } +} diff --git a/plugin/xapi/php-xapi/lrs-bundle/spec/Response/AttachmentResponseSpec.php b/plugin/xapi/php-xapi/lrs-bundle/spec/Response/AttachmentResponseSpec.php new file mode 100644 index 0000000000..8785f4d0b2 --- /dev/null +++ b/plugin/xapi/php-xapi/lrs-bundle/spec/Response/AttachmentResponseSpec.php @@ -0,0 +1,69 @@ +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()); + } +} diff --git a/plugin/xapi/php-xapi/lrs-bundle/spec/Response/MultipartResponseSpec.php b/plugin/xapi/php-xapi/lrs-bundle/spec/Response/MultipartResponseSpec.php new file mode 100644 index 0000000000..8ca897cd5b --- /dev/null +++ b/plugin/xapi/php-xapi/lrs-bundle/spec/Response/MultipartResponseSpec.php @@ -0,0 +1,31 @@ +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='); + } +} diff --git a/plugin/xapi/php-xapi/lrs-bundle/spec/XApiLrsBundleSpec.php b/plugin/xapi/php-xapi/lrs-bundle/spec/XApiLrsBundleSpec.php new file mode 100644 index 0000000000..fac5560eec --- /dev/null +++ b/plugin/xapi/php-xapi/lrs-bundle/spec/XApiLrsBundleSpec.php @@ -0,0 +1,13 @@ +shouldHaveType('Symfony\Component\HttpKernel\Bundle\Bundle'); + } +} diff --git a/plugin/xapi/php-xapi/lrs-bundle/src/Controller/StatementGetController.php b/plugin/xapi/php-xapi/lrs-bundle/src/Controller/StatementGetController.php new file mode 100644 index 0000000000..0ba4fce11a --- /dev/null +++ b/plugin/xapi/php-xapi/lrs-bundle/src/Controller/StatementGetController.php @@ -0,0 +1,194 @@ + + * + * 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 + */ +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".'); + } + } +} diff --git a/plugin/xapi/php-xapi/lrs-bundle/src/Controller/StatementPostController.php b/plugin/xapi/php-xapi/lrs-bundle/src/Controller/StatementPostController.php new file mode 100644 index 0000000000..6b47845ad1 --- /dev/null +++ b/plugin/xapi/php-xapi/lrs-bundle/src/Controller/StatementPostController.php @@ -0,0 +1,33 @@ + + * + * 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 + */ +final class StatementPostController +{ + public function postStatement(Request $request, Statement $statement) + { + } + + /** + * @param Request $request + * @param Statement[] $statements + */ + public function postStatements(Request $request, array $statements) + { + } +} diff --git a/plugin/xapi/php-xapi/lrs-bundle/src/Controller/StatementPutController.php b/plugin/xapi/php-xapi/lrs-bundle/src/Controller/StatementPutController.php new file mode 100644 index 0000000000..4a01645b59 --- /dev/null +++ b/plugin/xapi/php-xapi/lrs-bundle/src/Controller/StatementPutController.php @@ -0,0 +1,63 @@ + + * + * 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 + */ +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); + } +} diff --git a/plugin/xapi/php-xapi/lrs-bundle/src/DependencyInjection/Configuration.php b/plugin/xapi/php-xapi/lrs-bundle/src/DependencyInjection/Configuration.php new file mode 100644 index 0000000000..afc507a74a --- /dev/null +++ b/plugin/xapi/php-xapi/lrs-bundle/src/DependencyInjection/Configuration.php @@ -0,0 +1,35 @@ + + */ +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; + } +} diff --git a/plugin/xapi/php-xapi/lrs-bundle/src/DependencyInjection/XApiLrsExtension.php b/plugin/xapi/php-xapi/lrs-bundle/src/DependencyInjection/XApiLrsExtension.php new file mode 100644 index 0000000000..e2ff9e6d19 --- /dev/null +++ b/plugin/xapi/php-xapi/lrs-bundle/src/DependencyInjection/XApiLrsExtension.php @@ -0,0 +1,60 @@ + + * + * 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 + */ +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'; + } +} diff --git a/plugin/xapi/php-xapi/lrs-bundle/src/EventListener/AlternateRequestSyntaxListener.php b/plugin/xapi/php-xapi/lrs-bundle/src/EventListener/AlternateRequestSyntaxListener.php new file mode 100644 index 0000000000..fa2038c714 --- /dev/null +++ b/plugin/xapi/php-xapi/lrs-bundle/src/EventListener/AlternateRequestSyntaxListener.php @@ -0,0 +1,73 @@ + + * + * 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 + */ +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); + } + } +} diff --git a/plugin/xapi/php-xapi/lrs-bundle/src/EventListener/ExceptionListener.php b/plugin/xapi/php-xapi/lrs-bundle/src/EventListener/ExceptionListener.php new file mode 100644 index 0000000000..956f2f27e9 --- /dev/null +++ b/plugin/xapi/php-xapi/lrs-bundle/src/EventListener/ExceptionListener.php @@ -0,0 +1,17 @@ + + */ +class ExceptionListener +{ + public function onKernelException(GetResponseForExceptionEvent $event) + { + } +} diff --git a/plugin/xapi/php-xapi/lrs-bundle/src/EventListener/SerializerListener.php b/plugin/xapi/php-xapi/lrs-bundle/src/EventListener/SerializerListener.php new file mode 100644 index 0000000000..3bdda2f66b --- /dev/null +++ b/plugin/xapi/php-xapi/lrs-bundle/src/EventListener/SerializerListener.php @@ -0,0 +1,40 @@ + + */ +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); + } + } +} diff --git a/plugin/xapi/php-xapi/lrs-bundle/src/EventListener/VersionListener.php b/plugin/xapi/php-xapi/lrs-bundle/src/EventListener/VersionListener.php new file mode 100644 index 0000000000..f82af7595e --- /dev/null +++ b/plugin/xapi/php-xapi/lrs-bundle/src/EventListener/VersionListener.php @@ -0,0 +1,66 @@ + + * + * 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 + */ +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'); + } + } +} diff --git a/plugin/xapi/php-xapi/lrs-bundle/src/Model/StatementsFilterFactory.php b/plugin/xapi/php-xapi/lrs-bundle/src/Model/StatementsFilterFactory.php new file mode 100644 index 0000000000..9b30db92c4 --- /dev/null +++ b/plugin/xapi/php-xapi/lrs-bundle/src/Model/StatementsFilterFactory.php @@ -0,0 +1,88 @@ + + * + * 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 + */ +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; + } +} diff --git a/plugin/xapi/php-xapi/lrs-bundle/src/Resources/config/controller.xml b/plugin/xapi/php-xapi/lrs-bundle/src/Resources/config/controller.xml new file mode 100644 index 0000000000..053e487dfe --- /dev/null +++ b/plugin/xapi/php-xapi/lrs-bundle/src/Resources/config/controller.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + diff --git a/plugin/xapi/php-xapi/lrs-bundle/src/Resources/config/doctrine.xml b/plugin/xapi/php-xapi/lrs-bundle/src/Resources/config/doctrine.xml new file mode 100644 index 0000000000..1eb2760b65 --- /dev/null +++ b/plugin/xapi/php-xapi/lrs-bundle/src/Resources/config/doctrine.xml @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/plugin/xapi/php-xapi/lrs-bundle/src/Resources/config/event_listener.xml b/plugin/xapi/php-xapi/lrs-bundle/src/Resources/config/event_listener.xml new file mode 100644 index 0000000000..08ad38c13a --- /dev/null +++ b/plugin/xapi/php-xapi/lrs-bundle/src/Resources/config/event_listener.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugin/xapi/php-xapi/lrs-bundle/src/Resources/config/factory.xml b/plugin/xapi/php-xapi/lrs-bundle/src/Resources/config/factory.xml new file mode 100644 index 0000000000..4c43d9db12 --- /dev/null +++ b/plugin/xapi/php-xapi/lrs-bundle/src/Resources/config/factory.xml @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/plugin/xapi/php-xapi/lrs-bundle/src/Resources/config/orm.xml b/plugin/xapi/php-xapi/lrs-bundle/src/Resources/config/orm.xml new file mode 100644 index 0000000000..a7c8204783 --- /dev/null +++ b/plugin/xapi/php-xapi/lrs-bundle/src/Resources/config/orm.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + diff --git a/plugin/xapi/php-xapi/lrs-bundle/src/Resources/config/routing.xml b/plugin/xapi/php-xapi/lrs-bundle/src/Resources/config/routing.xml new file mode 100644 index 0000000000..27c4d2bc10 --- /dev/null +++ b/plugin/xapi/php-xapi/lrs-bundle/src/Resources/config/routing.xml @@ -0,0 +1,29 @@ + + + + + xapi_lrs.controller.statement.put:putStatement + statement + + true + + + + + xapi_lrs.controller.statement.post:postStatement + statement + + true + + + + + xapi_lrs.controller.statement.get:getStatement + + true + + + diff --git a/plugin/xapi/php-xapi/lrs-bundle/src/Resources/config/serializer.xml b/plugin/xapi/php-xapi/lrs-bundle/src/Resources/config/serializer.xml new file mode 100644 index 0000000000..2ce345a97a --- /dev/null +++ b/plugin/xapi/php-xapi/lrs-bundle/src/Resources/config/serializer.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugin/xapi/php-xapi/lrs-bundle/src/Response/AttachmentResponse.php b/plugin/xapi/php-xapi/lrs-bundle/src/Response/AttachmentResponse.php new file mode 100644 index 0000000000..a70f99f664 --- /dev/null +++ b/plugin/xapi/php-xapi/lrs-bundle/src/Response/AttachmentResponse.php @@ -0,0 +1,79 @@ + + * + * 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 + */ +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(); + } +} diff --git a/plugin/xapi/php-xapi/lrs-bundle/src/Response/MultipartResponse.php b/plugin/xapi/php-xapi/lrs-bundle/src/Response/MultipartResponse.php new file mode 100644 index 0000000000..935fab3d05 --- /dev/null +++ b/plugin/xapi/php-xapi/lrs-bundle/src/Response/MultipartResponse.php @@ -0,0 +1,139 @@ + + * + * 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 + */ +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; + } +} diff --git a/plugin/xapi/php-xapi/lrs-bundle/src/XApiLrsBundle.php b/plugin/xapi/php-xapi/lrs-bundle/src/XApiLrsBundle.php new file mode 100644 index 0000000000..33d7775ff1 --- /dev/null +++ b/plugin/xapi/php-xapi/lrs-bundle/src/XApiLrsBundle.php @@ -0,0 +1,26 @@ + + * + * 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 + */ +class XApiLrsBundle extends Bundle +{ + public function getContainerExtension() + { + return new XApiLrsExtension(); + } +} diff --git a/plugin/xapi/php-xapi/repository-doctrine-orm/LICENSE b/plugin/xapi/php-xapi/repository-doctrine-orm/LICENSE new file mode 100644 index 0000000000..de1d823c3d --- /dev/null +++ b/plugin/xapi/php-xapi/repository-doctrine-orm/LICENSE @@ -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. diff --git a/plugin/xapi/php-xapi/repository-doctrine-orm/README.md b/plugin/xapi/php-xapi/repository-doctrine-orm/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugin/xapi/php-xapi/repository-doctrine-orm/composer.json b/plugin/xapi/php-xapi/repository-doctrine-orm/composer.json new file mode 100644 index 0000000000..dace61cba6 --- /dev/null +++ b/plugin/xapi/php-xapi/repository-doctrine-orm/composer.json @@ -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" + } + } +} diff --git a/plugin/xapi/php-xapi/repository-doctrine-orm/metadata/Actor.orm.xml b/plugin/xapi/php-xapi/repository-doctrine-orm/metadata/Actor.orm.xml new file mode 100644 index 0000000000..040148a094 --- /dev/null +++ b/plugin/xapi/php-xapi/repository-doctrine-orm/metadata/Actor.orm.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + diff --git a/plugin/xapi/php-xapi/repository-doctrine-orm/metadata/Attachment.orm.xml b/plugin/xapi/php-xapi/repository-doctrine-orm/metadata/Attachment.orm.xml new file mode 100644 index 0000000000..4fb517de63 --- /dev/null +++ b/plugin/xapi/php-xapi/repository-doctrine-orm/metadata/Attachment.orm.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/plugin/xapi/php-xapi/repository-doctrine-orm/metadata/Context.orm.xml b/plugin/xapi/php-xapi/repository-doctrine-orm/metadata/Context.orm.xml new file mode 100644 index 0000000000..a33661e1cf --- /dev/null +++ b/plugin/xapi/php-xapi/repository-doctrine-orm/metadata/Context.orm.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugin/xapi/php-xapi/repository-doctrine-orm/metadata/Extensions.orm.xml b/plugin/xapi/php-xapi/repository-doctrine-orm/metadata/Extensions.orm.xml new file mode 100644 index 0000000000..efec346328 --- /dev/null +++ b/plugin/xapi/php-xapi/repository-doctrine-orm/metadata/Extensions.orm.xml @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/plugin/xapi/php-xapi/repository-doctrine-orm/metadata/Result.orm.xml b/plugin/xapi/php-xapi/repository-doctrine-orm/metadata/Result.orm.xml new file mode 100644 index 0000000000..d1ffe53054 --- /dev/null +++ b/plugin/xapi/php-xapi/repository-doctrine-orm/metadata/Result.orm.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugin/xapi/php-xapi/repository-doctrine-orm/metadata/Statement.orm.xml b/plugin/xapi/php-xapi/repository-doctrine-orm/metadata/Statement.orm.xml new file mode 100644 index 0000000000..321d5b8641 --- /dev/null +++ b/plugin/xapi/php-xapi/repository-doctrine-orm/metadata/Statement.orm.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugin/xapi/php-xapi/repository-doctrine-orm/metadata/StatementObject.orm.xml b/plugin/xapi/php-xapi/repository-doctrine-orm/metadata/StatementObject.orm.xml new file mode 100644 index 0000000000..22464ce3fa --- /dev/null +++ b/plugin/xapi/php-xapi/repository-doctrine-orm/metadata/StatementObject.orm.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugin/xapi/php-xapi/repository-doctrine-orm/metadata/Verb.orm.xml b/plugin/xapi/php-xapi/repository-doctrine-orm/metadata/Verb.orm.xml new file mode 100644 index 0000000000..2e2277c6d7 --- /dev/null +++ b/plugin/xapi/php-xapi/repository-doctrine-orm/metadata/Verb.orm.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/plugin/xapi/php-xapi/repository-doctrine-orm/phpunit.xml.dist b/plugin/xapi/php-xapi/repository-doctrine-orm/phpunit.xml.dist new file mode 100644 index 0000000000..5b855beb89 --- /dev/null +++ b/plugin/xapi/php-xapi/repository-doctrine-orm/phpunit.xml.dist @@ -0,0 +1,13 @@ + + + + + ./tests + + + + + ./src + + + diff --git a/plugin/xapi/php-xapi/repository-doctrine-orm/src/StatementRepository.php b/plugin/xapi/php-xapi/repository-doctrine-orm/src/StatementRepository.php new file mode 100644 index 0000000000..9049a5e3b8 --- /dev/null +++ b/plugin/xapi/php-xapi/repository-doctrine-orm/src/StatementRepository.php @@ -0,0 +1,50 @@ + + * + * 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 + */ +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(); + } + } +} diff --git a/plugin/xapi/php-xapi/repository-doctrine-orm/tests/Functional/StatementRepositoryTest.php b/plugin/xapi/php-xapi/repository-doctrine-orm/tests/Functional/StatementRepositoryTest.php new file mode 100644 index 0000000000..fdf8729195 --- /dev/null +++ b/plugin/xapi/php-xapi/repository-doctrine-orm/tests/Functional/StatementRepositoryTest.php @@ -0,0 +1,52 @@ + + * + * 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(); + } +} diff --git a/plugin/xapi/php-xapi/repository-doctrine-orm/tests/Unit/Repository/StatementRepositoryTest.php b/plugin/xapi/php-xapi/repository-doctrine-orm/tests/Unit/Repository/StatementRepositoryTest.php new file mode 100644 index 0000000000..9979d01efa --- /dev/null +++ b/plugin/xapi/php-xapi/repository-doctrine-orm/tests/Unit/Repository/StatementRepositoryTest.php @@ -0,0 +1,38 @@ + + * + * 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); + } +} diff --git a/plugin/xapi/php-xapi/repository-doctrine-orm/tests/data/.gitkeep b/plugin/xapi/php-xapi/repository-doctrine-orm/tests/data/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugin/xapi/php-xapi/repository-doctrine-orm/tests/proxies/.gitkeep b/plugin/xapi/php-xapi/repository-doctrine-orm/tests/proxies/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugin/xapi/src/Lrs/Entity/Actor.php b/plugin/xapi/src/Lrs/Entity/Actor.php deleted file mode 100644 index 5cc9f71a3d..0000000000 --- a/plugin/xapi/src/Lrs/Entity/Actor.php +++ /dev/null @@ -1,161 +0,0 @@ - - * - * 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 - * - * @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); - } -} diff --git a/plugin/xapi/src/Lrs/Entity/Attachment.php b/plugin/xapi/src/Lrs/Entity/Attachment.php deleted file mode 100644 index f7ff70d351..0000000000 --- a/plugin/xapi/src/Lrs/Entity/Attachment.php +++ /dev/null @@ -1,163 +0,0 @@ - - * - * 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 - * - * @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); - } -} diff --git a/plugin/xapi/src/Lrs/Entity/Context.php b/plugin/xapi/src/Lrs/Entity/Context.php deleted file mode 100644 index 484491ef29..0000000000 --- a/plugin/xapi/src/Lrs/Entity/Context.php +++ /dev/null @@ -1,268 +0,0 @@ - - * - * 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 - * - * @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; - } -} diff --git a/plugin/xapi/src/Lrs/Entity/Extensions.php b/plugin/xapi/src/Lrs/Entity/Extensions.php deleted file mode 100644 index ae8fa302ef..0000000000 --- a/plugin/xapi/src/Lrs/Entity/Extensions.php +++ /dev/null @@ -1,71 +0,0 @@ - - * - * 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 - * - * @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); - } -} diff --git a/plugin/xapi/src/Lrs/Entity/Result.php b/plugin/xapi/src/Lrs/Entity/Result.php deleted file mode 100644 index 3cc711e64a..0000000000 --- a/plugin/xapi/src/Lrs/Entity/Result.php +++ /dev/null @@ -1,154 +0,0 @@ - - * - * 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 - * - * @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); - } -} diff --git a/plugin/xapi/src/Lrs/Entity/Statement.php b/plugin/xapi/src/Lrs/Entity/Statement.php deleted file mode 100644 index 4472b59371..0000000000 --- a/plugin/xapi/src/Lrs/Entity/Statement.php +++ /dev/null @@ -1,212 +0,0 @@ - - * - * 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 - * - * @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 - ); - } -} diff --git a/plugin/xapi/src/Lrs/Entity/StatementObject.php b/plugin/xapi/src/Lrs/Entity/StatementObject.php deleted file mode 100644 index 67a3217558..0000000000 --- a/plugin/xapi/src/Lrs/Entity/StatementObject.php +++ /dev/null @@ -1,496 +0,0 @@ - - * - * 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 - * - * @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 - ); - } -} diff --git a/plugin/xapi/src/Lrs/Entity/Verb.php b/plugin/xapi/src/Lrs/Entity/Verb.php deleted file mode 100644 index 48ebc679d2..0000000000 --- a/plugin/xapi/src/Lrs/Entity/Verb.php +++ /dev/null @@ -1,86 +0,0 @@ - - * - * 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 - * - * @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; - } -} diff --git a/plugin/xapi/src/Lrs/StatementsController.php b/plugin/xapi/src/Lrs/StatementsController.php index a32de89340..dec9c97bd9 100644 --- a/plugin/xapi/src/Lrs/StatementsController.php +++ b/plugin/xapi/src/Lrs/StatementsController.php @@ -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); } /** diff --git a/plugin/xapi/src/XApiPlugin.php b/plugin/xapi/src/XApiPlugin.php index 21f6c8192d..dc55e59843 100644 --- a/plugin/xapi/src/XApiPlugin.php +++ b/plugin/xapi/src/XApiPlugin.php @@ -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; + } }