diff --git a/composer.json b/composer.json
index 7e1286e2e5..d61d48fc3d 100755
--- a/composer.json
+++ b/composer.json
@@ -51,7 +51,6 @@
"clue/graph": "~0.9.0",
"culqi/culqi-php": "1.3.4",
"ddeboer/data-import": "@stable",
- "gedmo/doctrine-extensions": "~2.3",
"doctrine/data-fixtures": "~1.0@dev",
"doctrine/dbal": "~2.5",
"doctrine/migrations": "~1.0@dev",
@@ -62,6 +61,7 @@
"ezyang/htmlpurifier": "~4.9",
"facebook/php-sdk-v4": "~5.0",
"firebase/php-jwt": "~5.0",
+ "gedmo/doctrine-extensions": "~2.3",
"graphp/algorithms": "~0.8.0",
"graphp/graphviz": "~0.2.0",
"guzzlehttp/guzzle": "~6.0",
@@ -81,9 +81,15 @@
"mpdf/mpdf": "6.1.*",
"ocramius/proxy-manager": "~1.0|2.0.*",
"onelogin/php-saml": "^3.0",
+ "packbackbooks/lti-1p3-tool": "^1.1",
"paragonie/random-lib": "2.0.0",
"patchwork/utf8": "~1.2",
"php-ffmpeg/php-ffmpeg": "0.5.1",
+ "php-http/guzzle6-adapter": "^2.0",
+ "php-xapi/client": "0.7.x-dev",
+ "php-xapi/repository-api": "dev-master as 0.3.1",
+ "php-xapi/repository-doctrine": "dev-master",
+ "php-xapi/symfony-serializer": "2.1.0 as 2.0",
"phpmailer/phpmailer": "~6.1",
"phpoffice/phpexcel": "~1.8",
"phpoffice/phpword": "~0.14",
@@ -102,8 +108,8 @@
"symfony/dom-crawler": "~3.4|~4.0",
"symfony/filesystem": "~3.0|~4.0",
"symfony/http-foundation": "~2.8|~3.0",
- "symfony/serializer": "~3.0|~4.0",
"symfony/security": "~3.0|~4.0",
+ "symfony/serializer": "~3.0|~4.0",
"symfony/validator": "~3.0|~4.0",
"symfony/yaml": "~3.0|~4.0",
"szymach/c-pchart": "~3.0",
@@ -111,16 +117,11 @@
"twig/extensions": "~1.0",
"twig/twig": "1.*",
"webit/eval-math": "1.0.1",
- "yuloh/bccomp-polyfill": "dev-master",
+ "yuloh/bccomp-polyfill": "dev-master",
"zendframework/zend-config": "~3.0",
"zendframework/zend-feed": "~2.6|^3.0",
"zendframework/zend-http": "~2.6|^3.0",
- "zendframework/zend-soap": "~2.6|^3.0",
- "php-http/guzzle6-adapter": "^2.0",
- "php-xapi/client": "0.7.x-dev",
- "php-xapi/repository-api": "dev-master as 0.3.1",
- "php-xapi/repository-doctrine": "dev-master",
- "php-xapi/symfony-serializer": "2.1.0 as 2.0"
+ "zendframework/zend-soap": "~2.6|^3.0"
},
"require-dev": {
"behat/behat": "~3.5",
diff --git a/plugin/lti_provider/Entity/Platform.php b/plugin/lti_provider/Entity/Platform.php
new file mode 100755
index 0000000000..1dcbacc1f9
--- /dev/null
+++ b/plugin/lti_provider/Entity/Platform.php
@@ -0,0 +1,247 @@
+id;
+ }
+
+ /**
+ * Set id.
+ *
+ * @param int $id
+ *
+ * @return Platform
+ */
+ public function setId(int $id): Platform
+ {
+ $this->id = $id;
+
+ return $this;
+ }
+
+ /**
+ *
+ * Get kid.
+ *
+ * @return string
+ */
+ public function getKid(): string
+ {
+ return $this->kid;
+ }
+
+ /**
+ * Set kid.
+ *
+ * @param string $kid
+ */
+ public function setKid(string $kid)
+ {
+ $this->kid = $kid;
+
+ return $this;
+ }
+
+ /**
+ * Get Issuer
+ *
+ * @return string
+ */
+ public function getIssuer(): string
+ {
+ return $this->issuer;
+ }
+
+ /**
+ * Set issuer
+ *
+ * @param string $issuer
+ */
+ public function setIssuer(string $issuer)
+ {
+ $this->issuer = $issuer;
+
+ return $this;
+ }
+
+ /**
+ * Get client ID
+ *
+ * @return string
+ */
+ public function getClientId(): string
+ {
+ return $this->clientId;
+ }
+
+ /**
+ * Set client ID
+ *
+ * @param string $clientId
+ */
+ public function setClientId(string $clientId)
+ {
+ $this->clientId = $clientId;
+
+ return $this;
+ }
+
+ /**
+ * Get auth login URL
+ *
+ * @return string
+ */
+ public function getAuthLoginUrl(): string
+ {
+ return $this->authLoginUrl;
+ }
+
+ /**
+ * Set auth login URL
+ *
+ * @param string $authLoginUrl
+ */
+ public function setAuthLoginUrl(string $authLoginUrl)
+ {
+ $this->authLoginUrl = $authLoginUrl;
+
+ return $this;
+ }
+
+ /**
+ * Get auth token URL
+ *
+ * @return string
+ */
+ public function getAuthTokenUrl(): string
+ {
+ return $this->authTokenUrl;
+ }
+
+ /**
+ * Set auth token URL
+ *
+ * @param string $authTokenUrl
+ */
+ public function setAuthTokenUrl(string $authTokenUrl)
+ {
+ $this->authTokenUrl = $authTokenUrl;
+
+ return $this;
+ }
+
+ /**
+ * Get key set URL
+ *
+ * @return string
+ */
+ public function getKeySetUrl(): string
+ {
+ return $this->keySetUrl;
+ }
+
+ /**
+ * Set key set URL
+ *
+ * @param string $keySetUrl
+ */
+ public function setKeySetUrl(string $keySetUrl)
+ {
+ $this->keySetUrl = $keySetUrl;
+
+ return $this;
+ }
+
+ /**
+ * Get Deployment ID
+ *
+ * @return string
+ */
+ public function getDeploymentId(): string
+ {
+ return $this->deploymentId;
+ }
+
+ /**
+ * Set Deployment ID
+ *
+ * @param string $deploymentId
+ */
+ public function setDeploymentId(string $deploymentId)
+ {
+ $this->deploymentId = $deploymentId;
+
+ return $this;
+ }
+}
diff --git a/plugin/lti_provider/Entity/PlatformKey.php b/plugin/lti_provider/Entity/PlatformKey.php
new file mode 100755
index 0000000000..8bcfb0db35
--- /dev/null
+++ b/plugin/lti_provider/Entity/PlatformKey.php
@@ -0,0 +1,129 @@
+id;
+ }
+
+ /**
+ * Set id.
+ */
+ public function setId(int $id): PlatformKey
+ {
+ $this->id = $id;
+
+ return $this;
+ }
+
+ /**
+ *
+ * Get kid.
+ *
+ * @return string
+ */
+ public function getKid(): string
+ {
+ return $this->kid;
+ }
+
+ /**
+ * Set kid.
+ *
+ * @param string $kid
+ */
+ public function setKid(string $kid)
+ {
+ $this->kid = $kid;
+
+ return $this;
+ }
+
+ /**
+ * Get privateKey.
+ *
+ * @return string
+ */
+ public function getPrivateKey(): string
+ {
+ return $this->privateKey;
+ }
+
+ /**
+ * Set privateKey.
+ *
+ * @param string $privateKey
+ *
+ * @return PlatformKey
+ */
+ public function setPrivateKey(string $privateKey): PlatformKey
+ {
+ $this->privateKey = $privateKey;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPublicKey(): string
+ {
+ return $this->publicKey;
+ }
+
+ /**
+ * @param string $publicKey
+ */
+ public function setPublicKey(string $publicKey)
+ {
+ $this->publicKey = $publicKey;
+
+ return $this;
+ }
+}
diff --git a/plugin/lti_provider/LtiProviderPlugin.php b/plugin/lti_provider/LtiProviderPlugin.php
new file mode 100644
index 0000000000..b3a5b5b3cd
--- /dev/null
+++ b/plugin/lti_provider/LtiProviderPlugin.php
@@ -0,0 +1,312 @@
+
+ */
+class LtiProviderPlugin extends Plugin
+{
+
+ const TABLE_PLATFORM = 'plugin_lti_provider_platform';
+
+ public $isAdminPlugin = true;
+
+ protected function __construct()
+ {
+ $version = '1.0';
+ $author = 'Christian Beeznest';
+
+ $message = Display::return_message($this->get_lang('Description'));
+
+ if ($this->areTablesCreated()) {
+ $publicKey = $this->getPublicKey();
+
+ $pkHtml = '
';
+ } else {
+ $pkHtml = $this->get_lang('GenerateKeyPairInfo');
+ }
+
+ $settings = [
+ $message => 'html',
+ 'name' => 'text',
+ 'launch_url' => 'text',
+ 'login_url' => 'text',
+ 'redirect_url' => 'text',
+ $pkHtml => 'html',
+ 'enabled' => 'boolean',
+ ];
+ parent::__construct($version, $author, $settings);
+
+ }
+
+ /**
+ * Get the public key
+ *
+ * @return string
+ */
+ public function getPublicKey(): string
+ {
+ $publicKey = '';
+ $platformKey = Database::getManager()
+ ->getRepository('ChamiloPluginBundle:LtiProvider\PlatformKey')
+ ->findOneBy([]);
+
+ if ($platformKey) {
+ $publicKey = $platformKey->getPublicKey();
+ }
+
+ return $publicKey;
+ }
+
+ /**
+ * Get the class instance
+ *
+ * @staticvar LtiProviderPlugin $result
+ * @return LtiProviderPlugin
+ */
+ public static function create(): LtiProviderPlugin
+ {
+ static $result = null;
+
+ return $result ?: $result = new self();
+ }
+
+ public static function isInstructor()
+ {
+ api_is_allowed_to_edit(false, true);
+ }
+
+ /**
+ * Get the plugin directory name
+ */
+ public function get_name(): string
+ {
+ return 'lti_provider';
+ }
+
+ /**
+ * Install the plugin. Setup the database
+ */
+ public function install()
+ {
+ $pluginEntityPath = $this->getEntityPath();
+
+ if (!is_dir($pluginEntityPath)) {
+ if (!is_writable(dirname($pluginEntityPath))) {
+ $message = get_lang('ErrorCreatingDir').': '.$pluginEntityPath;
+ Display::addFlash(Display::return_message($message, 'error'));
+
+ return;
+ }
+
+ mkdir($pluginEntityPath, api_get_permissions_for_new_directories());
+ }
+
+ $fs = new Filesystem();
+ $fs->mirror(__DIR__.'/Entity/', $pluginEntityPath, null, ['override']);
+
+ $this->createPluginTables();
+ }
+
+ /**
+ * @return string
+ */
+ public function getEntityPath(): string
+ {
+ return api_get_path(SYS_PATH).'src/Chamilo/PluginBundle/Entity/'.$this->getCamelCaseName();
+ }
+
+ /**
+ * Creates the plugin tables on database
+ *
+ * @return void
+ */
+ private function createPluginTables(): void
+ {
+ if ($this->areTablesCreated()) {
+ return;
+ }
+
+ $queries = [
+ "CREATE TABLE plugin_lti_provider_platform (
+ id int NOT NULL AUTO_INCREMENT,
+ issuer varchar(255) NOT NULL,
+ client_id varchar(255) NOT NULL,
+ kid varchar(255) NOT NULL,
+ auth_login_url varchar(255) NOT NULL,
+ auth_token_url varchar(255) NOT NULL,
+ key_set_url varchar(255) NOT NULL,
+ deployment_id varchar(255) NOT NULL,
+ PRIMARY KEY(id)
+ ) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB",
+ "CREATE TABLE plugin_lti_provider_platform_key (
+ id INT AUTO_INCREMENT NOT NULL,
+ kid VARCHAR(255) NOT NULL,
+ public_key LONGTEXT NOT NULL,
+ private_key LONGTEXT NOT NULL,
+ PRIMARY KEY(id)
+ ) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB",
+ ];
+
+ foreach ($queries as $query) {
+ Database::query($query);
+ }
+
+ }
+
+ private function areTablesCreated(): bool
+ {
+ $entityManager = Database::getManager();
+ $connection = $entityManager->getConnection();
+
+ return $connection->getSchemaManager()->tablesExist(self::TABLE_PLATFORM);
+ }
+
+ /**
+ * Save configuration for plugin.
+ *
+ * Generate a new key pair for platform when enabling plugin.
+ *
+ * @throws OptimisticLockException
+ * @throws \Doctrine\ORM\ORMException
+ *
+ * @return $this|Plugin
+ */
+ public function performActionsAfterConfigure()
+ {
+ $em = Database::getManager();
+
+ /** @var PlatformKey $platformKey */
+ $platformKey = $em
+ ->getRepository('ChamiloPluginBundle:LtiProvider\PlatformKey')
+ ->findOneBy([]);
+
+ if ($this->get('enabled') === 'true') {
+ if (!$platformKey) {
+ $platformKey = new PlatformKey();
+ }
+
+ $keyPair = self::generatePlatformKeys();
+
+ $platformKey->setKid($keyPair['kid']);
+ $platformKey->publicKey = $keyPair['public'];
+ $platformKey->setPrivateKey($keyPair['private']);
+
+ $em->persist($platformKey);
+ } else {
+ if ($platformKey) {
+ $em->remove($platformKey);
+ }
+ }
+
+ $em->flush();
+
+ return $this;
+ }
+
+ /**
+ * Generate a key pair and key id for the platform.
+ *
+ * Rerturn a associative array like ['kid' => '...', 'private' => '...', 'public' => '...'].
+ *
+ * @return array
+ */
+ private static function generatePlatformKeys(): array
+ {
+ // Create the private and public key
+ $res = openssl_pkey_new(
+ [
+ 'digest_alg' => 'sha256',
+ 'private_key_bits' => 2048,
+ 'private_key_type' => OPENSSL_KEYTYPE_RSA,
+ ]
+ );
+
+ // Extract the private key from $res to $privateKey
+ $privateKey = '';
+ openssl_pkey_export($res, $privateKey);
+
+ // Extract the public key from $res to $publicKey
+ $publicKey = openssl_pkey_get_details($res);
+
+ return [
+ 'kid' => bin2hex(openssl_random_pseudo_bytes(10)),
+ 'private' => $privateKey,
+ 'public' => $publicKey["key"],
+ ];
+ }
+
+ /**
+ * Unistall plugin. Clear the database
+ */
+ public function uninstall()
+ {
+ $pluginEntityPath = $this->getEntityPath();
+ $fs = new Filesystem();
+
+ if ($fs->exists($pluginEntityPath)) {
+ $fs->remove($pluginEntityPath);
+ }
+
+ try {
+ $this->dropPluginTables();
+ } catch (DBALException $e) {
+ error_log('Error while uninstalling IMS/LTI plugin: '.$e->getMessage());
+ }
+ }
+
+ /**
+ * Drops the plugin tables on database
+ *
+ * @return boolean
+ */
+ private function dropPluginTables(): bool
+ {
+ Database::query("DROP TABLE IF EXISTS plugin_lti_provider_platform");
+ Database::query("DROP TABLE IF EXISTS plugin_lti_provider_platform_key");
+
+ return true;
+ }
+
+ /**
+ * @param array $params
+ */
+ public function trimParams(array &$params)
+ {
+ foreach ($params as $key => $value) {
+ $newValue = preg_replace('/\s+/', ' ', $value);
+ $params[$key] = trim($newValue);
+ }
+ }
+
+ /**
+ * @throws Exception
+ *
+ * @return null|SimpleXMLElement
+ */
+ private function getRequestXmlElement(): ?SimpleXMLElement
+ {
+ $request = file_get_contents("php://input");
+
+ if (empty($request)) {
+ return null;
+ }
+
+ return new SimpleXMLElement($request);
+ }
+
+}
diff --git a/plugin/lti_provider/README.md b/plugin/lti_provider/README.md
new file mode 100644
index 0000000000..a206eb5960
--- /dev/null
+++ b/plugin/lti_provider/README.md
@@ -0,0 +1,43 @@
+Lti/Provider plugin
+===
+
+Version 1.0
+
+> This plugin is meant to be later integrated into Chamilo (in a major version
+release).
+
+Lti provider is compatible only for Lti 1.3 Advance, defines the possibility to integrate tools or content into Platforms LMS.
+In this case Chamilo is used as provider , this plugin allows a student inside a course to play in a breakout game with with certain difficulty options (Deep Linkings) which is scored (Assigment and Grade Services) and comparing with the members of the course (NRP Services).
+
+# Installation
+
+1. Install the plugin from the Plugins page
+2. Enable the plugin from the Lti Provider Plugin Settings page
+3. Assign to the Administrator region (in the regions management page)
+4. Add the LTI connection details to try out this app (Configure page)
+5. To configure the Platforms LMS for registration and deployment
+
+# DB tables
+
+## v1.0
+```sql
+CREATE TABLE plugin_lti_provider_platform (
+ id int NOT NULL AUTO_INCREMENT,
+ issuer varchar(255) NOT NULL,
+ client_id varchar(255) NOT NULL,
+ kid int(255) NOT NULL,
+ auth_login_url varchar(255) NOT NULL,
+ auth_token_url varchar(255) NOT NULL,
+ key_set_url varchar(255) NOT NULL,
+ deployment_id varchar(255) NOT NULL,
+ PRIMARY KEY(id)
+) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB;
+
+CREATE TABLE plugin_lti_provider_platform_key (
+ id INT AUTO_INCREMENT NOT NULL,
+ kid VARCHAR(255) NOT NULL,
+ public_key LONGTEXT NOT NULL,
+ private_key LONGTEXT NOT NULL,
+ PRIMARY KEY(id)
+) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB;
+```
diff --git a/plugin/lti_provider/admin.php b/plugin/lti_provider/admin.php
new file mode 100644
index 0000000000..086c681bbf
--- /dev/null
+++ b/plugin/lti_provider/admin.php
@@ -0,0 +1,34 @@
+get('enabled') !== 'true') {
+ api_not_allowed(true);
+}
+
+$em = Database::getManager();
+
+$platforms = $em->getRepository('ChamiloPluginBundle:LtiProvider\Platform')->findAll();
+
+$interbreadcrumb[] = ['url' => api_get_path(WEB_CODE_PATH).'admin/index.php', 'name' => get_lang('PlatformAdmin')];
+
+$htmlHeadXtra[] = api_get_css(
+ api_get_path(WEB_PLUGIN_PATH).'lti_provider/assets/style.css'
+);
+
+$template = new Template($plugin->get_title());
+$template->assign('platforms', $platforms);
+
+$content = $template->fetch('lti_provider/view/admin.tpl');
+
+$template->assign('header', $plugin->get_title());
+$template->assign('content', $content);
+$template->display_one_col_template();
diff --git a/plugin/lti_provider/create.php b/plugin/lti_provider/create.php
new file mode 100644
index 0000000000..c1a13eda33
--- /dev/null
+++ b/plugin/lti_provider/create.php
@@ -0,0 +1,57 @@
+build();
+
+if ($form->validate()) {
+ $formValues = $form->exportValues();
+ $platform = new Platform();
+ $platform->setIssuer($formValues['issuer']);
+ $platform->setClientId($formValues['client_id']);
+ $platform->setAuthLoginUrl($formValues['auth_login_url']);
+ $platform->setAuthTokenUrl($formValues['auth_token_url']);
+ $platform->setKeySetUrl($formValues['key_set_url']);
+ $platform->setDeploymentId($formValues['deployment_id']);
+ $platform->setKid($formValues['kid']);
+
+ $em->persist($platform);
+ $em->flush();
+
+ Display::addFlash(
+ Display::return_message($plugin->get_lang('PlatformConnectionAdded'), 'success')
+ );
+
+ header('Location: '.api_get_path(WEB_PLUGIN_PATH).'lti_provider/admin.php');
+ exit;
+}
+
+$form->setDefaultValues();
+
+$interbreadcrumb[] = ['url' => api_get_path(WEB_CODE_PATH).'admin/index.php', 'name' => get_lang('PlatformAdmin')];
+$interbreadcrumb[] = ['url' => api_get_path(WEB_PLUGIN_PATH).'lti_provider/admin.php', 'name' => $plugin->get_title()];
+
+$pageTitle = $plugin->get_lang('AddPlatform');
+
+$template = new Template($pageTitle);
+$template->assign('form', $form->returnForm());
+
+$content = $template->fetch('lti_provider/view/add.tpl');
+
+$template->assign('header', $pageTitle);
+$template->assign('content', $content);
+$template->display_one_col_template();
diff --git a/plugin/lti_provider/db/lti13_cache.php b/plugin/lti_provider/db/lti13_cache.php
new file mode 100644
index 0000000000..8630b9a4d6
--- /dev/null
+++ b/plugin/lti_provider/db/lti13_cache.php
@@ -0,0 +1,59 @@
+loadCache();
+
+ return $this->cache[$key];
+ }
+
+ private function loadCache()
+ {
+ $cache = file_get_contents(sys_get_temp_dir().'/lti_cache.txt');
+ if (empty($cache)) {
+ file_put_contents(sys_get_temp_dir().'/lti_cache.txt', '{}');
+ $this->cache = [];
+ }
+ $this->cache = json_decode($cache, true);
+ }
+
+ public function cacheLaunchData($key, $jwtBody): Lti13Cache
+ {
+ $this->cache[$key] = $jwtBody;
+ $this->saveCache();
+
+ return $this;
+ }
+
+ private function saveCache()
+ {
+ file_put_contents(sys_get_temp_dir().'/lti_cache.txt', json_encode($this->cache));
+ }
+
+ public function cacheNonce($nonce): Lti13Cache
+ {
+ $this->cache['nonce'][$nonce] = true;
+ $this->saveCache();
+
+ return $this;
+ }
+
+ public function checkNonce($nonce): bool
+ {
+ $this->loadCache();
+ if (!isset($this->cache['nonce'][$nonce])) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/plugin/lti_provider/db/lti13_cookie.php b/plugin/lti_provider/db/lti13_cookie.php
new file mode 100644
index 0000000000..c1811547fb
--- /dev/null
+++ b/plugin/lti_provider/db/lti13_cookie.php
@@ -0,0 +1,41 @@
+ time() + $exp,
+ ];
+
+ // SameSite none and secure will be required for tools to work inside iframes
+ $sameSiteOptions = [
+ 'samesite' => 'None',
+ 'secure' => false,
+ 'httponly' => true,
+ ];
+
+ setcookie($name, $value, array_merge($cookieOptions, $sameSiteOptions, $options));
+
+ // Set a second fallback cookie in the event that "SameSite" is not supported
+ setcookie("LEGACY_".$name, $value, array_merge($cookieOptions, $options));
+
+ return $this;
+ }
+}
diff --git a/plugin/lti_provider/db/lti13_database.php b/plugin/lti_provider/db/lti13_database.php
new file mode 100644
index 0000000000..f746343382
--- /dev/null
+++ b/plugin/lti_provider/db/lti13_database.php
@@ -0,0 +1,76 @@
+getLtiConnection();
+ if (empty($ltiCustomers[$iss])) {
+ return false;
+ }
+
+ if (!isset($clientId)) {
+ $clientId = $ltiCustomers[$iss]['client_id'];
+ }
+
+ return LtiRegistration::new()
+ ->setAuthLoginUrl($ltiCustomers[$iss]['auth_login_url'])
+ ->setAuthTokenUrl($ltiCustomers[$iss]['auth_token_url'])
+ ->setClientId($clientId)
+ ->setKeySetUrl($ltiCustomers[$iss]['key_set_url'])
+ ->setKid($ltiCustomers[$iss]['kid'])
+ ->setIssuer($iss)
+ ->setToolPrivateKey($this->getPrivateKey());
+ }
+
+ private function getLtiConnection(): array
+ {
+ $em = Database::getManager();
+ $platforms = $em->getRepository('ChamiloPluginBundle:LtiProvider\Platform')->findAll();
+
+ $ltiCustomers = [];
+ foreach ($platforms as $platform) {
+ $issuer = $platform->getIssuer();
+ $ltiCustomers[$issuer] = [
+ 'client_id' => $platform->getClientId(),
+ 'auth_login_url' => $platform->getAuthLoginUrl(),
+ 'auth_token_url' => $platform->getAuthTokenUrl(),
+ 'key_set_url' => $platform->getKeySetUrl(),
+ 'kid' => $platform->getKid(),
+ 'deployment' => [$platform->getDeploymentId()],
+ ];
+ }
+ Session::write('iss', $ltiCustomers);
+
+ return $ltiCustomers;
+ }
+
+ private function getPrivateKey()
+ {
+ $privateKey = '';
+ $platformKey = Database::getManager()
+ ->getRepository('ChamiloPluginBundle:LtiProvider\PlatformKey')
+ ->findOneBy([]);
+ if ($platformKey) {
+ $privateKey = $platformKey->getPrivateKey();
+ }
+
+ return $privateKey;
+ }
+
+ public function findDeployment($iss, $deploymentId, $clientId = null)
+ {
+ if (!in_array($deploymentId, $_SESSION['iss'][$iss]['deployment'])) {
+ return false;
+ }
+
+ return LtiDeployment::new()->setDeploymentId($deploymentId);
+ }
+}
diff --git a/plugin/lti_provider/delete.php b/plugin/lti_provider/delete.php
new file mode 100644
index 0000000000..a8cfff1b8e
--- /dev/null
+++ b/plugin/lti_provider/delete.php
@@ -0,0 +1,32 @@
+find('ChamiloPluginBundle:LtiProvider\Platform', $platformId);
+
+$em->remove($platform);
+$em->flush();
+
+Display::addFlash(
+ Display::return_message($plugin->get_lang('PlatformDeleted'), 'success')
+);
+
+header('Location: '.api_get_path(WEB_PLUGIN_PATH).'lti_provider/admin.php');
+exit;
diff --git a/plugin/lti_provider/edit.php b/plugin/lti_provider/edit.php
new file mode 100644
index 0000000000..0d151eb58b
--- /dev/null
+++ b/plugin/lti_provider/edit.php
@@ -0,0 +1,71 @@
+find('ChamiloPluginBundle:LtiProvider\Platform', $platformId);
+
+if (!$platform) {
+ Display::addFlash(
+ Display::return_message($plugin->get_lang('NoPlatform'), 'error')
+ );
+ header('Location: '.api_get_path(WEB_PLUGIN_PATH).'lti_provider/admin.php');
+ exit;
+}
+
+$form = new FrmEdit('lti_provider_edit_platform', [], $platform);
+$form->build();
+
+if ($form->validate()) {
+ $formValues = $form->exportValues();
+
+ $platform->setIssuer($formValues['issuer']);
+ $platform->setClientId($formValues['client_id']);
+ $platform->setAuthLoginUrl($formValues['auth_login_url']);
+ $platform->setAuthTokenUrl($formValues['auth_token_url']);
+ $platform->setKeySetUrl($formValues['key_set_url']);
+ $platform->setDeploymentId($formValues['deployment_id']);
+ $platform->setKid($formValues['kid']);
+
+ $em->persist($platform);
+ $em->flush();
+
+ Display::addFlash(
+ Display::return_message($plugin->get_lang('PlatformEdited'), 'success')
+ );
+
+ header('Location: '.api_get_path(WEB_PLUGIN_PATH).'lti_provider/admin.php');
+ exit;
+} else {
+ $form->setDefaultValues();
+}
+
+$interbreadcrumb[] = ['url' => api_get_path(WEB_CODE_PATH).'admin/index.php', 'name' => get_lang('PlatformAdmin')];
+$interbreadcrumb[] = ['url' => api_get_path(WEB_PLUGIN_PATH).'lti_provider/admin.php', 'name' => $plugin->get_title()];
+
+$template = new Template($plugin->get_lang('EditPlatform'));
+$template->assign('form', $form->returnForm());
+
+$content = $template->fetch('lti_provider/view/add.tpl');
+
+$template->assign('header', $plugin->get_title());
+$template->assign('content', $content);
+$template->display_one_col_template();
diff --git a/plugin/lti_provider/install.php b/plugin/lti_provider/install.php
new file mode 100644
index 0000000000..c37fcaf320
--- /dev/null
+++ b/plugin/lti_provider/install.php
@@ -0,0 +1,16 @@
+install();
diff --git a/plugin/lti_provider/lang/english.php b/plugin/lti_provider/lang/english.php
new file mode 100644
index 0000000000..cdaf848376
--- /dev/null
+++ b/plugin/lti_provider/lang/english.php
@@ -0,0 +1,31 @@
+get_info();
diff --git a/plugin/lti_provider/provider_settings.php b/plugin/lti_provider/provider_settings.php
new file mode 100644
index 0000000000..d2ea628212
--- /dev/null
+++ b/plugin/lti_provider/provider_settings.php
@@ -0,0 +1,63 @@
+getPublicKey();
+
+try {
+ if ($enabled !== 'true') {
+ throw new Exception(get_lang('Forbidden'));
+ }
+
+
+ $html = ''
+ .'
'.$plugin->get_lang('Name').'
'
+ .'
'.$name.'
'
+ .'
'
+ .''
+ .'
'.$plugin->get_lang('LaunchUrl').'
'
+ .'
'.$launchUrl.'
'
+ .'
'
+ .''
+ .'
'.$plugin->get_lang('LoginUrl').'
'
+ .'
'.$loginUrl.'
'
+ .'
'
+ .''
+ .'
'.$plugin->get_lang('RedirectUrl').'
'
+ .'
'.$redirectUrl.'
'
+ .'
'
+ .''
+ .'
'.$plugin->get_lang('PublicKey').'
'
+ .'
'
+ .'
';
+
+ $response->setContent($html);
+} catch (Exception $exception) {
+ $response->setContent(
+ Display::return_message($exception->getMessage(), 'error')
+ );
+}
+
+$response->send();
diff --git a/plugin/lti_provider/src/Form/FrmAdd.php b/plugin/lti_provider/src/Form/FrmAdd.php
new file mode 100644
index 0000000000..92d8320697
--- /dev/null
+++ b/plugin/lti_provider/src/Form/FrmAdd.php
@@ -0,0 +1,81 @@
+platform = $platform;
+ }
+
+ /**
+ * Build the form
+ */
+ public function build()
+ {
+ $plugin = LtiProviderPlugin::create();
+
+ $this->addHeader($plugin->get_lang('ConnectionDetails'));
+
+ $this->addText('issuer', $plugin->get_lang('PlatformName'));
+ $this->addUrl('auth_login_url', $plugin->get_lang('AuthLoginUrl'));
+ $this->addUrl('auth_token_url', $plugin->get_lang('AuthTokenUrl'));
+ $this->addUrl('key_set_url', $plugin->get_lang('KeySetUrl'));
+ $this->addText('client_id', $plugin->get_lang('ClientId'));
+ $this->addText('deployment_id', $plugin->get_lang('DeploymentId'));
+ $this->addText('kid', $plugin->get_lang('KeyId'));
+
+ $this->addButtonCreate($plugin->get_lang('AddPlatform'));
+ $this->applyFilter('__ALL__', 'trim');
+
+ }
+
+ public function setDefaultValues()
+ {
+ $defaults = [];
+
+ if (!$this->platform) {
+ $this->platform = new Platform();
+ }
+
+ $defaults['issuer'] = $this->platform->getIssuer();
+ $defaults['auth_login_url'] = $this->platform->getAuthLoginUrl();
+ $defaults['auth_token_url'] = $this->platform->getAuthTokenUrl();
+ $defaults['key_set_url'] = $this->platform->getKeySetUrl();
+ $defaults['client_id'] = $this->platform->getClientId();
+ $defaults['deployment_id'] = $this->platform->getDeploymentId();
+ $defaults['kid'] = $this->platform->getKid();
+
+ $this->setDefaults($defaults);
+ }
+
+ public function returnForm(): string
+ {
+ $js = "";
+
+ return $js.parent::returnForm(); // TODO: Change the autogenerated stub
+ }
+}
diff --git a/plugin/lti_provider/src/Form/FrmEdit.php b/plugin/lti_provider/src/Form/FrmEdit.php
new file mode 100644
index 0000000000..5e8116cc62
--- /dev/null
+++ b/plugin/lti_provider/src/Form/FrmEdit.php
@@ -0,0 +1,75 @@
+platform = $platform;
+ }
+
+ /**
+ * Build the form.
+ *
+ * @param bool $globalMode
+ *
+ * @throws Exception
+ */
+ public function build(bool $globalMode = true)
+ {
+ $plugin = LtiProviderPlugin::create();
+ $this->addHeader($plugin->get_lang('ConnectionDetails'));
+
+ $this->addText('issuer', $plugin->get_lang('PlatformName'));
+ $this->addUrl('auth_login_url', $plugin->get_lang('AuthLoginUrl'));
+ $this->addUrl('auth_token_url', $plugin->get_lang('AuthTokenUrl'));
+ $this->addUrl('key_set_url', $plugin->get_lang('KeySetUrl'));
+ $this->addText('client_id', $plugin->get_lang('ClientId'));
+ $this->addText('deployment_id', $plugin->get_lang('DeploymentId'));
+ $this->addText('kid', $plugin->get_lang('KeyId'));
+
+ $this->addButtonCreate($plugin->get_lang('EditPlatform'));
+ $this->addHidden('id', $this->platform->getId());
+ $this->addHidden('action', 'edit');
+ $this->applyFilter('__ALL__', 'trim');
+ }
+
+ /**
+ * @throws Exception
+ */
+ public function setDefaultValues()
+ {
+ $defaults = [];
+ $defaults['issuer'] = $this->platform->getIssuer();
+ $defaults['auth_login_url'] = $this->platform->getAuthLoginUrl();
+ $defaults['auth_token_url'] = $this->platform->getAuthTokenUrl();
+ $defaults['key_set_url'] = $this->platform->getKeySetUrl();
+ $defaults['client_id'] = $this->platform->getClientId();
+ $defaults['deployment_id'] = $this->platform->getDeploymentId();
+ $defaults['kid'] = $this->platform->getKid();
+
+ $this->setDefaults($defaults);
+ }
+}
diff --git a/plugin/lti_provider/src/LtiProvider.php b/plugin/lti_provider/src/LtiProvider.php
new file mode 100644
index 0000000000..e9981902b3
--- /dev/null
+++ b/plugin/lti_provider/src/LtiProvider.php
@@ -0,0 +1,65 @@
+doOidcLoginRedirect(api_get_path(WEB_PLUGIN_PATH)."lti_provider/web/game.php", $request)
+ ->doRedirect();
+ }
+
+ /**
+ * Lti Message Launch
+ *
+ * @param bool $fromCache
+ * @param null $launchId
+ *
+ * @throws Lti1p3\LtiException
+ *
+ * @return LtiMessageLaunch
+ */
+ public function launch(bool $fromCache = false, $launchId = null): LtiMessageLaunch
+ {
+ if ($fromCache) {
+ $launch = LtiMessageLaunch::fromCache($launchId, new Lti13Database(), new Lti13Cache());
+ } else {
+ $launch = LtiMessageLaunch::new(new Lti13Database(), new Lti13Cache(), new Lti13Cookie)->validate();
+ }
+
+ return $launch;
+ }
+
+}
diff --git a/plugin/lti_provider/uninstall.php b/plugin/lti_provider/uninstall.php
new file mode 100644
index 0000000000..db390eada4
--- /dev/null
+++ b/plugin/lti_provider/uninstall.php
@@ -0,0 +1,16 @@
+uninstall();
diff --git a/plugin/lti_provider/view/add.tpl b/plugin/lti_provider/view/add.tpl
new file mode 100644
index 0000000000..361c736d0f
--- /dev/null
+++ b/plugin/lti_provider/view/add.tpl
@@ -0,0 +1,5 @@
+
diff --git a/plugin/lti_provider/view/admin.tpl b/plugin/lti_provider/view/admin.tpl
new file mode 100644
index 0000000000..80da0feed7
--- /dev/null
+++ b/plugin/lti_provider/view/admin.tpl
@@ -0,0 +1,52 @@
+
+{{ 'LtiProviderDescription'|get_plugin_lang('LtiProviderPlugin') }}
+
+
+
+
+
diff --git a/plugin/lti_provider/web/api/score.php b/plugin/lti_provider/web/api/score.php
new file mode 100644
index 0000000000..2738b0776e
--- /dev/null
+++ b/plugin/lti_provider/web/api/score.php
@@ -0,0 +1,49 @@
+launch(true, $_REQUEST['launch_id']);
+
+if (!$launch->hasAgs()) {
+ throw new Exception("Don't have grades!");
+}
+
+$grades = $launch->getAgs();
+
+$score = Packback\Lti1p3\LtiGrade::new()
+ ->setScoreGiven($_REQUEST['score'])
+ ->setScoreMaximum(100)
+ ->setTimestamp(date(DateTime::ISO8601))
+ ->setActivityProgress('Completed')
+ ->setGradingProgress('FullyGraded')
+ ->setUserId($launch->getLaunchData()['sub']);
+
+
+$scoreLineitem = Packback\Lti1p3\LtiLineitem::new()
+ ->setTag('score')
+ ->setScoreMaximum(100)
+ ->setLabel('Score')
+ ->setResourceId($launch->getLaunchData()['https://purl.imsglobal.org/spec/lti/claim/resource_link']['id']);
+
+$grades->putGrade($score, $scoreLineitem);
+
+
+$time = Packback\Lti1p3\LtiGrade::new()
+ ->setScoreGiven($_REQUEST['time'])
+ ->setScoreMaximum(999)
+ ->setTimestamp(DateTime::ISO8601)
+ ->setActivityProgress('Completed')
+ ->setGradingProgress('FullyGraded')
+ ->setUserId($launch->getLaunchData()['sub']);
+
+$timeLineitem = Packback\Lti1p3\LtiLineitem::new()
+ ->setTag('time')
+ ->setScoreMaximum(999)
+ ->setLabel('Time Taken')
+ ->setResourceId('time'.$launch->getLaunchData()['https://purl.imsglobal.org/spec/lti/claim/resource_link']['id']);
+
+$grades->putGrade($time, $timeLineitem);
+
+echo '{"success" : true}';
\ No newline at end of file
diff --git a/plugin/lti_provider/web/api/scoreboard.php b/plugin/lti_provider/web/api/scoreboard.php
new file mode 100644
index 0000000000..f575279c08
--- /dev/null
+++ b/plugin/lti_provider/web/api/scoreboard.php
@@ -0,0 +1,51 @@
+launch(true, $_REQUEST['launch_id']);
+
+if (!$launch->hasNrps()) {
+ throw new Exception("Don't have names and roles!");
+}
+if (!$launch->hasAgs()) {
+ throw new Exception("Don't have grades!");
+}
+
+$ags = $launch->getAgs();
+
+$scoreLineitem = Packback\Lti1p3\LtiLineitem::new()
+ ->setTag('score')
+ ->setScoreMaximum(100)
+ ->setLabel('Score')
+ ->setResourceId($launch->getLaunchData()['https://purl.imsglobal.org/spec/lti/claim/resource_link']['id']);
+$scores = $ags->getGrades($scoreLineitem);
+
+$timeLineitem = Packback\Lti1p3\LtiLineitem::new()
+ ->setTag('time')
+ ->setScoreMaximum(999)
+ ->setLabel('Time Taken')
+ ->setResourceId('time'.$launch->getLaunchData()['https://purl.imsglobal.org/spec/lti/claim/resource_link']['id']);
+$times = $ags->getGrades($timeLineitem);
+
+$members = $launch->getNrps()->getMembers();
+
+$scoreboard = [];
+foreach ($scores as $score) {
+ $result = ['score' => $score['resultScore']];
+ foreach ($times as $time) {
+ if ($time['userId'] === $score['userId']) {
+ $result['time'] = $time['resultScore'];
+ break;
+}
+ }
+ foreach ($members as $member) {
+ if ($member['user_id'] === $score['userId']) {
+ $result['name'] = $member['name'];
+ break;
+ }
+ }
+ $scoreboard[] = $result;
+}
+echo json_encode($scoreboard);
\ No newline at end of file
diff --git a/plugin/lti_provider/web/configure.php b/plugin/lti_provider/web/configure.php
new file mode 100644
index 0000000000..b43ef1b54d
--- /dev/null
+++ b/plugin/lti_provider/web/configure.php
@@ -0,0 +1,20 @@
+launch(true, $_REQUEST['launch_id']);
+
+if (!$launch->isDeepLinkLaunch()) {
+ throw new Exception("Must be a deep link!");
+}
+
+$resource = LtiDeepLinkResource::new()
+ ->setUrl(api_get_path(WEB_PLUGIN_PATH)."lti_provider/web/game.php")
+ ->setCustomParams(['difficulty' => $_REQUEST['diff']])
+ ->setTitle('Breakout ' . $_REQUEST['diff'] . ' mode!');
+
+$launch->getDeepLink()
+ ->outputResponseForm([$resource]);
diff --git a/plugin/lti_provider/web/game.php b/plugin/lti_provider/web/game.php
new file mode 100644
index 0000000000..345bf28f23
--- /dev/null
+++ b/plugin/lti_provider/web/game.php
@@ -0,0 +1,32 @@
+launch();
+
+$htmlHeadXtra[] = api_get_css(
+ api_get_path(WEB_PLUGIN_PATH).'lti_provider/web/static/breakout.css'
+);
+
+$template = new Template('Game demo');
+
+$courseCode = $launch->getLaunchData()['https://purl.imsglobal.org/spec/lti/claim/context']['label'];
+$diff = 'normal';
+if ($launch->isDeepLinkLaunch()) {
+ $diff = $launch->getLaunchData()['https://purl.imsglobal.org/spec/lti/claim/custom']['difficulty'];
+}
+$username = $launch->getLaunchData()['given_name'];
+$template->assign('launch', $launch);
+$template->assign('courseCode', $courseCode);
+$template->assign('diff', $diff);
+$template->assign('username', $username);
+
+$content = $template->fetch('lti_provider/web/view/game.tpl');
+$template->assign('content', $content);
+$template->display_no_layout_template();
+
+
+?>
diff --git a/plugin/lti_provider/web/login.php b/plugin/lti_provider/web/login.php
new file mode 100644
index 0000000000..8af2fbdd0d
--- /dev/null
+++ b/plugin/lti_provider/web/login.php
@@ -0,0 +1,8 @@
+login($_REQUEST);
diff --git a/plugin/lti_provider/web/static/breakout.css b/plugin/lti_provider/web/static/breakout.css
new file mode 100644
index 0000000000..db6d8f2c76
--- /dev/null
+++ b/plugin/lti_provider/web/static/breakout.css
@@ -0,0 +1,56 @@
+body {
+ font-family: 'Gugi', cursive;
+}
+#scoreboard {
+ border: solid 1px #000;
+ border-left: none;
+ border-top-right-radius: 20px;
+ border-bottom-right-radius: 20px;
+ padding-bottom: 12px;
+ background: linear-gradient(to bottom, rgb(0, 0, 0), rgb(0, 0, 50) 500px);
+ color: white;
+}
+th, td {
+ color: white;
+}
+.dl-config {
+ font-family: Verdana, Geneva, Tahoma, sans-serif;
+ display: block;
+ width: 400px;
+ position: absolute;
+ left:50%;
+ margin-left: -200px;
+}
+.dl-config h1 {
+ text-align: center;
+}
+.dl-config ul {
+ list-style: none;
+ padding: 0;
+}
+.dl-config ul li a {
+ display: block;
+ width: 200px;
+ height:50px;
+ text-align: center;
+ border: black solid 1px;
+ border-radius: 15px;
+ margin:auto;
+ text-decoration: none;
+ color: black;
+ margin-top:8px;
+ font-size: 30px;
+ line-height: 46px;
+}
+.dl-config ul li a:hover {
+ background-color: #dddddd;
+}
+#game-screen {
+ position: relative;
+ height: 550px;
+ width: 100%;
+}
+#btn-again {
+ right: 0px;
+ position: absolute;
+}
\ No newline at end of file
diff --git a/plugin/lti_provider/web/static/breakout.js b/plugin/lti_provider/web/static/breakout.js
new file mode 100644
index 0000000000..e2d7cbb3d3
--- /dev/null
+++ b/plugin/lti_provider/web/static/breakout.js
@@ -0,0 +1,464 @@
+var c = document.getElementById("breakout");
+var ctx = window.c.getContext("2d");
+
+var bgctx = document.getElementById("breakoutbg").getContext("2d");
+bgctx.beginPath();
+var bggrad = ctx.createLinearGradient(0,0,0,c.height);
+bggrad.addColorStop(0,"rgb(0, 0, 0)");
+bggrad.addColorStop(1,"rgb(0, 0, 50)");
+bgctx.fillStyle = bggrad;
+bgctx.rect(0, 0, c.width, c.height);
+bgctx.fill();
+bgctx.font = "10px Gugi";
+bgctx.fillStyle = '#FFFFFF';
+bgctx.textAlign = "left";
+bgctx.fillText("Powered By Turnitin", 6, c.height-6);
+
+var score = 0;
+
+var difficulty = {
+ hard: {
+ speed_multiplier:1.5
+ },
+ normal: {
+ speed_multiplier:1
+ },
+ easy: {
+ speed_multiplier:0.7
+ },
+}
+
+var ball = {
+ pos : {
+ x: window.c.width/2-200,
+ y : window.c.height/2-2,
+ },
+ vel : {
+ x : 6 * difficulty[curr_diff]['speed_multiplier'],
+ y : 6 * difficulty[curr_diff]['speed_multiplier'],
+ },
+ r: 10,
+ rot: 0,
+ velr: 0,
+ render: function() {
+ this.pos.x += this.vel.x;
+ this.pos.y += this.vel.y;
+ if (this.oob(window.c.width, this.pos.x, this.r)) {
+ this.vel.x = -this.vel.x;
+ this.pos.x += this.vel.x;
+ this.pos.y -= this.vel.y;
+ }
+ if (this.oob(window.c.height, this.pos.y, this.r)) {
+ if (this.pos.y > window.c.height - this.r) {
+ window.endGame();
+ }
+ this.vel.y = -this.vel.y;
+ this.pos.y += this.vel.y;
+ this.pos.x -= this.vel.x;
+ }
+
+ window.ctx.save();
+ window.ctx.beginPath();
+ var gradient = ctx.createRadialGradient(this.pos.x, this.pos.y, 2, this.pos.x, this.pos.y, 10);
+ window.ctx.fillStyle = "rgb(255, 232, 102)";
+ window.ctx.strokeStyle = "rgb(255, 232, 102)";
+ window.ctx.setLineDash([5,5]);
+ window.ctx.lineWidth = 4;
+ window.ctx.translate(this.pos.x, this.pos.y);
+ window.ctx.rotate(this.rot * Math.PI);
+ window.ctx.arc(0, 0, this.r, 0, 2 * Math.PI);
+ if (this.vel.x > 0) {
+ this.velr = 0.01;
+ } else if (this.vel.x < 0) {
+ this.velr = -0.01;
+ } else {
+ this.velr = 0;
+ }
+ this.rot += this.velr;
+ window.ctx.fill();
+ window.ctx.stroke();
+ window.ctx.restore();
+
+ },
+ oob : function(max, curr, offset) {
+ if (curr < offset || curr > (max-offset)) {
+ return true;
+ }
+ },
+ left : function() {
+ return this.pos.x - this.r;
+ },
+ right : function() {
+ return this.pos.x + this.r;
+ },
+ top : function() {
+ return this.pos.y - this.r;
+ },
+ bottom : function() {
+ return this.pos.y + this.r;
+ },
+};
+
+var paddle = {
+ pos : {
+ x: window.c.width/2+2,
+ y : window.c.height-40,
+ },
+ width : 80,
+ height : 20,
+ render: function() {
+ window.ctx.beginPath();
+ var gradient = ctx.createLinearGradient(this.pos.x,this.pos.y,this.pos.x,this.pos.y + this.height);
+ gradient.addColorStop(0,"#999999");
+ gradient.addColorStop(0.7,"#eeeeee");
+ gradient.addColorStop(1,"#999999");
+ ctx.fillStyle = gradient;
+ var hh = this.height/2;
+ window.ctx.arc(this.pos.x + hh, this.pos.y + hh, hh, 0.5 * Math.PI, 1.5 * Math.PI);
+ window.ctx.rect(this.pos.x + hh, this.pos.y, this.width - this.height, this.height);
+ window.ctx.arc(this.pos.x + this.width - hh, this.pos.y + hh, hh, 1.5 * Math.PI, 0.5 * Math.PI);
+ window.ctx.fill();
+ window.ctx.stroke();
+ },
+ left : function() {
+ return this.pos.x;
+ },
+ right : function() {
+ return this.pos.x + this.width;
+ },
+ top : function() {
+ return this.pos.y;
+ },
+ bottom : function() {
+ return this.pos.y + this.height;
+ },
+ test_hit : function() {
+ var hitx = this.test_hit_x();
+ var hity = this.test_hit_y();
+ if (!hitx || !hity) {
+ return 0;
+ }
+ if (hity) {
+ window.ball.vel.y = -Math.abs(window.ball.vel.y);
+ window.ball.pos.y += window.ball.vel.y;
+ window.ball.pos.x -= window.ball.vel.x;
+ }
+ if (hitx) {
+ var xdiff = window.ball.pos.x - (this.pos.x + (this.width/2));
+ window.ball.vel.x = (xdiff > 0 ? Math.ceil(xdiff / 5) : Math.floor(xdiff / 5)) * difficulty[curr_diff]['speed_multiplier'];
+ window.ball.pos.x += window.ball.vel.x;
+ }
+ return 1;
+ },
+ test_hit_x : function() {
+ if (this.left() > window.ball.right()) {
+ return 0;
+ }
+ if (this.right() < window.ball.left()) {
+ return 0;
+ }
+ return 1;
+ },
+ test_hit_y : function() {
+ if (this.top() > window.ball.bottom()) {
+ return 0;
+ }
+ if (this.bottom() < window.ball.top()) {
+ return 0;
+ }
+ return 1;
+ },
+ move : function() {
+ if (window.press_left) {
+ if (this.pos.x > 0) {
+ this.pos.x -= 8;
+ }
+ }
+ if (window.press_right) {
+ if (this.pos.x < window.c.width - this.width) {
+ this.pos.x += 8;
+ }
+ }
+ }
+};
+
+function fire() {
+ this.r = 0,
+ this.a = 0,
+ this.render = function() {
+ if (this.a < 0.2) {
+ this.reset();
+ }
+
+ this.pos.x += this.vel.x + (Math.random() * 2) - 1;
+ this.pos.y += this.vel.y + (Math.random() * 2) - 1;
+
+ this.r *= 0.95;
+ this.a *= 0.95;
+
+ window.ctx.beginPath();
+ window.ctx.fillStyle = 'rgba(' + (239 - this.green) +', ' + this.green + ', 66,'+ this.a +')';
+ window.ctx.arc(this.pos.x, this.pos.y, this.r, 0, 2 * Math.PI);
+ window.ctx.fill();
+
+ if (this.green < 232) {
+ this.green += 8;
+ }
+ },
+ this.reset = function() {
+ this.pos = {
+ x: window.ball.pos.x,
+ y : window.ball.pos.y,
+ };
+ this.vel = {
+ x : (Math.random() * 4) - 2,
+ y : (Math.random() * 4) - 2,
+ };
+ this.r = (Math.random() * 5) + 1;
+ this.a = 0.9;
+ this.green = 62;
+ }
+}
+
+function brick() {
+ this.id = 0,
+ this.pos = {
+ x: 40,
+ y: 40,
+ },
+ this.vely = 0,
+ this.rot = 0,
+ this.velr = 0,
+ this.hit = false,
+ this.last_hitx = false,
+ this.last_hity = false,
+ this.width = 40,
+ this.height = 20,
+ this.render = function() {
+ if (this.hit) {
+ if (this.pos.y > window.c.height + 60) {
+ return;
+ }
+ this.vely++;
+ this.pos.y += this.vely;
+ ctx.save()
+ window.ctx.beginPath();
+ this.rot += this.velr;
+ ctx.translate(this.pos.x + (this.width/2), this.pos.y + (this.height/2));
+ ctx.rotate(this.rot * Math.PI);
+ var gradient = ctx.createRadialGradient(-(this.width/2) +10, -(this.height/2) +5, 0, -(this.width/2) + 40, -(this.height/2) + 15, 40);
+ gradient.addColorStop(0, 'rgba(137, 211, 234, 0.2)');
+ gradient.addColorStop(1, 'rgba(137, 211, 234, 1)');
+ ctx.strokeStyle = 'rgba(254, 254, 254, 0.8)';
+ ctx.fillStyle = gradient;
+ window.ctx.rect(-(this.width/2), -(this.height/2), this.width, this.height);
+ window.ctx.fill();
+ window.ctx.stroke();
+ ctx.restore();
+ return;
+ }
+ window.ctx.beginPath();
+ var gradient = ctx.createRadialGradient(this.pos.x + 10, this.pos.y + 5, 0, this.pos.x + 40, this.pos.y + 15, 40);
+ gradient.addColorStop(0, 'rgba(137, 211, 234, 0.2)');
+ gradient.addColorStop(1, 'rgba(137, 211, 234, 1)');
+ ctx.strokeStyle = 'rgba(254, 254, 254, 0.8)';
+ ctx.fillStyle = gradient;
+ window.ctx.rect(this.pos.x, this.pos.y, this.width, this.height);
+ window.ctx.fill();
+ window.ctx.stroke();
+ },
+ this.test_hit = function() {
+ if (this.hit) {
+ return 0;
+ }
+ var hitx = this.test_hit_x();
+ var hity = this.test_hit_y();
+ if (!hitx || !hity) {
+ this.last_hitx = hitx;
+ this.last_hity = hity;
+ return 0;
+ }
+ if (this.last_hity) {
+ window.ball.vel.y = -window.ball.vel.y;
+ window.ball.pos.y += window.ball.vel.y;
+ window.ball.pos.x -= window.ball.vel.x;
+ }
+ if (this.last_hitx) {
+ window.ball.vel.x = -window.ball.vel.x;
+ window.ball.pos.x += window.ball.vel.x;
+ window.ball.pos.y -= window.ball.vel.y;
+ }
+ if (!this.last_hity && this.last_hitx) {
+ window.ball.vel.x = -window.ball.vel.x;
+ window.ball.pos.x += window.ball.vel.x;
+ window.ball.vel.y = -window.ball.vel.y;
+ window.ball.pos.y += window.ball.vel.y;
+ }
+ this.last_hitx = hitx;
+ this.last_hity = hity;
+ this.hit = true;
+ this.velr = (Math.random() * 0.04) - 0.02;
+ window.score++;
+ return 1;
+ },
+ this.test_hit_x = function() {
+ if (this.left() > window.ball.right()) {
+ return 0;
+ }
+ if (this.right() < window.ball.left()) {
+ return 0;
+ }
+ return 1;
+ },
+ this.test_hit_y = function() {
+ if (this.top() > window.ball.bottom()) {
+ return 0;
+ }
+ if (this.bottom() < window.ball.top()) {
+ return 0;
+ }
+ return 1;
+ },
+
+
+ this.left = function() {
+ return this.pos.x;
+ },
+ this.right = function() {
+ return this.pos.x + this.width;
+ },
+ this.top = function() {
+ return this.pos.y;
+ },
+ this.bottom = function() {
+ return this.pos.y + this.height;
+ }
+};
+
+press_left = false;
+press_right = false;
+
+document.addEventListener('keydown', (event) => {
+ const keyName = event.key;
+ if (keyName == "ArrowLeft") {
+ press_left = true;
+ }
+ if (keyName == "ArrowRight") {
+ press_right = true;
+ }
+ });
+
+document.addEventListener('keyup', (event) => {
+ const keyName = event.key;
+ if (keyName == "ArrowLeft") {
+ press_left = false;
+ }
+ if (keyName == "ArrowRight") {
+ press_right = false;
+ }
+ if (keyName == " ") {
+ if (pause && !gameover) {
+ if (!start_time) {
+ start_time = Math.floor(Date.now() / 1000);
+ }
+ pause = false;
+ frame();
+ } else {
+ pause = true;
+ }
+ }
+});
+
+var bricks = [];
+
+for (var h = 0; h < 6; h++) {
+ for (var w = 0; w < 18; w++) {
+ var brickid = (18*h)+w;
+ bricks[brickid] = new brick();
+ bricks[brickid].pos.x = 40+(w*40);
+ bricks[brickid].pos.y = 40+(h*20);
+ bricks[brickid].id = brickid;
+ }
+}
+var fires = [];
+
+for (var i = 0; i < 80; i++) {
+ fires[i] = new fire();
+}
+startFireCount = 1;
+
+pause = true;
+gameover = false;
+
+var frame = function() {
+ if (window.score >= window.bricks.length) {
+ endGame();
+ }
+ window.ctx.clearRect(0, 0, window.c.width, window.c.height);
+ for (var i = 0; i < window.bricks.length; i++) {
+ window.bricks[i].render();
+ }
+ for (var i = 0; i < window.bricks.length; i++) {
+ if (window.bricks[i].test_hit()) {
+ break;
+ }
+ }
+ for (var i = 0; i < window.fires.length && i < startFireCount; i++) {
+ window.fires[i].render();
+ }
+ if (startFireCount <= window.fires.length) {
+ startFireCount++;
+ }
+ window.paddle.move();
+ window.paddle.render();
+ window.paddle.test_hit();
+ window.ball.render();
+
+ if (pause) {
+ if (!gameover) {
+ ctx.font = "50px Gugi";
+ ctx.fillStyle = '#FFFFFF';
+ ctx.textAlign = "center";
+ ctx.fillText("Ready " + curr_user_name, c.width/2, c.height/2);
+ ctx.fillText("Press Space to Start", c.width/2, c.height/2 + 60);
+ }
+ } else {
+ requestAnimationFrame(frame);
+ }
+}
+
+start_time = false;
+document.fonts.load('50px Gugi').then(frame);
+
+var endGame = function() {
+ window.pause = true;
+ window.gameover = true;
+ window.submitScore();
+}
+
+var refreshScoreBoard = function() {
+ var scores = JSON.parse(this.responseText);
+ console.log(scores);
+ var output = '| Score | Time | Name |
';
+ for (var i = 0; i < scores.length; i++) {
+ output += '| ' + scores[i].score + ' | ' + scores[i].time + 's | ' + scores[i].name + ' |
';
+ }
+ document.getElementById("leadertable").innerHTML = output;
+}
+
+var submitScore = function() {
+ var time_taken = Math.floor(Date.now() / 1000) - start_time;
+ var xhttp = new XMLHttpRequest();
+ xhttp.addEventListener("load", getScoreBoard);
+ xhttp.open("GET", "api/score.php?launch_id=" + launch_id + "&score=" + window.score + "&time=" + time_taken, false);
+ xhttp.send();
+}
+
+var getScoreBoard = function() {
+ var xhttp = new XMLHttpRequest();
+ xhttp.addEventListener("load", refreshScoreBoard);
+ xhttp.open("GET", "api/scoreboard.php?launch_id=" + launch_id, true);
+ xhttp.send();
+}
+
+getScoreBoard();
diff --git a/plugin/lti_provider/web/view/game.tpl b/plugin/lti_provider/web/view/game.tpl
new file mode 100644
index 0000000000..0cf54a7b67
--- /dev/null
+++ b/plugin/lti_provider/web/view/game.tpl
@@ -0,0 +1,35 @@
+
+{% if launch.isDeepLinkLaunch %}
+
+{% else %}
+
+
+
+
+
Scoreboard - {{ courseCode }}
+
+
+
+
+
+
+
+
+
+
+
+{% endif %}
\ No newline at end of file