├── .gitignore ├── Dockerfile ├── README.md ├── docker-compose.yml ├── src ├── composer.json ├── composer.phar ├── db │ ├── cert_suite_private.key │ ├── configs │ │ └── example.json │ ├── example_database.php │ ├── platform.key │ └── private.key └── web │ ├── api │ ├── score.php │ └── scoreboard.php │ ├── configure.php │ ├── game.php │ ├── jwks.php │ ├── login.php │ ├── oidc_demo │ ├── iframe.html │ ├── jwks.php │ ├── launcher.php │ ├── lti_launch.php │ ├── oidc_login_302.php │ ├── oidc_login_hybrid.php │ └── oidc_login_js.php │ ├── platform │ ├── index.php │ ├── jwks.php │ ├── login.php │ ├── services │ │ ├── ags.php │ │ ├── ags │ │ │ └── lineitems.php │ │ └── nrps │ │ │ └── index.php │ └── token.php │ └── static │ ├── breakout.css │ └── breakout.js └── vhost.conf /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | composer.lock 3 | src/db/keys/* 4 | src/db/configs/local.json 5 | src/web/platform/services/ags/*.txt 6 | .env 7 | .DS_Store 8 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.3.5-apache 2 | 3 | # COPY ./src /srv/app 4 | RUN mkdir /srv/app 5 | COPY ./vhost.conf /etc/apache2/sites-available/000-default.conf 6 | 7 | RUN chown -R www-data:www-data /srv/app \ 8 | && a2enmod rewrite 9 | 10 | COPY ./src/composer.phar /bin/composer.phar 11 | 12 | RUN chmod a+x /bin/composer.phar && apt-get update -y && apt-get install git unzip -y 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LTI 1.3 Advantage Demo Tool 2 | This code consists an example tool that utilize the library LTI 1.3 PHP library. 3 | 4 | # Running The Example Code 5 | 6 | ## Setup 7 | The example is all written in PHP, and it also contains a docker compose file for easy setup if you have docker installed. 8 | 9 | ### Registration and Deployment 10 | First thing you will need is to configure your registration and deployment in the example code's fake registrations database. 11 | 12 | This can be found in the example tool's code at `db/configs/local.json`. 13 | To configure your registration add a JSON object into a `db/configs/local.json` file in the following format. You can find an example of a registered tool in the `db/configs/example.json` file. 14 | 15 | ```javascript 16 | { 17 | "" : { // This will usually look something like 'http://example.com' 18 | "client_id" : "", // This is the id received in the 'aud' during a launch 19 | "auth_login_url" : "", // The platform's OIDC login endpoint 20 | "auth_token_url" : "", // The platform's service authorization endpoint 21 | "key_set_url" : "", // The platform's JWKS endpoint 22 | "private_key_file" : "", // Relative path to the tool's private key 23 | "deployment" : [ 24 | "" // The deployment_id passed by the platform during launch 25 | ] 26 | } 27 | } 28 | ``` 29 | 30 | To register your tool inside a platform, the platform will need two URLs 31 | 32 | ``` 33 | OIDC Login URL: http://localhost:9001/login.php 34 | LTI Launch URL: http://localhost:9001/game.php 35 | ``` 36 | 37 | These URLs may vary if you do not use docker-compose to run the tool and instead run it locally. 38 | 39 | ### Running in Docker 40 | To run in docker you will need both `docker` and `docker-compose` 41 | 42 | To get the examples up and running in docker simply run: 43 | ``` 44 | docker-compose up --build 45 | ``` 46 | 47 | You're now free to launch in and use the tool. 48 | 49 | # Example Platform 50 | Due to popular demand, an example platform has been added to show an example of launching into the example game. 51 | 52 | The registration and deployment between the example platform and tool is already set up, so no configuration is needed. 53 | 54 | To view the example platform, go to http://localhost:9001/platform 55 | 56 | **Note:** The platform is for example purposes only and not a full platform library. 57 | 58 | # Contributing 59 | If you have improvements, suggestions or bug fixes, feel free to make a pull request or issue and someone will take a look at it. 60 | 61 | You do not need to be an IMS Member to use or contribute to this library, however it is recommended for better access to support resources and certification. 62 | 63 | This library was initially created by @MartinLenord from Turnitin to help prove out the LTI 1.3 specification and accelerate tool development. 64 | 65 | **Note:** This library is for IMS LTI 1.3 based specifications only. Requests to include custom, off-spec or vendor-specific changes will be declined. 66 | 67 | ## Don't like PHP? 68 | If you don't like PHP and have a favorite language that you would like to make a library for, we'd love to hear about it! 69 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | app: 4 | build: . 5 | command: /bin/sh -c "cd /srv/app && php /bin/composer.phar install && php /bin/composer.phar update && apache2-foreground" 6 | image: breakout-docker 7 | ports: 8 | - ${BREAKOUT_PORT:-9001}:80 9 | volumes: 10 | - ./src:/srv/app -------------------------------------------------------------------------------- /src/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "imsglobal/lti-1p3-tool-example", 3 | "type": "project", 4 | "description": "Example project using the LTI 1.3 Advantage library", 5 | "repositories": [ 6 | { 7 | "type": "vcs", 8 | "url": "https://github.com/IMSGlobal/lti-1-3-php-library" 9 | } 10 | ], 11 | "authors": [ 12 | { 13 | "name": "Martin Lenord", 14 | "email": "ims.m@rtin.dev" 15 | } 16 | ], 17 | "require": { 18 | "imsglobal/lti-1p3-tool": "dev-master" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/composer.phar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1EdTech/lti-1-3-php-example-tool/523bba29a8dba3f9793d3630318d8182ec7074ab/src/composer.phar -------------------------------------------------------------------------------- /src/db/cert_suite_private.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAsW3eobPIj5LsyHcMGckVSSC621uL+0zkeMoWfXfNmvTH+zt5 3 | WOeEIdz+X7fK+F+lO7ic5WdJEGmp9/cjAf0Z6SsmnvvHlHV/xsWtJm4DiuuF2MAa 4 | hRQ5QEkhaEdh5QM2vAYyc8Nfxe504vA3czuynrW9MsOdZHeVzF+zWhhEl+olC5fW 5 | A1rhTUPpdxuZ0opVIrGJtI/QYfndoN+7zTs/4CXqG6WpB+AZio8j7c6fJLC7J33c 6 | pxB1+O+64Qbh+5sxz46cEByboAB8qerYCmcfxxfBbwyySBBK5X77aNHWA01B1kpO 7 | Q2VB8YKQk+OrXsPgJobPkR9ONWa9DC9JjEdUJwIDAQABAoIBAQCAA+qutt2NIY/v 8 | 71zuudO+yHupSzsLXOY3dG+XpTnWhKhJTxb1m00Ndbqe6yfp3nCET2X8anIgAmzc 9 | +RXsGGZ6gmTCLp1IMyK3EuckJBowQFB5G9nGjNnl1R3idCZgqtnx/XKnbZ6LW8o/ 10 | 9tu7K6ZrtmrE1riXxWRyadYoufu7ssNTqtj03oh3Tvw+Ze6xvF6hpaxnbVHxJcGt 11 | xZO51L6rGOSFq5CJ81BswyBDOKB/Z2OC0o3m2t4ZF4/2Lf070sB7RoejGD7mhYVe 12 | lEOoC95C14hfcspzmDEb8I/n0MvAxlwddM4KZRilAJ+e2R0rM9M1MnyYsmYUsMNX 13 | EKWcx+/5AoGBAOLtNVbIohpY5kbX4WREJ/0INPbbx0gf68ozEZTjsOzIP7oaIzry 14 | URmxyZzSpx446QCO8s26vuxrPGm7OAteNS7UpDdunzKsaIlZScZQEpE9htp3MKKw 15 | KXaA4l7H55uWWnaUAcDqjEdybhYL6SbPKhOaK53VeHOLro900FiRnfaDAoGBAMgp 16 | O8GwAI3LbD06Fn+DT+3hj/i8wxbWilgJlI+RU+wWfQ421jMKv2dck8zbnzKGxEwA 17 | 3WPh6gGMlkavEZ95d0qZ/TOkSh+VIjJuOrjcckRcrKcycYJJUzreO7ENsFbA+8xL 18 | Qp2gNV+NntiChzSUGY5Nup3keoaT9iV13oYDSdqNAoGARDn9Z3I7CqDf2zzcz0CO 19 | pUzqX64EZHL0eX6RMqqibw5l2pYxMW/ZYlhJvZS4GiYSJ9DSv3f+Hya+qytW1lQk 20 | uUfFd8USqDGd3G2z+KPqcTCGcviS7tb4IGDvrn976xNxb2VggZgDRRfqcUZzeu+e 21 | PvaDVpjv9g1xFkCQw5BEZfECgYBcSB5jywhGV14c0FYlDd5g9xiQfj6XnewEcM5M 22 | bp05gJjBX+jbeX4LYnRGA49fFSEVRWTMsxBXDIEQL5C5bJ/iBiLllz4RV4l/pLBw 23 | IDqSaAO1xhztC29S+bidhYkiRjEQ3DXnREC3QCzW9z7sr8ckg5OhTgBrYXYfiTtB 24 | n+yB1QKBgG/J+WhkqMEtZ8CgdoiTIqYKmFsLvl07wETAVU6Nv1sEI+jnhyug0QtQ 25 | yLAlBOVyrXuJ1DZMX6hTRij4L0jvnJFSq0Sv8COuLIH90xdq/NTNQ3LAy60l/3b1 26 | ojAnnRJORDegdJjCBxJ59Fch6Qfd+e8742DVsJu8zVo2garUVMH3 27 | -----END RSA PRIVATE KEY----- -------------------------------------------------------------------------------- /src/db/configs/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "http://imsglobal.org": { 3 | "client_id": "testing12345", 4 | "auth_login_url": "https://lti-ri.imsglobal.org/platforms/531/authorizations/new", 5 | "auth_token_url": "https://lti-ri.imsglobal.org/platforms/531/access_tokens", 6 | "key_set_url": "https://lti-ri.imsglobal.org/platforms/531/platform_keys/529.json", 7 | "private_key_file": "/private.key", 8 | "deployment": [ 9 | "1234" 10 | ] 11 | }, 12 | "ltiadvantagevalidator.imsglobal.org": { 13 | "client_id": "imstestuser", 14 | "auth_login_url": "https://ltiadvantagevalidator.imsglobal.org/ltitool/oidcauthurl.html", 15 | "auth_token_url": "https://oauth2server.imsglobal.org/oauth2server/authcodejwt", 16 | "key_set_url": "https://oauth2server.imsglobal.org/jwks", 17 | "private_key_file": "/cert_suite_private.key", 18 | "deployment": [ 19 | "testdeploy" 20 | ] 21 | }, 22 | "http://localhost:9001": { 23 | "client_id": "d42df408-70f5-4b60-8274-6c98d3b9468d", 24 | "auth_login_url": "http://localhost:9001/platform/login.php", 25 | "auth_token_url": "http://localhost/platform/token.php", 26 | "key_set_url": "http://localhost/platform/jwks.php", 27 | "private_key_file": "/private.key", 28 | "kid": "58f36e10-c1c1-4df0-af8b-85c857d1634f", 29 | "deployment": [ 30 | "8c49a5fa-f955-405e-865f-3d7e959e809f" 31 | ] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/db/example_database.php: -------------------------------------------------------------------------------- 1 | $reg_config) { 10 | $_SESSION['iss'] = array_merge($_SESSION['iss'], json_decode(file_get_contents(__DIR__ . "/configs/$reg_config"), true)); 11 | } 12 | class Example_Database implements LTI\Database { 13 | public function find_registration_by_issuer($iss) { 14 | if (empty($_SESSION['iss']) || empty($_SESSION['iss'][$iss])) { 15 | return false; 16 | } 17 | return LTI\LTI_Registration::new() 18 | ->set_auth_login_url($_SESSION['iss'][$iss]['auth_login_url']) 19 | ->set_auth_token_url($_SESSION['iss'][$iss]['auth_token_url']) 20 | ->set_auth_server($_SESSION['iss'][$iss]['auth_server']) 21 | ->set_client_id($_SESSION['iss'][$iss]['client_id']) 22 | ->set_key_set_url($_SESSION['iss'][$iss]['key_set_url']) 23 | ->set_kid($_SESSION['iss'][$iss]['kid']) 24 | ->set_issuer($iss) 25 | ->set_tool_private_key($this->private_key($iss)); 26 | } 27 | 28 | public function find_deployment($iss, $deployment_id) { 29 | if (!in_array($deployment_id, $_SESSION['iss'][$iss]['deployment'])) { 30 | return false; 31 | } 32 | return LTI\LTI_Deployment::new() 33 | ->set_deployment_id($deployment_id); 34 | } 35 | 36 | private function private_key($iss) { 37 | return file_get_contents(__DIR__ . $_SESSION['iss'][$iss]['private_key_file']); 38 | } 39 | } 40 | ?> -------------------------------------------------------------------------------- /src/db/platform.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEA8osiSa75nmqmakwNNocLA2N2huWM9At/tjSZOFX1r4+PDclS 3 | zxhMw+ZcgHH+E/05Ec6Vcfd75i8Z+Bxu4ctbYk2FNIvRMN5UgWqxZ5Pf70n8UFxj 4 | GqdwhUA7/n5KOFoUd9F6wLKa6Oh3OzE6v9+O3y6qL40XhZxNrJjCqxSEkLkOK3xJ 5 | 0J2npuZ59kipDEDZkRTWz3al09wQ0nvAgCc96DGH+jCgy0msA0OZQ9SmDE9CCMbD 6 | T86ogLugPFCvo5g5zqBBX9Ak3czsuLS6Ni9Wco8ZSxoaCIsPXK0RJpt6Jvbjclqb 7 | 4imsobifxy5LsAV0l/weNWmU2DpzJsLgeK6VVwIDAQABAoIBAQC2R1RUdfjJUrOQ 8 | rWk8so7XVBfO15NwEXhAkhUYnpmPAF/tZ4EhfMysaWLZcVIW6bbLKCtuRCVMX9ev 9 | fIbkkLU0ErhqPi3QATcXL/z1r8+bAUprhpNAg9fvfM/ZukXDRged6MPNMC11nseE 10 | p8HUU4oHNwXVyL6FvmstrHyYoEnkjIiMk34O2MFjAavoIJhM0gkoXVnxRP5MNi1n 11 | GPVhK+TfZyRri20x1Rh3CsIq36PUyxCICWkD7jftLGqVdQBfuii600LP5v7nuHz9 12 | LDsCeY7xRJu0eLdDk7/9ukb8fuq6/+3VYMYChYWvpw4DaH8qDHxZfWzMyaI489ma 13 | l27lhgdxAoGBAPkxH6WuZM/GOowjySuruRjAVyJ4stfe9l/x8MrqnFA2Q8stqK69 14 | 60Y9LDrSaAx7QutvzZ64br2WMlvnGdJw868z4/JmvoAqW3IHUXzqRAHgOk/8Y3ze 15 | Sjd7t3R0O3v6qAbQjyRYYgfAMZo7PzXW8FKNGsakAedEKW0b94HYndKpAoGBAPkr 16 | grtARp2nnd1WGuxgQMjX++HjT0p9x7fTMCtfvYhZguU9AlCx53VHFeGc6fqsDkUm 17 | BFv0dqMnw0TPzEQqLElBIh87TGS4JSXmcbQcejIx+ry2kMFuyMZIPuvZCnLfB/d7 18 | Qu2DU6mdeIBME/8AX5kBqn1ekddioESdSkHkkif/AoGAaPCeAjjZ7YHuP/wGCOUN 19 | UvYU+8hWkIAtwyPxIpMAdusTS6oTwlrqjK7QRIk9FhyGhv2TWwcSY7avyHIfNrco 20 | eBzjHr7T9MdhsTiRwYgqUZvrEqoX/4rhOFJaZKlaL5DUV+JWlZi+18LBYNEYgoTc 21 | ufcAUqzYvFrBE1jWt5DQjdkCgYATs6sMn1J2GNDUtYA/fITi3KEgBVc5rqRiFqLS 22 | aymTZHCDK8XJF6gTj+FdC4k8tuoR8aWal8Phtr0r7bpbEXKbADlwesHZnO3jB0uq 23 | UC4hVe5biZv8j4P0mbXP9ENtPdFlciuimCW/XaIvktRp71+fu4/9hcLGYxgFFOLQ 24 | PwCHhQKBgGMCxIcueUkLnI9r0KkjtXap9mIgdgERwQPN0Cm9Tx35ZEzRp95kf4C6 25 | MPsVOwZk5gNvvQngx4iaw9fNYG+PF2yNuDZ+EFwI0vpmGCKRQEke9/VCOFucMsjg 26 | jMhbU+jrqRIJKisP7MCE1NRhymCPpQf/stEPl0nS5rj+mZJHQEGq 27 | -----END RSA PRIVATE KEY----- -------------------------------------------------------------------------------- /src/db/private.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEA8osiSa75nmqmakwNNocLA2N2huWM9At/tjSZOFX1r4+PDclS 3 | zxhMw+ZcgHH+E/05Ec6Vcfd75i8Z+Bxu4ctbYk2FNIvRMN5UgWqxZ5Pf70n8UFxj 4 | GqdwhUA7/n5KOFoUd9F6wLKa6Oh3OzE6v9+O3y6qL40XhZxNrJjCqxSEkLkOK3xJ 5 | 0J2npuZ59kipDEDZkRTWz3al09wQ0nvAgCc96DGH+jCgy0msA0OZQ9SmDE9CCMbD 6 | T86ogLugPFCvo5g5zqBBX9Ak3czsuLS6Ni9Wco8ZSxoaCIsPXK0RJpt6Jvbjclqb 7 | 4imsobifxy5LsAV0l/weNWmU2DpzJsLgeK6VVwIDAQABAoIBAQC2R1RUdfjJUrOQ 8 | rWk8so7XVBfO15NwEXhAkhUYnpmPAF/tZ4EhfMysaWLZcVIW6bbLKCtuRCVMX9ev 9 | fIbkkLU0ErhqPi3QATcXL/z1r8+bAUprhpNAg9fvfM/ZukXDRged6MPNMC11nseE 10 | p8HUU4oHNwXVyL6FvmstrHyYoEnkjIiMk34O2MFjAavoIJhM0gkoXVnxRP5MNi1n 11 | GPVhK+TfZyRri20x1Rh3CsIq36PUyxCICWkD7jftLGqVdQBfuii600LP5v7nuHz9 12 | LDsCeY7xRJu0eLdDk7/9ukb8fuq6/+3VYMYChYWvpw4DaH8qDHxZfWzMyaI489ma 13 | l27lhgdxAoGBAPkxH6WuZM/GOowjySuruRjAVyJ4stfe9l/x8MrqnFA2Q8stqK69 14 | 60Y9LDrSaAx7QutvzZ64br2WMlvnGdJw868z4/JmvoAqW3IHUXzqRAHgOk/8Y3ze 15 | Sjd7t3R0O3v6qAbQjyRYYgfAMZo7PzXW8FKNGsakAedEKW0b94HYndKpAoGBAPkr 16 | grtARp2nnd1WGuxgQMjX++HjT0p9x7fTMCtfvYhZguU9AlCx53VHFeGc6fqsDkUm 17 | BFv0dqMnw0TPzEQqLElBIh87TGS4JSXmcbQcejIx+ry2kMFuyMZIPuvZCnLfB/d7 18 | Qu2DU6mdeIBME/8AX5kBqn1ekddioESdSkHkkif/AoGAaPCeAjjZ7YHuP/wGCOUN 19 | UvYU+8hWkIAtwyPxIpMAdusTS6oTwlrqjK7QRIk9FhyGhv2TWwcSY7avyHIfNrco 20 | eBzjHr7T9MdhsTiRwYgqUZvrEqoX/4rhOFJaZKlaL5DUV+JWlZi+18LBYNEYgoTc 21 | ufcAUqzYvFrBE1jWt5DQjdkCgYATs6sMn1J2GNDUtYA/fITi3KEgBVc5rqRiFqLS 22 | aymTZHCDK8XJF6gTj+FdC4k8tuoR8aWal8Phtr0r7bpbEXKbADlwesHZnO3jB0uq 23 | UC4hVe5biZv8j4P0mbXP9ENtPdFlciuimCW/XaIvktRp71+fu4/9hcLGYxgFFOLQ 24 | PwCHhQKBgGMCxIcueUkLnI9r0KkjtXap9mIgdgERwQPN0Cm9Tx35ZEzRp95kf4C6 25 | MPsVOwZk5gNvvQngx4iaw9fNYG+PF2yNuDZ+EFwI0vpmGCKRQEke9/VCOFucMsjg 26 | jMhbU+jrqRIJKisP7MCE1NRhymCPpQf/stEPl0nS5rj+mZJHQEGq 27 | -----END RSA PRIVATE KEY----- -------------------------------------------------------------------------------- /src/web/api/score.php: -------------------------------------------------------------------------------- 1 | has_ags()) { 8 | throw new Exception("Don't have grades!"); 9 | } 10 | $grades = $launch->get_ags(); 11 | 12 | $score = LTI\LTI_Grade::new() 13 | ->set_score_given($_REQUEST['score']) 14 | ->set_score_maximum(100) 15 | ->set_timestamp(date(DateTime::ISO8601)) 16 | ->set_activity_progress('Completed') 17 | ->set_grading_progress('FullyGraded') 18 | ->set_user_id($launch->get_launch_data()['sub']); 19 | $score_lineitem = LTI\LTI_Lineitem::new() 20 | ->set_tag('score') 21 | ->set_score_maximum(100) 22 | ->set_label('Score') 23 | ->set_resource_id($launch->get_launch_data()['https://purl.imsglobal.org/spec/lti/claim/resource_link']['id']); 24 | $grades->put_grade($score, $score_lineitem); 25 | 26 | 27 | $time = LTI\LTI_Grade::new() 28 | ->set_score_given($_REQUEST['time']) 29 | ->set_score_maximum(999) 30 | ->set_timestamp(date(DateTime::ISO8601)) 31 | ->set_activity_progress('Completed') 32 | ->set_grading_progress('FullyGraded') 33 | ->set_user_id($launch->get_launch_data()['sub']); 34 | $time_lineitem = LTI\LTI_Lineitem::new() 35 | ->set_tag('time') 36 | ->set_score_maximum(999) 37 | ->set_label('Time Taken') 38 | ->set_resource_id('time'.$launch->get_launch_data()['https://purl.imsglobal.org/spec/lti/claim/resource_link']['id']); 39 | $grades->put_grade($time, $time_lineitem); 40 | echo '{"success" : true}'; 41 | ?> -------------------------------------------------------------------------------- /src/web/api/scoreboard.php: -------------------------------------------------------------------------------- 1 | has_nrps()) { 8 | throw new Exception("Don't have names and roles!"); 9 | } 10 | if (!$launch->has_ags()) { 11 | throw new Exception("Don't have grades!"); 12 | } 13 | $ags = $launch->get_ags(); 14 | 15 | $score_lineitem = LTI\LTI_Lineitem::new() 16 | ->set_tag('score') 17 | ->set_score_maximum(100) 18 | ->set_label('Score') 19 | ->set_resource_id($launch->get_launch_data()['https://purl.imsglobal.org/spec/lti/claim/resource_link']['id']); 20 | $scores = $ags->get_grades($score_lineitem); 21 | 22 | $time_lineitem = LTI\LTI_Lineitem::new() 23 | ->set_tag('time') 24 | ->set_score_maximum(999) 25 | ->set_label('Time Taken') 26 | ->set_resource_id('time'.$launch->get_launch_data()['https://purl.imsglobal.org/spec/lti/claim/resource_link']['id']); 27 | $times = $ags->get_grades($time_lineitem); 28 | 29 | $members = $launch->get_nrps()->get_members(); 30 | 31 | $scoreboard = []; 32 | 33 | foreach ($scores as $score) { 34 | $result = ['score' => $score['resultScore']]; 35 | foreach ($times as $time) { 36 | if ($time['userId'] === $score['userId']) { 37 | $result['time'] = $time['resultScore']; 38 | break; 39 | } 40 | } 41 | foreach ($members as $member) { 42 | if ($member['user_id'] === $score['userId']) { 43 | $result['name'] = $member['name']; 44 | break; 45 | } 46 | } 47 | $scoreboard[] = $result; 48 | } 49 | echo json_encode($scoreboard); 50 | ?> -------------------------------------------------------------------------------- /src/web/configure.php: -------------------------------------------------------------------------------- 1 | is_deep_link_launch()) { 8 | throw new Exception("Must be a deep link!"); 9 | } 10 | $resource = LTI\LTI_Deep_Link_Resource::new() 11 | ->set_url(TOOL_HOST . "/game.php") 12 | ->set_custom_params(['difficulty' => $_REQUEST['diff']]) 13 | ->set_title('Breakout ' . $_REQUEST['diff'] . ' mode!'); 14 | $launch->get_deep_link() 15 | ->output_response_form([$resource]); 16 | ?> -------------------------------------------------------------------------------- /src/web/game.php: -------------------------------------------------------------------------------- 1 | validate(); 8 | 9 | ?> 10 | is_deep_link_launch()) { 13 | ?> 14 |
15 |

Pick a Difficulty

16 | 21 |
22 | 26 | 27 |
28 |
29 |
30 |

Scoreboard

31 | 32 |
33 |
34 | 35 | 36 | 37 | 38 |
39 |
40 | 46 | -------------------------------------------------------------------------------- /src/web/jwks.php: -------------------------------------------------------------------------------- 1 | file_get_contents(__DIR__ . '/../db/keys/private.key') 9 | ])->output_jwks(); 10 | 11 | ?> -------------------------------------------------------------------------------- /src/web/login.php: -------------------------------------------------------------------------------- 1 | do_oidc_login_redirect(TOOL_HOST . "/game.php") 9 | ->do_redirect(); 10 | ?> -------------------------------------------------------------------------------- /src/web/oidc_demo/iframe.html: -------------------------------------------------------------------------------- 1 |
    2 |
  • Fancy LMS
  • 3 |
  • Users
  • 4 |
  • Courses
  • 5 |
  • 302
  • 6 |
  • js
  • 7 |
  • hybrid
  • 8 |
  • Settings
  • 9 |
10 | 13 | -------------------------------------------------------------------------------- /src/web/oidc_demo/jwks.php: -------------------------------------------------------------------------------- 1 | file_get_contents(__DIR__ . '/../db/keys/private.key') 9 | ])->output_jwks(); 10 | 11 | ?> -------------------------------------------------------------------------------- /src/web/oidc_demo/launcher.php: -------------------------------------------------------------------------------- 1 | 'http://localhost:9001', 9 | "aud" => ['d42df408-70f5-4b60-8274-6c98d3b9468d'], 10 | "sub" => '0ae836b9-7fc9-4060-006f-27b2066ac545', 11 | "exp" => time() + 600, 12 | "iat" => time(), 13 | "nonce" => uniqid("nonce"), 14 | "https://purl.imsglobal.org/spec/lti/claim/deployment_id" => '8c49a5fa-f955-405e-865f-3d7e959e809f', 15 | "https://purl.imsglobal.org/spec/lti/claim/message_type" => "LtiResourceLinkRequest", 16 | "https://purl.imsglobal.org/spec/lti/claim/version" => "1.3.0", 17 | "https://purl.imsglobal.org/spec/lti/claim/target_link_uri" => TOOL_HOST . "/game.php", 18 | "https://purl.imsglobal.org/spec/lti/claim/roles" => [ 19 | "http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor" 20 | ], 21 | "https://purl.imsglobal.org/spec/lti/claim/resource_link" => [ 22 | "id" => "7b3c5109-b402-4eac-8f61-bdafa301cbb4", 23 | ], 24 | "https://purl.imsglobal.org/spec/lti-nrps/claim/namesroleservice" => [ 25 | "context_memberships_url" => "http://localhost/platform/services/nrps", 26 | "service_versions" => ["2.0"] 27 | ], 28 | "https://purl.imsglobal.org/spec/lti-ags/claim/endpoint" => [ 29 | "scope" => [ 30 | "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem", 31 | "https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly", 32 | "https://purl.imsglobal.org/spec/lti-ags/scope/score" 33 | ], 34 | "lineitems" => "http://localhost/platform/services/ags/lineitems.php", 35 | ] 36 | ]; 37 | $database = new Example_Database(); 38 | $jwt = JWT::encode( 39 | $message_jwt, 40 | file_get_contents(__DIR__ . '/../../db/platform.key'), 41 | 'RS256', 42 | 'fcec4f14-28a5-4697-87c3-e9ac361dada5' 43 | ); 44 | ?> 45 | 46 |
47 | 48 | 49 |
50 | 53 | -------------------------------------------------------------------------------- /src/web/oidc_demo/lti_launch.php: -------------------------------------------------------------------------------- 1 | validate(); 8 | ?> 9 |

Success

-------------------------------------------------------------------------------- /src/web/oidc_demo/oidc_login_302.php: -------------------------------------------------------------------------------- 1 | do_oidc_login_redirect(TOOL_HOST . "/oidc_demo/lti_launch.php") 9 | ->do_redirect(); 10 | ?> -------------------------------------------------------------------------------- /src/web/oidc_demo/oidc_login_hybrid.php: -------------------------------------------------------------------------------- 1 | do_oidc_login_redirect(TOOL_HOST . "/oidc_demo/lti_launch.php"); 9 | 10 | if (!empty($_COOKIE['thirdpartycookietest'])) { 11 | $redirect->do_redirect(); 12 | } 13 | 14 | $redirect_url = $redirect->get_redirect_url(); 15 | 16 | setcookie('thirdpartycookietest', 'true', time() + 3600); 17 | ?> 18 | Link if blocked by popup blocker 19 | -------------------------------------------------------------------------------- /src/web/oidc_demo/oidc_login_js.php: -------------------------------------------------------------------------------- 1 | do_oidc_login_redirect(TOOL_HOST . "/oidc_demo/lti_launch.php") 9 | ->get_redirect_url(); 10 | 11 | setcookie('thirdpartycookietest', 'true', time() + 3600); 12 | ?> 13 | Link if blocked by popup blocker 14 | -------------------------------------------------------------------------------- /src/web/platform/index.php: -------------------------------------------------------------------------------- 1 | 4 |
    5 |
  • Fancy LMS
  • 6 |
  • Users
  • 7 |
  • Courses
  • 8 |
  • Games 101
  • 9 |
  • Settings
  • 10 |
11 | 14 | -------------------------------------------------------------------------------- /src/web/platform/jwks.php: -------------------------------------------------------------------------------- 1 | file_get_contents(__DIR__ . '/../../db/platform.key') 9 | ])->output_jwks(); 10 | 11 | ?> -------------------------------------------------------------------------------- /src/web/platform/login.php: -------------------------------------------------------------------------------- 1 | 'http://localhost:9001', 9 | "aud" => ['d42df408-70f5-4b60-8274-6c98d3b9468d'], 10 | "sub" => '0ae836b9-7fc9-4060-006f-27b2066ac545', 11 | "exp" => time() + 600, 12 | "iat" => time(), 13 | "nonce" => uniqid("nonce"), 14 | "https://purl.imsglobal.org/spec/lti/claim/deployment_id" => '8c49a5fa-f955-405e-865f-3d7e959e809f', 15 | "https://purl.imsglobal.org/spec/lti/claim/message_type" => "LtiResourceLinkRequest", 16 | "https://purl.imsglobal.org/spec/lti/claim/version" => "1.3.0", 17 | "https://purl.imsglobal.org/spec/lti/claim/target_link_uri" => TOOL_HOST . "/game.php", 18 | "https://purl.imsglobal.org/spec/lti/claim/roles" => [ 19 | "http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor" 20 | ], 21 | "https://purl.imsglobal.org/spec/lti/claim/resource_link" => [ 22 | "id" => "7b3c5109-b402-4eac-8f61-bdafa301cbb4", 23 | ], 24 | "https://purl.imsglobal.org/spec/lti-nrps/claim/namesroleservice" => [ 25 | "context_memberships_url" => "http://localhost/platform/services/nrps", 26 | "service_versions" => ["2.0"] 27 | ], 28 | "https://purl.imsglobal.org/spec/lti-ags/claim/endpoint" => [ 29 | "scope" => [ 30 | "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem", 31 | "https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly", 32 | "https://purl.imsglobal.org/spec/lti-ags/scope/score" 33 | ], 34 | "lineitems" => "http://localhost/platform/services/ags/lineitems.php", 35 | ] 36 | ]; 37 | $database = new Example_Database(); 38 | $jwt = JWT::encode( 39 | $message_jwt, 40 | file_get_contents(__DIR__ . '/../../db/platform.key'), 41 | 'RS256', 42 | 'fcec4f14-28a5-4697-87c3-e9ac361dada5' 43 | ); 44 | ?> 45 | 46 |
47 | 48 | 49 |
50 | 53 | -------------------------------------------------------------------------------- /src/web/platform/services/ags.php: -------------------------------------------------------------------------------- 1 | "https://lms.example.com/context/2923/lineitems/1/results/5323497", 20 | "userId" => "0ae836b9-7fc9-4060-006f-27b2066ac545", 21 | "resultScore" => file_get_contents(__DIR__ . '/ags/score.txt') ?: 0, 22 | "resultMaximum" => 108, 23 | ], 24 | [ 25 | "id" => "https://lms.example.com/context/2923/lineitems/1/results/5323497", 26 | "userId" => "4d0b3941-83f5-47fe-bd8a-66b39aa0651d", 27 | "resultScore" => 60, 28 | "resultMaximum" => 108, 29 | ] 30 | ]); 31 | } else { 32 | echo json_encode([ 33 | [ 34 | "id" => "https://lms.example.com/context/2923/lineitems/1/results/5323497", 35 | "userId" => "0ae836b9-7fc9-4060-006f-27b2066ac545", 36 | "resultScore" => file_get_contents(__DIR__ . '/ags/time.txt') ?: 0, 37 | "resultMaximum" => 999, 38 | ], 39 | [ 40 | "id" => "https://lms.example.com/context/2923/lineitems/1/results/5323497", 41 | "userId" => "4d0b3941-83f5-47fe-bd8a-66b39aa0651d", 42 | "resultScore" => 82, 43 | "resultMaximum" => 999, 44 | ] 45 | ]); 46 | } 47 | break; 48 | } 49 | ?> -------------------------------------------------------------------------------- /src/web/platform/services/ags/lineitems.php: -------------------------------------------------------------------------------- 1 | "http://localhost/platform/services/ags.php?tag=time", 5 | "scoreMaximum" => 999, 6 | "label" => "Time", 7 | "tag" => "time", 8 | "resourceId" => "time7b3c5109-b402-4eac-8f61-bdafa301cbb4" 9 | ], 10 | [ 11 | "id" => "http://localhost/platform/services/ags.php?tag=score", 12 | "scoreMaximum" => 108, 13 | "label" => "Score", 14 | "tag" => "score", 15 | "resourceId" => "7b3c5109-b402-4eac-8f61-bdafa301cbb4" 16 | ] 17 | ]); 18 | ?> -------------------------------------------------------------------------------- /src/web/platform/services/nrps/index.php: -------------------------------------------------------------------------------- 1 | "http://localhost:9001/platform/nrps.php", 4 | "members" => [ 5 | [ 6 | "status" => "Active", 7 | "context_id" => "2923-abc", 8 | "name" => "Trudie Senaida", 9 | "given_name" => "Trudie", 10 | "family_name" => "Senaida", 11 | "user_id" => "0ae836b9-7fc9-4060-006f-27b2066ac545", 12 | "roles" => [ 13 | "Instructor" 14 | ], 15 | "message" => [] 16 | ], 17 | [ 18 | "status" => "Active", 19 | "context_id" => "2923-abc", 20 | "name" => "Marget Elke", 21 | "given_name" => "Marget", 22 | "family_name" => "Elke", 23 | "user_id" => "4d0b3941-83f5-47fe-bd8a-66b39aa0651d", 24 | "roles" => [ 25 | "Instructor" 26 | ], 27 | "message" => [] 28 | ] 29 | ] 30 | 31 | ]); 32 | ?> -------------------------------------------------------------------------------- /src/web/platform/token.php: -------------------------------------------------------------------------------- 1 | jwt['header']['kid'] && $key['alg'] == $this->jwt['header']['alg']) { 20 | try { 21 | return openssl_pkey_get_details(JWK::parseKey($key)); 22 | } catch(\Exception $e) { 23 | return false; 24 | } 25 | } 26 | } 27 | 28 | // Could not find public key with a matching kid and alg. 29 | throw new LTI_Exception("Unable to find public key", 1); 30 | } 31 | 32 | $jwt = JWT::decode($_POST['client_assertion'], get_public_key(TOOL_HOST . "/jwk.php"), array('RS256')); 33 | 34 | echo json_encode([ 35 | 'access_token' => '9a4b5056-cdce-4cdd-8981-053b610d0842' 36 | ]); 37 | 38 | ?> -------------------------------------------------------------------------------- /src/web/static/breakout.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Gugi', cursive; 3 | } 4 | #scoreboard { 5 | border: solid 1px #000; 6 | border-left: none; 7 | border-top-right-radius: 20px; 8 | border-bottom-right-radius: 20px; 9 | padding-bottom: 12px; 10 | background: linear-gradient(to bottom, rgb(0, 0, 0), rgb(0, 0, 50) 500px); 11 | color: white; 12 | } 13 | th, td { 14 | color: white; 15 | } 16 | .dl-config { 17 | font-family: Verdana, Geneva, Tahoma, sans-serif; 18 | display: block; 19 | width: 400px; 20 | position: absolute; 21 | left:50%; 22 | margin-left: -200px; 23 | } 24 | .dl-config h1 { 25 | text-align: center; 26 | } 27 | .dl-config ul { 28 | list-style: none; 29 | padding: 0; 30 | } 31 | .dl-config ul li a { 32 | display: block; 33 | width: 200px; 34 | height:50px; 35 | text-align: center; 36 | border: black solid 1px; 37 | border-radius: 15px; 38 | margin:auto; 39 | text-decoration: none; 40 | color: black; 41 | margin-top:8px; 42 | font-size: 30px; 43 | line-height: 46px; 44 | } 45 | .dl-config ul li a:hover { 46 | background-color: #dddddd; 47 | } -------------------------------------------------------------------------------- /src/web/static/breakout.js: -------------------------------------------------------------------------------- 1 | var c = document.getElementById("breakout"); 2 | var ctx = window.c.getContext("2d"); 3 | 4 | var bgctx = document.getElementById("breakoutbg").getContext("2d"); 5 | bgctx.beginPath(); 6 | var bggrad = ctx.createLinearGradient(0,0,0,c.height); 7 | bggrad.addColorStop(0,"rgb(0, 0, 0)"); 8 | bggrad.addColorStop(1,"rgb(0, 0, 50)"); 9 | bgctx.fillStyle = bggrad; 10 | bgctx.rect(0, 0, c.width, c.height); 11 | bgctx.fill(); 12 | bgctx.font = "10px Gugi"; 13 | bgctx.fillStyle = '#FFFFFF'; 14 | bgctx.textAlign = "left"; 15 | bgctx.fillText("Powered By Turnitin", 6, c.height-6); 16 | 17 | var score = 0; 18 | 19 | var difficulty = { 20 | hard: { 21 | speed_multiplier:1.5 22 | }, 23 | normal: { 24 | speed_multiplier:1 25 | }, 26 | easy: { 27 | speed_multiplier:0.7 28 | }, 29 | } 30 | 31 | var ball = { 32 | pos : { 33 | x: window.c.width/2-200, 34 | y : window.c.height/2-2, 35 | }, 36 | vel : { 37 | x : 6 * difficulty[curr_diff]['speed_multiplier'], 38 | y : 6 * difficulty[curr_diff]['speed_multiplier'], 39 | }, 40 | r: 10, 41 | rot: 0, 42 | velr: 0, 43 | render: function() { 44 | this.pos.x += this.vel.x; 45 | this.pos.y += this.vel.y; 46 | if (this.oob(window.c.width, this.pos.x, this.r)) { 47 | this.vel.x = -this.vel.x; 48 | this.pos.x += this.vel.x; 49 | this.pos.y -= this.vel.y; 50 | } 51 | if (this.oob(window.c.height, this.pos.y, this.r)) { 52 | if (this.pos.y > window.c.height - this.r) { 53 | window.endGame(); 54 | } 55 | this.vel.y = -this.vel.y; 56 | this.pos.y += this.vel.y; 57 | this.pos.x -= this.vel.x; 58 | } 59 | 60 | window.ctx.save(); 61 | window.ctx.beginPath(); 62 | var gradient = ctx.createRadialGradient(this.pos.x, this.pos.y, 2, this.pos.x, this.pos.y, 10); 63 | window.ctx.fillStyle = "rgb(255, 232, 102)"; 64 | window.ctx.strokeStyle = "rgb(255, 232, 102)"; 65 | window.ctx.setLineDash([5,5]); 66 | window.ctx.lineWidth = 4; 67 | window.ctx.translate(this.pos.x, this.pos.y); 68 | window.ctx.rotate(this.rot * Math.PI); 69 | window.ctx.arc(0, 0, this.r, 0, 2 * Math.PI); 70 | if (this.vel.x > 0) { 71 | this.velr = 0.01; 72 | } else if (this.vel.x < 0) { 73 | this.velr = -0.01; 74 | } else { 75 | this.velr = 0; 76 | } 77 | this.rot += this.velr; 78 | window.ctx.fill(); 79 | window.ctx.stroke(); 80 | window.ctx.restore(); 81 | 82 | }, 83 | oob : function(max, curr, offset) { 84 | if (curr < offset || curr > (max-offset)) { 85 | return true; 86 | } 87 | }, 88 | left : function() { 89 | return this.pos.x - this.r; 90 | }, 91 | right : function() { 92 | return this.pos.x + this.r; 93 | }, 94 | top : function() { 95 | return this.pos.y - this.r; 96 | }, 97 | bottom : function() { 98 | return this.pos.y + this.r; 99 | }, 100 | }; 101 | 102 | var paddle = { 103 | pos : { 104 | x: window.c.width/2+2, 105 | y : window.c.height-40, 106 | }, 107 | width : 80, 108 | height : 20, 109 | render: function() { 110 | window.ctx.beginPath(); 111 | var gradient = ctx.createLinearGradient(this.pos.x,this.pos.y,this.pos.x,this.pos.y + this.height); 112 | gradient.addColorStop(0,"#999999"); 113 | gradient.addColorStop(0.7,"#eeeeee"); 114 | gradient.addColorStop(1,"#999999"); 115 | ctx.fillStyle = gradient; 116 | var hh = this.height/2; 117 | window.ctx.arc(this.pos.x + hh, this.pos.y + hh, hh, 0.5 * Math.PI, 1.5 * Math.PI); 118 | window.ctx.rect(this.pos.x + hh, this.pos.y, this.width - this.height, this.height); 119 | window.ctx.arc(this.pos.x + this.width - hh, this.pos.y + hh, hh, 1.5 * Math.PI, 0.5 * Math.PI); 120 | window.ctx.fill(); 121 | window.ctx.stroke(); 122 | }, 123 | left : function() { 124 | return this.pos.x; 125 | }, 126 | right : function() { 127 | return this.pos.x + this.width; 128 | }, 129 | top : function() { 130 | return this.pos.y; 131 | }, 132 | bottom : function() { 133 | return this.pos.y + this.height; 134 | }, 135 | test_hit : function() { 136 | var hitx = this.test_hit_x(); 137 | var hity = this.test_hit_y(); 138 | if (!hitx || !hity) { 139 | return 0; 140 | } 141 | if (hity) { 142 | window.ball.vel.y = -Math.abs(window.ball.vel.y); 143 | window.ball.pos.y += window.ball.vel.y; 144 | window.ball.pos.x -= window.ball.vel.x; 145 | } 146 | if (hitx) { 147 | var xdiff = window.ball.pos.x - (this.pos.x + (this.width/2)); 148 | window.ball.vel.x = (xdiff > 0 ? Math.ceil(xdiff / 5) : Math.floor(xdiff / 5)) * difficulty[curr_diff]['speed_multiplier']; 149 | window.ball.pos.x += window.ball.vel.x; 150 | } 151 | return 1; 152 | }, 153 | test_hit_x : function() { 154 | if (this.left() > window.ball.right()) { 155 | return 0; 156 | } 157 | if (this.right() < window.ball.left()) { 158 | return 0; 159 | } 160 | return 1; 161 | }, 162 | test_hit_y : function() { 163 | if (this.top() > window.ball.bottom()) { 164 | return 0; 165 | } 166 | if (this.bottom() < window.ball.top()) { 167 | return 0; 168 | } 169 | return 1; 170 | }, 171 | move : function() { 172 | if (window.press_left) { 173 | if (this.pos.x > 0) { 174 | this.pos.x -= 8; 175 | } 176 | } 177 | if (window.press_right) { 178 | if (this.pos.x < window.c.width - this.width) { 179 | this.pos.x += 8; 180 | } 181 | } 182 | } 183 | }; 184 | 185 | function fire() { 186 | this.r = 0, 187 | this.a = 0, 188 | this.render = function() { 189 | if (this.a < 0.2) { 190 | this.reset(); 191 | } 192 | 193 | this.pos.x += this.vel.x + (Math.random() * 2) - 1; 194 | this.pos.y += this.vel.y + (Math.random() * 2) - 1; 195 | 196 | this.r *= 0.95; 197 | this.a *= 0.95; 198 | 199 | window.ctx.beginPath(); 200 | window.ctx.fillStyle = 'rgba(' + (239 - this.green) +', ' + this.green + ', 66,'+ this.a +')'; 201 | window.ctx.arc(this.pos.x, this.pos.y, this.r, 0, 2 * Math.PI); 202 | window.ctx.fill(); 203 | 204 | if (this.green < 232) { 205 | this.green += 8; 206 | } 207 | }, 208 | this.reset = function() { 209 | this.pos = { 210 | x: window.ball.pos.x, 211 | y : window.ball.pos.y, 212 | }; 213 | this.vel = { 214 | x : (Math.random() * 4) - 2, 215 | y : (Math.random() * 4) - 2, 216 | }; 217 | this.r = (Math.random() * 5) + 1; 218 | this.a = 0.9; 219 | this.green = 62; 220 | } 221 | } 222 | 223 | function brick() { 224 | this.id = 0, 225 | this.pos = { 226 | x: 40, 227 | y: 40, 228 | }, 229 | this.vely = 0, 230 | this.rot = 0, 231 | this.velr = 0, 232 | this.hit = false, 233 | this.last_hitx = false, 234 | this.last_hity = false, 235 | this.width = 40, 236 | this.height = 20, 237 | this.render = function() { 238 | if (this.hit) { 239 | if (this.pos.y > window.c.height + 60) { 240 | return; 241 | } 242 | this.vely++; 243 | this.pos.y += this.vely; 244 | ctx.save() 245 | window.ctx.beginPath(); 246 | this.rot += this.velr; 247 | ctx.translate(this.pos.x + (this.width/2), this.pos.y + (this.height/2)); 248 | ctx.rotate(this.rot * Math.PI); 249 | var gradient = ctx.createRadialGradient(-(this.width/2) +10, -(this.height/2) +5, 0, -(this.width/2) + 40, -(this.height/2) + 15, 40); 250 | gradient.addColorStop(0, 'rgba(137, 211, 234, 0.2)'); 251 | gradient.addColorStop(1, 'rgba(137, 211, 234, 1)'); 252 | ctx.strokeStyle = 'rgba(254, 254, 254, 0.8)'; 253 | ctx.fillStyle = gradient; 254 | window.ctx.rect(-(this.width/2), -(this.height/2), this.width, this.height); 255 | window.ctx.fill(); 256 | window.ctx.stroke(); 257 | ctx.restore(); 258 | return; 259 | } 260 | window.ctx.beginPath(); 261 | var gradient = ctx.createRadialGradient(this.pos.x + 10, this.pos.y + 5, 0, this.pos.x + 40, this.pos.y + 15, 40); 262 | gradient.addColorStop(0, 'rgba(137, 211, 234, 0.2)'); 263 | gradient.addColorStop(1, 'rgba(137, 211, 234, 1)'); 264 | ctx.strokeStyle = 'rgba(254, 254, 254, 0.8)'; 265 | ctx.fillStyle = gradient; 266 | window.ctx.rect(this.pos.x, this.pos.y, this.width, this.height); 267 | window.ctx.fill(); 268 | window.ctx.stroke(); 269 | }, 270 | this.test_hit = function() { 271 | if (this.hit) { 272 | return 0; 273 | } 274 | var hitx = this.test_hit_x(); 275 | var hity = this.test_hit_y(); 276 | if (!hitx || !hity) { 277 | this.last_hitx = hitx; 278 | this.last_hity = hity; 279 | return 0; 280 | } 281 | if (this.last_hity) { 282 | window.ball.vel.y = -window.ball.vel.y; 283 | window.ball.pos.y += window.ball.vel.y; 284 | window.ball.pos.x -= window.ball.vel.x; 285 | } 286 | if (this.last_hitx) { 287 | window.ball.vel.x = -window.ball.vel.x; 288 | window.ball.pos.x += window.ball.vel.x; 289 | window.ball.pos.y -= window.ball.vel.y; 290 | } 291 | if (!this.last_hity && this.last_hitx) { 292 | window.ball.vel.x = -window.ball.vel.x; 293 | window.ball.pos.x += window.ball.vel.x; 294 | window.ball.vel.y = -window.ball.vel.y; 295 | window.ball.pos.y += window.ball.vel.y; 296 | } 297 | this.last_hitx = hitx; 298 | this.last_hity = hity; 299 | this.hit = true; 300 | this.velr = (Math.random() * 0.04) - 0.02; 301 | window.score++; 302 | return 1; 303 | }, 304 | this.test_hit_x = function() { 305 | if (this.left() > window.ball.right()) { 306 | return 0; 307 | } 308 | if (this.right() < window.ball.left()) { 309 | return 0; 310 | } 311 | return 1; 312 | }, 313 | this.test_hit_y = function() { 314 | if (this.top() > window.ball.bottom()) { 315 | return 0; 316 | } 317 | if (this.bottom() < window.ball.top()) { 318 | return 0; 319 | } 320 | return 1; 321 | }, 322 | 323 | 324 | this.left = function() { 325 | return this.pos.x; 326 | }, 327 | this.right = function() { 328 | return this.pos.x + this.width; 329 | }, 330 | this.top = function() { 331 | return this.pos.y; 332 | }, 333 | this.bottom = function() { 334 | return this.pos.y + this.height; 335 | } 336 | }; 337 | 338 | press_left = false; 339 | press_right = false; 340 | 341 | document.addEventListener('keydown', (event) => { 342 | const keyName = event.key; 343 | if (keyName == "ArrowLeft") { 344 | press_left = true; 345 | } 346 | if (keyName == "ArrowRight") { 347 | press_right = true; 348 | } 349 | }); 350 | 351 | document.addEventListener('keyup', (event) => { 352 | const keyName = event.key; 353 | if (keyName == "ArrowLeft") { 354 | press_left = false; 355 | } 356 | if (keyName == "ArrowRight") { 357 | press_right = false; 358 | } 359 | if (keyName == " ") { 360 | if (pause && !gameover) { 361 | if (!start_time) { 362 | start_time = Math.floor(Date.now() / 1000); 363 | } 364 | pause = false; 365 | frame(); 366 | } else { 367 | pause = true; 368 | } 369 | } 370 | }); 371 | 372 | var bricks = []; 373 | 374 | for (var h = 0; h < 6; h++) { 375 | for (var w = 0; w < 18; w++) { 376 | var brickid = (18*h)+w; 377 | bricks[brickid] = new brick(); 378 | bricks[brickid].pos.x = 40+(w*40); 379 | bricks[brickid].pos.y = 40+(h*20); 380 | bricks[brickid].id = brickid; 381 | } 382 | } 383 | var fires = []; 384 | 385 | for (var i = 0; i < 80; i++) { 386 | fires[i] = new fire(); 387 | } 388 | startFireCount = 1; 389 | 390 | pause = true; 391 | gameover = false; 392 | 393 | var frame = function() { 394 | if (window.score >= window.bricks.length) { 395 | endGame(); 396 | } 397 | window.ctx.clearRect(0, 0, window.c.width, window.c.height); 398 | for (var i = 0; i < window.bricks.length; i++) { 399 | window.bricks[i].render(); 400 | } 401 | for (var i = 0; i < window.bricks.length; i++) { 402 | if (window.bricks[i].test_hit()) { 403 | break; 404 | } 405 | } 406 | for (var i = 0; i < window.fires.length && i < startFireCount; i++) { 407 | window.fires[i].render(); 408 | } 409 | if (startFireCount <= window.fires.length) { 410 | startFireCount++; 411 | } 412 | window.paddle.move(); 413 | window.paddle.render(); 414 | window.paddle.test_hit(); 415 | window.ball.render(); 416 | 417 | if (pause) { 418 | if (!gameover) { 419 | ctx.font = "50px Gugi"; 420 | ctx.fillStyle = '#FFFFFF'; 421 | ctx.textAlign = "center"; 422 | ctx.fillText("Ready " + curr_user_name, c.width/2, c.height/2); 423 | ctx.fillText("Press Space to Start", c.width/2, c.height/2 + 60); 424 | } 425 | } else { 426 | requestAnimationFrame(frame); 427 | } 428 | } 429 | 430 | start_time = false; 431 | document.fonts.load('50px Gugi').then(frame); 432 | 433 | var endGame = function() { 434 | window.pause = true; 435 | window.gameover = true; 436 | window.submitScore(); 437 | } 438 | 439 | var refreshScoreBoard = function() { 440 | var scores = JSON.parse(this.responseText); 441 | console.log(scores); 442 | var output = 'ScoreTimeName'; 443 | for (var i = 0; i < scores.length; i++) { 444 | output += '' + scores[i].score + '' + scores[i].time + 's' + scores[i].name + ''; 445 | } 446 | document.getElementById("leadertable").innerHTML = output; 447 | } 448 | 449 | var submitScore = function() { 450 | var time_taken = Math.floor(Date.now() / 1000) - start_time; 451 | var xhttp = new XMLHttpRequest(); 452 | xhttp.addEventListener("load", getScoreBoard); 453 | xhttp.open("GET", "api/score.php?launch_id=" + launch_id + "&score=" + window.score + "&time=" + time_taken, false); 454 | xhttp.send(); 455 | } 456 | 457 | var getScoreBoard = function() { 458 | var xhttp = new XMLHttpRequest(); 459 | xhttp.addEventListener("load", refreshScoreBoard); 460 | xhttp.open("GET", "api/scoreboard.php?launch_id=" + launch_id, true); 461 | xhttp.send(); 462 | } 463 | 464 | getScoreBoard(); 465 | -------------------------------------------------------------------------------- /vhost.conf: -------------------------------------------------------------------------------- 1 | 2 | DocumentRoot /srv/app/web 3 | 4 | 5 | AllowOverride all 6 | Require all granted 7 | 8 | 9 | ErrorLog ${APACHE_LOG_DIR}/error.log 10 | CustomLog ${APACHE_LOG_DIR}/access.log combined 11 | --------------------------------------------------------------------------------