This commit is contained in:
Xes
2025-08-14 22:39:38 +02:00
parent 3641e93527
commit 5403f346e3
3370 changed files with 327179 additions and 0 deletions

View File

@@ -0,0 +1,234 @@
<?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;
/**
* @var string
*
* @ORM\Column(name="tool_provider", type="text")
*/
private $toolProvider;
/**
* Get id.
*/
public function getId(): int
{
return $this->id;
}
/**
* Set id.
*/
public function setId(int $id): Platform
{
$this->id = $id;
return $this;
}
/**
* @return string
*/
public function getToolProvider()
{
return $this->toolProvider;
}
/**
* @param string $toolProvider
*/
public function setToolProvider(?string $toolProvider): void
{
$this->toolProvider = $toolProvider;
}
/**
* Get key id.
*/
public function getKid()
{
return $this->kid;
}
/**
* Set key id.
*/
public function setKid(string $kid): Platform
{
$this->kid = $kid;
return $this;
}
/**
* Get Issuer.
*/
public function getIssuer()
{
return $this->issuer;
}
/**
* Set issuer.
*/
public function setIssuer(string $issuer): Platform
{
$this->issuer = $issuer;
return $this;
}
/**
* Get client ID.
*/
public function getClientId()
{
return $this->clientId;
}
/**
* Set client ID.
*/
public function setClientId(string $clientId): Platform
{
$this->clientId = $clientId;
return $this;
}
/**
* Get auth login URL.
*/
public function getAuthLoginUrl()
{
return $this->authLoginUrl;
}
/**
* Set auth login URL.
*/
public function setAuthLoginUrl(string $authLoginUrl): Platform
{
$this->authLoginUrl = $authLoginUrl;
return $this;
}
/**
* Get auth token URL.
*/
public function getAuthTokenUrl()
{
return $this->authTokenUrl;
}
/**
* Set auth token URL.
*/
public function setAuthTokenUrl(string $authTokenUrl): Platform
{
$this->authTokenUrl = $authTokenUrl;
return $this;
}
/**
* Get key set URL.
*/
public function getKeySetUrl()
{
return $this->keySetUrl;
}
/**
* Set key set URL.
*/
public function setKeySetUrl(string $keySetUrl): Platform
{
$this->keySetUrl = $keySetUrl;
return $this;
}
/**
* Get Deployment ID.
*/
public function getDeploymentId()
{
return $this->deploymentId;
}
/**
* Set Deployment ID.
*/
public function setDeploymentId(string $deploymentId): Platform
{
$this->deploymentId = $deploymentId;
return $this;
}
}

View File

@@ -0,0 +1,116 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Entity\LtiProvider;
use Doctrine\ORM\Mapping as ORM;
/**
* Class PlatformKey.
*
* @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.
*/
public function getId(): int
{
return $this->id;
}
/**
* Set id.
*/
public function setId(int $id): PlatformKey
{
$this->id = $id;
return $this;
}
/**
* Get key id.
*/
public function getKid(): string
{
return $this->kid;
}
/**
* Set key id.
*/
public function setKid(string $kid): PlatformKey
{
$this->kid = $kid;
return $this;
}
/**
* Get privateKey.
*/
public function getPrivateKey(): string
{
return $this->privateKey;
}
/**
* Set privateKey.
*/
public function setPrivateKey(string $privateKey): PlatformKey
{
$this->privateKey = $privateKey;
return $this;
}
/**
* Get publicKey.
*/
public function getPublicKey(): string
{
return $this->publicKey;
}
/**
* Set publicKey.
*/
public function setPublicKey(string $publicKey): PlatformKey
{
$this->publicKey = $publicKey;
return $this;
}
}

View File

@@ -0,0 +1,267 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Entity\LtiProvider;
use Doctrine\ORM\Mapping as ORM;
/**
* Class Result.
*
* @ORM\Table(name="plugin_lti_provider_result")
* @ORM\Entity()
*/
class Result
{
/**
* @var int
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id()
* @ORM\GeneratedValue()
*/
protected $id;
/**
* @var string
*
* @ORM\Column(name="issuer", type="text")
*/
protected $issuer;
/**
* @var int
*
* @ORM\Column(name="user_id", type="integer", nullable=false)
*/
protected $userId;
/**
* @var string
*
* @ORM\Column(name="client_uid", type="string", nullable=false)
*/
protected $clientUId;
/**
* @var string
*
* @ORM\Column(name="course_code", type="string", length=40, nullable=true)
*/
protected $courseCode;
/**
* @var int
*
* @ORM\Column(name="tool_id", type="integer", nullable=false)
*/
protected $toolId;
/**
* @var string
*
* @ORM\Column(name="tool_name", type="string")
*/
protected $toolName;
/**
* @var float
*
* @ORM\Column(name="score", type="float", precision=6, scale=2, nullable=false)
*/
protected $score;
/**
* @var int
*
* @ORM\Column(name="progress", type="integer", nullable=false)
*/
protected $progress;
/**
* @var int
*
* @ORM\Column(name="duration", type="integer", nullable=false)
*/
protected $duration;
/**
* @var \DateTime
*
* @ORM\Column(name="start_date", type="datetime", nullable=false)
*/
protected $startDate;
/**
* @var string
*
* @ORM\Column(name="user_ip", type="string")
*/
protected $userIp;
/**
* @var string
*
* @ORM\Column(name="lti_launch_id", type="string")
*/
protected $ltiLaunchId;
public function getId(): int
{
return $this->id;
}
public function setId(int $id): Result
{
$this->id = $id;
return $this;
}
public function getIssuer(): string
{
return $this->issuer;
}
public function setIssuer(string $issuer): Result
{
$this->issuer = $issuer;
return $this;
}
public function getUserId(): int
{
return $this->userId;
}
public function setUserId(int $userId): Result
{
$this->userId = $userId;
return $this;
}
public function getClientUId(): string
{
return $this->clientUId;
}
public function setClientUId(string $clientUId): Result
{
$this->clientUId = $clientUId;
return $this;
}
public function getCourseCode(): string
{
return $this->courseCode;
}
/**
* @param string $tool
*/
public function setCourseCode(string $courseCode): Result
{
$this->courseCode = $courseCode;
return $this;
}
public function getToolId(): int
{
return $this->toolId;
}
public function setToolId(int $toolId): Result
{
$this->toolId = $toolId;
return $this;
}
public function getToolName(): string
{
return $this->toolName;
}
public function setToolName(string $toolName): Result
{
$this->toolName = $toolName;
return $this;
}
public function getScore(): float
{
return $this->score;
}
public function setScore(float $score): Result
{
$this->score = $score;
return $this;
}
public function getProgress(): int
{
return $this->progress;
}
public function setProgress(int $progress): Result
{
$this->progress = $progress;
return $this;
}
public function getDuration(): int
{
return $this->duration;
}
public function setDuration(int $duration): Result
{
$this->duration = $duration;
return $this;
}
public function getStartDate(): \DateTime
{
return $this->startDate;
}
public function setStartDate(\DateTime $startDate): Result
{
$this->startDate = $startDate;
return $this;
}
public function getUserIp(): string
{
return $this->userIp;
}
public function setUserIp(string $userIp): Result
{
$this->userIp = $userIp;
return $this;
}
public function getLtiLaunchId(): string
{
return $this->ltiLaunchId;
}
public function setLtiLaunchId(string $ltiLaunchId): Result
{
$this->ltiLaunchId = $ltiLaunchId;
return $this;
}
}

View File

@@ -0,0 +1,545 @@
<?php
/* For license terms, see /license.txt */
use Chamilo\PluginBundle\Entity\LtiProvider\Platform;
use Chamilo\PluginBundle\Entity\LtiProvider\PlatformKey;
use Chamilo\PluginBundle\Entity\LtiProvider\Result;
use Doctrine\ORM\OptimisticLockException;
use Doctrine\ORM\Tools\SchemaTool;
/**
* Description of LtiProvider.
*
* @author Christian Beeznest <christian.fasanando@beeznest.com>
*/
class LtiProviderPlugin extends Plugin
{
public const TABLE_PLATFORM = 'plugin_lti_provider_platform';
public const LAUNCH_PATH = 'lti_provider/tool/start.php';
public const LOGIN_PATH = 'lti_provider/tool/login.php';
public const REDIRECT_PATH = 'lti_provider/tool/start.php';
public const JWKS_URL = 'lti_provider/tool/jwks.php';
public $isAdminPlugin = true;
protected function __construct()
{
$version = '1.1';
$author = 'Christian Beeznest';
$message = Display::return_message($this->get_lang('Description'));
$launchUrlHtml = '';
$loginUrlHtml = '';
$redirectUrlHtml = '';
$jwksUrlHtml = '';
if ($this->areTablesCreated()) {
$publicKey = $this->getPublicKey();
$pkHtml = $this->getSettingHtmlReadOnly(
$this->get_lang('PublicKey'),
'public_key',
$publicKey
);
$launchUrlHtml = $this->getSettingHtmlReadOnly(
$this->get_lang('LaunchUrl'),
'launch_url',
api_get_path(WEB_PLUGIN_PATH).self::LAUNCH_PATH
);
$loginUrlHtml = $this->getSettingHtmlReadOnly(
$this->get_lang('LoginUrl'),
'login_url',
api_get_path(WEB_PLUGIN_PATH).self::LOGIN_PATH
);
$redirectUrlHtml = $this->getSettingHtmlReadOnly(
$this->get_lang('RedirectUrl'),
'redirect_url',
api_get_path(WEB_PLUGIN_PATH).self::REDIRECT_PATH
);
$jwksUrlHtml = $this->getSettingHtmlReadOnly(
$this->get_lang('KeySetUrlJwks'),
'jwks_url',
api_get_path(WEB_PLUGIN_PATH).self::JWKS_URL
);
} else {
$pkHtml = $this->get_lang('GenerateKeyPairInfo');
}
$settings = [
$message => 'html',
'name' => 'hidden',
$launchUrlHtml => 'html',
$loginUrlHtml => 'html',
$redirectUrlHtml => 'html',
$jwksUrlHtml => 'html',
$pkHtml => 'html',
'enabled' => 'boolean',
];
parent::__construct($version, $author, $settings);
}
/**
* Get the value by default and readonly for the configuration html form.
*
* @param $label
* @param $id
* @param $value
*
* @return string
*/
public function getSettingHtmlReadOnly($label, $id, $value)
{
$html = '<div class="form-group">
<label for="lti_provider_'.$id.'" class="col-sm-2 control-label">'
.$label.'</label>
<div class="col-sm-8">
<pre>'.$value.'</pre>
</div>
<div class="col-sm-2"></div>
<input type="hidden" name="'.$id.'" value="'.$value.'" />
</div>';
return $html;
}
/**
* Get a selectbox with quizzes in courses , used for a tool provider.
*
* @param null $clientId
*
* @return string
*/
public function getQuizzesSelect($clientId = null)
{
$courses = CourseManager::get_courses_list();
$toolProvider = $this->getToolProvider($clientId);
$htmlcontent = '<div class="form-group select-tool" id="select-quiz">
<label for="lti_provider_create_platform_kid" class="col-sm-2 control-label">'.$this->get_lang('ToolProvider').'</label>
<div class="col-sm-8">
<select name="tool_provider" class="sbox-tool" id="sbox-tool-quiz" disabled="disabled">';
$htmlcontent .= '<option value="">-- '.$this->get_lang('SelectOneActivity').' --</option>';
foreach ($courses as $course) {
$courseInfo = api_get_course_info($course['code']);
$optgroupLabel = "{$course['title']} : ".get_lang('Quizzes');
$htmlcontent .= '<optgroup label="'.$optgroupLabel.'">';
$exerciseList = ExerciseLib::get_all_exercises_for_course_id(
$courseInfo,
0,
$course['id'],
false
);
foreach ($exerciseList as $key => $exercise) {
$selectValue = "{$course['code']}@@quiz-{$exercise['iid']}";
$htmlcontent .= '<option value="'.$selectValue.'" '.($toolProvider == $selectValue ? ' selected="selected"' : '').'>'.Security::remove_XSS($exercise['title']).'</option>';
}
$htmlcontent .= '</optgroup>';
}
$htmlcontent .= "</select>";
$htmlcontent .= ' </div>
<div class="col-sm-2"></div>
</div>';
return $htmlcontent;
}
/**
* Get a selectbox with quizzes in courses , used for a tool provider.
*
* @param null $clientId
*
* @return string
*/
public function getLearnPathsSelect($clientId = null)
{
$courses = CourseManager::get_courses_list();
$toolProvider = $this->getToolProvider($clientId);
$htmlcontent = '<div class="form-group select-tool" id="select-lp" style="display:none">
<label for="lti_provider_create_platform_kid" class="col-sm-2 control-label">'.$this->get_lang('ToolProvider').'</label>
<div class="col-sm-8">
<select name="tool_provider" class="sbox-tool" id="sbox-tool-lp" disabled="disabled">';
$htmlcontent .= '<option value="">-- '.$this->get_lang('SelectOneActivity').' --</option>';
foreach ($courses as $course) {
$courseInfo = api_get_course_info($course['code']);
$optgroupLabel = "{$course['title']} : ".get_lang('Learnpath');
$htmlcontent .= '<optgroup label="'.$optgroupLabel.'">';
$list = new LearnpathList(
api_get_user_id(),
$courseInfo
);
$flatList = $list->get_flat_list();
foreach ($flatList as $id => $details) {
$selectValue = "{$course['code']}@@lp-{$id}";
$htmlcontent .= '<option value="'.$selectValue.'" '.($toolProvider == $selectValue ? ' selected="selected"' : '').'>'.Security::remove_XSS($details['lp_name']).'</option>';
}
$htmlcontent .= '</optgroup>';
}
$htmlcontent .= "</select>";
$htmlcontent .= ' </div>
<div class="col-sm-2"></div>
</div>';
return $htmlcontent;
}
/**
* Get the public key.
*/
public function getPublicKey(): string
{
$publicKey = '';
$platformKey = Database::getManager()
->getRepository('ChamiloPluginBundle:LtiProvider\PlatformKey')
->findOneBy([]);
if ($platformKey) {
$publicKey = $platformKey->getPublicKey();
}
return $publicKey;
}
/**
* Get the first access date of a user in a tool.
*
* @param $courseCode
* @param $toolId
* @param $userId
*
* @return string
*/
public function getUserFirstAccessOnToolLp($courseCode, $toolId, $userId)
{
$dql = "SELECT
a.startDate
FROM ChamiloPluginBundle:LtiProvider\Result a
WHERE
a.courseCode = '$courseCode' AND
a.toolName = 'lp' AND
a.toolId = $toolId AND
a.userId = $userId
ORDER BY a.startDate";
$qb = Database::getManager()->createQuery($dql);
$result = $qb->getArrayResult();
$firstDate = '';
if (isset($result[0])) {
$startDate = $result[0]['startDate'];
$firstDate = $startDate->format('Y-m-d H:i');
}
return $firstDate;
}
/**
* Get the results of users in tools lti.
*
* @param $startDate
* @param $endDate
*
* @return array
*/
public function getToolLearnPathResult($startDate, $endDate)
{
$dql = "SELECT
a.issuer,
count(DISTINCT(a.userId)) as cnt
FROM
ChamiloPluginBundle:LtiProvider\Result a
WHERE
a.toolName = 'lp' AND
a.startDate BETWEEN '$startDate' AND '$endDate'
GROUP BY a.issuer";
$qb = Database::getManager()->createQuery($dql);
$issuersValues = $qb->getResult();
$result = [];
if (!empty($issuersValues)) {
foreach ($issuersValues as $issuerValue) {
$issuer = $issuerValue['issuer'];
$dqlLp = "SELECT
a.toolId,
a.userId,
a.courseCode
FROM
ChamiloPluginBundle:LtiProvider\Result a
WHERE
a.toolName = 'lp' AND
a.startDate BETWEEN '$startDate' AND '$endDate' AND
a.issuer = '".$issuer."'
GROUP BY a.toolId, a.userId";
$qbLp = Database::getManager()->createQuery($dqlLp);
$lpValues = $qbLp->getResult();
$lps = [];
foreach ($lpValues as $lp) {
$uinfo = api_get_user_info($lp['userId']);
$firstAccess = self::getUserFirstAccessOnToolLp($lp['courseCode'], $lp['toolId'], $lp['userId']);
$lps[$lp['toolId']]['users'][$lp['userId']] = [
'firstname' => $uinfo['firstname'],
'lastname' => $uinfo['lastname'],
'first_access' => $firstAccess,
];
}
$result[] = [
'issuer' => $issuer,
'count_iss_users' => $issuerValue['cnt'],
'learnpaths' => $lps,
];
}
}
return $result;
}
/**
* Get the tool provider.
*/
public function getToolProvider($clientId): string
{
$toolProvider = '';
$platform = Database::getManager()
->getRepository('ChamiloPluginBundle:LtiProvider\Platform')
->findOneBy(['clientId' => $clientId]);
if ($platform) {
$toolProvider = $platform->getToolProvider();
}
return $toolProvider;
}
public function getToolProviderVars($clientId): array
{
$toolProvider = $this->getToolProvider($clientId);
list($courseCode, $tool) = explode('@@', $toolProvider);
list($toolName, $toolId) = explode('-', $tool);
$vars = ['courseCode' => $courseCode, 'toolName' => $toolName, 'toolId' => $toolId];
return $vars;
}
/**
* Get the class instance.
*
* @staticvar LtiProviderPlugin $result
*/
public static function create(): LtiProviderPlugin
{
static $result = null;
return $result ?: $result = new self();
}
/**
* Check whether the current user is a teacher in this context.
*/
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. Set the database up.
*
* @throws \Doctrine\ORM\Tools\ToolsException
*/
public function install()
{
$em = Database::getManager();
if ($em->getConnection()->getSchemaManager()->tablesExist([self::TABLE_PLATFORM])) {
return;
}
$schemaTool = new SchemaTool($em);
$schemaTool->createSchema(
[
$em->getClassMetadata(Platform::class),
$em->getClassMetadata(PlatformKey::class),
$em->getClassMetadata(Result::class),
]
);
}
/**
* 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;
}
/**
* Unistall plugin. Clear the database.
*/
public function uninstall()
{
$em = Database::getManager();
if (!$em->getConnection()->getSchemaManager()->tablesExist([self::TABLE_PLATFORM])) {
return;
}
$schemaTool = new SchemaTool($em);
$schemaTool->dropSchema(
[
$em->getClassMetadata(Platform::class),
$em->getClassMetadata(PlatformKey::class),
$em->getClassMetadata(Result::class),
]
);
}
public function trimParams(array &$params)
{
foreach ($params as $key => $value) {
$newValue = preg_replace('/\s+/', ' ', $value);
$params[$key] = trim($newValue);
}
}
public function saveResult($values, $ltiLaunchId = null)
{
$em = Database::getManager();
if (!empty($ltiLaunchId)) {
$repo = $em->getRepository(Result::class);
/** @var Result $objResult */
$objResult = $repo->findOneBy(
[
'ltiLaunchId' => $ltiLaunchId,
]
);
if ($objResult) {
$objResult->setScore($values['score']);
$objResult->setProgress($values['progress']);
$objResult->setDuration($values['duration']);
$em->persist($objResult);
$em->flush();
return $objResult->getId();
}
} else {
$objResult = new Result();
$objResult
->setIssuer($values['issuer'])
->setUserId($values['user_id'])
->setClientUId($values['client_uid'])
->setCourseCode($values['course_code'])
->setToolId($values['tool_id'])
->setToolName($values['tool_name'])
->setScore(0)
->setProgress(0)
->setDuration(0)
->setStartDate(new DateTime())
->setUserIp(api_get_real_ip())
->setLtiLaunchId($values['lti_launch_id'])
;
$em->persist($objResult);
$em->flush();
return $objResult->getId();
}
return false;
}
private function areTablesCreated(): bool
{
$entityManager = Database::getManager();
$connection = $entityManager->getConnection();
return $connection->getSchemaManager()->tablesExist(self::TABLE_PLATFORM);
}
/**
* Generate a key pair and key id for the platform.
*
* Return a associative array like ['kid' => '...', 'private' => '...', 'public' => '...'].
*/
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"],
];
}
/**
* Get a SimpleXMLElement object with the request received on php://input.
*
* @throws Exception
*/
private function getRequestXmlElement(): ?SimpleXMLElement
{
$request = file_get_contents("php://input");
if (empty($request)) {
return null;
}
return new SimpleXMLElement($request);
}
}

View File

@@ -0,0 +1,84 @@
Lti/Provider plugin
===
Version 1.0
> This plugin is meant to be later integrated into Chamilo (in a major version
release).
The LTI provider feature is only compatible with LTI 1.3 Advantage, and demonstrates the possibility to integrate tools or content from Chamilo into other LMS platforms.
In this case, Chamilo is used as provider , and this plugin allows a student inside a course to play in a breakout game with certain difficulty options (Deep Linkings) which is scored (Assigment and Grade Services) and compared with the other members of the course (NRP Services).
# Installation
*Prior to installing/uninstalling this plugin, you will need to make sure the src/Chamilo/PluginBundle/Entity folder is
temporarily writeable by the web server.*
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 (will appear on the management page)
4. Add the LTI connection details to try out the little demo app (Configuration page)
5. Configure the LMS platforms for registration and deployment
To be able to acces LTI content from a different domain in an iframe, the hosting provider will have to enable it by activating this configuration in the app/config/configuration.php file :
```
// Enable samesite:None parameter for session cookie.
// More info: https://www.chromium.org/updates/same-site
// Also: https://developers.google.com/search/blog/2020/01/get-ready-for-new-samesitenone-secure
$_configuration['security_session_cookie_samesite_none'] = true;
```
# DB tables
These tables are normally created during the activation of the plugin. They are mentioned here for practical purposes.
Note: "kid" means "Key ID", not "child".
## 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,
tool_provider varchar(255) 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;
CREATE TABLE plugin_lti_provider_result (
id int(11) NOT NULL AUTO_INCREMENT,
issuer longtext NOT NULL,
user_id int(11) NOT NULL,
client_uid int(11) NOT NULL,
course_code varchar(40) NOT NULL,
tool_id int(11) NOT NULL,
tool_name varchar(255) NOT NULL,
score double NOT NULL,
progress int(11) NOT NULL,
duration int(11) NOT NULL,
start_date datetime NOT NULL,
user_ip varchar(255) NOT NULL,
lti_launch_id varchar(255) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
```
## v1.1
### Database changes
You need to execute this SQL query in your database after updating your Chamilo after version 1.11.18 if the plugin was already installed before.
```sql
ALTER TABLE plugin_lti_provider_result MODIFY client_uid varchar(255) NOT NULL;
```

View File

@@ -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/provider_admin.tpl');
$template->assign('header', $plugin->get_title());
$template->assign('content', $content);
$template->display_one_col_template();

View File

@@ -0,0 +1,59 @@
<?php
/* For license terms, see /license.txt */
$cidReset = true;
require_once __DIR__.'/../../main/inc/global.inc.php';
use Chamilo\PluginBundle\Entity\LtiProvider\Platform;
use Chamilo\PluginBundle\LtiProvider\Form\FrmAdd;
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']);
$toolProvider = (isset($formValues['tool_provider']) ? $formValues['tool_provider'] : $_POST['tool_provider']);
$platform->setToolProvider($toolProvider);
$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();

View File

@@ -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];
}
public function cacheLaunchData($key, $jwtBody): Lti13Cache
{
$this->cache[$key] = $jwtBody;
$this->saveCache();
return $this;
}
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;
}
private function loadCache()
{
$cache = file_get_contents(api_get_path(SYS_ARCHIVE_PATH).'lti_cache.txt');
if (empty($cache)) {
file_put_contents(api_get_path(SYS_ARCHIVE_PATH).'lti_cache.txt', '{}');
$this->cache = [];
}
$this->cache = json_decode($cache, true);
}
private function saveCache()
{
file_put_contents(api_get_path(SYS_ARCHIVE_PATH).'lti_cache.txt', json_encode($this->cache));
}
}

View File

@@ -0,0 +1,45 @@
<?php
/* For license terms, see /license.txt */
use Packback\Lti1p3\Interfaces\Cookie as Lti1p3Cookie;
class Lti13Cookie implements Lti1p3Cookie
{
public function getCookie($name)
{
if (isset($_REQUEST['state']) && $name === 'lti1p3_'.$_REQUEST['state']) {
return $_REQUEST['state'];
}
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;
}
}

View File

@@ -0,0 +1,91 @@
<?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)
{
if (!isset($clientId)) {
$clientId = $this->getClientIdByIssuer($iss);
}
$ltiCustomers = $this->getLtiConnection();
if (empty($ltiCustomers[$clientId])) {
return false;
}
return LtiRegistration::new()
->setAuthLoginUrl($ltiCustomers[$clientId]['auth_login_url'])
->setAuthTokenUrl($ltiCustomers[$clientId]['auth_token_url'])
->setClientId($clientId)
->setKeySetUrl($ltiCustomers[$clientId]['key_set_url'])
->setKid($ltiCustomers[$clientId]['kid'])
->setIssuer($iss)
->setToolPrivateKey($this->getPrivateKey());
}
public function findDeployment($iss, $deploymentId, $clientId = null)
{
$issSession = Session::read('iss');
if (!in_array($deploymentId, $issSession[$clientId]['deployment'])) {
return false;
}
return LtiDeployment::new()->setDeploymentId($deploymentId);
}
private function getLtiConnection(): array
{
$em = Database::getManager();
$platforms = $em->getRepository('ChamiloPluginBundle:LtiProvider\Platform')->findAll();
$ltiCustomers = [];
foreach ($platforms as $platform) {
$clientId = $platform->getClientId();
$ltiCustomers[$clientId] = [
'client_id' => $clientId,
'issuer' => $platform->getIssuer(),
'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 getClientIdByIssuer($issuer)
{
$clientId = '';
$platform = Database::getManager()
->getRepository('ChamiloPluginBundle:LtiProvider\Platform')
->findOneBy(['issuer' => $issuer]);
if ($platform) {
$clientId = $platform->getClientId();
}
return $clientId;
}
private function getPrivateKey()
{
$privateKey = '';
$platformKey = Database::getManager()
->getRepository('ChamiloPluginBundle:LtiProvider\PlatformKey')
->findOneBy([]);
if ($platformKey) {
$privateKey = $platformKey->getPrivateKey();
}
return $privateKey;
}
}

View File

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

View File

@@ -0,0 +1,73 @@
<?php
/* For license terms, see /license.txt */
use Chamilo\PluginBundle\Entity\LtiProvider\Platform;
use Chamilo\PluginBundle\LtiProvider\Form\FrmEdit;
$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']);
$toolProvider = (isset($formValues['tool_provider']) ? $formValues['tool_provider'] : $_POST['tool_provider']);
$platform->setToolProvider($toolProvider);
$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();

View File

@@ -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()) {
exit('You must have admin permissions to install plugins');
}
LtiProviderPlugin::create()->install();

View File

@@ -0,0 +1,34 @@
<?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 has been added.';
$strings['ToolProvider'] = 'Provider tool';
$strings['SelectOneActivity'] = 'Select one activity';
$strings['Quizzes'] = 'Exercises';
$strings['KeySetUrlJwks'] = 'Keyset url (jwks)';

View File

@@ -0,0 +1,32 @@
<?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.';
$strings['ToolProvider'] = 'Outil fournisseur';
$strings['SelectOneActivity'] = 'Sélectionnez une activité';
$strings['Quizzes'] = 'Exercices';
$strings['KeySetUrlJwks'] = 'Keyset url (jwks)';

View File

@@ -0,0 +1,34 @@
<?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.';
$strings['ToolProvider'] = 'Herramienta del proveedor';
$strings['SelectOneActivity'] = 'Selecccione una actividad';
$strings['Quizzes'] = 'Ejercicios';
$strings['KeySetUrlJwks'] = 'Clave url (jwks)';

View File

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

View File

@@ -0,0 +1,61 @@
<?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');
$jwksUrl = api_get_plugin_setting('lti_provider', 'jwks_url');
if (empty($jwksUrl)) {
$jwksUrl = api_get_path(WEB_PLUGIN_PATH).LtiProviderPlugin::JWKS_URL;
}
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('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('KeySetUrlJwks').'</strong></div>'
.'<div class="col-xs-10">'.$jwksUrl.'</div>'
.'</div>';
$response->setContent($html);
} catch (Exception $exception) {
$response->setContent(
Display::return_message($exception->getMessage(), 'error')
);
}
$response->send();

View File

@@ -0,0 +1,88 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\LtiProvider\Form;
use Chamilo\PluginBundle\Entity\LtiProvider\Platform;
use FormValidator;
use LtiProviderPlugin;
/**
* Class FrmAdd.
*/
class FrmAdd extends FormValidator
{
/**
* @var Platform|null
*/
private $platform;
/**
* FrmAdd constructor.
*/
public function __construct(
string $name,
array $attributes = [],
Platform $platform = null
) {
parent::__construct($name, 'POST', '', '', $attributes, self::LAYOUT_HORIZONTAL);
$this->platform = $platform;
}
/**
* Build the form.
*/
public function build(): void
{
$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'), false);
$this->addRadio(
'tool_type',
get_lang('ToolProvider'),
[
'quiz' => $plugin->get_lang('Quizzes'),
'lp' => $plugin->get_lang('Learnpaths'),
],
[
'onclick' => 'selectToolProvider(this.value)',
]
);
$this->addElement('html', $plugin->getLearnPathsSelect());
$this->addElement('html', $plugin->getQuizzesSelect());
$this->addButtonCreate($plugin->get_lang('AddPlatform'));
$this->applyFilter('__ALL__', 'trim');
}
public function setDefaultValues(): void
{
$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();
$defaults['tool_type'] = 'quiz';
$this->setDefaults($defaults);
}
}

View File

@@ -0,0 +1,94 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\LtiProvider\Form;
use Chamilo\PluginBundle\Entity\LtiProvider\Platform;
use Exception;
use FormValidator;
use LtiProviderPlugin;
/**
* Class FrmEdit.
*/
class FrmEdit extends FormValidator
{
/**
* @var Platform|null
*/
private $platform;
/**
* FrmEdit constructor.
*/
public function __construct(
string $name,
array $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'), false);
$this->addRadio(
'tool_type',
get_lang('ToolProvider'),
[
'quiz' => $plugin->get_lang('Quizzes'),
'lp' => $plugin->get_lang('Learnpaths'),
],
[
'onclick' => 'selectToolProvider(this.value)',
]
);
$this->addElement('html', $plugin->getLearnPathsSelect($this->platform->getClientId()));
$this->addElement('html', $plugin->getQuizzesSelect($this->platform->getClientId()));
$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(): void
{
$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();
$toolProvider = $this->platform->getToolProvider();
list($courseCode, $tool) = explode('@@', $toolProvider);
list($toolName, $toolId) = explode('-', $tool);
$defaults['tool_type'] = $toolName;
$defaults['tool_provider'] = $toolProvider;
$this->setDefaults($defaults);
}
}

View File

@@ -0,0 +1,170 @@
<?php
/* For licensing terms, see /license.txt */
use ChamiloSession as Session;
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(?array $request = null)
{
$launchUrl = Security::remove_XSS($request['target_link_uri']);
LtiOidcLogin::new(new Lti13Database(), new Lti13Cache(), new Lti13Cookie())
->doOidcLoginRedirect($launchUrl, $request)
->doRedirect();
}
/**
* It removes user and oLP session.
*/
public function logout(string $toolName = '')
{
Session::erase('_user');
Session::erase('is_platformAdmin');
Session::erase('is_allowedCreateCourse');
Session::erase('_uid');
if ('lp' == $toolName) {
// Deleting the objects
Session::erase('oLP');
Session::erase('lpobject');
Session::erase('scorm_view_id');
Session::erase('scorm_item_id');
Session::erase('exerciseResult');
Session::erase('objExercise');
Session::erase('questionList');
}
Session::erase('is_allowed_in_course');
Session::erase('_real_cid');
Session::erase('_cid');
Session::erase('_course');
}
/**
* Lti Message Launch.
*/
public function launch(bool $fromCache = false, ?string $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;
}
/**
* Verify if user is in the provider platform to create it and login (true) or not (false).
*/
public function validateUser(array $launchData, string $courseCode, string $toolName): bool
{
if (empty($launchData)) {
return false;
}
$authSource = IMS_LTI_SOURCE;
$username = md5($launchData['iss'].'_'.$launchData['sub']);
$userInfo = api_get_user_info_from_username($username, $authSource);
if (empty($userInfo)) {
$email = $username.'@'.$authSource.'.com';
if (!empty($launchData['email'])) {
$email = $launchData['email'];
}
$firstName = $launchData['aud'];
if (!empty($launchData['given_name'])) {
$firstName = $launchData['given_name'];
}
$lastName = $launchData['sub'];
if (!empty($launchData['family_name'])) {
$lastName = $launchData['family_name'];
}
$password = api_generate_password();
$userId = UserManager::create_user(
$firstName,
$lastName,
STUDENT,
$email,
$username,
$password,
'',
'',
'',
'',
$authSource
);
} else {
$userId = $userInfo['user_id'];
}
if (!CourseManager::is_user_subscribed_in_course($userId, $courseCode)) {
CourseManager::subscribeUser($userId, $courseCode);
}
$this->logout($toolName);
$login = UserManager::loginAsUser($userId, false);
if ($login && CourseManager::is_user_subscribed_in_course($userId, $courseCode)) {
$_course = api_get_course_info($courseCode);
Session::write('is_allowed_in_course', true);
Session::write('_real_cid', $_course['real_id']);
Session::write('_cid', $_course['code']);
Session::write('_course', $_course);
}
return $login;
}
/**
* It checks if request is from lti customer.
*
* @param $request
* @param $session
*
* @return bool
*/
public function isLtiRequest($request, $session)
{
$isLti = false;
if (isset($request['lti_message_hint'])) {
$isLti = true;
} elseif (isset($request['state'])) {
$isLti = true;
} elseif (isset($request['lti_launch_id']) && 'learnpath' === api_get_origin()) {
$isLti = true;
} elseif (isset($request['lti_launch_id'])) {
$isLti = true;
} elseif (isset($session['oLP']->lti_launch_id)) {
$isLti = true;
}
return $isLti;
}
}

View File

@@ -0,0 +1,61 @@
<?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!");
}
if (!isset($_REQUEST['lti_result_id'])) {
throw new Exception("Any tool result");
}
$launchData = $launch->getLaunchData();
$courseCode = $_REQUEST['cidReq'];
$courseId = api_get_course_int_id($courseCode);
$toolName = $_REQUEST['lti_tool'];
if (in_array($toolName, ['quiz', 'lp'])) {
if ('quiz' == $toolName) {
$objExercise = new Exercise($courseId);
$trackInfo = $objExercise->get_stat_track_exercise_info_by_exe_id($_REQUEST['lti_result_id']);
$score = $trackInfo['exe_result'];
$weight = $trackInfo['exe_weighting'];
$timestamp = date(DATE_ISO8601);
} else {
$lpId = (int) $_REQUEST['lti_result_id'];
$lpProgress = learnpath::getProgress(
$lpId,
api_get_user_id(),
api_get_course_int_id(),
api_get_session_id()
);
$score = $lpProgress;
$weight = 100;
$timestamp = date(DATE_ISO8601);
}
$grades = $launch->getAgs();
$scoreGrade = Packback\Lti1p3\LtiGrade::new()
->setScoreGiven($score)
->setScoreMaximum($weight)
->setTimestamp($timestamp)
->setActivityProgress('Completed')
->setGradingProgress('FullyGraded')
->setUserId($launchData['sub']);
$grades->putGrade($scoreGrade);
$plugin = LtiProviderPlugin::create();
$values = [];
$values['score'] = $score;
$values['progress'] = 0;
$values['duration'] = 0;
$plugin->saveResult($values, $_REQUEST['launch_id']);
echo '{"success" : true}';
}

View File

@@ -0,0 +1,49 @@
<?php
/* For licensing terms, see /license.txt */
use Chamilo\PluginBundle\Entity\LtiProvider\PlatformKey;
use Firebase\JWT\JWT;
use phpseclib\Crypt\RSA;
$cidReset = true;
require_once __DIR__.'/../../../main/inc/global.inc.php';
$plugin = LtiProviderPlugin::create();
if ('true' !== $plugin->get('enabled')) {
exit;
}
/** @var PlatformKey $platformKey */
$platformKey = Database::getManager()
->getRepository('ChamiloPluginBundle:LtiProvider\PlatformKey')
->findOneBy([]);
if (!$platformKey) {
exit;
}
$privateKey = $platformKey->getPrivateKey();
$jwks = [];
$key = new RSA();
$key->setHash('sha256');
$key->loadKey($platformKey->getPrivateKey());
$key->setPublicKey(false, RSA::PUBLIC_FORMAT_PKCS8);
if ($key->publicExponent) {
$jwks = [
'kty' => 'RSA',
'alg' => 'RS256',
'use' => 'sig',
'e' => JWT::urlsafeB64Encode($key->publicExponent->toBytes()),
'n' => JWT::urlsafeB64Encode($key->modulus->toBytes()),
'kid' => $platformKey->getKid(),
];
}
header('Content-Type: application/json');
echo json_encode(['keys' => [$jwks]]);

View File

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

View File

@@ -0,0 +1,10 @@
<?php
/* For license terms, see /license.txt */
require_once __DIR__.'/../../../main/inc/global.inc.php';
require_once __DIR__.'/../src/LtiProvider.php';
require_once __DIR__.'/../LtiProviderPlugin.php';
LtiProvider::create()->logout();
header('Location: '.api_get_path(WEB_PATH));
exit;

View File

@@ -0,0 +1,44 @@
<?php
/* For license terms, see /license.txt */
use ChamiloSession as Session;
require_once __DIR__.'/../../../main/inc/global.inc.php';
require_once __DIR__.'/../src/LtiProvider.php';
require_once __DIR__.'/../LtiProviderPlugin.php';
$launch = LtiProvider::create()->launch();
if (!$launch->hasNrps()) {
// throw new Exception("Don't have names and roles!");
}
$launchData = $launch->getLaunchData();
$plugin = LtiProviderPlugin::create();
$toolVars = $plugin->getToolProviderVars($launchData['aud']);
$login = LtiProvider::create()->validateUser($launchData, $toolVars['courseCode'], $toolVars['toolName']);
$ltiSession = [];
if ($login) {
$values = [];
$values['issuer'] = $launchData['iss'];
$values['user_id'] = api_get_user_id();
$values['client_uid'] = $launchData['sub'];
$values['course_code'] = $toolVars['courseCode'];
$values['tool_id'] = $toolVars['toolId'];
$values['tool_name'] = $toolVars['toolName'];
$values['lti_launch_id'] = $launch->getLaunchId();
$plugin->saveResult($values);
$ltiSession = $values;
}
$cidReq = 'cidReq='.$toolVars['courseCode'].'&id_session=0&gidReq=0&gradebook=0';
if ('lp' == $toolVars['toolName']) {
$launchUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.$cidReq.'&action=view&lp_id='.$toolVars['toolId'].'&isStudentView=true&lti_launch_id='.$launch->getLaunchId();
} else {
$launchUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.$cidReq.'&origin=embeddable&exerciseId='.$toolVars['toolId'].'&lti_launch_id='.$launch->getLaunchId();
}
$ltiSession['launch_url'] = $launchUrl;
Session::write('_ltiProvider', $ltiSession);
header('Location: '.$launchUrl);
exit;

View File

@@ -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()) {
exit('You must have admin permissions to install plugins');
}
LtiProviderPlugin::create()->uninstall();

View File

@@ -0,0 +1,19 @@
<div class="row">
<div class="col-sm-9 col-md-offset-3">
{{ form }}
</div>
</div>
<script>
$(function() {
if ($("input[name='tool_type']").length > 0) {
var toolType = $("input[name='tool_type']:checked").val();
selectToolProvider(toolType)
}
});
function selectToolProvider(tool) {
$(".sbox-tool").attr('disabled', 'disabled');
$(".select-tool").hide();
$("#select-"+tool).show();
$("#sbox-tool-"+tool).removeAttr('disabled');
}
</script>

View File

@@ -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">{{ 'URLs'|get_plugin_lang('LtiProviderPlugin') }}</th>
<th class="text-center">{{ 'ToolProvider'|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>
<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>{{ platform.getToolProvider }}</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>