pull/3944/head
Yannick Warnier 4 years ago
commit 9b18d267e0
  1. 19
      composer.json
  2. 247
      plugin/lti_provider/Entity/Platform.php
  3. 129
      plugin/lti_provider/Entity/PlatformKey.php
  4. 312
      plugin/lti_provider/LtiProviderPlugin.php
  5. 43
      plugin/lti_provider/README.md
  6. 34
      plugin/lti_provider/admin.php
  7. 57
      plugin/lti_provider/create.php
  8. 59
      plugin/lti_provider/db/lti13_cache.php
  9. 41
      plugin/lti_provider/db/lti13_cookie.php
  10. 76
      plugin/lti_provider/db/lti13_database.php
  11. 32
      plugin/lti_provider/delete.php
  12. 71
      plugin/lti_provider/edit.php
  13. 16
      plugin/lti_provider/install.php
  14. 31
      plugin/lti_provider/lang/english.php
  15. 28
      plugin/lti_provider/lang/french.php
  16. 30
      plugin/lti_provider/lang/spanish.php
  17. 6
      plugin/lti_provider/plugin.php
  18. 63
      plugin/lti_provider/provider_settings.php
  19. 81
      plugin/lti_provider/src/Form/FrmAdd.php
  20. 75
      plugin/lti_provider/src/Form/FrmEdit.php
  21. 65
      plugin/lti_provider/src/LtiProvider.php
  22. 16
      plugin/lti_provider/uninstall.php
  23. 5
      plugin/lti_provider/view/add.tpl
  24. 52
      plugin/lti_provider/view/admin.tpl
  25. 49
      plugin/lti_provider/web/api/score.php
  26. 51
      plugin/lti_provider/web/api/scoreboard.php
  27. 20
      plugin/lti_provider/web/configure.php
  28. 32
      plugin/lti_provider/web/game.php
  29. 8
      plugin/lti_provider/web/login.php
  30. 56
      plugin/lti_provider/web/static/breakout.css
  31. 464
      plugin/lti_provider/web/static/breakout.js
  32. 35
      plugin/lti_provider/web/view/game.tpl

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

@ -0,0 +1,247 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Entity\LtiProvider;
use Doctrine\ORM\Mapping as ORM;
/**
* Class Platform
*
* @package Chamilo\PluginBundle\Entity\LtiProvider
*
* @ORM\Table(name="plugin_lti_provider_platform")
* @ORM\Entity()
*/
class Platform
{
/**
* @var string
*
* @ORM\Column(name="issuer", type="text")
*/
public $issuer;
/**
* @var int
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id()
* @ORM\GeneratedValue()
*/
protected $id;
/**
* @var string
*
* @ORM\Column(name="kid", type="string")
*/
private $kid;
/**
* @var string
*
* @ORM\Column(name="client_id", type="text")
*/
private $clientId;
/**
* @var string
*
* @ORM\Column(name="auth_login_url", type="text")
*/
private $authLoginUrl;
/**
* @var string
*
* @ORM\Column(name="auth_token_url", type="text")
*/
private $authTokenUrl;
/**
* @var string
*
* @ORM\Column(name="key_set_url", type="text")
*/
private $keySetUrl;
/**
* @var string
*
* @ORM\Column(name="deployment_id", type="text")
*/
private $deploymentId;
/**
* Get id.
*
* @return int
*/
public function getId(): int
{
return $this->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;
}
}

@ -0,0 +1,129 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Entity\LtiProvider;
use Doctrine\ORM\Mapping as ORM;
/**
* Class Platform
*
* @package Chamilo\PluginBundle\Entity\LtiProvider
*
* @ORM\Table(name="plugin_lti_provider_platform_key")
* @ORM\Entity()
*/
class PlatformKey
{
/**
* @var string
*
* @ORM\Column(name="public_key", type="text")
*/
public $publicKey;
/**
* @var int
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id()
* @ORM\GeneratedValue()
*/
protected $id;
/**
* @var string
*
* @ORM\Column(name="kid", type="string")
*/
private $kid;
/**
* @var string
*
* @ORM\Column(name="private_key", type="text")
*/
private $privateKey;
/**
* Get id.
*
* @return int
*/
public function getId(): int
{
return $this->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;
}
}

@ -0,0 +1,312 @@
<?php
/* For license terms, see /license.txt */
use Chamilo\PluginBundle\Entity\LtiProvider\PlatformKey;
use Doctrine\DBAL\DBALException;
use Doctrine\ORM\OptimisticLockException;
use Symfony\Component\Filesystem\Filesystem;
/**
* Description of LtiProvider
*
* @author Christian Beeznest <christian.fasanando@beeznest.com>
*/
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 = '<div class="form-group">
<label for="lti_provider_public_key" class="col-sm-2 control-label">'
.$this->get_lang('PublicKey').'</label>
<div class="col-sm-8">
<pre>'.$publicKey.'</pre>
</div>
<div class="col-sm-2"></div>
</div>';
} 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);
}
}

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

@ -0,0 +1,34 @@
<?php
/* For license terms, see /license.txt */
$cidReset = true;
require_once __DIR__.'/../../main/inc/global.inc.php';
require_once __DIR__.'/LtiProviderPlugin.php';
api_protect_admin_script();
$plugin = LtiProviderPlugin::create();
if ($plugin->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();

@ -0,0 +1,57 @@
<?php
/* For license terms, see /license.txt */
use Chamilo\PluginBundle\Entity\LtiProvider\Platform;
$cidReset = true;
require_once __DIR__.'/../../main/inc/global.inc.php';
require_once __DIR__.'/src/Form/FrmAdd.php';
require_once __DIR__.'/LtiProviderPlugin.php';
api_protect_admin_script();
$plugin = LtiProviderPlugin::create();
$em = Database::getManager();
$form = new FrmAdd('lti_provider_create_platform');
$form->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();

@ -0,0 +1,59 @@
<?php
/* For license terms, see /license.txt */
use Packback\Lti1p3\Interfaces\Cache as Lti1p3Cache;
class Lti13Cache implements Lti1p3Cache
{
public const NONCE_PREFIX = 'nonce_';
private $cache;
public function getLaunchData($key)
{
$this->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;
}
}

@ -0,0 +1,41 @@
<?php
/* For license terms, see /license.txt */
use Packback\Lti1p3\Interfaces\Cookie as Lti1p3Cookie;
class Lti13Cookie implements Lti1p3Cookie
{
public function getCookie($name)
{
if (isset($_COOKIE[$name])) {
return $_COOKIE[$name];
}
// Look for backup cookie if same site is not supported by the user's browser.
if (isset($_COOKIE["LEGACY_".$name])) {
return $_COOKIE["LEGACY_".$name];
}
return false;
}
public function setCookie($name, $value, $exp = 3600, $options = []): self
{
$cookieOptions = [
'expires' => 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;
}
}

@ -0,0 +1,76 @@
<?php
/* For license terms, see /license.txt */
use ChamiloSession as Session;
use Packback\Lti1p3\Interfaces;
use Packback\Lti1p3\LtiDeployment;
use Packback\Lti1p3\LtiRegistration;
class Lti13Database implements Interfaces\Database
{
public function findRegistrationByIssuer($iss, $clientId = null)
{
$ltiCustomers = $this->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);
}
}

@ -0,0 +1,32 @@
<?php
/* For license terms, see /license.txt */
$cidReset = true;
require_once __DIR__.'/../../main/inc/global.inc.php';
require_once __DIR__.'/src/Form/FrmEdit.php';
require_once __DIR__.'/LtiProviderPlugin.php';
api_protect_admin_script();
if (!isset($_REQUEST['id'])) {
api_not_allowed(true);
}
$platformId = (int) $_REQUEST['id'];
$plugin = LtiProviderPlugin::create();
$em = Database::getManager();
/** @var Platform $platform */
$platform = $em->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;

@ -0,0 +1,71 @@
<?php
/* For license terms, see /license.txt */
use Chamilo\PluginBundle\Entity\LtiProvider\Platform;
$cidReset = true;
require_once __DIR__.'/../../main/inc/global.inc.php';
require_once __DIR__.'/src/Form/FrmEdit.php';
require_once __DIR__.'/LtiProviderPlugin.php';
api_protect_admin_script();
if (!isset($_REQUEST['id'])) {
api_not_allowed(true);
}
$platformId = (int) $_REQUEST['id'];
$plugin = LtiProviderPlugin::create();
$em = Database::getManager();
/** @var Platform $platform */
$platform = $em->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();

@ -0,0 +1,16 @@
<?php
/* For license terms, see /license.txt */
/**
* Install the Lti/Provider Plugin
*
* @package chamilo.plugin.lti_provider
*/
require_once __DIR__.'/../../main/inc/global.inc.php';
require_once __DIR__.'/LtiProviderPlugin.php';
if (!api_is_platform_admin()) {
die('You must have admin permissions to install plugins');
}
LtiProviderPlugin::create()->install();

@ -0,0 +1,31 @@
<?php
/* For license terms, see /license.txt */
$strings['plugin_title'] = 'LTI 1.3 Advantage Provider';
$strings['plugin_comment'] = 'Simple application developed as a way to demonstrate how to build an IMS LTI tool provider';
$strings['Description'] = 'The application allows a student inside a course of the platform with external tools which is scored (Assigment and Grade Services) and generating reporting of their members (NRP Services).
Here are your LTI connection details to try out this app:';
$strings['LtiProviderDescription'] = 'First thing you will need is to configure your registration and deployment. To configure your registration
fill in the form the required values for deployment in your platform';
$strings['ConnectionDetails'] = 'LTI connection details';
$strings['AddPlatform'] = 'Add a customer platform';
$strings['EditPlatform'] = 'Edit the customer platform';
$strings['PlatformName'] = 'LMS (issuer)';
$strings['PlatformEdited'] = 'Platform details edited';
$strings['PlatformDeleted'] = 'Platform deleted';
$strings['ClientId'] = 'Client ID';
$strings['LaunchUrl'] = 'Launch URL';
$strings['LoginUrl'] = 'Login URL';
$strings['RedirectUrl'] = 'Redirect URL';
$strings['AuthLoginUrl'] = 'OIDC Auth URL';
$strings['AuthTokenUrl'] = 'OAuth2 Access Token URL';
$strings['KeySetUrl'] = 'Keyset URL';
$strings['DeploymentId'] = 'Deployment ID';
$strings['KeyId'] = 'key id (kid)';
$strings['PublicKey'] = 'Public key';
$strings['Name'] = 'Provider name';
$strings['Enabled'] = 'Enabled';
$strings['GenerateKeyPairInfo'] = 'A new private and public key pair will be created when enabling.';
$strings['URLs'] = 'Endpoints Urls';
$strings['PlatformConnectionAdded'] = 'A platform connection is added.';

@ -0,0 +1,28 @@
<?php
/* For license terms, see /license.txt */
$strings['plugin_title'] = 'LTI 1.3 Advantage Provider';
$strings['plugin_comment'] = 'Une application simple développée pour démontrer comment créer un outil IMS LTI';
$strings['Description'] = 'L\'application permet à un étudiant à l\'intérieur d\'un cours de la plateforme d\'utiliser des outils externes notés (Services d\'affectation et de notes) et de générer des rapports de leurs membres (Services NRP). Voici vos détails de connexion LTI pour essayer cette application :';
$strings['LtiProviderDescription'] = 'La première chose dont vous aurez besoin est de configurer votre enregistrement et votre déploiement. Pour configurer votre inscription remplissez le formulaire les valeurs requises pour le déploiement dans votre plateforme';
$strings['ConnectionDetails'] = 'Détails de connexion LTI';
$strings['AddPlatform'] = 'Ajouter une plateforme client';
$strings['EditPlatform'] = 'Modifier la plateforme client';
$strings['PlatformName'] = 'LMS (émetteur)';
$strings['PlatformEdited'] = 'Détails de la plateforme modifiés';
$strings['PlatformDeleted'] = 'Plateforme supprimée';
$strings['ClientId'] = 'Client ID';
$strings['LaunchUrl'] = 'Lancer l\'URL';
$strings['LoginUrl'] = 'URL de connexion';
$strings['RedirectUrl'] = 'URL de redirection';
$strings['AuthLoginUrl'] = 'URL d\'authentification OIDC';
$strings['AuthTokenUrl'] = 'URL du jeton d\'accès OAuth2';
$strings['KeySetUrl'] = 'URL du jeu de clés';
$strings['DeploymentId'] = 'ID de déploiement';
$strings['KeyId'] = 'Key Id';
$strings['PublicKey'] = 'Clé publique';
$strings['Name'] = 'Nom du fournisseur';
$strings['Enabled'] = 'Activé';
$strings['GenerateKeyPairInfo'] = 'Une nouvelle paire de clés privée et publique sera créée lors de l\'activation.';
$strings['URLs'] = 'URLs Endpoints';
$strings['PlatformConnectionAdded'] = 'La nouvelle connexion inter-plateforme a été ajoutée.';

@ -0,0 +1,30 @@
<?php
/* For license terms, see /license.txt */
$strings['plugin_title'] = 'Proveedor de ventajas LTI 1.3';
$strings['plugin_comment'] = 'Aplicación simple desarrollada como una forma de demostrar cómo construir un proveedor de herramientas IMS LTI';
$strings['Description'] = 'La aplicación permite a un alumno entrar en un curso de la plataforma con herramientas externas que se puntúan (Assigment and Grade Services) y generar informes de sus miembros (NRP Services).
Aquí están los detalles de su conexión LTI para probar esta aplicación: ';
$strings['LtiProviderDescription'] = 'Lo primero que necesitará es configurar su registro y despliegue. Para configurar su registro
complete el formulario con los valores requeridos para el despliegue en su plataforma ';
$strings['ConnectionDetails'] = 'Detalles de la conexión LTI';
$strings['AddPlatform'] = 'Agregar una plataforma de cliente';
$strings['EditPlatform'] = 'Edite la plataforma del cliente';
$strings['PlatformName'] = 'LMS (emisor)';
$strings['PlatformEdited'] = 'Detalles de la plataforma editados';
$strings['PlatformDeleted'] = 'Plataforma eliminada';
$strings['ClientId'] = 'ID de cliente';
$strings['LaunchUrl'] = 'URL de lanzamiento';
$strings['LoginUrl'] = 'URL de inicio de sesión';
$strings['RedirectUrl'] = 'URL de redireccionamiento';
$strings['AuthLoginUrl'] = 'URL de autenticación OIDC';
$strings['AuthTokenUrl'] = 'URL del token de acceso de OAuth2';
$strings['KeySetUrl'] = 'URL del conjunto de claves';
$strings['DeploymentId'] = 'ID de implementación';
$strings['KeyId'] = 'Key Id';
$strings['PublicKey'] = 'Clave pública';
$strings['Name'] = 'Nombre del proveedor';
$strings['Enabled'] = 'Habilitado';
$strings['GenerateKeyPairInfo'] = 'Se creará un nuevo par de claves pública y privada cuando se habilite.';
$strings['URLs'] = 'URL de puntos finales';
$strings['PlatformConnectionAdded'] = 'Se agrega una conexión de plataforma.';

@ -0,0 +1,6 @@
<?php
/* For license terms, see /license.txt */
require_once __DIR__.'/LtiProviderPlugin.php';
$plugin_info = LtiProviderPlugin::create()->get_info();

@ -0,0 +1,63 @@
<?php
/* For licensing terms, see /license.txt */
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
$cidReset = true;
require_once __DIR__.'/../../main/inc/global.inc.php';
require_once __DIR__.'/LtiProviderPlugin.php';
api_block_anonymous_users(false);
$plugin = LtiProviderPlugin::create();
$webPluginPath = api_get_path(WEB_PLUGIN_PATH).'lti_provider/';
$request = Request::createFromGlobals();
$response = new Response();
$em = Database::getManager();
$enabled = api_get_plugin_setting('lti_provider', 'enabled');
$name = api_get_plugin_setting('lti_provider', 'name');
$launchUrl = api_get_plugin_setting('lti_provider', 'launch_url');
$loginUrl = api_get_plugin_setting('lti_provider', 'login_url');
$redirectUrl = api_get_plugin_setting('lti_provider', 'redirect_url');
$publicKey = $plugin->getPublicKey();
try {
if ($enabled !== 'true') {
throw new Exception(get_lang('Forbidden'));
}
$html = '<div class="row">'
.'<div class="col-xs-2 text-right"><strong>'.$plugin->get_lang('Name').'</strong></div>'
.'<div class="col-xs-10">'.$name.'</div>'
.'</div>'
.'<div class="row">'
.'<div class="col-xs-2 text-right"><strong>'.$plugin->get_lang('LaunchUrl').'</strong></div>'
.'<div class="col-xs-10">'.$launchUrl.'</div>'
.'</div>'
.'<div class="row">'
.'<div class="col-xs-2 text-right"><strong>'.$plugin->get_lang('LoginUrl').'</strong></div>'
.'<div class="col-xs-10">'.$loginUrl.'</div>'
.'</div>'
.'<div class="row">'
.'<div class="col-xs-2 text-right"><strong>'.$plugin->get_lang('RedirectUrl').'</strong></div>'
.'<div class="col-xs-10">'.$redirectUrl.'</div>'
.'</div>'
.'<div class="row">'
.'<div class="col-xs-2 text-right"><strong>'.$plugin->get_lang('PublicKey').'</strong></div>'
.'<div class="col-xs-10"><pre>'.$publicKey.'</pre></div>'
.'</div>';
$response->setContent($html);
} catch (Exception $exception) {
$response->setContent(
Display::return_message($exception->getMessage(), 'error')
);
}
$response->send();

@ -0,0 +1,81 @@
<?php
/* For licensing terms, see /license.txt */
use Chamilo\PluginBundle\Entity\LtiProvider\Platform;
/**
* Class FrmAdd.
*/
class FrmAdd extends FormValidator
{
/**
* @var Platform|null
*/
private $platform;
/**
* FrmAdd constructor.
*
* @param string $name
* @param array $attributes
* @param Platform|null $platform
*/
public function __construct(
$name,
$attributes = [],
Platform $platform = null
) {
parent::__construct($name, 'POST', '', '', $attributes, self::LAYOUT_HORIZONTAL);
$this->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 = "<script>
</script>";
return $js.parent::returnForm(); // TODO: Change the autogenerated stub
}
}

@ -0,0 +1,75 @@
<?php
/* For licensing terms, see /license.txt */
use Chamilo\PluginBundle\Entity\LtiProvider\Platform;
/**
* Class FrmAdd.
*/
class FrmEdit extends FormValidator
{
/**
* @var Platform|null
*/
private $platform;
/**
* FrmAdd constructor.
*
* @param string $name
* @param array $attributes
* @param Platform|null $platform
*/
public function __construct(
$name,
$attributes = [],
Platform $platform = null
) {
parent::__construct($name, 'POST', '', '', $attributes, self::LAYOUT_HORIZONTAL);
$this->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);
}
}

@ -0,0 +1,65 @@
<?php
/* For licensing terms, see /license.txt */
use Packback\Lti1p3;
use Packback\Lti1p3\LtiMessageLaunch;
use Packback\Lti1p3\LtiOidcLogin;
require_once __DIR__.'/../db/lti13_cookie.php';
require_once __DIR__.'/../db/lti13_cache.php';
require_once __DIR__.'/../db/lti13_database.php';
/**
* Class LtiProvider
*/
class LtiProvider
{
/**
* Get the class instance
*
* @staticvar LtiProvider $result
* @return LtiProvider
*/
public static function create()
{
static $result = null;
return $result ?: $result = new self();
}
/**
* Oidc login and register
*
* @throws Lti1p3\OidcException
*/
public function login($request = null)
{
LtiOidcLogin::new(new Lti13Database, new Lti13Cache(), new Lti13Cookie)
->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;
}
}

@ -0,0 +1,16 @@
<?php
/* For license terms, see /license.txt */
/**
* Install the Lti/Provider Plugin
*
* @package chamilo.plugin.lti_provider
*/
require_once __DIR__.'/../../main/inc/global.inc.php';
require_once __DIR__.'/LtiProviderPlugin.php';
if (!api_is_platform_admin()) {
die('You must have admin permissions to install plugins');
}
LtiProviderPlugin::create()->uninstall();

@ -0,0 +1,5 @@
<div class="row">
<div class="col-sm-9 col-md-offset-3">
{{ form }}
</div>
</div>

@ -0,0 +1,52 @@
{{ 'LtiProviderDescription'|get_plugin_lang('LtiProviderPlugin') }}
<div class="btn-toolbar">
<a href="{{ _p.web_plugin }}lti_provider/provider_settings.php" class="btn btn-link pull-right ajax" data-title="{{ 'ConnectionDetails'|get_plugin_lang('LtiProviderPlugin') }}">
<span class="fa fa-cogs fa-fw" aria-hidden="true"></span> {{ 'ConnectionDetails'|get_plugin_lang('LtiProviderPlugin') }}
</a>
<a href="{{ _p.web_plugin }}lti_provider/create.php" class="btn btn-primary">
<span class="fa fa-plus fa-fw" aria-hidden="true"></span> {{ 'AddPlatform'|get_plugin_lang('LtiProviderPlugin') }}
</a>
</div>
<div class="table-responsive">
<table class="table table-striped table-hover admin-tools">
<thead>
<tr>
<th>{{ 'PlatformName'|get_plugin_lang('LtiProviderPlugin') }}</th>
<th>{{ 'ClientId'|get_plugin_lang('LtiProviderPlugin') }}</th>
<th class="text-center">{{ 'DeploymentId'|get_plugin_lang('LtiProviderPlugin') }}</th>
<th class="text-center">{{ 'KeyId'|get_plugin_lang('LtiProviderPlugin') }}</th>
<th class="text-center">{{ 'URLs'|get_plugin_lang('LtiProviderPlugin') }}</th>
<th class="text-right">{{ 'Actions'|get_lang }}</th>
</tr>
</thead>
<tbody>
{% for platform in platforms %}
{% set url_params = {'id': platform.getId}|url_encode() %}
<tr>
<td>{{ platform.getIssuer }}</td>
<td>{{ platform.getClientId }}</td>
<td>{{ platform.getDeploymentId }}</td>
<td>{{ platform.getKid }}</td>
<td>
<p><strong>{{ 'AuthLoginUrl'|get_plugin_lang('LtiProviderPlugin') }}:</strong><br> {{ platform.getAuthLoginUrl }}</p>
<p><strong>{{ 'AuthTokenUrl'|get_plugin_lang('LtiProviderPlugin') }}:</strong><br> {{ platform.getAuthTokenUrl }}</p>
<p><strong>{{ 'KeySetUrl'|get_plugin_lang('LtiProviderPlugin') }}:</strong><br> {{ platform.getKeySetUrl }}</p>
</td>
<td>
<a href="{{ _p.web_plugin }}lti_provider/edit.php?{{ url_params }}">
{{ 'edit.png'|img(22, 'Edit'|get_lang) }}
</a>
<a href="{{ _p.web_plugin }}lti_provider/delete.php?{{ url_params }}">
{{ 'delete.png'|img(22, 'Delete'|get_lang) }}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>

@ -0,0 +1,49 @@
<?php
/* For license terms, see /license.txt */
require_once __DIR__.'/../../../../main/inc/global.inc.php';
require_once __DIR__ . '/../../src/LtiProvider.php';
$launch = LtiProvider::create()->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}';

@ -0,0 +1,51 @@
<?php
/* For license terms, see /license.txt */
require_once __DIR__.'/../../../../main/inc/global.inc.php';
require_once __DIR__ . '/../../src/LtiProvider.php';
$launch = LtiProvider::create()->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);

@ -0,0 +1,20 @@
<?php
/* For license terms, see /license.txt */
require_once __DIR__.'/../../../main/inc/global.inc.php';
require_once __DIR__ . '/../src/LtiProvider.php';
use Packback\Lti1p3;
$launch = LtiProvider::create()->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]);

@ -0,0 +1,32 @@
<?php
/* For license terms, see /license.txt */
require_once __DIR__.'/../../../main/inc/global.inc.php';
require_once __DIR__ . '/../src/LtiProvider.php';
use Packback\Lti1p3;
$launch = LtiProvider::create()->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();
?>

@ -0,0 +1,8 @@
<?php
/* For license terms, see /license.txt */
require_once __DIR__.'/../../../main/inc/global.inc.php';
require_once __DIR__ . '/../src/LtiProvider.php';
use Packback\Lti1p3;
LtiProvider::create()->login($_REQUEST);

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

@ -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 = '<tr><th>Score</th><th>Time</th><th>Name</th></tr>';
for (var i = 0; i < scores.length; i++) {
output += '<tr><td>' + scores[i].score + '</td><td>' + scores[i].time + 's</td><td>' + scores[i].name + '</td></tr>';
}
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();

@ -0,0 +1,35 @@
<link href="https://fonts.googleapis.com/css?family=Gugi" rel="stylesheet">
{% if launch.isDeepLinkLaunch %}
<div class="dl-config">
<h1>Pick a Difficulty</h1>
<ul>
<li><a href="{{ _p.web_plugin }}lti_provider/web/configure.php?diff=easy&launch_id={{ launch.getLaunchId }}">Easy</a></li>
<li><a href="{{ _p.web_plugin }}lti_provider/web/configure.php?diff=normal&launch_id={{ launch.getLaunchId }}">Normal</a></li>
<li><a href="{{ _p.web_plugin }}lti_provider/web/configure.php?diff=hard&launch_id={{ launch.getLaunchId }}">Hard</a></li>
</ul>
</div>
{% else %}
<div id="game-screen">
<div style="position:absolute;width:1000px;margin-left:-500px;left:50%; display:block">
<div id="scoreboard" style="position:absolute; right:0; width:200px; height:486px">
<h3 style="margin-left:12px;">Scoreboard - {{ courseCode }}</h3>
<table id="leadertable" style="margin-left:12px;">
</table>
</div>
<canvas id="breakoutbg" width="800" height="500" style="position:absolute;left:0;border:0;">
</canvas>
<canvas id="breakout" width="800" height="500" style="position:absolute;left:0;">
</canvas>
</div>
</div>
<div class="clearfix"></div>
<button onclick="location.reload()" class="btn btn-sm" id="btn-again">Play Again</button>
<script>
var curr_diff = "{{ diff }}";
var curr_user_name = "{{ username }}";
var launch_id = "{{ launch.getLaunchId }}";
</script>
<script type="text/javascript" src="{{ _p.web_plugin }}lti_provider/web/static/breakout.js" charset="utf-8"></script>
{% endif %}
Loading…
Cancel
Save