├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── VERSION ├── X ├── authors.md ├── benchmarks ├── Example │ └── BootstrapBench.php ├── autoload.php └── readme.md ├── changelog.md ├── circle.yml ├── composer.json ├── composer.lock ├── conformance └── readme.md ├── contributors.md ├── generate-docs.sh ├── migrations ├── 20150906233033_StatementRefs.php └── 20161003203242_ExtensionKeys.php ├── mongo-migrator.yaml ├── phpbench.json.dist ├── phpunit.xml ├── public ├── .htaccess ├── assets │ ├── images │ │ ├── lxHive.logo.png │ │ └── lxHive.logo.svg │ └── styles │ │ ├── main.css │ │ └── pure │ │ ├── grids-responsive-min.css │ │ ├── grids-responsive-old-ie-min.css │ │ └── pure-min.css ├── index.php └── robots.txt ├── sami.config.php ├── src └── xAPI │ ├── Admin.php │ ├── Admin │ ├── AdminException.php │ ├── Auth.php │ ├── LrsReport.php │ ├── Setup.php │ ├── User.php │ └── Validator.php │ ├── AppInitException.php │ ├── BaseTrait.php │ ├── Bootstrap.php │ ├── Command.php │ ├── Config.php │ ├── Config │ └── Templates │ │ ├── Config.development.yml │ │ ├── Config.production.yml │ │ ├── Config.yml │ │ └── UnitTest.yml │ ├── Console │ ├── Application.php │ ├── BasicTokenCreateCommand.php │ ├── BasicTokenDeleteCommand.php │ ├── BasicTokenExpireCommand.php │ ├── BasicTokenListCommand.php │ ├── LrsReportCommand.php │ ├── OAuthClientCreateCommand.php │ ├── OAuthClientListCommand.php │ ├── SetupCommand.php │ └── UserCreateCommand.php │ ├── Container.php │ ├── ContainerException.php │ ├── Controller.php │ ├── Controller │ ├── Error.php │ └── V10 │ │ ├── About.php │ │ ├── Activities.php │ │ ├── Activities │ │ ├── Profile.php │ │ └── State.php │ │ ├── Agents.php │ │ ├── Agents │ │ └── Profile.php │ │ ├── Attachments.php │ │ ├── Auth │ │ └── Tokens.php │ │ ├── Home.php │ │ ├── Oauth │ │ ├── Authorize.php │ │ ├── Login.php │ │ └── Token.php │ │ └── Statements.php │ ├── ControllerInterface.php │ ├── Document.php │ ├── Document │ ├── AccessToken.php │ ├── BasicToken.php │ ├── Generic.php │ ├── OAuthToken.php │ └── Statement.php │ ├── DocumentInterface.php │ ├── DocumentState.php │ ├── Extensions │ ├── .gitignore │ ├── ExtendedQuery │ │ ├── Controller │ │ │ └── V10 │ │ │ │ └── ExtendedQuery.php │ │ ├── ExtendedQuery.php │ │ ├── Service │ │ │ └── Statement.php │ │ ├── Storage │ │ │ ├── Adapter │ │ │ │ └── Mongo │ │ │ │ │ └── ExtendedStatement.php │ │ │ └── Query │ │ │ │ └── ExtendedStatementInterface.php │ │ └── View │ │ │ └── V10 │ │ │ └── ProjectedStatement.php │ ├── ExtensionException.php │ └── ExtensionInterface.php │ ├── HttpException.php │ ├── Parser │ ├── ParserInterface.php │ ├── ParserResult.php │ └── PsrRequest.php │ ├── Routes.php │ ├── Service.php │ ├── Service │ ├── Activity.php │ ├── ActivityProfile.php │ ├── ActivityState.php │ ├── AgentProfile.php │ ├── Attachment.php │ ├── Auth.php │ ├── Auth │ │ ├── AuthInterface.php │ │ ├── Basic.php │ │ ├── Exception.php │ │ └── OAuth.php │ ├── Exception.php │ ├── Log.php │ ├── Statement.php │ └── User.php │ ├── Storage │ ├── Adapter │ │ ├── Mongo.php │ │ └── Mongo │ │ │ ├── Activity.php │ │ │ ├── ActivityProfile.php │ │ │ ├── ActivityState.php │ │ │ ├── AgentProfile.php │ │ │ ├── Attachment.php │ │ │ ├── BasicAuth.php │ │ │ ├── Expression.php │ │ │ ├── ExpressionInterface.php │ │ │ ├── FieldType.php │ │ │ ├── Log.php │ │ │ ├── OAuth.php │ │ │ ├── OAuthClients.php │ │ │ ├── Schema.php │ │ │ ├── Statement.php │ │ │ └── User.php │ ├── AdapterException.php │ ├── AdapterInterface.php │ ├── Provider.php │ ├── Query │ │ ├── ActivityInterface.php │ │ ├── ActivityProfileInterface.php │ │ ├── ActivityStateInterface.php │ │ ├── AgentProfileInterface.php │ │ ├── AttachmentInterface.php │ │ ├── BasicAuthInterface.php │ │ ├── DeletionResult.php │ │ ├── DocumentResult.php │ │ ├── LogInterface.php │ │ ├── OAuthClientsInterface.php │ │ ├── OAuthInterface.php │ │ ├── QueryInterface.php │ │ ├── StatementInterface.php │ │ ├── StatementResult.php │ │ └── UserInterface.php │ └── SchemaInterface.php │ ├── Util │ ├── ArrayableInterface.php │ ├── Collection.php │ ├── CollectionInterface.php │ ├── Date.php │ ├── Filesystem.php │ ├── OAuth.php │ ├── Versioning.php │ └── xAPI.php │ ├── Validator.php │ ├── Validator │ ├── Exception.php │ ├── JsonSchema │ │ └── Constraints │ │ │ ├── Factory.php │ │ │ └── FormatConstraint.php │ └── V10 │ │ ├── Attachment.php │ │ ├── Schema │ │ └── Statements.json │ │ └── Statement.php │ ├── View.php │ └── View │ └── V10 │ ├── About.php │ ├── Activity.php │ ├── ActivityProfile.php │ ├── ActivityState.php │ ├── Agent.php │ ├── AgentProfile.php │ ├── BaseDocument.php │ ├── BasicAuth │ └── AccessToken.php │ ├── OAuth │ ├── AccessToken.php │ ├── Authorize.php │ ├── Login.php │ └── Templates │ │ ├── authorize.twig │ │ ├── footer.twig │ │ ├── head.twig │ │ └── login.twig │ └── Statements.php └── tests ├── .gitignore ├── Config.template.php ├── Integration ├── Parser │ └── PsrRequestParserTest.php └── StreamContext │ ├── Stream.php │ └── StreamContextTest.php ├── MongoTestCase.php ├── TestCase.php ├── Unit ├── ExampleTest.php └── PHPUnit │ ├── BootstrapUnitTestDatabaseTest.php │ ├── CoherentContainersTest.php │ └── MongoTestCaseTest.php ├── autoload.php ├── readme.md └── src └── xAPI ├── Admin ├── SetupTest.php └── ValidatorTest.php ├── BaseTraitTest.php ├── BootstrapTest.php ├── ConfigTest.php ├── ContainerTest.php ├── Extensions └── ExtensionExceptionTest.php ├── Service ├── AuthTest.php └── mock_config_supported_auth_scopes.json ├── Storage └── Adapter │ ├── Mongo │ ├── ActivityProfileTest.php │ ├── ActivityStateTest.php │ ├── ActivityTest.php │ ├── AgentProfileTest.php │ ├── AttachmentTest.php │ ├── BasicAuthTest.php │ ├── LogTest.php │ ├── OAuthClientsTest.php │ ├── OAuthTest.php │ ├── SchemaTest.php │ ├── StatementTest.php │ └── UserTest.php │ └── MongoTest.php ├── Util └── xAPITest.php ├── Validator.php └── Validator └── Validator.php /.gitignore: -------------------------------------------------------------------------------- 1 | # vendor libs 2 | vendor/* 3 | 4 | # config 5 | src/xAPI/Config/*.yml 6 | !src/xAPI/Config/Templates/*.yml 7 | 8 | # file storage 9 | storage/logs/* 10 | storage/files/* 11 | storage/.cache/* 12 | cache/* 13 | 14 | # doc generator 15 | sami.phar 16 | docs/* 17 | 18 | # composer 19 | composer.phar 20 | 21 | # phpBench 22 | phpbench.phar 23 | phpbench.phar.pubkey 24 | phpbench.json 25 | 26 | benchmarks/* 27 | !benchmarks/Example 28 | !benchmarks/*.md 29 | !benchmarks/autoload.php 30 | 31 | # development 32 | htrouter.phar 33 | sftp-config.json 34 | .DS_Store 35 | dev/* 36 | 37 | # linting, code standards 38 | .php_cs.cache 39 | phplint.sh 40 | 41 | src/xAPI/View/V10/OAuth/Templates/* 42 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at support@g3i.com.au. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We strongly encourage you to contribute to lxHive's open source efforts by implementing new features, 4 | enhancing existing features, and fixing bugs. We also welcome your participation 5 | in writing documentation and guides. 6 | 7 | To maintain code quality, all code changes are reviewed by a core set of lxHive project 8 | maintainers. 9 | 10 | As you have ideas for features you want to implement, follow the contribution 11 | steps outlined in the sections, below. For more details on specific steps, and 12 | to get a deeper understanding of lxHive in general, make sure to check out our Wiki. 13 | Lastly, visit the links listed in the *Additional Resources* section, below. 14 | 15 | ## Getting Started 16 | 17 | * [Create a new issue](https://github.com/g3i/lxHive/issues/new) for your issue. If a ticket 18 | already exists for the issue, participate via the existing ticket. 19 | * Describe the issue clearly. If it is a bug, include steps to reproduce it. 20 | * Select an appropriate label for the issue. 21 | 22 | ## Making Changes 23 | 24 | * Fork the lxHive repository. 25 | * Create a **new** branch from the affected existing branch. 26 | * Commit logical units of work. 27 | * Follow the [PSR-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md), 28 | [PSR-1](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md), 29 | [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) and 30 | [Symfony](http://symfony.com/doc/current/contributing/code/standards.html) code standards. 31 | * Include a reference to the issue (e.g. #13) in your commit messages. 32 | For example: 33 | 34 | Improve code readability - #13 35 | 36 | * *Test* your changes thoroughly! Consider the wide variety of operating systems, database versions, application servers, and other related technologies. 37 | 38 | * Unit tests: see docs in `tests` folder 39 | * [ADL Conformance Test Suite](https://github.com/adlnet/lrs-conformance-test-suite) 40 | * [xAPITests](https://github.com/sraka1/xAPITests) 41 | 42 | ## Submitting Changes 43 | 44 | * Push changes in your branch to your fork. 45 | * [Create a pull request](https://github.com/g3i/lxHive/compare) and mention the issue it fixes [as described here](https://github.com/blog/1506-closing-issues-via-pull-requests). 46 | * You're done! Your changes will be reviewed. Please respond to comments and questions to your pull request until it is closed. 47 | 48 | ## Additional Resources 49 | 50 | * [lxHive Website](http://www.lxhive.com/) 51 | * [General GitHub documentation](http://help.github.com/) 52 | * [GitHub pull request 53 | documentation](http://help.github.com/send-pull-requests/) 54 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.10.0 2 | -------------------------------------------------------------------------------- /X: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | $argv = $_SERVER['argv']; 25 | 26 | if (isset($argv[0]) && is_dir($argv[0])) { 27 | $workingDirectory = $argv[0]; 28 | array_shift($argv); 29 | $_SERVER['argv'] = $argv; 30 | } else { 31 | $workingDirectory = __DIR__; 32 | } 33 | 34 | if (!file_exists($workingDirectory . '/public/index.php')) { 35 | echo 'You must run X from the root directory of your lxHive install or specify its correct path as the first argument!' . PHP_EOL; 36 | exit; 37 | } 38 | 39 | if (!file_exists($workingDirectory . '/vendor/autoload.php')) { 40 | echo 'You must run composer install before running X!' . PHP_EOL; 41 | exit; 42 | } 43 | 44 | require_once $workingDirectory . '/vendor/autoload.php'; 45 | 46 | use API\Bootstrap; 47 | use API\Console\Application; 48 | use API\Console\UserCreateCommand; 49 | use API\Console\BasicTokenCreateCommand; 50 | use API\Console\BasicTokenListCommand; 51 | use API\Console\BasicTokenDeleteCommand; 52 | use API\Console\BasicTokenExpireCommand; 53 | use API\Console\OAuthClientCreateCommand; 54 | use API\Console\OAuthClientListCommand; 55 | use API\Console\SetupCommand; 56 | use API\Console\SetupOAuthCommand; 57 | use API\Console\LrsReportCommand; 58 | use API\AppInitException; 59 | 60 | // If container cannot be instantiated due to missing Config file, allow only Setup command to be executed! 61 | try { 62 | $bootstrapper = Bootstrap::factory(Bootstrap::Console); 63 | $application = $bootstrapper->bootCliApp(); 64 | } catch (AppInitException $e) { 65 | $application = new Application(); 66 | $application->add(new SetupCommand()); 67 | $application->add(new LrsReportCommand()); 68 | $application->run(); 69 | } 70 | 71 | $application->add(new UserCreateCommand()); 72 | $application->add(new BasicTokenListCommand()); 73 | $application->add(new BasicTokenDeleteCommand()); 74 | $application->add(new BasicTokenExpireCommand()); 75 | $application->add(new BasicTokenCreateCommand()); 76 | $application->add(new OAuthClientCreateCommand()); 77 | $application->add(new OAuthClientListCommand()); 78 | $application->add(new SetupCommand()); 79 | $application->add(new LrsReportCommand()); 80 | $application->run(); 81 | ?> 82 | -------------------------------------------------------------------------------- /authors.md: -------------------------------------------------------------------------------- 1 | Jakob Murko - sraka1 2 | -------------------------------------------------------------------------------- /benchmarks/Example/BootstrapBench.php: -------------------------------------------------------------------------------- 1 | . 19 | * 20 | * For authorship information, please view the AUTHORS 21 | * file that was distributed with this source code. 22 | */ 23 | 24 | error_reporting(E_ALL); 25 | 26 | $loader = require __DIR__.'/../vendor/autoload.php'; 27 | $loader->setPsr4('Bench\\', __DIR__); 28 | -------------------------------------------------------------------------------- /benchmarks/readme.md: -------------------------------------------------------------------------------- 1 | # Benchmarks 2 | 3 | lxHive supports testing with [PHPBench](http://phpbench.readthedocs.org/). 4 | 5 | A `phpbench.json.dist` file is setup in the project root folder of your lxHive app. 6 | 7 | ## Installing PHPBench 8 | 9 | PHPBench requires PHP >= 7.1 and is therefore NOT included as a composer dependency. 10 | 11 | You can either [install PHPBench globally](http://phpbench.readthedocs.io/en/latest/installing.html) or fetch the Phar locally into lxHive root. 12 | 13 | ``` 14 | cd 15 | curl -o phpbench.phar https://phpbench.github.io/phpbench/phpbench.phar 16 | curl -o phpbench.phar.pubkey https://phpbench.github.io/phpbench/phpbench.phar.pubkey 17 | 18 | ``` 19 | 20 | phpbench.phar and phpbench.pharpubkey are excluded via .gitignore. 21 | 22 | ## Usage 23 | 24 | ```bash 25 | php phpbench.phar run 26 | ``` 27 | 28 | You may create your custom configuration file (`phpbench.json`) in the root folder. This file is excluded via .gitignore 29 | 30 | ## Structure 31 | 32 | | namespace | folder | notes | 33 | |--- |--- |--- | 34 | | `\Bench` | `benchmarks/*` | | 35 | 36 | * Benchmark Classes have to have the `*Bench` suffix, i.e. `BootstrapBench` 37 | * Benchmark methods have to have the `bench*` prefix, i.e. `benchFactory()` 38 | 39 | Benchmark files are included in `.gitignore` (except the example), thus you can freely create your own tests. 40 | 41 | See documentation: http://phpbench.readthedocs.io/en/latest/writing-benchmarks.html 42 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | php: 3 | version: 7.1.9 4 | node: 5 | version: 6.1.0 6 | dependencies: 7 | pre: 8 | - pecl install mongodb 9 | - echo "extension=mongodb.so" > /opt/circleci/php/$(phpenv global)/etc/conf.d/mongodb.ini 10 | post: 11 | - npm install -g newman newman-reporter-phpunit 12 | - git clone git@github.com:sraka1/xAPITests.git 13 | test: 14 | pre: 15 | - yes '' | ./X setup 16 | - chmod -R 0770 storage 17 | - ./X user:create --name=test --description=test --email=test@test.com --password=Test123! --permissions=super 18 | - ./X auth:basic:create --name=test --description=test --expiration=1914156000 --email=test@test.com --scopes=super --key=test --secret=test 19 | - php -S localhost:8080 -t ./public/: 20 | background: true 21 | override: 22 | - php vendor/bin/phpunit 23 | post: 24 | - newman run ./xAPITests/tests/1.0.3/xAPI.json -e ./xAPITests/environments/local.json -r cli,phpunit 25 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "g3i/lxhive", 3 | "description": "An open-source LRS from G3 International Pty Ltd", 4 | "license": "GPL-3.0", 5 | "keywords": ["experience api", "xapi", "lrs"], 6 | "type": "project", 7 | "homepage": "http://www.lxhive.com/", 8 | "require": { 9 | "slim/slim": "3.*", 10 | "justinrainbow/json-schema": "~4.0", 11 | "league/url": "~3.3", 12 | "ramsey/uuid": "3.*", 13 | "league/flysystem": "^1.0", 14 | "league/flysystem-aws-s3-v3": "^1.0", 15 | "symfony/console": "*", 16 | "symfony/yaml": "~2.6", 17 | "monolog/monolog": "^1.21", 18 | "slim/twig-view": "^2.1", 19 | "symfony/event-dispatcher": "^3.2", 20 | "ext-mongodb": "*", 21 | "container-interop/container-interop": "^1.2", 22 | "pimple/pimple": "^3.0" 23 | }, 24 | "require-dev": { 25 | "phpunit/phpunit": "5.5.*" 26 | }, 27 | "autoload": { 28 | "psr-4": { 29 | "API\\": "src/xAPI" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /conformance/readme.md: -------------------------------------------------------------------------------- 1 | # Conformance Testing 2 | 3 | This folder provides space for conformance docs and installation of external test suites. 4 | 5 | ## [ADL LRS Conformance Test Suite](https://github.com/adlnet/lrs-conformance-test-suite) 6 | 7 | * tests the 'MUST' requirements of the xAPI Spec based on the ADL [testing requirements](https://github.com/adlnet/xapi-lrs-conformance-requirements) 8 | * requires npm and nodejs 9 | 10 | ```bash 11 | git clone https://github.com/adlnet/lrs-conformance-test-suite.git 12 | cd lrs-conformance-test-suite && npm install 13 | ``` 14 | 15 | 2. Follow the [setup and usage instructions](https://github.com/adlnet/lrs-conformance-test-suite) 16 | -------------------------------------------------------------------------------- /contributors.md: -------------------------------------------------------------------------------- 1 | * Jakob Murko - @sraka1 2 | * Joerg Boeselt - @RoboSparrow 3 | * Maarten de Boer - @mdeboer -------------------------------------------------------------------------------- /generate-docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PHAR_FILE='sami.phar' 4 | PHAR_URI='http://get.sensiolabs.org/' 5 | CONFIG_FILE='sami.config.php' 6 | 7 | RED='\033[00;31m' 8 | GREEN='\033[00;32m' 9 | YELLOW='\033[00;33m' 10 | GREY='\033[00;37m' 11 | CLEAR='\033[00;39m' 12 | 13 | echo ${YELLOW} 14 | echo "----------------------- " 15 | echo " (1/2) Downloading ${PHAR_URI}${PHAR_FILE}.." 16 | echo "----------------------- " 17 | echo ${CLEAR} 18 | 19 | curl -O ${PHAR_URI}${PHAR_FILE} 20 | 21 | if [ ! -e sami.phar ] 22 | then 23 | echo "${RED}-----------------------${CLEAR}" 24 | echo "${RED}ERROR$ downloading${CLEAR} ${PHAR_URI}${PHAR_FILE}" 25 | echo " - download ${PHAR_FILE} manually from: ${GREY}https://github.com/FriendsOfPHP/Sami${CLEAR}" 26 | echo " - run: ${GREY}php sami.phar update ${CONFIG_FILE}${CLEAR}" 27 | echo "${RED}-----------------------${CLEAR}" 28 | exit 1 29 | fi 30 | 31 | echo ${YELLOW} 32 | echo "----------------------- " 33 | echo "Compiling docs.. (using ${CONFIG_FILE})" 34 | echo "----------------------- " 35 | echo ${CLEAR} 36 | 37 | php sami.phar update ${CONFIG_FILE} 38 | 39 | echo ${GREEN} 40 | echo "----------------------- " 41 | echo "Docs compiled to ${GREY}./docs/${CLEAR}" 42 | echo "----------------------- " 43 | echo ${CLEAR} 44 | -------------------------------------------------------------------------------- /migrations/20150906233033_StatementRefs.php: -------------------------------------------------------------------------------- 1 | mongo->getCollection('statements'); 12 | $cursor = $collection->find(); 13 | foreach ($cursor as $statementDocument) { 14 | $this->addReferences($statementDocument); 15 | } 16 | } 17 | 18 | private function addReferences($statementDocument) 19 | { 20 | try { 21 | if ($statementDocument->isReferencing()) { 22 | 23 | $referencedStatement = $statementDocument->getReferencedStatement(); 24 | 25 | if ($referencedStatement->isReferencing()) { 26 | $this->addReferences($referencedStatement); 27 | } 28 | 29 | $existingReferences = []; 30 | if (null !== $referencedStatement->getReferences()) { 31 | $existingReferences = $referencedStatement->getReferences(); 32 | } 33 | $existingReferences[] = $referencedStatement->getStatement(); 34 | $statementDocument->setReferences($existingReferences); 35 | $statementDocument->save(); 36 | } 37 | } catch (\Exception $e) { 38 | return false; 39 | } 40 | } 41 | 42 | public function down() 43 | { 44 | // Remove references 45 | $slimInstance = Slim::getInstance(); 46 | $collection = $slimInstance->mongo->getCollection('statements'); 47 | $cursor = $collection->find(); 48 | foreach ($cursor as $statementDocument) { 49 | $this->removeReferences($statementDocument); 50 | } 51 | } 52 | 53 | private function removeReferences($statementDocument) 54 | { 55 | if ($statementDocument->isReferencing()) { 56 | $referencedStatement = $statementDocument->getReferencedStatement(); 57 | 58 | if ($referencedStatement->isReferencing()) { 59 | $this->removeReferences($referencedStatement); 60 | } 61 | 62 | $noReferences = []; 63 | $statementDocument->setReferences($noReferences); 64 | $statementDocument->save(); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /migrations/20161003203242_ExtensionKeys.php: -------------------------------------------------------------------------------- 1 | mongo->getCollection('statements'); 12 | $cursor = $collection->find(); 13 | foreach ($cursor as $statementDocument) { 14 | $statementDocument->convertExtensionKeysToUnicode(); 15 | $statementDocument->save(); 16 | } 17 | } 18 | 19 | public function down() 20 | { 21 | // Remove references 22 | $slimInstance = Slim::getInstance(); 23 | $collection = $slimInstance->mongo->getCollection('statements'); 24 | $cursor = $collection->find(); 25 | foreach ($cursor as $statementDocument) { 26 | $statementDocument->convertExtensionKeysFromUnicode(); 27 | $statementDocument->save(); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /mongo-migrator.yaml: -------------------------------------------------------------------------------- 1 | default_environment: development 2 | 3 | path: 4 | migrations: migrations 5 | 6 | environments: 7 | development: 8 | dsn: mongodb://localhost 9 | 10 | default_database: test 11 | 12 | log_database: test 13 | log_collection: migrations 14 | 15 | staging: 16 | dsn: mongodb://localhost 17 | 18 | default_database: test 19 | 20 | log_database: test 21 | log_collection: migrations 22 | 23 | production: 24 | dsn: mongodb://localhost 25 | 26 | default_database: test 27 | 28 | log_database: test 29 | log_collection: migrations -------------------------------------------------------------------------------- /phpbench.json.dist: -------------------------------------------------------------------------------- 1 | { 2 | "bootstrap": "benchmarks/autoload.php", 3 | "path": "benchmarks" 4 | } 5 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | ./tests 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine On 2 | 3 | # Some hosts may require you to use the `RewriteBase` directive. 4 | # If you need to use the `RewriteBase` directive, it should be the 5 | # absolute physical path to the directory that contains this htaccess file. 6 | # 7 | # RewriteBase / 8 | 9 | RewriteCond %{REQUEST_FILENAME} !-f 10 | RewriteRule ^ index.php [QSA,L] 11 | -------------------------------------------------------------------------------- /public/assets/images/lxHive.logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g3i/lxHive/cffee6d90ca91cf4b86c4e7814ef0bbd9f682d5c/public/assets/images/lxHive.logo.png -------------------------------------------------------------------------------- /public/assets/styles/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Raleway', sans-serif; 3 | background-color: #f9f9f9; 4 | color: rgb(85, 86, 90); 5 | padding-bottom: 2em; 6 | } 7 | .text-center { 8 | text-align: center; 9 | } 10 | .text-center { 11 | margin-top: 1em; 12 | } 13 | /** 14 | * Branding 15 | */ 16 | 17 | #lxhive-logo, 18 | #custom-logo { 19 | max-height: 50px; 20 | margin: 0 auto; 21 | } 22 | /* 23 | * Alerts 24 | */ 25 | 26 | .alert-error { 27 | position: relative; 28 | margin-bottom: 1em; 29 | padding: 1em; 30 | background-color: rgb(202, 60, 60); 31 | color: #ffffff; 32 | } 33 | .alert { 34 | border-radius: 5px; 35 | } 36 | ul.alert { 37 | list-style-type: none; 38 | } 39 | 40 | /* 41 | * Headlines 42 | */ 43 | 44 | h1, 45 | h2 { 46 | text-align: center; 47 | } 48 | h1 { 49 | margin-top: 2em; 50 | margin-bottom: 2em; 51 | } 52 | h2 { 53 | margin-top: 0; 54 | padding-top: 0; 55 | } 56 | 57 | /* 58 | * Buttons 59 | */ 60 | 61 | .pure-button { 62 | border-radius: 5px; 63 | } 64 | pure-button { 65 | text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); 66 | } 67 | .button-xlarge { 68 | font-weight: bold; 69 | } 70 | .button-allow { 71 | padding-left: 4em; 72 | padding-right: 4em; 73 | background: rgb(28, 184, 65); 74 | } 75 | .button-disallow { 76 | background: rgb(202, 60, 60); 77 | font-weight: normal; 78 | } 79 | .pure-form fieldset { 80 | background: #ffffff; 81 | padding: 2em 1em; 82 | border-radius: 5px; 83 | } 84 | .form-row, 85 | #id_username, 86 | #id_password, 87 | #id_remember_me, 88 | #id_login{ 89 | display:block; 90 | margin-left: auto; 91 | margin-right: auto; 92 | min-width: 250px; 93 | max-width: 250px; 94 | } 95 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | // Require the autoloader 26 | require __DIR__.'/../vendor/autoload.php'; 27 | 28 | use API\Bootstrap; 29 | 30 | $bootstrapper = Bootstrap::factory(Bootstrap::Web); 31 | $app = $bootstrapper->bootWebApp(); 32 | $app->run(); 33 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / -------------------------------------------------------------------------------- /sami.config.php: -------------------------------------------------------------------------------- 1 | files() 21 | ->name('*.php') 22 | ->in(LXHIVE_DOCS_SRC); 23 | 24 | // options 25 | $sami = new Sami($iterator, array( 26 | 'title' => 'lxHive', 27 | 'build_dir' => LXHIVE_DOCS_BUILD, 28 | 'cache_dir' => LXHIVE_DOCS_BUILD, 29 | 'default_opened_level' => 2, 30 | )); 31 | 32 | // Document private and protected functions/properties 33 | $sami['filter'] = function () { 34 | return new TrueFilter(); 35 | }; 36 | 37 | return $sami; 38 | -------------------------------------------------------------------------------- /src/xAPI/Admin.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API; 26 | 27 | /** 28 | * Base Admin class 29 | */ 30 | abstract class Admin 31 | { 32 | use BaseTrait; 33 | 34 | /** 35 | * constructor 36 | * @param \Psr\Container\ContainerInterface $container 37 | */ 38 | public function __construct($container) 39 | { 40 | $this->setContainer($container); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/xAPI/Admin/AdminException.php: -------------------------------------------------------------------------------- 1 | . 19 | * 20 | * For authorship information, please view the AUTHORS 21 | * file that was distributed with this source code. 22 | */ 23 | namespace API\Admin; 24 | 25 | /** 26 | * Exceptions for Admin API 27 | */ 28 | class AdminException extends \RunTimeException 29 | { 30 | } 31 | -------------------------------------------------------------------------------- /src/xAPI/AppInitException.php: -------------------------------------------------------------------------------- 1 | . 19 | * 20 | * For authorship information, please view the AUTHORS 21 | * file that was distributed with this source code. 22 | */ 23 | namespace API; 24 | 25 | /** 26 | * Exceptions for Application Initialization (Bootstrap, Config) 27 | */ 28 | class AppInitException extends \Exception 29 | { 30 | } 31 | -------------------------------------------------------------------------------- /src/xAPI/BaseTrait.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API; 26 | 27 | use Interop\Container\ContainerInterface; 28 | 29 | trait BaseTrait 30 | { 31 | /** 32 | * @var ContainerInterface $container Holds service container 33 | */ 34 | private $container; 35 | 36 | /** 37 | * Sets service container. 38 | * 39 | * @param ContainerInterface $container 40 | * @return self 41 | */ 42 | public function setContainer(ContainerInterface $container) 43 | { 44 | $this->container = $container; 45 | return $this; 46 | } 47 | 48 | /** 49 | * Gets service ontainer. 50 | * 51 | * @return ContainerInterface 52 | */ 53 | public function getContainer() 54 | { 55 | if (!$this->container) { 56 | throw new \Exception('Basetrait: no container was set.'); 57 | } 58 | return $this->container; 59 | } 60 | 61 | /** 62 | * Gets storage service. 63 | * 64 | * @return \Storage\AdapterInterface 65 | * @throws \Exception 66 | * @throws \API\ContainerException 67 | */ 68 | public function getStorage() 69 | { 70 | if (!$this->container) { 71 | throw new \Exception('Basetrait: no container was set.'); 72 | } 73 | return $this->container->get('storage'); 74 | } 75 | 76 | /** 77 | * Gets log service. 78 | * 79 | * @return \Monolog\Monolog 80 | * @throws \Exception 81 | * @throws \API\ContainerException 82 | */ 83 | public function getLog() 84 | { 85 | if (!$this->container) { 86 | throw new \Exception('Basetrait: no container was set.'); 87 | } 88 | return $this->container->get('log'); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/xAPI/Command.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API; 26 | 27 | use Symfony\Component\Console\Command\Command as SymfonyCommand; 28 | 29 | class Command extends SymfonyCommand 30 | { 31 | /** 32 | * {@inheritDoc} 33 | */ 34 | public function __construct() 35 | { 36 | parent::__construct(); 37 | 38 | $this->init(); 39 | } 40 | 41 | /** 42 | * Default init, use for overwrite only. 43 | * {@inheritDoc} 44 | */ 45 | public function init() 46 | { 47 | } 48 | 49 | /** 50 | * Get service container 51 | * 52 | * @return Interop\Container\ContainerInterface 53 | */ 54 | public function getContainer() 55 | { 56 | return $this->getApplication()->getContainer(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/xAPI/Config/Templates/Config.development.yml: -------------------------------------------------------------------------------- 1 | # Development configuration overrides 2 | log: 3 | enabled: true 4 | handlers: [ErrorLogHandler, FirePHPHandler] 5 | name: Development 6 | debug: true 7 | -------------------------------------------------------------------------------- /src/xAPI/Config/Templates/Config.production.yml: -------------------------------------------------------------------------------- 1 | # Production configuration overrides 2 | log: 3 | enabled: true 4 | handlers: [StreamHandler] 5 | name: Production 6 | debug: false 7 | -------------------------------------------------------------------------------- /src/xAPI/Config/Templates/UnitTest.yml: -------------------------------------------------------------------------------- 1 | description: 'A test yml file for unit testing' 2 | -------------------------------------------------------------------------------- /src/xAPI/Console/Application.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Console; 26 | 27 | use Symfony\Component\Console\Application as SymfonyApplication; 28 | use API\BaseTrait; 29 | 30 | class Application extends SymfonyApplication 31 | { 32 | use BaseTrait; 33 | 34 | /** 35 | * {@inheritDoc} 36 | */ 37 | public function __construct($container = null, $name = 'UNKNOWN', $version = 'UNKNOWN') 38 | { 39 | if ($container) { 40 | $this->setContainer($container); 41 | } 42 | parent::__construct($name, $version); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/xAPI/Console/BasicTokenDeleteCommand.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Console; 26 | 27 | use API\Command; 28 | use Symfony\Component\Console\Input\InputInterface; 29 | use Symfony\Component\Console\Output\OutputInterface; 30 | use Symfony\Component\Console\Question\Question; 31 | use Symfony\Component\Console\Question\ConfirmationQuestion; 32 | use API\Admin\Auth; 33 | 34 | class BasicTokenDeleteCommand extends Command 35 | { 36 | /** 37 | * {@inheritDoc} 38 | */ 39 | protected function configure() 40 | { 41 | $this 42 | ->setName('auth:basic:delete') 43 | ->setDescription('Deletes a token') 44 | ; 45 | } 46 | 47 | /** 48 | * {@inheritDoc} 49 | */ 50 | protected function execute(InputInterface $input, OutputInterface $output) 51 | { 52 | $authAdmin = new Auth($this->getContainer()); 53 | $keys = $authAdmin->listBasicTokenIds(); 54 | 55 | // 1. key 56 | $helper = $this->getHelper('question'); 57 | $question = new Question('Please enter the key of the token you wish to delete: '); 58 | $question->setAutocompleterValues($keys); 59 | $key = $helper->ask($input, $output, $question); 60 | 61 | //2. confirm 62 | $question = new ConfirmationQuestion('Are you sure (y/n): ', false); 63 | if (!$helper->ask($input, $output, $question)) { 64 | return; 65 | } 66 | 67 | // 2. delete 68 | $authAdmin->deleteBasicToken($key); 69 | 70 | $output->writeln('Token successfully deleted!'); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/xAPI/Console/BasicTokenExpireCommand.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Console; 26 | 27 | use API\Command; 28 | use Symfony\Component\Console\Input\InputInterface; 29 | use Symfony\Component\Console\Output\OutputInterface; 30 | use Symfony\Component\Console\Question\Question; 31 | use Symfony\Component\Console\Question\ConfirmationQuestion; 32 | use API\Admin\Auth; 33 | 34 | class BasicTokenExpireCommand extends Command 35 | { 36 | /** 37 | * {@inheritDoc} 38 | */ 39 | protected function configure() 40 | { 41 | $this 42 | ->setName('auth:basic:expire') 43 | ->setDescription('Expires a token') 44 | ; 45 | } 46 | 47 | /** 48 | * {@inheritDoc} 49 | */ 50 | protected function execute(InputInterface $input, OutputInterface $output) 51 | { 52 | $authAdmin = new Auth($this->getContainer()); 53 | $keys = $authAdmin->listBasicTokenIds(); 54 | 55 | // 1. key 56 | $helper = $this->getHelper('question'); 57 | $question = new Question('Please enter the key of the token you wish to expire: '); 58 | $question->setAutocompleterValues($keys); 59 | $key = $helper->ask($input, $output, $question); 60 | 61 | // 2. confirm 62 | $question = new ConfirmationQuestion('Are you sure (y/n): ', false); 63 | if (!$helper->ask($input, $output, $question)) { 64 | return; 65 | } 66 | 67 | //3. expire document 68 | $authAdmin->expireBasicToken($key); 69 | 70 | $output->writeln('Token successfully expired!'); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/xAPI/Console/BasicTokenListCommand.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Console; 26 | 27 | use API\Command; 28 | use Symfony\Component\Console\Input\InputInterface; 29 | use Symfony\Component\Console\Output\OutputInterface; 30 | use API\Admin\Auth; 31 | 32 | // TODO 0.11.x review, command seems to have no real use 33 | 34 | class BasicTokenListCommand extends Command 35 | { 36 | /** 37 | * {@inheritDoc} 38 | */ 39 | protected function configure() 40 | { 41 | $this 42 | ->setName('auth:basic:list') 43 | ->setDescription('List tokens') 44 | ; 45 | } 46 | 47 | /** 48 | * {@inheritDoc} 49 | */ 50 | protected function execute(InputInterface $input, OutputInterface $output) 51 | { 52 | $authAdmin = new Auth($this->getContainer()); 53 | $textArray = $authAdmin->listBasicTokens(); 54 | 55 | $text = json_encode($textArray, JSON_PRETTY_PRINT); 56 | 57 | $output->writeln('Tokens successfully fetched!'); 58 | $output->writeln('Info:'); 59 | $output->writeln($text); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/xAPI/Console/OAuthClientCreateCommand.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Console; 26 | 27 | use API\Command; 28 | use Symfony\Component\Console\Input\InputInterface; 29 | use Symfony\Component\Console\Output\OutputInterface; 30 | use Symfony\Component\Console\Question\Question; 31 | 32 | use API\Admin\Auth; 33 | use API\Admin; 34 | 35 | class OAuthClientCreateCommand extends Command 36 | { 37 | /** 38 | * {@inheritDoc} 39 | */ 40 | protected function configure() 41 | { 42 | $this 43 | ->setName('oauth:client:create') 44 | ->setDescription('Creates a new OAuth client') 45 | ; 46 | } 47 | 48 | /** 49 | * {@inheritDoc} 50 | */ 51 | protected function execute(InputInterface $input, OutputInterface $output) 52 | { 53 | $authAdmin = new Auth($this->getContainer()); 54 | $validator = new Admin\Validator(); 55 | 56 | // 1. name 57 | $helper = $this->getHelper('question'); 58 | $question = new Question('Please enter a name: ', ''); 59 | $question->setMaxAttempts(null); 60 | $question->setValidator(function ($answer) use ($validator) { 61 | $validator->validateName($answer); 62 | return $answer; 63 | }); 64 | $name = $helper->ask($input, $output, $question); 65 | 66 | // 2. description 67 | $helper = $this->getHelper('question'); 68 | $question = new Question('Please enter a description: ', ''); 69 | $description = $helper->ask($input, $output, $question); 70 | 71 | // 3. redirect Uri 72 | $helper = $this->getHelper('question'); 73 | $question = new Question('Please enter a redirect URI: '); 74 | $question->setMaxAttempts(null); 75 | $question->setValidator(function ($answer) use ($validator) { 76 | $validator->validateRedirectUri($answer); 77 | return $answer; 78 | }); 79 | $redirectUri = $helper->ask($input, $output, $question); 80 | 81 | // 4. write record 82 | $client = $authAdmin->addOAuthClient($name, $description, $redirectUri); 83 | $text = json_encode($client, JSON_PRETTY_PRINT); 84 | 85 | $output->writeln('OAuth client successfully created!'); 86 | $output->writeln('Info:'); 87 | $output->writeln($text); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/xAPI/Console/OAuthClientListCommand.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Console; 26 | 27 | use API\Command; 28 | use Symfony\Component\Console\Input\InputInterface; 29 | use Symfony\Component\Console\Output\OutputInterface; 30 | use API\Admin\Auth; 31 | 32 | // TODO 0.11.x review, command seems to have no real use 33 | 34 | class OAuthClientListCommand extends Command 35 | { 36 | 37 | /** 38 | * {@inheritDoc} 39 | */ 40 | protected function configure() 41 | { 42 | $this 43 | ->setName('oauth:client:list') 44 | ->setDescription('Lists OAuth clients') 45 | ; 46 | } 47 | 48 | /** 49 | * {@inheritDoc} 50 | */ 51 | protected function execute(InputInterface $input, OutputInterface $output) 52 | { 53 | $authAdmin = new Auth($this->getContainer()); 54 | $textArray = $authAdmin->listOAuthClients(); 55 | $text = json_encode($textArray, JSON_PRETTY_PRINT); 56 | 57 | $output->writeln('Clients successfully fetched!'); 58 | $output->writeln('Info:'); 59 | $output->writeln($text); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/xAPI/ContainerException.php: -------------------------------------------------------------------------------- 1 | . 19 | * 20 | * For authorship information, please view the AUTHORS 21 | * file that was distributed with this source code. 22 | */ 23 | namespace API; 24 | 25 | use InvalidArgumentException; 26 | use Interop\Container\Exception\ContainerException as InteropContainerException; 27 | 28 | class ContainerException extends InvalidArgumentException implements InteropContainerException 29 | { 30 | } 31 | -------------------------------------------------------------------------------- /src/xAPI/Controller/Error.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Controller\V10; 26 | 27 | use API\Controller; 28 | use API\Service\Activity as ActivityService; 29 | use API\View\V10\Activity as ActivityView; 30 | 31 | class Activities extends Controller 32 | { 33 | /** 34 | * @var \API\Service\Activity 35 | */ 36 | private $activityService; 37 | 38 | /** 39 | * Get activity service. 40 | */ 41 | public function init() 42 | { 43 | $this->activityService = new ActivityService($this->getContainer()); 44 | } 45 | 46 | // Boilerplate code until this is figured out... 47 | public function get() 48 | { 49 | // Check authentication 50 | $this->getContainer()->get('auth')->requirePermission('profile'); 51 | 52 | $activityDocument = $this->activityService->activityGet(); 53 | 54 | // Render them 55 | $view = new ActivityView($this->getResponse(), $this->getContainer()); 56 | 57 | $view = $view->renderGetSingle($activityDocument); 58 | return $this->jsonResponse(Controller::STATUS_OK, $view); 59 | } 60 | 61 | public function options() 62 | { 63 | //Handle options request 64 | $this->setResponse($this->getResponse()->withHeader('Allow', 'GET')); 65 | return $this->response(Controller::STATUS_OK); 66 | } 67 | 68 | /** 69 | * Gets the value of activityService. 70 | * 71 | * @return \API\Service\Activity 72 | */ 73 | public function getActivityService() 74 | { 75 | return $this->activityService; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/xAPI/Controller/V10/Agents.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Controller\V10; 26 | 27 | use API\Controller; 28 | use API\View\V10\Agent as AgentView; 29 | use API\Util\Collection; 30 | 31 | class Agents extends Controller 32 | { 33 | /** 34 | * Handler for GET call 35 | * @return Psr\Http\Message\ResponseInterface 36 | */ 37 | public function get() 38 | { 39 | // Check authentication 40 | $this->getContainer()->get('auth')->requirePermission('profile'); 41 | 42 | // TODO 0.11.x request validation 43 | 44 | $request = $this->getContainer()->get('parser')->getData(); 45 | $params = new Collection($request->getParameters()); 46 | 47 | $agent = $params->get('agent'); 48 | $agent = json_decode($agent, true); 49 | 50 | $view = new AgentView($this->getResponse(), $this->getContainer()); 51 | $view = $view->renderGet($agent); 52 | 53 | return $this->jsonResponse(Controller::STATUS_OK, $view); 54 | } 55 | 56 | /** 57 | * Handler for OPTIONS call 58 | * @return Psr\Http\Message\ResponseInterface 59 | */ 60 | public function options() 61 | { 62 | // Handle options request 63 | $this->setResponse($this->getResponse()->withHeader('Allow', 'GET')); 64 | return $this->response(Controller::STATUS_OK); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/xAPI/Controller/V10/Attachments.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Controller\V10; 26 | 27 | use API\Controller; 28 | use API\Service\Attachment as AttachmentService; 29 | use API\Util; 30 | 31 | class Attachments extends Controller 32 | { 33 | /** 34 | * @var \API\Service\Attachment 35 | */ 36 | private $attachmentService; 37 | 38 | /** 39 | * Get statement service. 40 | */ 41 | public function init() 42 | { 43 | $this->attachmentService = new AttachmentService($this->getContainer()); 44 | } 45 | 46 | public function get() 47 | { 48 | $request = $this->getContainer()->get('parser')->getData(); 49 | 50 | // Check authentication 51 | $this->getContainer()->get('auth')->requirePermission('attachments'); 52 | 53 | $params = new Util\Collection($request->getParameters()); 54 | if (!$params->has('sha2')) { 55 | throw new \Exception('Missing sha2 parameter!', Controller::STATUS_BAD_REQUEST); 56 | } 57 | 58 | $sha2 = $params->get('sha2'); 59 | $encoding = $params->get('encoding'); 60 | 61 | // Fetch attachment metadata and data 62 | $metadata = $this->attachmentService->fetchMetadataBySha2($sha2); 63 | $data = $this->attachmentService->fetchFileBySha2($sha2); 64 | if ($encoding !== 'binary') { 65 | $data = base64_encode($data); 66 | } 67 | 68 | $metadataDocument = new \API\Document\Generic($metadata); 69 | $this->setResponse($this->getResponse()->withHeader('Content-Type', $metadataDocument->getContentType())); 70 | 71 | return $this->response(Controller::STATUS_OK, $data); 72 | } 73 | 74 | public function options() 75 | { 76 | //Handle options request 77 | $this->setResponse($this->getResponse()->withHeader('Allow', 'GET')); 78 | return $this->response(Controller::STATUS_OK); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/xAPI/Controller/V10/Home.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Controller\V10; 26 | 27 | class Home extends About 28 | { 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/xAPI/Controller/V10/Oauth/Token.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Controller\V10\Oauth; 26 | 27 | use API\Controller; 28 | use API\Service\Auth\OAuth as OAuthService; 29 | use API\View\V10\OAuth\AccessToken as AccessTokenView; 30 | 31 | class Token extends Controller 32 | { 33 | /** 34 | * @var \API\Service\Auth\OAuth 35 | */ 36 | private $oAuthService; 37 | 38 | /** 39 | * Get agent profile service. 40 | */ 41 | public function init() 42 | { 43 | $this->oAuthService = new OAuthService($this->getContainer()); 44 | } 45 | 46 | public function post() 47 | { 48 | // TODO 0.11.x request validation 49 | 50 | $accessTokenDocument = $this->oAuthService->accessTokenPost(); 51 | // Authorization is always requested 52 | $view = new AccessTokenView($this->getResponse(), $this->getContainer()); 53 | $view = $view->render($accessTokenDocument); 54 | return $this->jsonResponse(Controller::STATUS_OK, $view); 55 | } 56 | 57 | public function options() 58 | { 59 | //Handle options request 60 | $this->setResponse($this->getResponse()->withHeader('Allow', 'POST')); 61 | return $this->response(Controller::STATUS_OK); 62 | } 63 | 64 | /** 65 | * Gets the value of oAuthService. 66 | * 67 | * @return \API\Service\Auth\OAuth 68 | */ 69 | public function getOAuthService() 70 | { 71 | return $this->oAuthService; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/xAPI/ControllerInterface.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API; 26 | 27 | interface ControllerInterface 28 | { 29 | /** 30 | * Initializes Controller 31 | */ 32 | public function init(); 33 | 34 | /** 35 | * Http GET callback 36 | */ 37 | public function get(); 38 | 39 | /** 40 | * Http POST callback 41 | */ 42 | public function post(); 43 | 44 | /** 45 | * Http PUT callback 46 | */ 47 | public function put(); 48 | 49 | /** 50 | * Http DELETE callback 51 | */ 52 | public function delete(); 53 | 54 | /** 55 | * Http OPTIONS callback 56 | */ 57 | public function options(); 58 | } 59 | -------------------------------------------------------------------------------- /src/xAPI/Document/BasicToken.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Document; 26 | 27 | class BasicToken extends AccessToken 28 | { 29 | public function generateAuthority() 30 | { 31 | $host = $this->getHost(); 32 | $authority = (object)[ 33 | 'objectType' => 'Agent', 34 | 'account' => [ 35 | 'homePage' => $host, 36 | 'name' => $this->getUser()->email, 37 | ], 38 | ]; 39 | 40 | return $authority; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/xAPI/Document/Generic.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | * 24 | * Projected Usage 25 | * 26 | * POST/PUT: 27 | * $document = new \API\Document\Statement($parsedJson, 'UNTRUSTED', '1.0.3'); 28 | * $statement = $document->validate()->normalize()->document(); // validated and normalized stdClass, ready for storage, changes the state with each chain ['UNTRUSTED->VALIDTED->READY] 29 | * 30 | * REST response 31 | * $document = new \API\Document\Statement($mongoDocument, 'TRUSTED', '1.0.3'); 32 | * $document->validate()->normalize(); //deals with minor incositencies, will in future also remove meta properties 33 | * $json = json_encode($document); 34 | * 35 | * $document will have convenience methods and reveal the convenience methods of subproperties 36 | * $document->isReferencing(); 37 | * $document->actor->isAgent(); 38 | * $document->object->isSubStatement(); 39 | * 40 | * etc.. 41 | */ 42 | 43 | namespace API\Document; 44 | 45 | use API\Document; 46 | 47 | // TODO 0.11.x: Define interface for normalize, validate, etc. (GraphQL) 48 | 49 | 50 | class Generic extends Document 51 | { 52 | } 53 | -------------------------------------------------------------------------------- /src/xAPI/Document/OAuthToken.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Document; 26 | 27 | class OAuthToken extends AccessToken 28 | { 29 | public function generateAuthority() 30 | { 31 | $host = $this->getHost(); 32 | $authority = [ 33 | 'objectType' => 'Group', 34 | 'member' => (object)[ 35 | [ 36 | 'account' => (object)[ 37 | 'homePage' => $host.'/oauth/token', 38 | 'name' => 'oauth_consumer_'.$this->getClientId(), 39 | ], 40 | ], 41 | [ 42 | 'mbox' => 'mailto:'.$this->getUser()->email, 43 | ], 44 | ], 45 | ]; 46 | 47 | return $authority; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/xAPI/DocumentInterface.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API; 26 | 27 | interface DocumentInterface extends \JsonSerializable 28 | { 29 | /** 30 | * Constructor 31 | * 32 | * @param array $data xAPI data 33 | * @param string $documentState EUNUM string of i/o state of the document (i.e 'TRUSTED', 'UNTRUSTED', etc..) 34 | * @param string $version xAPI version 35 | * @return void 36 | */ 37 | public function __construct($data = [], $documentState = null, $version = null); 38 | 39 | /** 40 | * Get stored data 41 | * 42 | * @return array xAPI data 43 | */ 44 | public function getData(); 45 | 46 | /** 47 | * Get stored document state 48 | * 49 | * @return string i/o state of the document 50 | */ 51 | public function getState(); 52 | 53 | /** 54 | * Get stored xAPI version 55 | * 56 | * @return string 57 | */ 58 | public function getVersion(); 59 | 60 | /** 61 | * Get stored document state 62 | * 63 | * @return string i/o state of the document 64 | */ 65 | public function toArray(); 66 | } 67 | -------------------------------------------------------------------------------- /src/xAPI/DocumentState.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | * 24 | * Projected Usage 25 | * 26 | * POST/PUT: 27 | * $document = new \API\Document\Statement($parsedJson, 'UNTRUSTED', '1.0.3'); 28 | * $statement = $document->validate()->normalize()->document(); // validated and normalized stdClass, ready for storage, changes the state with each chain ['UNTRUSTED->VALIDTED->READY] 29 | * 30 | * REST response 31 | * $document = new \API\Document\Statement($mongoDocument, 'TRUSTED', '1.0.3'); 32 | * $document->validate()->normalize(); //deals with minor incositencies, will in future also remove meta properties 33 | * $json = json_encode($document); 34 | * 35 | * $document will have convenience methods and reveal the convenience methods of subproperties 36 | * $document->isReferencing(); 37 | * $document->actor->isAgent(); 38 | * $document->object->isSubStatement(); 39 | * 40 | * etc.. 41 | */ 42 | 43 | namespace API; 44 | 45 | class DocumentState 46 | { 47 | const UNTRUSTED = 0; 48 | const TRUSTED = 1; 49 | const VALIDATED = 2; 50 | } 51 | -------------------------------------------------------------------------------- /src/xAPI/Extensions/.gitignore: -------------------------------------------------------------------------------- 1 | # ignore everything 2 | 3 | * 4 | 5 | # add interfaces 6 | !.gitignore 7 | !ExtensionInterface.php 8 | !ExtensionException.php 9 | 10 | # add core extensions 11 | !ExtendedQuery 12 | -------------------------------------------------------------------------------- /src/xAPI/Extensions/ExtendedQuery/Storage/Query/ExtendedStatementInterface.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Extensions\ExtendedQuery\Storage\Query; 26 | 27 | interface ExtendedStatementInterface 28 | { 29 | /** 30 | * Query the statements collection 31 | * @param array $parameters hashmap of GET params 32 | * @return \API\Storage\Query\StatementInterface $statementResult 33 | */ 34 | public function extendedQuery($parameters); 35 | } 36 | -------------------------------------------------------------------------------- /src/xAPI/Extensions/ExtendedQuery/View/V10/ProjectedStatement.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Extensions\ExtendedQuery\View\V10; 26 | 27 | use API\View; 28 | 29 | /** 30 | * Statement view 31 | * @see \API\View 32 | */ 33 | class ProjectedStatement extends View 34 | { 35 | /** 36 | * Render response view 37 | * @param \API\Storage\Query\StatementInterface $statementResult 38 | * @return array hashmap of view properites, ready to be serialized into json 39 | */ 40 | public function render($statementResult) 41 | { 42 | $view = []; 43 | $idArray = []; 44 | $resultArray = []; 45 | 46 | $view['statements'] = []; 47 | $view['more'] = ''; 48 | $view['totalCount'] = $statementResult->getTotalCount(); 49 | 50 | foreach ($statementResult->getCursor() as $result) { 51 | if (isset($result->statement)) { 52 | $idArray[] = $result->_id; 53 | $result = $result->statement; 54 | $resultArray[] = $result; 55 | } 56 | } 57 | 58 | if ($statementResult->getHasMore()) { 59 | $latestId = end($idArray); 60 | $latestId = $latestId->__toString(); 61 | if ($statementResult->getSortDescending()) { 62 | $this->getContainer()->get('url')->getQuery()->modify(['until_id' => $latestId]); 63 | } else { //Ascending 64 | $this->getContainer()->get('url')->getQuery()->modify(['since_id' => $latestId]); 65 | } 66 | $view['more'] = $this->getContainer()->get('url')->getRelativeUrl(); 67 | } 68 | 69 | $view['statements'] = array_values($resultArray); 70 | 71 | return $view; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/xAPI/Extensions/ExtensionException.php: -------------------------------------------------------------------------------- 1 | . 19 | * 20 | * For authorship information, please view the AUTHORS 21 | * file that was distributed with this source code. 22 | */ 23 | 24 | namespace API\Extensions; 25 | 26 | use API\HttpException; 27 | 28 | class ExtensionException extends HttpException 29 | { 30 | } 31 | -------------------------------------------------------------------------------- /src/xAPI/Extensions/ExtensionInterface.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Extensions; 26 | 27 | interface ExtensionInterface 28 | { 29 | /** 30 | * Returns any event listeners that need to be added for this extension. 31 | * 32 | * @return array Format: [['event' => 'statement.get', 'callable' => function(), 'priority' => 1 (optional)], [], ...] 33 | */ 34 | public function getEventListeners(); 35 | 36 | /** 37 | * Returns any routes that need to be added for this extension. 38 | * 39 | * @return array Format: [['pattern' => '/plus/superstatements', 'callable' => function(), 'methods' => ['GET', 'HEAD']], [], ...] 40 | */ 41 | public function getRoutes(); 42 | 43 | /** 44 | * Install extension, apply models and configurations 45 | * 46 | * @return void 47 | * @throws \API\Storage\AdapterException 48 | * @throws \MongoDB\Driver\Exception\Exception 49 | */ 50 | public function install(); 51 | 52 | /** 53 | * Provide information for /about endpoint 54 | * @see https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Communication.md#aboutresource 55 | * @return array 56 | */ 57 | public function about(); 58 | } 59 | -------------------------------------------------------------------------------- /src/xAPI/HttpException.php: -------------------------------------------------------------------------------- 1 | . 19 | * 20 | * For authorship information, please view the AUTHORS 21 | * file that was distributed with this source code. 22 | */ 23 | 24 | namespace API; 25 | 26 | class HttpException extends \Exception 27 | { 28 | private $data = null; 29 | 30 | /** 31 | * Prepares a json response exception. 32 | * 33 | * @see API/Controller::error() 34 | * 35 | * @param string $message 36 | * @param int $statusCode valid httpd status code 37 | * @param array|object|null $data extra data to be included in json response 38 | * @param \Exception $previous 39 | * 40 | * @throws \Exception 41 | */ 42 | public function __construct($message, $statusCode = 400, $data = [], \Exception $previous = null) 43 | { 44 | $this->data = $data; 45 | parent::__construct($message, $statusCode, $previous); 46 | } 47 | 48 | /** 49 | * Get data. 50 | * 51 | * @return mixed $data 52 | */ 53 | public function getData() 54 | { 55 | return $this->data; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/xAPI/Parser/ParserInterface.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Parser; 26 | 27 | interface ParserInterface 28 | { 29 | /** 30 | * Get the main part. 31 | * 32 | * @return ParserResult an object or array, given the payload 33 | */ 34 | public function getData(); 35 | 36 | /** 37 | * Get the additional parts. 38 | * 39 | * @return \Traversable an array of the parts 40 | */ 41 | public function getAttachments(); 42 | 43 | /** 44 | * Get the parts of the request. 45 | * 46 | * @return \Traversable an array of the parts 47 | */ 48 | public function getParts(); 49 | } 50 | -------------------------------------------------------------------------------- /src/xAPI/Service.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API; 26 | 27 | abstract class Service 28 | { 29 | use BaseTrait; 30 | 31 | /** 32 | * @constructor 33 | */ 34 | public function __construct($container) 35 | { 36 | $this->setContainer($container); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/xAPI/Service/Activity.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Service; 26 | 27 | use API\Service; 28 | use API\Util\Collection; 29 | 30 | class Activity extends Service 31 | { 32 | /** 33 | * Fetches activity profiles according to the given parameters. 34 | * 35 | * @param array $request The incoming HTTP request 36 | * 37 | * @return array An array of activityProfile objects. 38 | */ 39 | public function activityGet() 40 | { 41 | $request = $this->getContainer()->get('parser')->getData(); 42 | $params = new Collection($request->getParameters()); 43 | 44 | $activityDocument = $this->getStorage()->getActivityStorage()->fetchById($params->get('activityId')); 45 | 46 | return $activityDocument; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/xAPI/Service/Attachment.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Service; 26 | 27 | use API\Service; 28 | use API\Config; 29 | 30 | class Attachment extends Service 31 | { 32 | /** 33 | * Fetches file metadata from Mongo. 34 | * 35 | * @param string $sha2 The sha2 hash of the file 36 | * 37 | * @return \API\Document\Attachment The attachment document 38 | */ 39 | public function fetchMetadataBySha2($sha2) 40 | { 41 | $document = $this->getStorage()->getAttachmentStorage()->fetchMetadataBySha2($sha2); 42 | 43 | return $document; 44 | } 45 | 46 | /** 47 | * Fetches the actual file from the filesystem. 48 | * 49 | * @param string $sha2 The sha2 hash of the file 50 | * 51 | * @return string File contents 52 | */ 53 | public function fetchFileBySha2($sha2) 54 | { 55 | $fsAdapter = \API\Util\Filesystem::generateAdapter(Config::get('filesystem')); 56 | $contents = $fsAdapter->read($sha2); 57 | 58 | return $contents; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/xAPI/Service/Auth/AuthInterface.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Service\Auth; 26 | 27 | use Slim\Http\Request; 28 | 29 | interface AuthInterface 30 | { 31 | /** 32 | * Fetches the token document, parsing it from the request. 33 | * 34 | * @param Request $request Slim request 35 | * 36 | * @throws AuthFailureException If authentication cannot be extracted from the Request 37 | * 38 | * @return AbstractToken $user 39 | */ 40 | public function extractToken(Request $request); 41 | } 42 | -------------------------------------------------------------------------------- /src/xAPI/Service/Auth/Exception.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Service\Auth; 26 | 27 | class Exception extends \Exception 28 | { 29 | } 30 | -------------------------------------------------------------------------------- /src/xAPI/Service/Exception.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Service; 26 | 27 | class Exception extends \Exception 28 | { 29 | } 30 | -------------------------------------------------------------------------------- /src/xAPI/Service/Log.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Service; 26 | 27 | use API\Service; 28 | use API\Util; 29 | 30 | class Log extends Service 31 | { 32 | /** 33 | * Creates a log entry from the given request. 34 | * 35 | * @param Slim\Http\Request $request The request 36 | * 37 | * @return \API\Document\Log The log document 38 | */ 39 | public function logRequest($request) 40 | { 41 | $ip = $request->getServerParam('REMOTE_ADDR'); 42 | $method = $request->getMethod(); 43 | $target = $request->getRequestTarget(); 44 | $currentDate = Util\Date::dateTimeExact(); 45 | $logStorage = $this->getStorage()->getLogStorage(); 46 | $document = $logStorage->logRequest($ip, $method, $target, $currentDate); 47 | 48 | return $document; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/xAPI/Storage/Adapter/Mongo/Activity.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Storage\Adapter\Mongo; 26 | 27 | use API\Storage\SchemaInterface; 28 | use API\Storage\Query\ActivityInterface; 29 | 30 | use API\Controller; 31 | use API\Storage\Provider; 32 | 33 | use API\Storage\AdapterException; 34 | 35 | class Activity extends Provider implements ActivityInterface, SchemaInterface 36 | { 37 | const COLLECTION_NAME = 'activities'; 38 | 39 | /** 40 | * @var array $indexes 41 | * 42 | * @see https://docs.mongodb.com/manual/reference/command/createIndexes/ 43 | * [ 44 | * name: , 45 | * key: [ 46 | * , 47 | * , 48 | * ... 49 | * ], 50 | * , 51 | * , 52 | * ... 53 | * ], 54 | */ 55 | private $indexes = [ 56 | [ 57 | 'name' => 'id.unique', 58 | 'key' => [ 59 | 'id' => 1 60 | ], 61 | 'unique' => true, 62 | ] 63 | ]; 64 | 65 | /** 66 | * {@inheritDoc} 67 | */ 68 | public function install() 69 | { 70 | $container = $this->getContainer()->get('storage'); 71 | $container->executeCommand(['create' => self::COLLECTION_NAME]); 72 | $container->createIndexes(self::COLLECTION_NAME, $this->indexes); 73 | } 74 | 75 | /** 76 | * {@inheritDoc} 77 | */ 78 | public function getIndexes() 79 | { 80 | return $this->indexes; 81 | } 82 | 83 | /** 84 | * {@inheritDoc} 85 | */ 86 | public function fetchById($id) 87 | { 88 | $storage = $this->getContainer()->get('storage'); 89 | $expression = $storage->createExpression(); 90 | 91 | $expression->where('id', $id); 92 | 93 | if ($storage->count(self::COLLECTION_NAME, $expression) === 0) { 94 | throw new AdapterException('Activity does not exist.', Controller::STATUS_NOT_FOUND); 95 | } 96 | 97 | $document = $storage->findOne(self::COLLECTION_NAME, $expression); 98 | 99 | return $document; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/xAPI/Storage/Adapter/Mongo/FieldType.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | * 24 | * This file was adapted from sokil/php-mongo. 25 | * License information is available at https://github.com/sokil/php-mongo/blob/master/LICENSE 26 | * 27 | */ 28 | 29 | namespace API\Storage\Adapter\Mongo; 30 | 31 | class FieldType 32 | { 33 | const DOUBLE = 1; 34 | const STRING = 2; 35 | const OBJECT = 3; 36 | const ARRAY_TYPE = 4; 37 | const BINARY_DATA = 5; 38 | const UNDEFINED = 6; // deprecated 39 | const OBJECT_ID = 7; 40 | const BOOLEAN = 8; 41 | const DATE = 9; 42 | const NULL = 10; 43 | const REGULAR_EXPRESSION = 11; 44 | const JAVASCRIPT = 13; 45 | const SYMBOL = 14; 46 | const JAVASCRIPT_WITH_SCOPE = 15; 47 | const INT32 = 16; 48 | const TIMESTAMP = 17; 49 | const INT64 = 18; 50 | const MIN_KEY = 255; 51 | const MAX_KEY = 127; 52 | } 53 | -------------------------------------------------------------------------------- /src/xAPI/Storage/Adapter/Mongo/Log.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Storage\Adapter\Mongo; 26 | 27 | use API\Storage\SchemaInterface; 28 | use API\Storage\Query\LogInterface; 29 | 30 | use API\Util; 31 | use API\Storage\Provider; 32 | 33 | class Log extends Provider implements LogInterface, SchemaInterface 34 | { 35 | const COLLECTION_NAME = 'logs'; 36 | 37 | /** 38 | * @var array $indexes 39 | * 40 | * @see https://docs.mongodb.com/manual/reference/command/createIndexes/ 41 | * [ 42 | * name: , 43 | * key: [ 44 | * , 45 | * , 46 | * ... 47 | * ], 48 | * , 49 | * , 50 | * ... 51 | * ], 52 | */ 53 | private $indexes = []; 54 | 55 | /** 56 | * {@inheritDoc} 57 | */ 58 | public function install() 59 | { 60 | $container = $this->getContainer()->get('storage'); 61 | $container->executeCommand(['create' => self::COLLECTION_NAME]); 62 | $container->createIndexes(self::COLLECTION_NAME, $this->indexes); 63 | } 64 | 65 | /** 66 | * {@inheritDoc} 67 | */ 68 | public function getIndexes() 69 | { 70 | return $this->indexes; 71 | } 72 | 73 | /** 74 | * {@inheritDoc} 75 | */ 76 | public function logRequest($ip, $method, $endpoint, $timestamp) 77 | { 78 | $storage = $this->getContainer()->get('storage'); 79 | $document = new \API\Document\Generic(); 80 | 81 | $document->setIp($ip); 82 | $document->setMethod($method); 83 | $document->setEndpoint($endpoint); 84 | $document->setTimestamp(Util\Date::dateTimeToMongoDate($timestamp)); 85 | 86 | $storage->insertOne(self::COLLECTION_NAME, $document); 87 | 88 | return $document; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/xAPI/Storage/AdapterException.php: -------------------------------------------------------------------------------- 1 | . 19 | * 20 | * For authorship information, please view the AUTHORS 21 | * file that was distributed with this source code. 22 | */ 23 | namespace API\Storage; 24 | 25 | use API\HttpException; 26 | 27 | class AdapterException extends HttpException 28 | { 29 | } 30 | -------------------------------------------------------------------------------- /src/xAPI/Storage/Provider.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Storage; 26 | 27 | use API\BaseTrait; 28 | 29 | abstract class Provider 30 | { 31 | use BaseTrait; 32 | 33 | /** 34 | * Constructor. 35 | * 36 | * @param \Psr\Container\ContainerInterface $container 37 | */ 38 | public function __construct($container) 39 | { 40 | $this->setContainer($container); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/xAPI/Storage/Query/ActivityInterface.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Storage\Query; 26 | 27 | interface ActivityInterface extends QueryInterface 28 | { 29 | /** 30 | * Find record by Mongo ObjectId 31 | * @param string $id 32 | * 33 | * @return \API\DocumentInterface|null 34 | */ 35 | public function fetchById($id); 36 | } 37 | -------------------------------------------------------------------------------- /src/xAPI/Storage/Query/ActivityProfileInterface.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Storage\Query; 26 | 27 | interface ActivityProfileInterface extends QueryInterface 28 | { 29 | /** 30 | * Find single record by Mongo ObjectId 31 | * 32 | * @param array $parameters map of query params 33 | * 34 | * @return \API\DocumentInterface 35 | */ 36 | public function getFiltered($parameters); 37 | 38 | /** 39 | * Upsert a single record 40 | * 41 | * @param array $parameters map of query params 42 | * @param stdClass $profileObject 43 | * 44 | * @return \API\DocumentInterface 45 | */ 46 | public function post($parameters, $profileObject); 47 | 48 | /** 49 | * Upsert a single record 50 | * 51 | * @param array $parameters map of query params 52 | * @param stdClass $profileObject 53 | * 54 | * @return \API\DocumentInterface 55 | */ 56 | public function put($parameters, $profileObject); 57 | 58 | /** 59 | * Delete a single record 60 | * 61 | * @param array $parameters map of query params 62 | * @return \API\Storage\Query\API\DeletionResult 63 | */ 64 | public function delete($parameters); 65 | } 66 | -------------------------------------------------------------------------------- /src/xAPI/Storage/Query/ActivityStateInterface.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Storage\Query; 26 | 27 | interface ActivityStateInterface extends QueryInterface 28 | { 29 | /** 30 | * Find records by request params 31 | * 32 | * @param array $parameters map of query params 33 | * 34 | * @return \API\DocumentInterface 35 | */ 36 | public function getFiltered($parameters); 37 | 38 | /** 39 | * Upsert a single record 40 | * 41 | * @param array $parameters map of query params 42 | * @param stdClass $profileObject 43 | * 44 | * @return \API\DocumentInterface 45 | */ 46 | public function post($parameters, $stateObject); 47 | 48 | /** 49 | * Upsert a single record 50 | * 51 | * @param array $parameters map of query params 52 | * @param stdClass $profileObject 53 | * 54 | * @return \API\DocumentInterface 55 | */ 56 | public function put($parameters, $stateObject); 57 | 58 | /** 59 | * Delete a single record 60 | * 61 | * @param array $parameters map of query params 62 | * @return \API\Storage\Query\API\DeletionResult 63 | */ 64 | public function delete($parameters); 65 | } 66 | -------------------------------------------------------------------------------- /src/xAPI/Storage/Query/AgentProfileInterface.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Storage\Query; 26 | 27 | interface AgentProfileInterface extends QueryInterface 28 | { 29 | /** 30 | * Find single record by Mongo ObjectId 31 | * 32 | * @param array $parameters map of query params 33 | * 34 | * @return \API\DocumentInterface 35 | */ 36 | public function getFiltered($parameters); 37 | 38 | /** 39 | * Upsert a single record 40 | * 41 | * @param array $parameters map of query params 42 | * @param stdClass $profileObject 43 | * 44 | * @return \API\DocumentInterface 45 | */ 46 | public function post($parameters, $profileObject); 47 | 48 | /** 49 | * Upsert a single record 50 | * 51 | * @param array $parameters map of query params 52 | * @param stdClass $profileObject 53 | * 54 | * @return \API\DocumentInterface 55 | */ 56 | public function put($parameters, $profileObject); 57 | 58 | /** 59 | * Delete a single record 60 | * 61 | * @param array $parameters map of query params 62 | * @return \API\Storage\Query\API\DeletionResult 63 | */ 64 | public function delete($parameters); 65 | } 66 | -------------------------------------------------------------------------------- /src/xAPI/Storage/Query/AttachmentInterface.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Storage\Query; 26 | 27 | interface AttachmentInterface extends QueryInterface 28 | { 29 | /** 30 | * Store a record 31 | * 32 | * @param string $sha2 hash 33 | * @param string $contentType 34 | * @param int $timestamp 35 | * 36 | * @return \API\DocumentInterface 37 | */ 38 | public function store($sha2, $contentType, $timestamp = null); 39 | 40 | /** 41 | * Fetch a record by sha2 hash 42 | * 43 | * @param string $sha2 hash 44 | * 45 | * @return \API\DocumentInterface 46 | */ 47 | public function fetchMetadataBySha2($sha2); 48 | } 49 | -------------------------------------------------------------------------------- /src/xAPI/Storage/Query/BasicAuthInterface.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Storage\Query; 26 | 27 | interface BasicAuthInterface extends QueryInterface 28 | { 29 | 30 | /** 31 | * Find record by Mongo ObjectId 32 | * @param string $name 33 | * @param string $description 34 | * @param int $expiresAt unix timestamp 35 | * @param object $user user storage record 36 | * @param array[string] $permissions 37 | * @param string $key token key 38 | * @param string $secret token secret 39 | * 40 | * @return \API\DocumentInterface 41 | */ 42 | public function storeToken($name, $description, $expiresAt, $user, $permissions, $key = null, $secret = null); 43 | 44 | /** 45 | * Find record by token key and token secret 46 | * @param string $key token key 47 | * @param string $secret token secret 48 | * 49 | * @return \API\DocumentInterface 50 | */ 51 | public function getToken($key, $secret); 52 | 53 | /** 54 | * Delete record by token key 55 | * @param string $key token key 56 | * 57 | * @return \API\Storage\Query\API\DeletionResult 58 | */ 59 | public function deleteToken($key); 60 | 61 | /** 62 | * Expire record by token key 63 | * @param string $key token key 64 | * 65 | * @return \MongoDB\Driver\Cursor 66 | */ 67 | public function expireToken($key); 68 | 69 | /** 70 | * Find all records 71 | * 72 | * @return \API\DocumentInterface 73 | */ 74 | public function getTokens(); 75 | } 76 | -------------------------------------------------------------------------------- /src/xAPI/Storage/Query/DeletionResult.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Storage\Query; 26 | 27 | class DeletionResult 28 | { 29 | /** 30 | * Whether the deletion was successful or not. 31 | * 32 | * @var bool 33 | */ 34 | protected $success; 35 | 36 | /** 37 | * Gets the Whether the deletion was successful or not. 38 | * 39 | * @return bool 40 | */ 41 | public function getSuccess() 42 | { 43 | return $this->success; 44 | } 45 | 46 | /** 47 | * Sets the Whether the deletion was successful or not. 48 | * 49 | * @param bool $success the success 50 | * 51 | * @return self 52 | */ 53 | public function setSuccess($success) 54 | { 55 | $this->success = $success; 56 | 57 | return $this; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/xAPI/Storage/Query/LogInterface.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Storage\Query; 26 | 27 | interface LogInterface 28 | { 29 | /** 30 | * Find record by Mongo ObjectId 31 | * @param string $ip ip address 32 | * @param string $method request method 33 | * @param string $endpoint request endpoint 34 | * @param int $timestamp Unix timestamp 35 | * 36 | * @return \API\DocumentInterface 37 | */ 38 | public function logRequest($ip, $method, $endpoint, $timestamp); 39 | } 40 | -------------------------------------------------------------------------------- /src/xAPI/Storage/Query/OAuthClientsInterface.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Storage\Query; 26 | 27 | interface OAuthClientsInterface extends QueryInterface 28 | { 29 | 30 | /** 31 | * Find record by Mongo ObjectId 32 | * @param string $id 33 | * 34 | * @return \API\DocumentInterface|null 35 | */ 36 | public function getClientById($id); 37 | 38 | /** 39 | * Find all records 40 | * 41 | * @return \API\DocumentInterface 42 | */ 43 | public function getClients(); 44 | 45 | /** 46 | * Adds a record 47 | * @param string $name 48 | * @param string $description 49 | * @param string $redirectUri 50 | * 51 | * @return \API\DocumentInterface 52 | */ 53 | public function addClient($name, $description, $redirectUri); 54 | } 55 | -------------------------------------------------------------------------------- /src/xAPI/Storage/Query/OAuthInterface.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Storage\Query; 26 | 27 | interface OAuthInterface extends QueryInterface 28 | { 29 | 30 | /** 31 | * Find record by Mongo ObjectId 32 | * @param int $expiresAt unix timestamp 33 | * @param object $user user storage record 34 | * @param object $client oAuth client storage record 35 | * @param array[object] $scopes 36 | * @param string|null $code 37 | * 38 | * @return \API\DocumentInterface 39 | */ 40 | public function storeToken($expiresAt, $user, $client, array $scopes = [], $code = null); 41 | 42 | /** 43 | * Find record by token 44 | * @param string $accessToken 45 | * 46 | * @return \API\DocumentInterface|null 47 | */ 48 | public function getToken($accessToken); 49 | 50 | /** 51 | * Delete record by token 52 | * @param string $accessToken 53 | * 54 | * @return \API\Storage\Query\API\DeletionResult 55 | */ 56 | public function deleteToken($accessToken); 57 | 58 | /** 59 | * Expire record by token 60 | * @param string $accessToken 61 | * 62 | * @return \MongoDB\Driver\Cursor 63 | */ 64 | public function expireToken($accessToken); 65 | 66 | /** 67 | * Fetch a token with a time code (?) 68 | * @param array map of query params 69 | * 70 | * @return \MongoDB\Driver\Cursor|null 71 | */ 72 | public function getTokenWithOneTimeCode($params); 73 | } 74 | -------------------------------------------------------------------------------- /src/xAPI/Storage/Query/QueryInterface.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Storage\Query; 26 | 27 | interface QueryInterface 28 | { 29 | } 30 | -------------------------------------------------------------------------------- /src/xAPI/Storage/Query/StatementInterface.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Storage\Query; 26 | 27 | interface StatementInterface extends QueryInterface 28 | { 29 | /** 30 | * Get statements using filters. 31 | * @param array map of query params 32 | * 33 | * @return StatementResult 34 | */ 35 | public function get($parameters); 36 | 37 | /** 38 | * Find single statement by statementId (not ObjectId!) 39 | * @param string $statementId 40 | * 41 | * @return StatementResult|null 42 | */ 43 | public function getById($statementId); 44 | 45 | /** 46 | * Insert single document with params[statementId] 47 | * @param array $parameters map of quer yparams 48 | * 49 | * @return StatementResult 50 | * @throws API\Storage\AdapterException 51 | * @throws \MongoDB\Driver\Exception\Exception 52 | */ 53 | public function put($parameters, $statementObject); 54 | 55 | /** 56 | * Transforms statementObject (parser) into a statementDocument (storage) 57 | * @param object $statementObject 58 | * 59 | * @return \API\DocumentInterface statement document 60 | * @throws \API\Storage\AdapterException 61 | * @throws \MongoDB\Driver\Exception\Exception 62 | */ 63 | public function transformForInsert($statementObject); 64 | 65 | 66 | /** 67 | * Insert single document 68 | * @param object $statementObject 69 | * 70 | * @return StatementResult|null 71 | * @throws \MongoDB\Driver\Exception\Exception 72 | */ 73 | public function insertOne($statementObject); 74 | 75 | /** 76 | * Insert collection of documents 77 | * @param array $statementObjects 78 | * 79 | * @return StatementResult 80 | * @throws API\Storage\AdapterException 81 | * @throws \MongoDB\Driver\Exception\Exception 82 | */ 83 | public function insertMultiple($statementObjects); 84 | 85 | 86 | /** 87 | * Ensures that deletion of statements is impossible by throwing always an exception 88 | * 89 | * @throws API\Storage\AdapterException 90 | * @throws \MongoDB\Driver\Exception\Exception 91 | */ 92 | public function delete($parameters); 93 | } 94 | -------------------------------------------------------------------------------- /src/xAPI/Storage/Query/UserInterface.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Storage\Query; 26 | 27 | interface UserInterface extends QueryInterface 28 | { 29 | 30 | /** 31 | * Find record by Mongo ObjectId 32 | * @param MongoDB\BSON\ObjectID $id 33 | * 34 | * @return \API\DocumentInterface|null 35 | */ 36 | public function findById($id); 37 | 38 | /** 39 | * Add a user 40 | * The only validation we do at this level is ensuring that the email is unique 41 | * 42 | * @param string $name 43 | * @param string $description 44 | * @param string $email valid email address 45 | * @param string $password 46 | * @param array $permissions valid array of permission names 47 | * 48 | * @throws \MongoDB\Driver\Exception\Exception 49 | */ 50 | public function addUser($name, $user, $email, $password, $permissions); 51 | 52 | /** 53 | * Find all records 54 | * @return \API\DocumentInterface 55 | */ 56 | public function fetchAll(); 57 | 58 | /** 59 | * Check if collection contains a user with a specified email 60 | * @param string $email 61 | * 62 | * @return bool 63 | */ 64 | public function hasEmail($email); 65 | 66 | /** 67 | * Fetch a user record for specified email and password 68 | * 69 | * @param string $username 70 | * @param string $password 71 | * 72 | * @return \API\DocumentInterface|null 73 | */ 74 | public function findByEmailAndPassword($username, $password); 75 | } 76 | -------------------------------------------------------------------------------- /src/xAPI/Storage/SchemaInterface.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | * 24 | * This file was adapted from sokil/php-mongo. 25 | * License information is available at https://github.com/sokil/php-mongo/blob/master/LICENSE 26 | * 27 | */ 28 | 29 | namespace API\Storage; 30 | 31 | interface SchemaInterface 32 | { 33 | /** 34 | * Install hook for collection 35 | * 36 | * @return void 37 | * @throws \API\Storage\AdapterException 38 | * @throws \MongoDB\Driver\Exception\Exception 39 | */ 40 | public function install(); 41 | 42 | /** 43 | * Gets model indexes configuration array 44 | * 45 | * @return array 46 | */ 47 | public function getIndexes(); 48 | } 49 | -------------------------------------------------------------------------------- /src/xAPI/Util/ArrayableInterface.php: -------------------------------------------------------------------------------- 1 | . 19 | * 20 | * For authorship information, please view the AUTHORS 21 | * file that was distributed with this source code. 22 | * 23 | * This file was adapted from slim. 24 | * License information is available at https://github.com/slimphp/Slim/blob/3.x/LICENSE.md 25 | * 26 | */ 27 | 28 | /* 29 | * Slightly altered version of Slim\Interfaces\CollectionInterface (Slim 3) 30 | * @author https://github.com/slimphp 31 | * @see https://github.com/slimphp/Slim/blob/3.x/Slim/Interfaces/CollectionInterface.php 32 | * @see https://www.slimframework.com/ 33 | */ 34 | 35 | namespace API\Util; 36 | 37 | use API\Util\ArrayableInterface as ArrayableInterface; 38 | 39 | interface CollectionInterface extends \ArrayAccess, \Countable, \IteratorAggregate, \JsonSerializable, ArrayableInterface 40 | { 41 | public function set($key, $value); 42 | 43 | public function get($key, $default = null); 44 | 45 | public function replace(array $items); 46 | 47 | public function all(); 48 | 49 | public function has($key); 50 | 51 | public function remove($key); 52 | 53 | public function clear(); 54 | } 55 | -------------------------------------------------------------------------------- /src/xAPI/Util/Date.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Util; 26 | 27 | use DateTime; 28 | 29 | class Date 30 | { 31 | public static function dateTimeExact() 32 | { 33 | $date = DateTime::createFromFormat('U.u', sprintf('%.f', microtime(true))); 34 | 35 | return $date; 36 | } 37 | 38 | public static function dateStringToMongoDate($dateString) 39 | { 40 | $date = new DateTime($dateString); 41 | $mongoDate = self::dateTimeToMongoDate($date); 42 | 43 | return $mongoDate; 44 | } 45 | 46 | public static function dateTimeToMongoDateLegacy($dateTime) 47 | { 48 | $seconds = $dateTime->getTimestamp(); 49 | $microseconds = $dateTime->format('u'); 50 | $mongoDate = new \MongoDate($seconds, $microseconds); 51 | 52 | return $mongoDate; 53 | } 54 | 55 | public static function dateTimeToMongoDate($dateTime) 56 | { 57 | $seconds = $dateTime->getTimestamp(); 58 | $microseconds = $dateTime->format('u'); 59 | $milliSecondTotal = $seconds*1000+(int)($microseconds/1000); 60 | $mongoDate = new \MongoDB\BSON\UTCDateTime($milliSecondTotal); 61 | 62 | return $mongoDate; 63 | } 64 | 65 | public static function mongoDateToTimestamp(\MongoDB\BSON\UTCDateTime $mongoDate) 66 | { 67 | $dateTime = $mongoDate->toDateTime(); 68 | $timestamp = $dateTime->getTimestamp(); 69 | return $timestamp; 70 | } 71 | 72 | public static function dateTimeToISO8601($dateTime) 73 | { 74 | $dateTime = $dateTime->format('c'); 75 | 76 | return $dateTime; 77 | } 78 | 79 | public static function secondsUntil($dateTime) 80 | { 81 | $seconds = $dateTime->getTimestamp(); 82 | $currentTimestamp = time(); 83 | 84 | $diff = $seconds - $currentTimestamp; 85 | 86 | if ($diff < 0) { 87 | return 0; 88 | } else { 89 | return $diff; 90 | } 91 | } 92 | 93 | public static function dateFromSeconds($seconds) 94 | { 95 | $dateTime = new DateTime(time() + $seconds); 96 | $output = self::dateTimeToISO8601($dateTime); 97 | 98 | return $output; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/xAPI/Util/Filesystem.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Util; 26 | 27 | use League\Flysystem\Filesystem as FS; 28 | use League\Flysystem\Adapter\Local; 29 | use Aws\S3\S3Client; 30 | use League\Flysystem\AwsS3v3\AwsS3Adapter as S3Adapter; 31 | 32 | use API\Controller; 33 | use API\Config; 34 | 35 | // Maybe move this to API/Service and remove ODM dependency on Services. Check out the semantics of this... 36 | class Filesystem 37 | { 38 | public static function generateAdapter($config) 39 | { 40 | $typeInUse = $config['in_use']; 41 | if ($typeInUse === 'local') { 42 | $root = Config::get('publicRoot'); 43 | $filesystem = new FS( 44 | new Local( 45 | $root.'/'.$config['local']['root_dir'], 46 | \LOCK_EX, 47 | Local::DISALLOW_LINKS, 48 | [ 49 | 'file' => [ 50 | 'public' => 0744, 51 | 'private' => 0700, 52 | ], 53 | 'dir' => [ 54 | 'public' => 0755, 55 | 'private' => 0700, 56 | ] 57 | ] 58 | ) 59 | ); 60 | } elseif ($typeInUse === 's3') { 61 | $client = S3Client::factory(array( 62 | 'key' => $config['s3']['key'], 63 | 'secret' => $config['s3']['secret'], 64 | )); 65 | $filesystem = new FS(new S3Adapter($client, $config['s3']['bucket_name'], $config['s3']['prefix'])); 66 | } else { 67 | throw new \Exception('Server error.', Controller::STATUS_INTERNAL_SERVER_ERROR); 68 | } 69 | 70 | return $filesystem; 71 | } 72 | 73 | public static function generateSHA2($rawData) 74 | { 75 | $hash = hash('sha256', $rawData); 76 | 77 | return $hash; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/xAPI/Util/OAuth.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Util; 26 | 27 | class OAuth 28 | { 29 | public static function generateToken() 30 | { 31 | // Generate a token 32 | $stripped = ''; 33 | $length = 40; 34 | do { 35 | $bytes = openssl_random_pseudo_bytes($length, $strong); 36 | if ($bytes === false || $strong === false) { 37 | throw new \Exception('Error generating access token. OpenSSL is probably missing!'); 38 | } 39 | $stripped .= str_replace(['/', '+', '='], '', base64_encode($bytes)); 40 | } while (strlen($stripped) < $length); 41 | 42 | $token = substr($stripped, 0, $length); 43 | 44 | return $token; 45 | } 46 | 47 | public static function loadSession() 48 | { 49 | if (session_status() == PHP_SESSION_NONE) { 50 | session_start(); 51 | } 52 | } 53 | 54 | public static function generateCsrfToken() 55 | { 56 | return sha1(serialize($_SERVER).rand(0, 0xffffffff)); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/xAPI/Validator/Exception.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Validator; 26 | 27 | class Exception extends \Exception 28 | { 29 | } 30 | -------------------------------------------------------------------------------- /src/xAPI/Validator/JsonSchema/Constraints/Factory.php: -------------------------------------------------------------------------------- 1 | . 19 | * 20 | * For authorship information, please view the AUTHORS 21 | * file that was distributed with this source code. 22 | */ 23 | 24 | 25 | namespace API\Validator\JsonSchema\Constraints; 26 | 27 | use JsonSchema; 28 | 29 | /** 30 | * Factory for centralize constraint initialization. 31 | */ 32 | class Factory extends JsonSchema\Constraints\Factory 33 | { 34 | /** 35 | * @var array $customConstraintMap 36 | */ 37 | protected $customConstraints = [ 38 | 'format' => '\API\Validator\JsonSchema\Constraints\FormatConstraint', 39 | ]; 40 | 41 | /** 42 | * 43 | * @param JsonSchema\SchemaStorageInterface $schemaStorage 44 | * @param JsonSchema\UriRetrieverInterface $uriRetriever 45 | * @param int $checkMode 46 | * 47 | * @see JsonSchema\Constraints\Factory 48 | */ 49 | public function __construct( 50 | JsonSchema\SchemaStorageInterface $schemaStorage = null, 51 | JsonSchema\UriRetrieverInterface $uriRetriever = null, 52 | $checkMode = JsonSchema\Constraints\Constraint::CHECK_MODE_NORMAL 53 | ) { 54 | // Merge custom constraints 55 | $this->constraintMap = array_merge($this->constraintMap, $this->customConstraints); 56 | parent::__construct($schemaStorage, $uriRetriever, $checkMode); 57 | } 58 | 59 | /** 60 | * Gets merge class map of constraints (for tests) 61 | * 62 | * @return array $constraintMap 63 | */ 64 | public function getConstraintMap() 65 | { 66 | return $this->constraintMap; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/xAPI/Validator/V10/Attachment.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\Validator\V10; 26 | 27 | use API\Validator; 28 | 29 | class Attachment extends Validator 30 | { 31 | } 32 | -------------------------------------------------------------------------------- /src/xAPI/View.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API; 26 | 27 | use API\Util\Collection; 28 | 29 | abstract class View extends Collection 30 | { 31 | use BaseTrait; 32 | 33 | protected $items; 34 | 35 | protected $response; 36 | 37 | /** 38 | * @constructor 39 | */ 40 | public function __construct($response, $container, $items = []) 41 | { 42 | parent::__construct($items); 43 | $this->setResponse($response); 44 | $this->setContainer($container); 45 | $this->items = $items; 46 | } 47 | 48 | /** 49 | * Gets items. 50 | * 51 | * @return mixed 52 | */ 53 | public function getItems() 54 | { 55 | return $this->items; 56 | } 57 | 58 | /** 59 | * Gets response. 60 | * 61 | * @return mixed 62 | */ 63 | public function getResponse() 64 | { 65 | return $this->response; 66 | } 67 | 68 | /** 69 | * Sets the items. 70 | * 71 | * @param mixed $items the items 72 | * 73 | * @return self 74 | */ 75 | protected function setItems($items) 76 | { 77 | $this->items = $items; 78 | 79 | return $this; 80 | } 81 | 82 | /** 83 | * Sets the response. 84 | * 85 | * @param mixed $response the response 86 | * 87 | * @return self 88 | */ 89 | protected function setResponse($response) 90 | { 91 | $this->response = $response; 92 | 93 | return $this; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/xAPI/View/V10/About.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\View\V10; 26 | 27 | use API\View; 28 | 29 | //use API\Document\Statement as StatementDocument; Re-do later 30 | 31 | class About extends View 32 | { 33 | public function render() 34 | { 35 | $object = [ 36 | 'version' => $this->versions, 37 | 'extensions' => $this->extensions 38 | ]; 39 | 40 | return $object; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/xAPI/View/V10/Activity.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\View\V10; 26 | 27 | use API\View; 28 | 29 | class Activity extends View 30 | { 31 | /** 32 | * Render the activity 33 | * @param stdClass $activityDocument Activity as an object 34 | * @return stdClass Modified activity 35 | */ 36 | public function renderGetSingle($activityDocument) 37 | { 38 | if (isset($activityDocument->_id)) { 39 | unset($activityDocument->_id); 40 | } 41 | 42 | return $activityDocument; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/xAPI/View/V10/ActivityProfile.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\View\V10; 26 | 27 | use API\View; 28 | 29 | class ActivityProfile extends BaseDocument 30 | { 31 | const IDENTIFIER = 'profileId'; 32 | } 33 | -------------------------------------------------------------------------------- /src/xAPI/View/V10/ActivityState.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\View\V10; 26 | 27 | use API\View; 28 | 29 | class ActivityState extends BaseDocument 30 | { 31 | const IDENTIFIER = 'stateId'; 32 | } 33 | -------------------------------------------------------------------------------- /src/xAPI/View/V10/Agent.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\View\V10; 26 | 27 | use API\View; 28 | use API\Util\Collection; 29 | 30 | class Agent extends View 31 | { 32 | public function renderGet($agent) 33 | { 34 | $agent = new Collection($agent); 35 | 36 | $object = ['objectType' => 'Person']; 37 | if ($agent->has('name')) { 38 | $object['name'] = [$agent->get('name')]; 39 | } 40 | 41 | if ($agent->has('mbox')) { 42 | $object['mbox'] = [$agent->get('mbox')]; 43 | } 44 | 45 | if ($agent->has('mbox_sha1sum')) { 46 | $object['mbox_sha1sum'] = [$agent->get('mbox_sha1sum')]; 47 | } 48 | 49 | if ($agent->has('openid')) { 50 | $object['openid'] = [$agent->get('openid')]; 51 | } 52 | 53 | if ($agent->has('account')) { 54 | $object['account'] = [$agent->get('account')]; 55 | } 56 | 57 | return $object; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/xAPI/View/V10/AgentProfile.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\View\V10; 26 | 27 | use API\View; 28 | 29 | class AgentProfile extends BaseDocument 30 | { 31 | const IDENTIFIER = 'profileId'; 32 | } 33 | -------------------------------------------------------------------------------- /src/xAPI/View/V10/BaseDocument.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\View\V10; 26 | 27 | use API\View; 28 | 29 | abstract class BaseDocument extends View 30 | { 31 | public function renderGet($documentResult) 32 | { 33 | $idArray = []; 34 | 35 | $cursor = $documentResult->getCursor(); 36 | 37 | foreach ($cursor as $document) { 38 | $idArray[] = $document->{static::IDENTIFIER}; 39 | } 40 | 41 | return $idArray; 42 | } 43 | 44 | public function renderGetSingle($documentResult) 45 | { 46 | $document = current($documentResult->getCursor()->toArray()); 47 | $document = new \API\Document\Generic($document); 48 | $content = $document->getContent(); 49 | 50 | // Write content 51 | $newResponse = $this->getResponse()->withHeader('ETag', '"'.$document->getHash().'"') 52 | ->withHeader('Content-Type', $document->getContentType()); 53 | // Write body 54 | $body = $newResponse->getBody(); 55 | $body->write($content); 56 | 57 | return $newResponse; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/xAPI/View/V10/BasicAuth/AccessToken.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\View\V10\BasicAuth; 26 | 27 | use API\View; 28 | use API\Util; 29 | 30 | class AccessToken extends View 31 | { 32 | public function render($accessTokenDocument) 33 | { 34 | $view = [ 35 | 'key' => $accessTokenDocument->getKey(), 36 | 'secret' => $accessTokenDocument->getSecret(), 37 | 'expiresAt' => (null === $accessTokenDocument->getExpiresAt()) ? null : Util\Date::mongoDateToTimestamp($accessTokenDocument->getExpiresAt()), 38 | 'expiresIn' => $accessTokenDocument->getExpiresIn(), 39 | 'createdAt' => (null === $accessTokenDocument->getCreatedAt()) ? null : Util\Date::mongoDateToTimestamp($accessTokenDocument->getCreatedAt()), 40 | 'expired' => $accessTokenDocument->isExpired(), 41 | //'scopes' => array_values($accessTokenDocument->scopes), 42 | //'user' => $accessTokenDocument->user->renderSummary(), 43 | ]; 44 | 45 | return $view; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/xAPI/View/V10/OAuth/AccessToken.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\View\V10\OAuth; 26 | 27 | use API\View; 28 | 29 | //use API\Document\Statement as StatementDocument; Re-do later 30 | 31 | class AccessToken extends View 32 | { 33 | public function render($accessTokenDocument) 34 | { 35 | $view = [ 36 | 'token' => $accessTokenDocument->getToken(), 37 | 'expiresAt' => (null === $accessTokenDocument->getExpiresAt()) ? null : $accessTokenDocument->getExpiresAt()->toDateTime()->getTimestamp(), 38 | 'expiresIn' => $accessTokenDocument->getExpiresIn(), 39 | 'createdAt' => (null === $accessTokenDocument->getCreatedAt()) ? null : $accessTokenDocument->getCreatedAt()->toDateTime()->getTimestamp(), 40 | 'expired' => $accessTokenDocument->isExpired(), 41 | //'scopes' => array_values($accessTokenDocument->scopes), 42 | //'user' => $accessTokenDocument->user->renderSummary(), 43 | //'client' => $accessTokenDocument->client->renderSummary(), 44 | ]; 45 | 46 | return $view; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/xAPI/View/V10/OAuth/Authorize.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\View\V10\OAuth; 26 | 27 | use API\View; 28 | use API\Config; 29 | 30 | class Authorize extends View 31 | { 32 | public function renderGet($user, $client, $scopes) 33 | { 34 | $view = $this->getContainer()->get('view'); 35 | $this->setItems(['csrfToken' => $_SESSION['csrfToken'], 36 | 'name' => Config::get(['name']), 37 | 'branding' => Config::get(['xAPI', 'oauth', 'branding']), 38 | 'user' => $user, 39 | 'client' => $client, 40 | 'scopes' => $scopes 41 | ]); 42 | $response = $this->getResponse()->withHeader('Content-Type', 'text/html'); 43 | $output = $view->render($response, 'authorize.twig', $this->getItems()); 44 | 45 | return $output; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/xAPI/View/V10/OAuth/Login.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | namespace API\View\V10\OAuth; 26 | 27 | use API\View; 28 | use API\Config; 29 | 30 | class Login extends View 31 | { 32 | public function renderGet($errors = []) 33 | { 34 | $view = $this->getContainer()->get('view'); 35 | $this->setItems(['csrfToken' => $_SESSION['csrfToken'], 'name' => Config::get(['name']), 'branding' => Config::get(['xAPI', 'oauth', 'branding']), 'errors' => $errors]); 36 | $response = $this->getResponse()->withHeader('Content-Type', 'text/html'); 37 | $output = $view->render($response, 'login.twig', $this->getItems()); 38 | 39 | 40 | return $output; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/xAPI/View/V10/OAuth/Templates/authorize.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Authorize 6 | Log in to {{ name }} 7 | {% include 'head.twig' %} 8 | 9 | 10 |
11 |
12 | 13 |
14 |

15 | {% if branding.enabled and branding.logo_path %} 16 | 17 | {% else %} 18 | 19 | {% endif %} 20 |

21 | 22 |
23 |
24 |

Authorize application {{ client.name }}?

25 |
Logged in as {{ user.email }}
26 | {% if branding.enabled and branding.header %} 27 | {{ branding.header }} 28 | {% endif %} 29 |
30 | This application will be able to:

31 |
    32 | {% for scope in scopes %} 33 |
  • {{ scope.description }}
  • 34 | {% else %} 35 |
  • Do nothing (you must specify a scope parameter!).
  • 36 | {% endfor %} 37 |
38 |
39 | 40 |
41 | 42 | 43 |
44 |
45 |
46 | 47 |
48 | 49 |
50 |
51 | {% include 'footer.twig' %} 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/xAPI/View/V10/OAuth/Templates/footer.twig: -------------------------------------------------------------------------------- 1 |
2 |
3 | 10 |
11 |
12 | -------------------------------------------------------------------------------- /src/xAPI/View/V10/OAuth/Templates/head.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | {% if branding.enabled and branding.css_path %} 20 | 21 | {% endif %} -------------------------------------------------------------------------------- /src/xAPI/View/V10/OAuth/Templates/login.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Log in to {{ name }} 7 | {% include 'head.twig' %} 8 | 9 | 10 |
11 |
12 | 13 |
14 |

15 | {% if branding.enabled and branding.logo_path %} 16 | 17 | {% else %} 18 | 19 | {% endif %} 20 |

21 | 22 | {% if errors %} 23 |
    24 | {% for error in errors %} 25 |
  • {{ error }}
  • 26 | {% endfor %} 27 |
28 | {% endif %} 29 | 30 |
31 |
32 |

Log in to {{ name }}

33 | {% if branding.enabled and branding.header %} 34 | {{ branding.header }} 35 | {% endif %} 36 | 37 |
38 | 39 |
40 |
41 | 42 |
43 |
44 |
45 |
Remember me 46 |
47 |
48 |
49 | 50 |
51 |
52 |
53 | 54 |
55 | 56 |
57 |
58 | {% include 'footer.twig' %} 59 | 60 | 61 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | Config.php 2 | -------------------------------------------------------------------------------- /tests/Config.template.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | namespace Tests; 25 | 26 | /** 27 | * Configuration for unit tests 28 | */ 29 | class Config 30 | { 31 | /** 32 | * @var array $lrs remote lrs configuration 33 | */ 34 | public static $lrs = [ 35 | 'production' => [ 36 | 'baseuri' => '', // base endpoint without trailing slash, e.g. http://sandbox.experience.at 37 | 'version' => '1.0.2', // targeted xAPI version 38 | 'user' => '', // basicAuth: username 39 | 'password' => '', // basicAuth: password 40 | ], 41 | 'development' => [ 42 | 'baseuri' => '', // base endpoint without trailing slash, e.g. http://sandbox.experience.at 43 | 'version' => '1.0.2', // targeted xAPI version 44 | 'user' => '', // basicAuth: username 45 | 'password' => '', // basicAuth: password 46 | ] 47 | ]; 48 | } 49 | -------------------------------------------------------------------------------- /tests/Integration/Parser/PsrRequestParserTest.php: -------------------------------------------------------------------------------- 1 | mockJsonRequest('/statements', 'POST', self::MOCK_STATEMENT); 22 | $parser = new PsrRequest($mockRequest); 23 | 24 | $parserResult = $parser->getData(); 25 | $this->assertInstanceOf('\API\Parser\ParserResult', $parserResult); 26 | 27 | $payload = $parserResult->getPayload(); 28 | $this->assertInstanceOf('stdClass', $payload); 29 | } 30 | 31 | private function mockRequest($uri, $method) 32 | { 33 | $env = Environment::mock([ 34 | 'REQUEST_METHOD' => 'POST', 35 | 'CONTENT_TYPE' => 'application/json;charset=utf8' 36 | ]); 37 | $uri = Uri::createFromString($uri); 38 | $headers = Headers::createFromEnvironment($env); 39 | $cookies = []; 40 | $serverParams = $env->all(); 41 | $body = new RequestBody(); 42 | $uploadedFiles = UploadedFile::createFromEnvironment($env); 43 | $request = new Request($method, $uri, $headers, $cookies, $serverParams, $body, $uploadedFiles); 44 | $request->registerMediaTypeParser('application/json', function ($input) { 45 | return json_decode($input); 46 | }); 47 | 48 | return $request; 49 | } 50 | 51 | private function mockJsonRequest($uri, $method, $body) 52 | { 53 | $request = $this->mockRequest($uri, $method); 54 | // Write the string into the body 55 | $stream = fopen('php://memory', 'r+'); 56 | fwrite($stream, $body); 57 | rewind($stream); 58 | $body = new \Slim\Http\Stream($stream); 59 | $request = $request->withBody($body)->reparseBody(); 60 | return $request; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/Integration/StreamContext/Stream.php: -------------------------------------------------------------------------------- 1 | baseuri = $baseuri; 12 | $this->version = $version; 13 | $this->user = $user; 14 | $this->password = $password; 15 | } 16 | 17 | public function postJson($endpoint, $json, $opts = []) 18 | { 19 | $url = $this->baseuri.$endpoint; 20 | 21 | $options = array_merge([ 22 | 'ignore_errors' => false, 23 | 'method' => 'POST', 24 | 'content' => $json, 25 | 'header' => array( 26 | 'Content-Type: application/json', 27 | 'X-Experience-Api-Version: '.$this->version, 28 | 'Authorization: Basic '.base64_encode($this->user.':'.$this->password), 29 | ) 30 | ], $opts); 31 | 32 | $context = stream_context_create([ 33 | 'http' => $options 34 | ]); 35 | 36 | $fp = fopen($url, 'rb', false, $context); 37 | if (! $fp) { 38 | throw new \Exception('fopen(POST) failed with: '.print_r(error_get_last(), true)); 39 | } 40 | 41 | $meta = stream_get_meta_data($fp); 42 | $content = stream_get_contents($fp); 43 | fclose($fp); 44 | 45 | return [ 46 | 'options' => $options, 47 | 'meta' => $meta, 48 | 'content' => $content 49 | ]; 50 | } 51 | 52 | public function getJson($endpoint, $opts = []) 53 | { 54 | $url = $this->baseuri.$endpoint; 55 | 56 | $options = array_merge([ 57 | 'ignore_errors' => false, 58 | 'method' => 'GET', 59 | 'header' => array( 60 | 'Content-Type: application/json', 61 | 'X-Experience-Api-Version: '.$this->version, 62 | 'Authorization: Basic '.base64_encode($this->user.':'.$this->password), 63 | ) 64 | ], $opts); 65 | 66 | $context = stream_context_create([ 67 | 'http' => $options 68 | ]); 69 | 70 | $fp = fopen($url, 'rb', false, $context); 71 | if (! $fp) { 72 | throw new \Exception('fopen(GET) failed with: '.print_r(error_get_last(), true)); 73 | } 74 | 75 | $meta = stream_get_meta_data($fp); 76 | $content = stream_get_contents($fp); 77 | fclose($fp); 78 | 79 | return [ 80 | 'options' => $options, 81 | 'meta' => $meta, 82 | 'content' => $content 83 | ]; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /tests/Integration/StreamContext/StreamContextTest.php: -------------------------------------------------------------------------------- 1 | markTestIncomplete( 17 | 'class \Tests\Config does not exist or is invalid.' 18 | ); 19 | return; 20 | } 21 | 22 | $this->lrs = \Tests\Config::$lrs['production']; 23 | 24 | $this->stream = new \Stream( 25 | $this->lrs['baseuri'], 26 | $this->lrs['version'], 27 | $this->lrs['user'], 28 | $this->lrs['password'] 29 | ); 30 | } 31 | 32 | public function testPostWithFullUri() 33 | { 34 | $json = '{ 35 | "actor":{ 36 | "mbox":"mailto:lxunit@lxhive.com" 37 | }, 38 | "verb":{ 39 | "id":"http://adlnet.gov/expapi/verbs/attempted" 40 | }, 41 | "object":{ 42 | "id":"http://lxhive.com/activities/lxunit/streamcontexttest" 43 | } 44 | }'; 45 | 46 | $res = $this->stream->postJson('/statements', $json, [ 47 | 'request_fulluri' => 1 48 | ]); 49 | $data = json_decode($res['content']); 50 | $statementId = $data[0]; 51 | 52 | $this->assertEquals(json_last_error(), \JSON_ERROR_NONE); 53 | $this->assertEquals(substr_count($statementId, '-'), 4); 54 | $this->assertEquals($res['options']['request_fulluri'], 1); 55 | $this->assertFalse($res['meta']['timed_out']); 56 | $this->assertContains('200 OK', $res['meta']['wrapper_data'][0]); 57 | } 58 | 59 | /** 60 | * @depends testPostWithFullUri 61 | */ 62 | public function testGetWithFullUri() 63 | { 64 | $res = $this->stream->getJson('/statements?limit=2', [ 65 | 'request_fulluri' => 1 66 | ]); 67 | $data = json_decode($res['content']); 68 | 69 | $this->assertEquals(json_last_error(), \JSON_ERROR_NONE); 70 | $this->assertEquals($res['options']['request_fulluri'], 1); 71 | $this->assertFalse($res['meta']['timed_out']); 72 | $this->assertContains('200 OK', $res['meta']['wrapper_data'][0]); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | * For authorship information, please view the AUTHORS 22 | * file that was distributed with this source code. 23 | */ 24 | namespace Tests; 25 | 26 | use PHPUnit_Framework_TestCase as BaseTestCase; 27 | 28 | use API\Bootstrap; 29 | 30 | abstract class TestCase extends BaseTestCase 31 | { 32 | /** 33 | * Called before the first test of the test case class is run 34 | * Loads db config 35 | */ 36 | public static function setUpBeforeClass() 37 | { 38 | Bootstrap::reset(); 39 | Bootstrap::factory(Bootstrap::Testing); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/Unit/ExampleTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 16 | } 17 | 18 | public static function check() 19 | { 20 | echo 'test'; 21 | die(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Unit/PHPUnit/BootstrapUnitTestDatabaseTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(defined('LXHIVE_UNITTEST')); 12 | } 13 | 14 | public function testConfigHasUniTestDatabase() 15 | { 16 | $db_name = Config::get(['storage', 'Mongo', 'db_name']); 17 | $this->assertEquals($db_name, 'LXHIVE_UNITTEST'); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/Unit/PHPUnit/CoherentContainersTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('string', $container->get('testService')); 15 | } 16 | 17 | public function testApiContainerInteropInterfaceHandle() 18 | { 19 | $container = new ApiContainer(); 20 | $container['testService'] = 'string'; 21 | $this->assertEquals('string', $container->get('testService')); 22 | } 23 | 24 | /** 25 | * @depends testSlimContainerInteropInterfaceHandle 26 | */ 27 | public function testSlimContainerObjectHandle() 28 | { 29 | $container = new SlimContainer(); 30 | $container['testService'] = 'string'; 31 | $this->assertEquals('string', $container->testService); 32 | } 33 | 34 | /** 35 | * @depends testSlimContainerInteropInterfaceHandle 36 | * @expectedException PHPUnit_Framework_Error 37 | */ 38 | public function testApiContainerObjectHandle() 39 | { 40 | $container = new ApiContainer(); 41 | $container['testService'] = 'string'; 42 | $this->assertEquals('string', $container->testService); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/Unit/PHPUnit/MongoTestCaseTest.php: -------------------------------------------------------------------------------- 1 | dropCollection($this->collection); 17 | 18 | $bulk = new BulkWrite(); 19 | 20 | $bulk->insert(['_id' => 1, 'x' => 1]); 21 | $bulk->insert(['_id' => 2, 'x' => 2]); 22 | $bulk->update(['x' => 2], ['$set' => ['x' => 1]]); 23 | $bulk->insert(['_id' => 3, 'x' => 3]); 24 | $bulk->delete(['x' => 1]); 25 | 26 | $result = $this->bulkWrite($this->collection, $bulk); 27 | 28 | $this->assertInstanceOf(WriteResult::class, $result); 29 | $this->assertTrue($result->isAcknowledged()); 30 | $this->assertNull($result->getWriteConcernError()); 31 | $this->assertEmpty($result->getWriteErrors()); 32 | 33 | $this->assertEquals($result->getDeletedCount(), 2); // incl update! 34 | $this->assertEquals($result->getInsertedCount(), 3); 35 | $this->assertEquals($result->getModifiedCount (), 1); 36 | } 37 | 38 | public function testBulkWriteError() 39 | { 40 | $this->dropCollection($this->collection); 41 | 42 | $bulk = new BulkWrite(); 43 | 44 | $bulk->insert(['_id' => 1, 'x' => 1]); 45 | $bulk->insert(['_id' => 1, 'x' => 2]); 46 | 47 | $this->expectException(\Exception::class); 48 | $result = $this->bulkWrite($this->collection, $bulk); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/autoload.php: -------------------------------------------------------------------------------- 1 | . 19 | * 20 | * For authorship information, please view the AUTHORS 21 | * file that was distributed with this source code. 22 | */ 23 | 24 | error_reporting(E_ALL); 25 | 26 | $loader = require __DIR__.'/../vendor/autoload.php'; 27 | $loader->setPsr4('Tests\\', __DIR__); 28 | $loader->addPsr4('Tests\\API\\', __DIR__.'/src/xAPI'); 29 | -------------------------------------------------------------------------------- /tests/readme.md: -------------------------------------------------------------------------------- 1 | # Unit tests 2 | 3 | lxHive supports testing with [PHPUnit](https://phpunit.de/). 4 | PHPunit is included as a `require-dev` dependency. 5 | A `phpunit.xml` file is setup in the project root folder of your lxHive app. 6 | 7 | ## Structure 8 | 9 | The `tests` directory contains two sub-directories directories: 10 | 11 | | namespace | folder | notes | 12 | |--- |--- |--- | 13 | | `\Tests\Unit` | `./Unit/*` | Assorted Unit tests, tests for issues, feature tests etc | 14 | | `\Tests\API` | `./src/xAPI/*` | Mirrors the app code's `\API` namespace and folder structure | 15 | | `\Tests\Integration` | `./Integration/*` | Integration tests, most notably for testing (mock) requests and request validation | 16 | 17 | Test files have to follow the [*Test.php](https://phpunit.de/manual/current/en/organizing-tests.html#organizing-tests.filesystem) suffix pattern, i.e. `FoobarTest.php` 18 | 19 | An ExampleTest.php file is provided in the `Unit` test directory. 20 | 21 | ## Installation 22 | 23 | Some tests may require a http-like connection x to your LRS, including basic authentication and xAPI headers. 24 | 25 | * Copy `Config.template.php` to `Config.php` 26 | * Update `Config::$lrs` with your lrs data including valid authentication 27 | 28 | ## Usage 29 | 30 | ```bash 31 | ./vendor/bin/phpunit 32 | ``` 33 | 34 | For more information and available command line options see the [PHPUnit](https://phpunit.de/manual/current/en/textui.html) documentation 35 | -------------------------------------------------------------------------------- /tests/src/xAPI/BaseTraitTest.php: -------------------------------------------------------------------------------- 1 | setContainer($container); 19 | 20 | $services = $this->getContainer(); 21 | $this->assertEquals(pi(), $container->get('pi')); 22 | } 23 | 24 | public function testInvalidContainerType() 25 | { 26 | $threw = false; 27 | try { 28 | $services = $this->setContainer(array()); 29 | } catch (\Throwable $t) { 30 | $threw = 'PHP 7.x'; 31 | } catch (\Exception $e) { 32 | $threw = 'PHP 5.x'; 33 | } 34 | $this->assertNotFalse($threw); 35 | } 36 | 37 | public function testNoContainer() 38 | { 39 | $this->expectException(\Exception::class); 40 | $services = $this->getContainer(); 41 | } 42 | 43 | public function testNoStorageContainer() 44 | { 45 | $container = new Container(); 46 | $this->setContainer($container); 47 | 48 | $this->expectException(ContainerException::class); 49 | $services = $this->getStorage(); 50 | } 51 | 52 | public function testNoLogContainer() 53 | { 54 | $container = new Container(); 55 | 56 | $this->setContainer($container); 57 | $this->expectException(ContainerException::class); 58 | $services = $this->getStorage(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/src/xAPI/Extensions/ExtensionExceptionTest.php: -------------------------------------------------------------------------------- 1 | getData(); 17 | $this->assertEquals($gets, $data, 'inherits public method HttpException::getData()'); 18 | 19 | $this->expectException(HttpException::class); 20 | throw $ex; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /tests/src/xAPI/Service/mock_config_supported_auth_scopes.json: -------------------------------------------------------------------------------- 1 | { 2 | "statements\/write": { 3 | "module": "xAPI", 4 | "description": "Write statements", 5 | "inherits": [ 6 | "attachments" 7 | ] 8 | }, 9 | "statements\/read": { 10 | "module": "xAPI", 11 | "description": "Read statements", 12 | "inherits": [ 13 | "attachments" 14 | ] 15 | }, 16 | "statements\/read\/mine": { 17 | "module": "xAPI", 18 | "description": "Read own statements", 19 | "inherits": [ 20 | "attachments" 21 | ] 22 | }, 23 | "state": { 24 | "module": "xAPI", 25 | "description": "Access the State API", 26 | "inherits": [] 27 | }, 28 | "profile": { 29 | "module": "xAPI", 30 | "description": "Access the Profile API", 31 | "inherits": [] 32 | }, 33 | "define": { 34 | "module": "xAPI", 35 | "description": "(Re)define new Activities", 36 | "inherits": [] 37 | }, 38 | "all\/read": { 39 | "module": "xAPI", 40 | "description": "Full read-only access", 41 | "inherits": [ 42 | "statements\/read\/mine", 43 | "attachments", 44 | "state", 45 | "profile", 46 | "attachments" 47 | ] 48 | }, 49 | "all": { 50 | "module": "xAPI", 51 | "description": "Full access", 52 | "inherits": [ 53 | "statements\/write", 54 | "statements\/read\/mine", 55 | "statements\/read", 56 | "state", 57 | "define", 58 | "profile", 59 | "all\/read", 60 | "attachments" 61 | ] 62 | }, 63 | "super": { 64 | "module": "System", 65 | "description": "Administrate the LRS", 66 | "inherits": [ 67 | "statements\/write", 68 | "statements\/read\/mine", 69 | "statements\/read", 70 | "state", 71 | "define", 72 | "profile", 73 | "all\/read", 74 | "all", 75 | "attachments" 76 | ] 77 | }, 78 | "attachments": { 79 | "module": "System", 80 | "description": "Include attachments", 81 | "inherits": [] 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /tests/src/xAPI/Storage/Adapter/Mongo/ActivityProfileTest.php: -------------------------------------------------------------------------------- 1 | collection = ActivityProfile::COLLECTION_NAME; 16 | } 17 | 18 | public function testGetIndexes() 19 | { 20 | $coll = new ActivityProfile(Bootstrap::getContainer()); 21 | $indexes = $coll->getIndexes(); 22 | 23 | $this->assertTrue(is_array($indexes)); 24 | } 25 | 26 | /** 27 | * @depends testGetIndexes 28 | */ 29 | public function testInstall() 30 | { 31 | $this->dropCollection($this->collection); 32 | 33 | $coll = new ActivityProfile(Bootstrap::getContainer()); 34 | $coll->install(); 35 | // has passed without exception 36 | 37 | $indexes = $this->command([ 38 | 'listIndexes' => $this->collection 39 | ])->toArray(); 40 | $configured = array_keys($coll->getIndexes()); 41 | $installed = array_map(function($i) { 42 | return $i->name; 43 | }, $indexes); 44 | 45 | foreach ($configured as $name) { 46 | $this->assertContains($name, $installed); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/src/xAPI/Storage/Adapter/Mongo/ActivityStateTest.php: -------------------------------------------------------------------------------- 1 | collection = ActivityState::COLLECTION_NAME; 16 | } 17 | 18 | public function testGetIndexes() 19 | { 20 | $coll = new ActivityState(Bootstrap::getContainer()); 21 | $indexes = $coll->getIndexes(); 22 | 23 | $this->assertTrue(is_array($indexes)); 24 | } 25 | 26 | /** 27 | * @depends testGetIndexes 28 | */ 29 | public function testInstall() 30 | { 31 | $this->dropCollection($this->collection); 32 | 33 | $coll = new ActivityState(Bootstrap::getContainer()); 34 | $coll->install(); 35 | // has passed without exception 36 | 37 | $indexes = $this->command([ 38 | 'listIndexes' => $this->collection 39 | ])->toArray(); 40 | $configured = array_keys($coll->getIndexes()); 41 | $installed = array_map(function($i) { 42 | return $i->name; 43 | }, $indexes); 44 | 45 | foreach ($configured as $name) { 46 | $this->assertContains($name, $installed); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/src/xAPI/Storage/Adapter/Mongo/ActivityTest.php: -------------------------------------------------------------------------------- 1 | collection = Activity::COLLECTION_NAME; 16 | } 17 | 18 | public function testGetIndexes() 19 | { 20 | $coll = new Activity(Bootstrap::getContainer()); 21 | $indexes = $coll->getIndexes(); 22 | 23 | $this->assertTrue(is_array($indexes)); 24 | } 25 | 26 | /** 27 | * @depends testGetIndexes 28 | */ 29 | public function testInstall() 30 | { 31 | $this->dropCollection($this->collection); 32 | 33 | $coll = new Activity(Bootstrap::getContainer()); 34 | $coll->install(); 35 | // has passed without exception 36 | 37 | $indexes = $this->command([ 38 | 'listIndexes' => $this->collection 39 | ])->toArray(); 40 | $configured = array_keys($coll->getIndexes()); 41 | $installed = array_map(function($i) { 42 | return $i->name; 43 | }, $indexes); 44 | 45 | foreach ($configured as $name) { 46 | $this->assertContains($name, $installed); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/src/xAPI/Storage/Adapter/Mongo/AgentProfileTest.php: -------------------------------------------------------------------------------- 1 | collection = AgentProfile::COLLECTION_NAME; 16 | } 17 | 18 | public function testGetIndexes() 19 | { 20 | $coll = new AgentProfile(Bootstrap::getContainer()); 21 | $indexes = $coll->getIndexes(); 22 | 23 | $this->assertTrue(is_array($indexes)); 24 | } 25 | 26 | /** 27 | * @depends testGetIndexes 28 | */ 29 | public function testInstall() 30 | { 31 | $this->dropCollection($this->collection); 32 | 33 | $coll = new AgentProfile(Bootstrap::getContainer()); 34 | $coll->install(); 35 | // has passed without exception 36 | 37 | $indexes = $this->command([ 38 | 'listIndexes' => $this->collection 39 | ])->toArray(); 40 | $configured = array_keys($coll->getIndexes()); 41 | $installed = array_map(function($i) { 42 | return $i->name; 43 | }, $indexes); 44 | 45 | foreach ($configured as $name) { 46 | $this->assertContains($name, $installed); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/src/xAPI/Storage/Adapter/Mongo/AttachmentTest.php: -------------------------------------------------------------------------------- 1 | collection = Attachment::COLLECTION_NAME; 16 | } 17 | 18 | public function testGetIndexes() 19 | { 20 | $coll = new Attachment(Bootstrap::getContainer()); 21 | $indexes = $coll->getIndexes(); 22 | 23 | $this->assertTrue(is_array($indexes)); 24 | } 25 | 26 | /** 27 | * @depends testGetIndexes 28 | */ 29 | public function testInstall() 30 | { 31 | $this->dropCollection($this->collection); 32 | 33 | $coll = new Attachment(Bootstrap::getContainer()); 34 | $coll->install(); 35 | // has passed without exception 36 | 37 | $indexes = $this->command([ 38 | 'listIndexes' => $this->collection 39 | ])->toArray(); 40 | $configured = array_keys($coll->getIndexes()); 41 | $installed = array_map(function($i) { 42 | return $i->name; 43 | }, $indexes); 44 | 45 | foreach ($configured as $name) { 46 | $this->assertContains($name, $installed); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/src/xAPI/Storage/Adapter/Mongo/BasicAuthTest.php: -------------------------------------------------------------------------------- 1 | collection = BasicAuth::COLLECTION_NAME; 16 | } 17 | 18 | public function testGetIndexes() 19 | { 20 | $coll = new BasicAuth(Bootstrap::getContainer()); 21 | $indexes = $coll->getIndexes(); 22 | 23 | $this->assertTrue(is_array($indexes)); 24 | } 25 | 26 | /** 27 | * @depends testGetIndexes 28 | */ 29 | public function testInstall() 30 | { 31 | $this->dropCollection($this->collection); 32 | 33 | $coll = new BasicAuth(Bootstrap::getContainer()); 34 | $coll->install(); 35 | // has passed without exception 36 | 37 | $indexes = $this->command([ 38 | 'listIndexes' => $this->collection 39 | ])->toArray(); 40 | $configured = array_keys($coll->getIndexes()); 41 | $installed = array_map(function($i) { 42 | return $i->name; 43 | }, $indexes); 44 | 45 | foreach ($configured as $name) { 46 | $this->assertContains($name, $installed); 47 | } 48 | } 49 | 50 | /** 51 | * @depends testInstall 52 | * 53 | * Note: we are NOT testing any user relations 54 | */ 55 | public function testStoreToken() 56 | { 57 | $now = time(); 58 | $mock = (object)[ 59 | 'name' => 'testStoreToken', 60 | 'description' => 'testdescription', 61 | 'expiresAt' => $now + 3600, 62 | 'user' => (object) [ 63 | '_id' => new \MongoDB\BSON\ObjectID() 64 | ], 65 | 'client' => (object) [ 66 | '_id' => new \MongoDB\BSON\ObjectID() 67 | ], 68 | 'permissions' => ['statement/write', 'statments/read/mine'], 69 | ]; 70 | 71 | $service = new BasicAuth(Bootstrap::getContainer()); 72 | $service->storeToken($mock->name, $mock->description, $mock->expiresAt, $mock->user, $mock->permissions); 73 | 74 | // fetch record independently to rule out any side effects 75 | $q = $this->query(BasicAuth::COLLECTION_NAME, ['name' => $mock->name]); 76 | $t = $q->toArray()[0]; 77 | 78 | $this->assertEquals($t->name, $mock->name); 79 | $this->assertEquals($t->description, $mock->description); 80 | $this->assertEquals($t->expiresAt->toDateTime()->getTimestamp(), $mock->expiresAt); 81 | $this->assertEquals((string) $t->userId, (string) $mock->user->_id); 82 | $this->assertEquals($t->permissions, $mock->permissions); 83 | 84 | $this->assertTrue(isset($t->key), 'a default key was created'); 85 | $this->assertTrue(isset($t->secret), 'a default secret was created'); 86 | $this->assertGreaterThan(3, strlen($t->key)); 87 | $this->assertGreaterThan(3, strlen($t->secret)); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /tests/src/xAPI/Storage/Adapter/Mongo/LogTest.php: -------------------------------------------------------------------------------- 1 | collection = Log::COLLECTION_NAME; 16 | } 17 | 18 | public function testGetIndexes() 19 | { 20 | $coll = new Log(Bootstrap::getContainer()); 21 | $indexes = $coll->getIndexes(); 22 | 23 | $this->assertTrue(is_array($indexes)); 24 | } 25 | 26 | /** 27 | * @depends testGetIndexes 28 | */ 29 | public function testInstall() 30 | { 31 | $this->dropCollection($this->collection); 32 | 33 | $coll = new Log(Bootstrap::getContainer()); 34 | $coll->install(); 35 | // has passed without exception 36 | 37 | $indexes = $this->command([ 38 | 'listIndexes' => $this->collection 39 | ])->toArray(); 40 | $configured = array_keys($coll->getIndexes()); 41 | $installed = array_map(function($i) { 42 | return $i->name; 43 | }, $indexes); 44 | 45 | foreach ($configured as $name) { 46 | $this->assertContains($name, $installed); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/src/xAPI/Storage/Adapter/Mongo/OAuthClientsTest.php: -------------------------------------------------------------------------------- 1 | collection = OAuthClients::COLLECTION_NAME; 16 | } 17 | 18 | public function testGetIndexes() 19 | { 20 | $coll = new OAuthClients(Bootstrap::getContainer()); 21 | $indexes = $coll->getIndexes(); 22 | 23 | $this->assertTrue(is_array($indexes)); 24 | } 25 | 26 | /** 27 | * @depends testGetIndexes 28 | */ 29 | public function testInstall() 30 | { 31 | $this->dropCollection($this->collection); 32 | 33 | $coll = new OAuthClients(Bootstrap::getContainer()); 34 | $coll->install(); 35 | // has passed without exception 36 | 37 | $indexes = $this->command([ 38 | 'listIndexes' => $this->collection 39 | ])->toArray(); 40 | $configured = array_keys($coll->getIndexes()); 41 | $installed = array_map(function($i) { 42 | return $i->name; 43 | }, $indexes); 44 | 45 | foreach ($configured as $name) { 46 | $this->assertContains($name, $installed); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/src/xAPI/Storage/Adapter/Mongo/OAuthTest.php: -------------------------------------------------------------------------------- 1 | collection = OAuth::COLLECTION_NAME; 16 | } 17 | 18 | public function testGetIndexes() 19 | { 20 | $coll = new OAuth(Bootstrap::getContainer()); 21 | $indexes = $coll->getIndexes(); 22 | 23 | $this->assertTrue(is_array($indexes)); 24 | } 25 | 26 | /** 27 | * @depends testGetIndexes 28 | */ 29 | public function testInstall() 30 | { 31 | $this->dropCollection($this->collection); 32 | 33 | $coll = new OAuth(Bootstrap::getContainer()); 34 | $coll->install(); 35 | // has passed without exception 36 | 37 | $indexes = $this->command([ 38 | 'listIndexes' => $this->collection 39 | ])->toArray(); 40 | $configured = array_keys($coll->getIndexes()); 41 | $installed = array_map(function($i) { 42 | return $i->name; 43 | }, $indexes); 44 | 45 | foreach ($configured as $name) { 46 | $this->assertContains($name, $installed); 47 | } 48 | } 49 | 50 | /** 51 | * @depends testInstall 52 | * 53 | * Note: we are NOT testing any user relations 54 | */ 55 | public function testStoreToken() 56 | { 57 | //storeToken($expiresAt, $user, $client, array $permissions = [], $code = null) 58 | $now = time(); 59 | $mock = (object)[ 60 | 'expiresAt' => $now + 3600, 61 | 'user' => (object) [ 62 | '_id' => new \MongoDB\BSON\ObjectID() 63 | ], 64 | 'client' => (object) [ 65 | '_id' => new \MongoDB\BSON\ObjectID() 66 | ], 67 | 'permissions' => ['statement/write', 'statments/read/mine'], 68 | ]; 69 | 70 | $service = new OAuth(Bootstrap::getContainer()); 71 | $res = $service->storeToken($mock->expiresAt, $mock->user, $mock->client, $mock->permissions); 72 | 73 | // fetch LAST record independently to rule out any side effects 74 | $q = $this->query(OAuth::COLLECTION_NAME, [], [ 75 | 'sort' => [ 76 | '_id' => 1 77 | ] 78 | ]); 79 | $t = $q->toArray()[0]; 80 | 81 | $this->assertEquals($t->expiresAt->toDateTime()->getTimestamp(), $mock->expiresAt); 82 | $this->assertEquals((string) $t->userId, (string) $mock->user->_id); 83 | $this->assertEquals($t->permissions, $mock->permissions); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /tests/src/xAPI/Storage/Adapter/Mongo/SchemaTest.php: -------------------------------------------------------------------------------- 1 | mapCollections(); 16 | $this->assertGreaterThan(1, count($collections)); 17 | foreach($collections as $key => $value) { 18 | $this->assertTrue(is_string($key)); 19 | $this->assertTrue(is_string($value)); 20 | $this->assertTrue(!empty($key)); 21 | $this->assertTrue(!empty($value)); 22 | } 23 | } 24 | 25 | /** 26 | * @depends testMapCollections 27 | */ 28 | public function testGetIndexes() 29 | { 30 | $schema = new Schema(Bootstrap::getContainer()); 31 | 32 | $collections = $schema->mapCollections(); 33 | $schemas = $schema->getIndexes(); 34 | 35 | $collectionKeys = implode(',', array_keys($collections)); 36 | $schemaKeys = implode(',', array_keys($schemas)); 37 | 38 | $this->assertGreaterThan(1, count($schemas)); 39 | $this->assertEquals($collectionKeys, $schemaKeys); 40 | 41 | } 42 | 43 | /** 44 | * @depends testGetIndexes 45 | */ 46 | public function testInstallIndexes() 47 | { 48 | // drop database 49 | $this->dropDatabase(); 50 | 51 | $schema = new Schema(Bootstrap::getContainer()); 52 | $schema->install(); 53 | // passed if no exception are thrown by MongoDriver 54 | 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/src/xAPI/Storage/Adapter/Mongo/StatementTest.php: -------------------------------------------------------------------------------- 1 | collection = Statement::COLLECTION_NAME; 16 | } 17 | 18 | public function testGetIndexes() 19 | { 20 | $coll = new Statement(Bootstrap::getContainer()); 21 | $indexes = $coll->getIndexes(); 22 | 23 | $this->assertTrue(is_array($indexes)); 24 | } 25 | 26 | /** 27 | * @depends testGetIndexes 28 | */ 29 | public function testInstall() 30 | { 31 | $this->dropCollection($this->collection); 32 | 33 | $coll = new Statement(Bootstrap::getContainer()); 34 | $coll->install(); 35 | // has passed without exception 36 | 37 | $indexes = $this->command([ 38 | 'listIndexes' => $this->collection 39 | ])->toArray(); 40 | $configured = array_keys($coll->getIndexes()); 41 | $installed = array_map(function($i) { 42 | return $i->name; 43 | }, $indexes); 44 | 45 | foreach ($configured as $name) { 46 | $this->assertContains($name, $installed); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/src/xAPI/Storage/Adapter/MongoTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(is_object($result)); 17 | $this->assertTrue(!empty($result->version)); 18 | } 19 | 20 | public function testSupportsCommand() 21 | { 22 | $mongo = new Mongo(Bootstrap::getContainer()); 23 | 24 | $result = $mongo->supportsCommand('buildInfo'); 25 | $this->assertTrue($result); 26 | $result = $mongo->supportsCommand('notAMongoCommand'); 27 | $this->assertFalse($result); 28 | } 29 | 30 | public function testGetDatabaseVersion() 31 | { 32 | $mongo = new Mongo(Bootstrap::getContainer()); 33 | 34 | $result = $mongo->getDatabaseVersion(); 35 | $this->assertNotEmpty($result); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/src/xAPI/Validator/Validator.php: -------------------------------------------------------------------------------- 1 | createSchemaValidator($factory); 20 | 21 | $this->assertInstanceOf(JsonSchema\Validator::class, $sv); 22 | $this->assertInstanceOf(JsonSchema\Validator::class, $sv); 23 | $this->assertTrue(method_exists($sv, 'isValid1')); 24 | } 25 | } 26 | --------------------------------------------------------------------------------