Actualización
This commit is contained in:
1
plugin/ims_lti/.htaccess
Normal file
1
plugin/ims_lti/.htaccess
Normal file
@@ -0,0 +1 @@
|
||||
AcceptPathInfo On
|
||||
930
plugin/ims_lti/Entity/ImsLtiTool.php
Normal file
930
plugin/ims_lti/Entity/ImsLtiTool.php
Normal file
@@ -0,0 +1,930 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
namespace Chamilo\PluginBundle\Entity\ImsLti;
|
||||
|
||||
use Chamilo\CoreBundle\Entity\Course;
|
||||
use Chamilo\CoreBundle\Entity\GradebookEvaluation;
|
||||
use Chamilo\CoreBundle\Entity\Session;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* Class ImsLtiTool.
|
||||
*
|
||||
* @ORM\Table(name="plugin_ims_lti_tool")
|
||||
* @ORM\Entity()
|
||||
*/
|
||||
class ImsLtiTool
|
||||
{
|
||||
/**
|
||||
* @var string|null
|
||||
*
|
||||
* @ORM\Column(name="public_key", type="text", nullable=true)
|
||||
*/
|
||||
public $publicKey;
|
||||
/**
|
||||
* @var int
|
||||
*
|
||||
* @ORM\Column(name="id", type="integer")
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
*/
|
||||
protected $id;
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(name="name", type="string")
|
||||
*/
|
||||
private $name = '';
|
||||
/**
|
||||
* @var string|null
|
||||
*
|
||||
* @ORM\Column(name="description", type="text", nullable=true)
|
||||
*/
|
||||
private $description = null;
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(name="launch_url", type="string")
|
||||
*/
|
||||
private $launchUrl = '';
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(name="consumer_key", type="string", nullable=true)
|
||||
*/
|
||||
private $consumerKey = '';
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(name="shared_secret", type="string", nullable=true)
|
||||
*/
|
||||
private $sharedSecret = '';
|
||||
/**
|
||||
* @var string|null
|
||||
*
|
||||
* @ORM\Column(name="custom_params", type="text", nullable=true)
|
||||
*/
|
||||
private $customParams = null;
|
||||
/**
|
||||
* @var bool
|
||||
*
|
||||
* @ORM\Column(name="active_deep_linking", type="boolean", nullable=false, options={"default": false})
|
||||
*/
|
||||
private $activeDeepLinking = false;
|
||||
/**
|
||||
* @var string|null
|
||||
*
|
||||
* @ORM\Column(name="privacy", type="text", nullable=true, options={"default": null})
|
||||
*/
|
||||
private $privacy = null;
|
||||
/**
|
||||
* @var Course|null
|
||||
*
|
||||
* @ORM\ManyToOne(targetEntity="Chamilo\CoreBundle\Entity\Course")
|
||||
* @ORM\JoinColumn(name="c_id", referencedColumnName="id")
|
||||
*/
|
||||
private $course = null;
|
||||
/**
|
||||
* @var Session|null
|
||||
*
|
||||
* @ORM\ManyToOne(targetEntity="Chamilo\CoreBundle\Entity\Session")
|
||||
* @ORM\JoinColumn(name="session_id", referencedColumnName="id")
|
||||
*/
|
||||
private $session = null;
|
||||
/**
|
||||
* @var GradebookEvaluation|null
|
||||
*
|
||||
* @ORM\ManyToOne(targetEntity="Chamilo\CoreBundle\Entity\GradebookEvaluation")
|
||||
* @ORM\JoinColumn(name="gradebook_eval_id", referencedColumnName="id", onDelete="SET NULL")
|
||||
*/
|
||||
private $gradebookEval = null;
|
||||
/**
|
||||
* @var ImsLtiTool|null
|
||||
*
|
||||
* @ORM\ManyToOne(targetEntity="Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool", inversedBy="children")
|
||||
* @ORM\JoinColumn(name="parent_id", referencedColumnName="id", onDelete="CASCADE")
|
||||
*/
|
||||
private $parent;
|
||||
/**
|
||||
* @var ArrayCollection
|
||||
*
|
||||
* @ORM\OneToMany(targetEntity="Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool", mappedBy="parent")
|
||||
*/
|
||||
private $children;
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(name="client_id", type="string", nullable=true)
|
||||
*/
|
||||
private $clientId;
|
||||
/**
|
||||
* @var string|null
|
||||
*
|
||||
* @ORM\Column(name="login_url", type="string", nullable=true)
|
||||
*/
|
||||
private $loginUrl;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*
|
||||
* @ORM\Column(name="redirect_url", type="string", nullable=true)
|
||||
*/
|
||||
private $redirectUrl;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*
|
||||
* @ORM\Column(name="jwks_url", type="string", nullable=true)
|
||||
*/
|
||||
private $jwksUrl;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*
|
||||
* @ORM\Column(name="advantage_services", type="json", nullable=true)
|
||||
*/
|
||||
private $advantageServices;
|
||||
|
||||
/**
|
||||
* @var ArrayCollection
|
||||
*
|
||||
* @ORM\OneToMany(targetEntity="Chamilo\PluginBundle\Entity\ImsLti\LineItem", mappedBy="tool")
|
||||
*/
|
||||
private $lineItems;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(name="version", type="string", options={"default": "lti1p1"})
|
||||
*/
|
||||
private $version;
|
||||
/**
|
||||
* @var array
|
||||
*
|
||||
* @ORM\Column(name="launch_presentation", type="json")
|
||||
*/
|
||||
private $launchPresentation;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*
|
||||
* @ORM\Column(name="replacement_params", type="json")
|
||||
*/
|
||||
private $replacementParams;
|
||||
|
||||
/**
|
||||
* ImsLtiTool constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->description = null;
|
||||
$this->customParams = null;
|
||||
$this->activeDeepLinking = false;
|
||||
$this->course = null;
|
||||
$this->gradebookEval = null;
|
||||
$this->privacy = null;
|
||||
$this->children = new ArrayCollection();
|
||||
$this->consumerKey = null;
|
||||
$this->sharedSecret = null;
|
||||
$this->lineItems = new ArrayCollection();
|
||||
$this->version = \ImsLti::V_1P3;
|
||||
$this->launchPresentation = [
|
||||
'document_target' => 'iframe',
|
||||
];
|
||||
$this->replacementParams = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return ImsLtiTool
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $description
|
||||
*
|
||||
* @return ImsLtiTool
|
||||
*/
|
||||
public function setDescription($description)
|
||||
{
|
||||
$this->description = $description;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getLaunchUrl()
|
||||
{
|
||||
return $this->launchUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $launchUrl
|
||||
*
|
||||
* @return ImsLtiTool
|
||||
*/
|
||||
public function setLaunchUrl($launchUrl)
|
||||
{
|
||||
$this->launchUrl = $launchUrl;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getCustomParams()
|
||||
{
|
||||
return $this->customParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $customParams
|
||||
*
|
||||
* @return ImsLtiTool
|
||||
*/
|
||||
public function setCustomParams($customParams)
|
||||
{
|
||||
$this->customParams = $customParams;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isGlobal()
|
||||
{
|
||||
return $this->course === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function encodeCustomParams(array $params)
|
||||
{
|
||||
if (empty($params)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$pairs = [];
|
||||
|
||||
foreach ($params as $key => $value) {
|
||||
$pairs[] = "$key=$value";
|
||||
}
|
||||
|
||||
return implode("\n", $pairs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getCustomParamsAsArray()
|
||||
{
|
||||
$params = [];
|
||||
$lines = explode("\n", $this->customParams);
|
||||
$lines = array_filter($lines);
|
||||
|
||||
foreach ($lines as $line) {
|
||||
list($key, $value) = explode('=', $line, 2);
|
||||
|
||||
$key = self::filterSpecialChars($key);
|
||||
$value = self::filterSpaces($value);
|
||||
|
||||
$params[$key] = $value;
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function parseCustomParams()
|
||||
{
|
||||
if (empty($this->customParams)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$params = [];
|
||||
$strings = explode("\n", $this->customParams);
|
||||
|
||||
foreach ($strings as $string) {
|
||||
if (empty($string)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$pairs = explode('=', $string, 2);
|
||||
$key = self::filterSpecialChars($pairs[0]);
|
||||
$value = $pairs[1];
|
||||
|
||||
$params['custom_'.$key] = $value;
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get activeDeepLinking.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isActiveDeepLinking()
|
||||
{
|
||||
return $this->activeDeepLinking;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set activeDeepLinking.
|
||||
*
|
||||
* @param bool $activeDeepLinking
|
||||
*
|
||||
* @return ImsLtiTool
|
||||
*/
|
||||
public function setActiveDeepLinking($activeDeepLinking)
|
||||
{
|
||||
$this->activeDeepLinking = $activeDeepLinking;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get course.
|
||||
*
|
||||
* @return Course|null
|
||||
*/
|
||||
public function getCourse()
|
||||
{
|
||||
return $this->course;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set course.
|
||||
*
|
||||
* @return ImsLtiTool
|
||||
*/
|
||||
public function setCourse(Course $course = null)
|
||||
{
|
||||
$this->course = $course;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get session.
|
||||
*
|
||||
* @return Session|null
|
||||
*/
|
||||
public function getSession()
|
||||
{
|
||||
return $this->session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set session.
|
||||
*
|
||||
* @param Session|null $course
|
||||
*
|
||||
* @return ImsLtiTool
|
||||
*/
|
||||
public function setSession(Session $session = null)
|
||||
{
|
||||
$this->session = $session;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get gradebookEval.
|
||||
*
|
||||
* @return GradebookEvaluation|null
|
||||
*/
|
||||
public function getGradebookEval()
|
||||
{
|
||||
return $this->gradebookEval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set gradebookEval.
|
||||
*
|
||||
* @param GradebookEvaluation|null $gradebookEval
|
||||
*
|
||||
* @return ImsLtiTool
|
||||
*/
|
||||
public function setGradebookEval($gradebookEval)
|
||||
{
|
||||
$this->gradebookEval = $gradebookEval;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isSharingName()
|
||||
{
|
||||
$unserialize = $this->unserializePrivacy();
|
||||
|
||||
return (bool) $unserialize['share_name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function unserializePrivacy()
|
||||
{
|
||||
return \UnserializeApi::unserialize('not_allowed_classes', $this->privacy);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isSharingEmail()
|
||||
{
|
||||
$unserialize = $this->unserializePrivacy();
|
||||
|
||||
return (bool) $unserialize['share_email'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isSharingPicture()
|
||||
{
|
||||
$unserialize = $this->unserializePrivacy();
|
||||
|
||||
return (bool) $unserialize['share_picture'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ImsLtiTool|null
|
||||
*/
|
||||
public function getParent()
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ImsLtiTool
|
||||
*/
|
||||
public function setParent(ImsLtiTool $parent)
|
||||
{
|
||||
$this->parent = $parent;
|
||||
|
||||
$this->sharedSecret = $parent->getSharedSecret();
|
||||
$this->consumerKey = $parent->getConsumerKey();
|
||||
$this->privacy = $parent->getPrivacy();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getSharedSecret()
|
||||
{
|
||||
return $this->sharedSecret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sharedSecret
|
||||
*
|
||||
* @return ImsLtiTool
|
||||
*/
|
||||
public function setSharedSecret($sharedSecret)
|
||||
{
|
||||
$this->sharedSecret = $sharedSecret;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getConsumerKey()
|
||||
{
|
||||
return $this->consumerKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $consumerKey
|
||||
*
|
||||
* @return ImsLtiTool
|
||||
*/
|
||||
public function setConsumerKey($consumerKey)
|
||||
{
|
||||
$this->consumerKey = $consumerKey;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get privacy.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getPrivacy()
|
||||
{
|
||||
return $this->privacy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set privacy.
|
||||
*
|
||||
* @param bool $shareName
|
||||
* @param bool $shareEmail
|
||||
* @param bool $sharePicture
|
||||
*
|
||||
* @return ImsLtiTool
|
||||
*/
|
||||
public function setPrivacy($shareName = false, $shareEmail = false, $sharePicture = false)
|
||||
{
|
||||
$this->privacy = serialize(
|
||||
[
|
||||
'share_name' => $shareName,
|
||||
'share_email' => $shareEmail,
|
||||
'share_picture' => $sharePicture,
|
||||
]
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get loginUrl.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getLoginUrl()
|
||||
{
|
||||
return $this->loginUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set loginUrl.
|
||||
*
|
||||
* @param string|null $loginUrl
|
||||
*
|
||||
* @return ImsLtiTool
|
||||
*/
|
||||
public function setLoginUrl($loginUrl)
|
||||
{
|
||||
$this->loginUrl = $loginUrl;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get redirectUlr.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getRedirectUrl()
|
||||
{
|
||||
return $this->redirectUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set redirectUrl.
|
||||
*
|
||||
* @param string|null $redirectUrl
|
||||
*
|
||||
* @return ImsLtiTool
|
||||
*/
|
||||
public function setRedirectUrl($redirectUrl)
|
||||
{
|
||||
$this->redirectUrl = $redirectUrl;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get jwksUrl.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getJwksUrl()
|
||||
{
|
||||
return $this->jwksUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set jwksUrl.
|
||||
*
|
||||
* @param string|null $jwksUrl
|
||||
*
|
||||
* @return ImsLtiTool
|
||||
*/
|
||||
public function setJwksUrl($jwksUrl)
|
||||
{
|
||||
$this->jwksUrl = $jwksUrl;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get clientId.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getClientId()
|
||||
{
|
||||
return $this->clientId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set clientId.
|
||||
*
|
||||
* @param string $clientId
|
||||
*
|
||||
* @return ImsLtiTool
|
||||
*/
|
||||
public function setClientId($clientId)
|
||||
{
|
||||
$this->clientId = $clientId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get advantageServices.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAdvantageServices()
|
||||
{
|
||||
if (empty($this->advantageServices)) {
|
||||
$this->advantageServices = [];
|
||||
}
|
||||
|
||||
return array_merge(
|
||||
[
|
||||
'ags' => \LtiAssignmentGradesService::AGS_NONE,
|
||||
'nrps' => \LtiNamesRoleProvisioningService::NRPS_NONE,
|
||||
],
|
||||
$this->advantageServices
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set advantageServices.
|
||||
*
|
||||
* @param array $advantageServices
|
||||
*
|
||||
* @return ImsLtiTool
|
||||
*/
|
||||
public function setAdvantageServices($advantageServices)
|
||||
{
|
||||
$this->advantageServices = $advantageServices;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add LineItem to lineItems.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addLineItem(LineItem $lineItem)
|
||||
{
|
||||
$lineItem->setTool($this);
|
||||
|
||||
$this->lineItems[] = $lineItem;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $resourceLinkId
|
||||
* @param int $resourceId
|
||||
* @param string $tag
|
||||
* @param int $limit
|
||||
* @param int $page
|
||||
*
|
||||
* @return ArrayCollection
|
||||
*/
|
||||
public function getLineItems($resourceLinkId = 0, $resourceId = 0, $tag = '', $limit = 0, $page = 1)
|
||||
{
|
||||
$criteria = Criteria::create();
|
||||
|
||||
if ($resourceLinkId) {
|
||||
$criteria->andWhere(Criteria::expr()->eq('tool', $resourceId));
|
||||
}
|
||||
|
||||
if ($resourceId) {
|
||||
$criteria->andWhere(Criteria::expr()->eq('tool', $resourceId));
|
||||
}
|
||||
|
||||
if (!empty($tag)) {
|
||||
$criteria->andWhere(Criteria::expr()->eq('tag', $tag));
|
||||
}
|
||||
|
||||
$limit = (int) $limit;
|
||||
$page = (int) $page;
|
||||
|
||||
if ($limit > 0) {
|
||||
$criteria->setMaxResults($limit);
|
||||
|
||||
if ($page > 0) {
|
||||
$criteria->setFirstResult($page * $limit);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->lineItems->matching($criteria);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set lineItems.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setLineItems(ArrayCollection $lineItems)
|
||||
{
|
||||
$this->lineItems = $lineItems;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get version.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getVersion()
|
||||
{
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set version.
|
||||
*
|
||||
* @param string $version
|
||||
*
|
||||
* @return ImsLtiTool
|
||||
*/
|
||||
public function setVersion($version)
|
||||
{
|
||||
$this->version = $version;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getVersionName()
|
||||
{
|
||||
if (\ImsLti::V_1P1 === $this->version) {
|
||||
return 'LTI 1.0 / 1.1';
|
||||
}
|
||||
|
||||
return 'LTI 1.3';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ArrayCollection
|
||||
*/
|
||||
public function getChildren()
|
||||
{
|
||||
return $this->children;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $target
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setDocumenTarget($target)
|
||||
{
|
||||
$this->launchPresentation['document_target'] = in_array($target, ['iframe', 'window']) ? $target : 'iframe';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDocumentTarget()
|
||||
{
|
||||
return $this->launchPresentation['document_target'] ?: 'iframe';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getLaunchPresentation()
|
||||
{
|
||||
return $this->launchPresentation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $replacement
|
||||
*
|
||||
* @return ImsLtiTool
|
||||
*/
|
||||
public function setReplacementForUserId($replacement)
|
||||
{
|
||||
$this->replacementParams['user_id'] = $replacement;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getReplacementForUserId()
|
||||
{
|
||||
if (!empty($this->replacementParams['user_id'])) {
|
||||
return $this->replacementParams['user_id'];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ArrayCollection
|
||||
*/
|
||||
public function getChildrenInCourses(array $coursesId)
|
||||
{
|
||||
return $this->children->filter(
|
||||
function (ImsLtiTool $child) use ($coursesId) {
|
||||
return in_array($child->getCourse()->getId(), $coursesId);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map the key from custom param.
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function filterSpecialChars($key)
|
||||
{
|
||||
$newKey = '';
|
||||
$key = strtolower($key);
|
||||
$split = str_split($key);
|
||||
|
||||
foreach ($split as $char) {
|
||||
if (
|
||||
($char >= 'a' && $char <= 'z') || ($char >= '0' && $char <= '9')
|
||||
) {
|
||||
$newKey .= $char;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$newKey .= '_';
|
||||
}
|
||||
|
||||
return $newKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function filterSpaces($value)
|
||||
{
|
||||
$newValue = preg_replace('/\s+/', ' ', $value);
|
||||
|
||||
return trim($newValue);
|
||||
}
|
||||
}
|
||||
241
plugin/ims_lti/Entity/LineItem.php
Normal file
241
plugin/ims_lti/Entity/LineItem.php
Normal file
@@ -0,0 +1,241 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
namespace Chamilo\PluginBundle\Entity\ImsLti;
|
||||
|
||||
use Chamilo\CoreBundle\Entity\GradebookEvaluation;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* Class LineItem.
|
||||
*
|
||||
* @package Chamilo\PluginBundle\Entity\ImsLti
|
||||
*
|
||||
* @ORM\Table(name="plugin_ims_lti_lineitem")
|
||||
* @ORM\Entity()
|
||||
*/
|
||||
class LineItem
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*
|
||||
* @ORM\Column(name="id", type="integer")
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
*/
|
||||
protected $id;
|
||||
/**
|
||||
* @var ImsLtiTool
|
||||
*
|
||||
* @ORM\ManyToOne(targetEntity="Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool", inversedBy="lineItems")
|
||||
* @ORM\JoinColumn(name="tool_id", referencedColumnName="id", nullable=false, onDelete="CASCADE")
|
||||
*/
|
||||
private $tool;
|
||||
/**
|
||||
* @var GradebookEvaluation
|
||||
*
|
||||
* @ORM\OneToOne(targetEntity="Chamilo\CoreBundle\Entity\GradebookEvaluation")
|
||||
* @ORM\JoinColumn(name="evaluation", referencedColumnName="id", nullable=false, onDelete="CASCADE")
|
||||
*/
|
||||
private $evaluation;
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(name="resource_id", type="string", nullable=true)
|
||||
*/
|
||||
private $resourceId;
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(name="tag", type="string", nullable=true)
|
||||
*/
|
||||
private $tag;
|
||||
/**
|
||||
* @var \DateTime
|
||||
*
|
||||
* @ORM\Column(name="start_date", type="datetime", nullable=true)
|
||||
*/
|
||||
private $startDate;
|
||||
/**
|
||||
* @var \DateTime
|
||||
*
|
||||
* @ORM\Column(name="end_date", type="datetime", nullable=true)
|
||||
*/
|
||||
private $endDate;
|
||||
|
||||
/**
|
||||
* Get id.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tool.
|
||||
*
|
||||
* @return ImsLtiTool
|
||||
*/
|
||||
public function getTool()
|
||||
{
|
||||
return $this->tool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set tool.
|
||||
*
|
||||
* @param ImsLtiTool $tool
|
||||
*
|
||||
* @return LineItem
|
||||
*/
|
||||
public function setTool($tool)
|
||||
{
|
||||
$this->tool = $tool;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get evaluation.
|
||||
*
|
||||
* @return GradebookEvaluation
|
||||
*/
|
||||
public function getEvaluation()
|
||||
{
|
||||
return $this->evaluation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set evaluation.
|
||||
*
|
||||
* @param GradebookEvaluation $evaluation
|
||||
*
|
||||
* @return LineItem
|
||||
*/
|
||||
public function setEvaluation($evaluation)
|
||||
{
|
||||
$this->evaluation = $evaluation;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tag.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTag()
|
||||
{
|
||||
return $this->tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set tag.
|
||||
*
|
||||
* @param string $tag
|
||||
*
|
||||
* @return LineItem
|
||||
*/
|
||||
public function setTag($tag)
|
||||
{
|
||||
$this->tag = $tag;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get startDate.
|
||||
*
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function getStartDate()
|
||||
{
|
||||
return $this->startDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set startDate.
|
||||
*
|
||||
* @param \DateTime $startDate
|
||||
*
|
||||
* @return LineItem
|
||||
*/
|
||||
public function setStartDate($startDate)
|
||||
{
|
||||
$this->startDate = $startDate;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get endDate.
|
||||
*
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function getEndDate()
|
||||
{
|
||||
return $this->endDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set endDate.
|
||||
*
|
||||
* @param \DateTime $endDate
|
||||
*
|
||||
* @return LineItem
|
||||
*/
|
||||
public function setEndDate($endDate)
|
||||
{
|
||||
$this->endDate = $endDate;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getResourceId()
|
||||
{
|
||||
return $this->resourceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $resourceId
|
||||
*
|
||||
* @return LineItem
|
||||
*/
|
||||
public function setResourceId($resourceId)
|
||||
{
|
||||
$this->resourceId = $resourceId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
$baseTool = $this->tool->getParent() ?: $this->tool;
|
||||
|
||||
$data = [
|
||||
'scoreMaximum' => $this->evaluation->getMax(),
|
||||
'label' => $this->evaluation->getName(),
|
||||
'tag' => (string) $this->tag,
|
||||
'resourceLinkId' => (string) $baseTool->getId(),
|
||||
'resourceId' => (string) $this->resourceId,
|
||||
];
|
||||
|
||||
if ($this->startDate) {
|
||||
$data['startDateTime'] = $this->startDate->format(\DateTime::ATOM);
|
||||
}
|
||||
|
||||
if ($this->endDate) {
|
||||
$data['endDateTime'] = $this->endDate->format(\DateTime::ATOM);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
112
plugin/ims_lti/Entity/Platform.php
Normal file
112
plugin/ims_lti/Entity/Platform.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
namespace Chamilo\PluginBundle\Entity\ImsLti;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* Class Platform.
|
||||
*
|
||||
* @package Chamilo\PluginBundle\Entity\ImsLti
|
||||
*
|
||||
* @ORM\Table(name="plugin_ims_lti_platform")
|
||||
* @ORM\Entity()
|
||||
*/
|
||||
class Platform
|
||||
{
|
||||
/**
|
||||
* @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()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set id.
|
||||
*
|
||||
* @param int $id
|
||||
*
|
||||
* @return Platform
|
||||
*/
|
||||
public function setId($id)
|
||||
{
|
||||
$this->id = $id;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get kid.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getKid()
|
||||
{
|
||||
return $this->kid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set kid.
|
||||
*
|
||||
* @param string $kid
|
||||
*/
|
||||
public function setKid($kid)
|
||||
{
|
||||
$this->kid = $kid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get privateKey.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPrivateKey()
|
||||
{
|
||||
return $this->privateKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set privateKey.
|
||||
*
|
||||
* @param string $privateKey
|
||||
*
|
||||
* @return Platform
|
||||
*/
|
||||
public function setPrivateKey($privateKey)
|
||||
{
|
||||
$this->privateKey = $privateKey;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
187
plugin/ims_lti/Entity/Token.php
Normal file
187
plugin/ims_lti/Entity/Token.php
Normal file
@@ -0,0 +1,187 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
namespace Chamilo\PluginBundle\Entity\ImsLti;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* Class Token.
|
||||
*
|
||||
* @package Chamilo\PluginBundle\Entity\ImsLti
|
||||
*
|
||||
* @ORM\Table(name="plugin_ims_lti_token")
|
||||
* @ORM\Entity()
|
||||
*/
|
||||
class Token
|
||||
{
|
||||
const TOKEN_LIFETIME = 3600;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*
|
||||
* @ORM\Column(name="id", type="integer")
|
||||
* @ORM\Id()
|
||||
* @ORM\GeneratedValue()
|
||||
*/
|
||||
protected $id;
|
||||
/**
|
||||
* @var ImsLtiTool
|
||||
*
|
||||
* @ORM\ManyToOne(targetEntity="Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool")
|
||||
* @ORM\JoinColumn(name="tool_id", referencedColumnName="id", onDelete="CASCADE")
|
||||
*/
|
||||
private $tool;
|
||||
/**
|
||||
* @var array
|
||||
*
|
||||
* @ORM\Column(name="scope", type="json")
|
||||
*/
|
||||
private $scope;
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(name="hash", type="string")
|
||||
*/
|
||||
private $hash;
|
||||
/**
|
||||
* @var int
|
||||
*
|
||||
* @ORM\Column(name="created_at", type="integer")
|
||||
*/
|
||||
private $createdAt;
|
||||
/**
|
||||
* @var int
|
||||
*
|
||||
* @ORM\Column(name="expires_at", type="integer")
|
||||
*/
|
||||
private $expiresAt;
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ImsLtiTool
|
||||
*/
|
||||
public function getTool()
|
||||
{
|
||||
return $this->tool;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImsLtiTool $tool
|
||||
*
|
||||
* @return Token
|
||||
*/
|
||||
public function setTool($tool)
|
||||
{
|
||||
$this->tool = $tool;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getScope()
|
||||
{
|
||||
return $this->scope;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $scope
|
||||
*
|
||||
* @return Token
|
||||
*/
|
||||
public function setScope($scope)
|
||||
{
|
||||
$this->scope = $scope;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getHash()
|
||||
{
|
||||
return $this->hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $hash
|
||||
*
|
||||
* @return Token
|
||||
*/
|
||||
public function setHash($hash)
|
||||
{
|
||||
$this->hash = $hash;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCreatedAt()
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $createdAt
|
||||
*
|
||||
* @return Token
|
||||
*/
|
||||
public function setCreatedAt($createdAt)
|
||||
{
|
||||
$this->createdAt = $createdAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getExpiresAt()
|
||||
{
|
||||
return $this->expiresAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $expiresAt
|
||||
*
|
||||
* @return Token
|
||||
*/
|
||||
public function setExpiresAt($expiresAt)
|
||||
{
|
||||
$this->expiresAt = $expiresAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getScopeInString()
|
||||
{
|
||||
return implode(' ', $this->scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate unique hash.
|
||||
*
|
||||
* @return Token
|
||||
*/
|
||||
public function generateHash()
|
||||
{
|
||||
$this->hash = sha1(uniqid(mt_rand()));
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
755
plugin/ims_lti/ImsLtiPlugin.php
Normal file
755
plugin/ims_lti/ImsLtiPlugin.php
Normal file
@@ -0,0 +1,755 @@
|
||||
<?php
|
||||
|
||||
/* For license terms, see /license.txt */
|
||||
|
||||
use Chamilo\CoreBundle\Entity\Course;
|
||||
use Chamilo\CoreBundle\Entity\Session;
|
||||
use Chamilo\CourseBundle\Entity\CTool;
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\LineItem;
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\Platform;
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\Token;
|
||||
use Chamilo\UserBundle\Entity\User;
|
||||
use Doctrine\ORM\Tools\SchemaTool;
|
||||
use Firebase\JWT\JWK;
|
||||
|
||||
/**
|
||||
* Description of MsiLti.
|
||||
*
|
||||
* @author Angel Fernando Quiroz Campos <angel.quiroz@beeznest.com>
|
||||
*/
|
||||
class ImsLtiPlugin extends Plugin
|
||||
{
|
||||
const TABLE_TOOL = 'plugin_ims_lti_tool';
|
||||
const TABLE_PLATFORM = 'plugin_ims_lti_platform';
|
||||
|
||||
public $isAdminPlugin = true;
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
$version = '1.9.0';
|
||||
$author = 'Angel Fernando Quiroz Campos';
|
||||
|
||||
$message = Display::return_message($this->get_lang('GenerateKeyPairInfo'));
|
||||
$settings = [
|
||||
$message => 'html',
|
||||
'enabled' => 'boolean',
|
||||
];
|
||||
|
||||
parent::__construct($version, $author, $settings);
|
||||
|
||||
$this->setCourseSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the class instance.
|
||||
*
|
||||
* @staticvar MsiLtiPlugin $result
|
||||
*
|
||||
* @return ImsLtiPlugin
|
||||
*/
|
||||
public static function create()
|
||||
{
|
||||
static $result = null;
|
||||
|
||||
return $result ?: $result = new self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the plugin directory name.
|
||||
*/
|
||||
public function get_name()
|
||||
{
|
||||
return 'ims_lti';
|
||||
}
|
||||
|
||||
/**
|
||||
* Install the plugin. Setup the database.
|
||||
*
|
||||
* @throws \Doctrine\ORM\Tools\ToolsException
|
||||
*/
|
||||
public function install()
|
||||
{
|
||||
$this->createPluginTables();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save configuration for plugin.
|
||||
*
|
||||
* Generate a new key pair for platform when enabling plugin.
|
||||
*
|
||||
* @throws \Doctrine\ORM\OptimisticLockException
|
||||
*
|
||||
* @return $this|Plugin
|
||||
*/
|
||||
public function performActionsAfterConfigure()
|
||||
{
|
||||
$em = Database::getManager();
|
||||
|
||||
/** @var Platform $platform */
|
||||
$platform = $em
|
||||
->getRepository('ChamiloPluginBundle:ImsLti\Platform')
|
||||
->findOneBy([]);
|
||||
|
||||
if ($this->get('enabled') === 'true') {
|
||||
if (!$platform) {
|
||||
$platform = new Platform();
|
||||
}
|
||||
|
||||
$keyPair = self::generatePlatformKeys();
|
||||
|
||||
$platform->setKid($keyPair['kid']);
|
||||
$platform->publicKey = $keyPair['public'];
|
||||
$platform->setPrivateKey($keyPair['private']);
|
||||
|
||||
$em->persist($platform);
|
||||
} else {
|
||||
if ($platform) {
|
||||
$em->remove($platform);
|
||||
}
|
||||
}
|
||||
|
||||
$em->flush();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unistall plugin. Clear the database.
|
||||
*/
|
||||
public function uninstall()
|
||||
{
|
||||
try {
|
||||
$this->dropPluginTables();
|
||||
$this->removeTools();
|
||||
} catch (Exception $e) {
|
||||
error_log('Error while uninstalling IMS/LTI plugin: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CTool
|
||||
*/
|
||||
public function findCourseToolByLink(Course $course, ImsLtiTool $ltiTool)
|
||||
{
|
||||
$em = Database::getManager();
|
||||
$toolRepo = $em->getRepository('ChamiloCourseBundle:CTool');
|
||||
|
||||
/** @var CTool $cTool */
|
||||
$cTool = $toolRepo->findOneBy(
|
||||
[
|
||||
'cId' => $course,
|
||||
'link' => self::generateToolLink($ltiTool),
|
||||
]
|
||||
);
|
||||
|
||||
return $cTool;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Doctrine\ORM\OptimisticLockException
|
||||
*/
|
||||
public function updateCourseTool(CTool $courseTool, ImsLtiTool $ltiTool)
|
||||
{
|
||||
$em = Database::getManager();
|
||||
|
||||
$courseTool->setName($ltiTool->getName());
|
||||
|
||||
if ('iframe' !== $ltiTool->getDocumentTarget()) {
|
||||
$courseTool->setTarget('_blank');
|
||||
} else {
|
||||
$courseTool->setTarget('_self');
|
||||
}
|
||||
|
||||
$em->persist($courseTool);
|
||||
$em->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the course tool.
|
||||
*
|
||||
* @param bool $isVisible
|
||||
*
|
||||
* @throws \Doctrine\ORM\OptimisticLockException
|
||||
*/
|
||||
public function addCourseTool(Course $course, ImsLtiTool $ltiTool, $isVisible = true)
|
||||
{
|
||||
$cTool = $this->createLinkToCourseTool(
|
||||
$ltiTool->getName(),
|
||||
$course->getId(),
|
||||
null,
|
||||
self::generateToolLink($ltiTool)
|
||||
);
|
||||
$cTool
|
||||
->setTarget(
|
||||
$ltiTool->getDocumentTarget() === 'iframe' ? '_self' : '_blank'
|
||||
)
|
||||
->setVisibility($isVisible);
|
||||
|
||||
$em = Database::getManager();
|
||||
$em->persist($cTool);
|
||||
$em->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the course session tool.
|
||||
*
|
||||
* @param bool $isVisible
|
||||
*
|
||||
* @throws \Doctrine\ORM\OptimisticLockException
|
||||
*/
|
||||
public function addCourseSessionTool(Course $course, Session $session, ImsLtiTool $ltiTool, $isVisible = true)
|
||||
{
|
||||
$cTool = $this->createLinkToCourseTool(
|
||||
$ltiTool->getName(),
|
||||
$course->getId(),
|
||||
null,
|
||||
self::generateToolLink($ltiTool),
|
||||
$session->getId()
|
||||
);
|
||||
$cTool
|
||||
->setTarget(
|
||||
$ltiTool->getDocumentTarget() === 'iframe' ? '_self' : '_blank'
|
||||
)
|
||||
->setVisibility($isVisible);
|
||||
|
||||
$em = Database::getManager();
|
||||
$em->persist($cTool);
|
||||
$em->flush();
|
||||
}
|
||||
|
||||
public static function isInstructor()
|
||||
{
|
||||
api_is_allowed_to_edit(false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getRoles(User $user)
|
||||
{
|
||||
$roles = ['http://purl.imsglobal.org/vocab/lis/v2/system/person#User'];
|
||||
|
||||
if (DRH === $user->getStatus()) {
|
||||
$roles[] = 'http://purl.imsglobal.org/vocab/lis/v2/membership#Mentor';
|
||||
$roles[] = 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#Mentor';
|
||||
|
||||
return $roles;
|
||||
}
|
||||
|
||||
if (!api_is_allowed_to_edit(false, true)) {
|
||||
$roles[] = 'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner';
|
||||
$roles[] = 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#Student';
|
||||
|
||||
if ($user->getStatus() === INVITEE) {
|
||||
$roles[] = 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#Guest';
|
||||
}
|
||||
|
||||
return $roles;
|
||||
}
|
||||
|
||||
$roles[] = 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#Instructor';
|
||||
$roles[] = 'http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor';
|
||||
|
||||
if (api_is_platform_admin_by_id($user->getId())) {
|
||||
$roles[] = 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#Administrator';
|
||||
$roles[] = 'http://purl.imsglobal.org/vocab/lis/v2/system/person#SysAdmin';
|
||||
$roles[] = 'http://purl.imsglobal.org/vocab/lis/v2/system/person#Administrator';
|
||||
}
|
||||
|
||||
return $roles;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getUserRoles(User $user)
|
||||
{
|
||||
if (DRH === $user->getStatus()) {
|
||||
return 'urn:lti:role:ims/lis/Mentor';
|
||||
}
|
||||
|
||||
if ($user->getStatus() === INVITEE) {
|
||||
return 'Learner,urn:lti:role:ims/lis/Learner/GuestLearner';
|
||||
}
|
||||
|
||||
if (!api_is_allowed_to_edit(false, true)) {
|
||||
return 'Learner';
|
||||
}
|
||||
|
||||
$roles = ['Instructor'];
|
||||
|
||||
if (api_is_platform_admin_by_id($user->getId())) {
|
||||
$roles[] = 'urn:lti:role:ims/lis/Administrator';
|
||||
}
|
||||
|
||||
return implode(',', $roles);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $userId
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function generateToolUserId($userId)
|
||||
{
|
||||
$siteName = api_get_setting('siteName');
|
||||
$institution = api_get_setting('Institution');
|
||||
$toolUserId = "$siteName - $institution - $userId";
|
||||
$toolUserId = api_replace_dangerous_char($toolUserId);
|
||||
|
||||
return $toolUserId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getLaunchUserIdClaim(ImsLtiTool $tool, User $user)
|
||||
{
|
||||
if (null !== $tool->getParent()) {
|
||||
$tool = $tool->getParent();
|
||||
}
|
||||
|
||||
$replacement = $tool->getReplacementForUserId();
|
||||
|
||||
if (empty($replacement)) {
|
||||
if ($tool->getVersion() === ImsLti::V_1P1) {
|
||||
return self::generateToolUserId($user->getId());
|
||||
}
|
||||
|
||||
return (string) $user->getId();
|
||||
}
|
||||
|
||||
$replaced = str_replace(
|
||||
['$User.id', '$User.username'],
|
||||
[$user->getId(), $user->getUsername()],
|
||||
$replacement
|
||||
);
|
||||
|
||||
return $replaced;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getRoleScopeMentor(User $currentUser, ImsLtiTool $tool)
|
||||
{
|
||||
$scope = self::getRoleScopeMentorAsArray($currentUser, $tool, true);
|
||||
|
||||
return implode(',', $scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tool User IDs which the user DRH can access as a mentor.
|
||||
*
|
||||
* @param bool $generateIdForTool. Optional. Set TRUE for LTI 1.x.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getRoleScopeMentorAsArray(User $user, ImsLtiTool $tool, $generateIdForTool = false)
|
||||
{
|
||||
if (DRH !== $user->getStatus()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$followedUsers = UserManager::get_users_followed_by_drh($user->getId(), 0, true);
|
||||
$scope = [];
|
||||
/** @var array $userInfo */
|
||||
foreach ($followedUsers as $userInfo) {
|
||||
if ($generateIdForTool) {
|
||||
$followedUser = api_get_user_entity($userInfo['user_id']);
|
||||
|
||||
$scope[] = self::getLaunchUserIdClaim($tool, $followedUser);
|
||||
} else {
|
||||
$scope[] = (string) $userInfo['user_id'];
|
||||
}
|
||||
}
|
||||
|
||||
return $scope;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Doctrine\ORM\OptimisticLockException
|
||||
*/
|
||||
public function saveItemAsLtiLink(array $contentItem, ImsLtiTool $baseLtiTool, Course $course)
|
||||
{
|
||||
$em = Database::getManager();
|
||||
$ltiToolRepo = $em->getRepository('ChamiloPluginBundle:ImsLti\ImsLtiTool');
|
||||
|
||||
$url = empty($contentItem['url']) ? $baseLtiTool->getLaunchUrl() : $contentItem['url'];
|
||||
|
||||
/** @var ImsLtiTool $newLtiTool */
|
||||
$newLtiTool = $ltiToolRepo->findOneBy(['launchUrl' => $url, 'parent' => $baseLtiTool, 'course' => $course]);
|
||||
|
||||
if (null === $newLtiTool) {
|
||||
$newLtiTool = new ImsLtiTool();
|
||||
$newLtiTool
|
||||
->setLaunchUrl($url)
|
||||
->setParent(
|
||||
$baseLtiTool
|
||||
)
|
||||
->setPrivacy(
|
||||
$baseLtiTool->isSharingName(),
|
||||
$baseLtiTool->isSharingEmail(),
|
||||
$baseLtiTool->isSharingPicture()
|
||||
)
|
||||
->setCourse($course);
|
||||
}
|
||||
|
||||
$newLtiTool
|
||||
->setName(
|
||||
!empty($contentItem['title']) ? $contentItem['title'] : $baseLtiTool->getName()
|
||||
)
|
||||
->setDescription(
|
||||
!empty($contentItem['text']) ? $contentItem['text'] : null
|
||||
);
|
||||
|
||||
if (!empty($contentItem['custom'])) {
|
||||
$newLtiTool
|
||||
->setCustomParams(
|
||||
$newLtiTool->encodeCustomParams($contentItem['custom'])
|
||||
);
|
||||
}
|
||||
|
||||
$em->persist($newLtiTool);
|
||||
$em->flush();
|
||||
|
||||
$courseTool = $this->findCourseToolByLink($course, $newLtiTool);
|
||||
|
||||
if ($courseTool) {
|
||||
$this->updateCourseTool($courseTool, $newLtiTool);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->addCourseTool($course, $newLtiTool);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ImsLtiServiceResponse|null
|
||||
*/
|
||||
public function processServiceRequest()
|
||||
{
|
||||
$xml = $this->getRequestXmlElement();
|
||||
|
||||
if (empty($xml)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$request = ImsLtiServiceRequestFactory::create($xml);
|
||||
|
||||
return $request->process();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $toolId
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function existsToolInCourse($toolId, Course $course)
|
||||
{
|
||||
$em = Database::getManager();
|
||||
$toolRepo = $em->getRepository('ChamiloPluginBundle:ImsLti\ImsLtiTool');
|
||||
|
||||
/** @var ImsLtiTool $tool */
|
||||
$tool = $toolRepo->findOneBy(['id' => $toolId, 'course' => $course]);
|
||||
|
||||
return !empty($tool);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $configUrl
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLaunchUrlFromCartridge($configUrl)
|
||||
{
|
||||
$options = [
|
||||
CURLOPT_CUSTOMREQUEST => 'GET',
|
||||
CURLOPT_POST => false,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HEADER => false,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_ENCODING => '',
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
];
|
||||
|
||||
$ch = curl_init($configUrl);
|
||||
curl_setopt_array($ch, $options);
|
||||
$content = curl_exec($ch);
|
||||
$errno = curl_errno($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($errno !== 0) {
|
||||
throw new Exception($this->get_lang('NoAccessToUrl'));
|
||||
}
|
||||
|
||||
$xml = new SimpleXMLElement($content);
|
||||
$result = $xml->xpath('blti:launch_url');
|
||||
|
||||
if (empty($result)) {
|
||||
throw new Exception($this->get_lang('LaunchUrlNotFound'));
|
||||
}
|
||||
|
||||
$launchUrl = $result[0];
|
||||
|
||||
return (string) $launchUrl;
|
||||
}
|
||||
|
||||
public function trimParams(array &$params)
|
||||
{
|
||||
foreach ($params as $key => $value) {
|
||||
$newValue = preg_replace('/\s+/', ' ', $value);
|
||||
$params[$key] = trim($newValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function removeUrlParamsFromLaunchParams(ImsLtiTool $tool, array &$params)
|
||||
{
|
||||
$urlQuery = parse_url($tool->getLaunchUrl(), PHP_URL_QUERY);
|
||||
|
||||
if (empty($urlQuery)) {
|
||||
return $params;
|
||||
}
|
||||
|
||||
$queryParams = [];
|
||||
parse_str($urlQuery, $queryParams);
|
||||
$queryKeys = array_keys($queryParams);
|
||||
|
||||
foreach ($queryKeys as $key) {
|
||||
if (isset($params[$key])) {
|
||||
unset($params[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Avoid conflict with foreign key when deleting a course.
|
||||
*
|
||||
* @param int $courseId
|
||||
*/
|
||||
public function doWhenDeletingCourse($courseId)
|
||||
{
|
||||
$em = Database::getManager();
|
||||
$q = $em
|
||||
->createQuery(
|
||||
'DELETE FROM ChamiloPluginBundle:ImsLti\ImsLtiTool tool
|
||||
WHERE tool.course = :c_id and tool.parent IS NOT NULL'
|
||||
);
|
||||
$q->execute(['c_id' => (int) $courseId]);
|
||||
|
||||
$em->createQuery('DELETE FROM ChamiloPluginBundle:ImsLti\ImsLtiTool tool WHERE tool.course = :c_id')
|
||||
->execute(['c_id' => (int) $courseId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getIssuerUrl()
|
||||
{
|
||||
$webPath = api_get_path(WEB_PATH);
|
||||
|
||||
return trim($webPath, " /");
|
||||
}
|
||||
|
||||
public static function getCoursesForParentTool(ImsLtiTool $tool, Session $session = null)
|
||||
{
|
||||
if ($tool->getParent()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$children = $tool->getChildren();
|
||||
|
||||
if ($session) {
|
||||
$children = $children->filter(function (ImsLtiTool $tool) use ($session) {
|
||||
if (null === $tool->getSession()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($tool->getSession()->getId() !== $session->getId()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
return $children->map(function (ImsLtiTool $tool) {
|
||||
return $tool->getCourse();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* It gets the public key from jwks or rsa keys.
|
||||
*
|
||||
* @param ImsLtiTool $tool
|
||||
*
|
||||
* @return mixed|string|null
|
||||
*/
|
||||
public static function getToolPublicKey(ImsLtiTool $tool)
|
||||
{
|
||||
$publicKey = '';
|
||||
if (!empty($tool->getJwksUrl())) {
|
||||
$publicKeySet = json_decode(file_get_contents($tool->getJwksUrl()), true);
|
||||
$pk = [];
|
||||
foreach ($publicKeySet['keys'] as $key) {
|
||||
$pk = openssl_pkey_get_details(
|
||||
JWK::parseKeySet(['keys' => [$key]])[$key['kid']]
|
||||
);
|
||||
}
|
||||
if (!empty($pk)) {
|
||||
$publicKey = $pk['key'];
|
||||
}
|
||||
} else {
|
||||
$publicKey = $tool->publicKey;
|
||||
};
|
||||
|
||||
return $publicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getConfigExtraText()
|
||||
{
|
||||
$text = $this->get_lang('ImsLtiDescription');
|
||||
$text .= sprintf(
|
||||
$this->get_lang('ManageToolButton'),
|
||||
api_get_path(WEB_PLUGIN_PATH).'ims_lti/admin.php'
|
||||
);
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the plugin tables on database.
|
||||
*
|
||||
* @throws \Doctrine\ORM\Tools\ToolsException
|
||||
*/
|
||||
private function createPluginTables()
|
||||
{
|
||||
$em = Database::getManager();
|
||||
|
||||
if ($em->getConnection()->getSchemaManager()->tablesExist([self::TABLE_TOOL])) {
|
||||
return;
|
||||
};
|
||||
|
||||
$schemaTool = new SchemaTool($em);
|
||||
$schemaTool->createSchema(
|
||||
[
|
||||
$em->getClassMetadata(ImsLtiTool::class),
|
||||
$em->getClassMetadata(LineItem::class),
|
||||
$em->getClassMetadata(Platform::class),
|
||||
$em->getClassMetadata(Token::class),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops the plugin tables on database.
|
||||
*/
|
||||
private function dropPluginTables()
|
||||
{
|
||||
$em = Database::getManager();
|
||||
|
||||
if (!$em->getConnection()->getSchemaManager()->tablesExist([self::TABLE_TOOL])) {
|
||||
return;
|
||||
};
|
||||
|
||||
$schemaTool = new SchemaTool($em);
|
||||
$schemaTool->dropSchema(
|
||||
[
|
||||
$em->getClassMetadata(ImsLtiTool::class),
|
||||
$em->getClassMetadata(LineItem::class),
|
||||
$em->getClassMetadata(Platform::class),
|
||||
$em->getClassMetadata(Token::class),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
private function removeTools()
|
||||
{
|
||||
$sql = "DELETE FROM c_tool WHERE link LIKE 'ims_lti/start.php%' AND category = 'plugin'";
|
||||
Database::query($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the course settings.
|
||||
*/
|
||||
private function setCourseSettings()
|
||||
{
|
||||
$button = Display::toolbarButton(
|
||||
$this->get_lang('ConfigureExternalTool'),
|
||||
api_get_path(WEB_PLUGIN_PATH).'ims_lti/configure.php?'.api_get_cidreq(),
|
||||
'cog',
|
||||
'primary'
|
||||
);
|
||||
|
||||
// This setting won't be saved in the database.
|
||||
$this->course_settings = [
|
||||
[
|
||||
'name' => $this->get_lang('ImsLtiDescription').$button.'<hr>',
|
||||
'type' => 'html',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private static function generateToolLink(ImsLtiTool $tool)
|
||||
{
|
||||
return 'ims_lti/start.php?id='.$tool->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SimpleXMLElement|null
|
||||
*/
|
||||
private function getRequestXmlElement()
|
||||
{
|
||||
$request = file_get_contents("php://input");
|
||||
|
||||
if (empty($request)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new SimpleXMLElement($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a key pair and key id for the platform.
|
||||
*
|
||||
* Rerturn a associative array like ['kid' => '...', 'private' => '...', 'public' => '...'].
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function generatePlatformKeys()
|
||||
{
|
||||
// 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"],
|
||||
];
|
||||
}
|
||||
}
|
||||
241
plugin/ims_lti/README.md
Normal file
241
plugin/ims_lti/README.md
Normal file
@@ -0,0 +1,241 @@
|
||||
IMS/LTI plugin
|
||||
===
|
||||
|
||||
Version 1.9.0
|
||||
|
||||
> This plugin is meant to be later integrated into Chamilo (in a major version
|
||||
> release).
|
||||
|
||||
This plugin allows certified support for LTI 1.0, 1.1, 1.1.1, Deep Linking 1.x, Outcome Services 1.x.
|
||||
You can get information about the LTI Certification on [this page][certification link].
|
||||
The LTI 1.3 is being developed.
|
||||
|
||||
IMS/LTI defines the possibility to integrate tools or content into Chamilo.
|
||||
This plugin allows the integration of a new tool into courses, obtaining
|
||||
data back from those tools and recording them as gradebook "external" activities.
|
||||
|
||||
As a platform admin, you can register external tools available for all courses.
|
||||
You need set the tools settings in the IMS/LTI administration page.
|
||||
Then the registered tools should be added in each course individually.
|
||||
|
||||
As a teacher, you can register external tools available only for the current
|
||||
course. You need follow the link in the IMS/LTI block located in the Course
|
||||
Settings tool. Then select a tool registered previously or register a new
|
||||
external tool.
|
||||
|
||||
# Installation
|
||||
|
||||
* Prepare your web server to allow to send cookies in all contexts, set the `SameSite` attribute to `None`
|
||||
* i.e. Apache configuration
|
||||
|
||||
```apacheconf
|
||||
Header edit Set-Cookie ^(.*)$ $1;HttpOnly;Secure;SameSite=None
|
||||
```
|
||||
|
||||
1. Install the plugin from the Plugins page
|
||||
2. Enable the plugin from the IMS/LTI Plugin Settings page
|
||||
3. Assign to the Administrator region (in the regions management page)
|
||||
|
||||
# Changelog
|
||||
|
||||
## v1.9
|
||||
|
||||
> Requires DB changes to upgrade, see [v1.9](#to-v190).
|
||||
|
||||
* Add option to add LTI tool to sessions
|
||||
|
||||
## v1.8
|
||||
|
||||
> Requires DB changes to upgrade, see [v1.8](#to-v180).
|
||||
|
||||
* Add option to add replacements for launch params
|
||||
|
||||
## v1.7
|
||||
|
||||
> Requires DB changes to upgrade, see [v1.8](#to-v170).
|
||||
|
||||
* Fix auth params
|
||||
* Add option to show LTI tool in iframe or new window.
|
||||
|
||||
## v1.6
|
||||
|
||||
> Requires DB changes to upgrade, see [v1.8](#to-v160).
|
||||
|
||||
* Add support to LTI 1.3 and Advantage Services
|
||||
|
||||
## v1.5
|
||||
|
||||
> Requires DB changes to upgrade, see [v1.8](#to-v151).
|
||||
|
||||
* Plugin has passed the tests from the LTI Certification suite.
|
||||
* Add support for substitution of variable.
|
||||
See `ImsLti::getSubstitutableParams()`.
|
||||
* Outcome services has a unique URL and sourced ID.
|
||||
|
||||
## v1.4
|
||||
|
||||
> Requires DB changes to upgrade, see [v1.8](#to-v140).
|
||||
|
||||
* Allow create external tools when there is no key/secret available for launch
|
||||
|
||||
## v1.3
|
||||
|
||||
> Requires DB changes to upgrade, see [v1.8](#to-v130).
|
||||
|
||||
* Privacy settings added. Allow to indicate id the launcher's data
|
||||
should be sent in request.
|
||||
|
||||
## v1.2
|
||||
|
||||
> Requires DB changes to upgrade, see [v1.8](#to-v120).
|
||||
|
||||
* Register course in which the tool was added.
|
||||
* Register parent tool from which the new tool comes from.
|
||||
|
||||
## v1.1
|
||||
|
||||
> Requires DB changes to upgrade, see [v1.8](#to-v110).
|
||||
|
||||
* Support for Deep-Linking added.
|
||||
* Support for outcomes services. And register score on course gradebook.
|
||||
|
||||
# Upgrading
|
||||
|
||||
Run this changes on database:
|
||||
|
||||
## To v1.9.0
|
||||
|
||||
```sql
|
||||
ALTER TABLE plugin_ims_lti_tool
|
||||
ADD session_id INT DEFAULT NULL;
|
||||
ALTER TABLE plugin_ims_lti_tool
|
||||
ADD CONSTRAINT FK_C5E47F7C613FECDF FOREIGN KEY (session_id) REFERENCES session (id);
|
||||
CREATE INDEX IDX_C5E47F7C613FECDF ON plugin_ims_lti_tool (session_id);
|
||||
```
|
||||
|
||||
## To v1.8.0
|
||||
|
||||
```sql
|
||||
ALTER TABLE plugin_ims_lti_tool
|
||||
ADD replacement_params LONGTEXT NOT NULL COMMENT '(DC2Type:json)';
|
||||
```
|
||||
|
||||
## To v1.7.0
|
||||
|
||||
```sql
|
||||
ALTER TABLE plugin_ims_lti_tool
|
||||
ADD launch_presentation LONGTEXT NOT NULL COMMENT '(DC2Type:json)';
|
||||
```
|
||||
|
||||
## To v1.6.0
|
||||
|
||||
```sql
|
||||
CREATE TABLE plugin_ims_lti_platform
|
||||
(
|
||||
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_ims_lti_token
|
||||
(
|
||||
id INT AUTO_INCREMENT NOT NULL,
|
||||
tool_id INT DEFAULT NULL,
|
||||
scope LONGTEXT NOT NULL COMMENT '(DC2Type:json)',
|
||||
hash VARCHAR(255) NOT NULL,
|
||||
created_at INT NOT NULL,
|
||||
expires_at INT NOT NULL,
|
||||
INDEX IDX_F7B5692F8F7B22CC (tool_id),
|
||||
PRIMARY KEY (id)
|
||||
) DEFAULT CHARACTER SET utf8
|
||||
COLLATE utf8_unicode_ci
|
||||
ENGINE = InnoDB;
|
||||
ALTER TABLE plugin_ims_lti_token
|
||||
ADD CONSTRAINT FK_F7B5692F8F7B22CC FOREIGN KEY (tool_id) REFERENCES plugin_ims_lti_tool (id) ON DELETE CASCADE;
|
||||
ALTER TABLE plugin_ims_lti_tool
|
||||
ADD client_id VARCHAR(255) DEFAULT NULL,
|
||||
ADD public_key LONGTEXT DEFAULT NULL,
|
||||
ADD login_url VARCHAR(255) DEFAULT NULL,
|
||||
ADD redirect_url VARCHAR(255) DEFAULT NULL,
|
||||
ADD advantage_services LONGTEXT DEFAULT NULL COMMENT '(DC2Type:json)',
|
||||
ADD version VARCHAR(255) DEFAULT 'lti1p1' NOT NULL;
|
||||
CREATE TABLE plugin_ims_lti_lineitem
|
||||
(
|
||||
id INT AUTO_INCREMENT NOT NULL,
|
||||
tool_id INT NOT NULL,
|
||||
evaluation INT NOT NULL,
|
||||
resource_id VARCHAR(255) DEFAULT NULL,
|
||||
tag VARCHAR(255) DEFAULT NULL,
|
||||
start_date DATETIME DEFAULT NULL,
|
||||
end_date DATETIME DEFAULT NULL,
|
||||
INDEX IDX_BA81BBF08F7B22CC (tool_id),
|
||||
UNIQUE INDEX UNIQ_BA81BBF01323A575 (evaluation),
|
||||
PRIMARY KEY (id)
|
||||
) DEFAULT CHARACTER SET utf8
|
||||
COLLATE utf8_unicode_ci
|
||||
ENGINE = InnoDB;
|
||||
ALTER TABLE plugin_ims_lti_lineitem
|
||||
ADD CONSTRAINT FK_BA81BBF08F7B22CC FOREIGN KEY (tool_id) REFERENCES plugin_ims_lti_tool (id) ON DELETE CASCADE;
|
||||
ALTER TABLE plugin_ims_lti_lineitem
|
||||
ADD CONSTRAINT FK_BA81BBF01323A575 FOREIGN KEY (evaluation) REFERENCES gradebook_evaluation (id) ON DELETE CASCADE;
|
||||
```
|
||||
|
||||
## To v1.5.1
|
||||
|
||||
```sql
|
||||
ALTER TABLE plugin_ims_lti_tool
|
||||
DROP FOREIGN KEY FK_C5E47F7C727ACA70,
|
||||
ADD FOREIGN KEY (parent_id) REFERENCES plugin_ims_lti_tool (id) ON DELETE CASCADE ON UPDATE RESTRICT;
|
||||
```
|
||||
|
||||
## To v1.4
|
||||
|
||||
```sql
|
||||
ALTER TABLE plugin_ims_lti_tool
|
||||
CHANGE consumer_key consumer_key VARCHAR(255) DEFAULT NULL,
|
||||
CHANGE shared_secret shared_secret VARCHAR(255) DEFAULT NULL;
|
||||
```
|
||||
|
||||
## To v1.3
|
||||
|
||||
```sql
|
||||
ALTER TABLE plugin_ims_lti_tool
|
||||
ADD privacy LONGTEXT DEFAULT NULL;
|
||||
```
|
||||
|
||||
## To v1.2
|
||||
|
||||
```sql
|
||||
ALTER TABLE plugin_ims_lti_tool
|
||||
ADD c_id INT DEFAULT NULL;
|
||||
ALTER TABLE plugin_ims_lti_tool
|
||||
ADD CONSTRAINT FK_C5E47F7C91D79BD3 FOREIGN KEY (c_id) REFERENCES course (id);
|
||||
CREATE INDEX IDX_C5E47F7C91D79BD3 ON plugin_ims_lti_tool (c_id);
|
||||
|
||||
ALTER TABLE plugin_ims_lti_tool
|
||||
ADD parent_id INT DEFAULT NULL,
|
||||
DROP is_global;
|
||||
ALTER TABLE plugin_ims_lti_tool
|
||||
ADD CONSTRAINT FK_C5E47F7C727ACA70 FOREIGN KEY (parent_id) REFERENCES plugin_ims_lti_tool (id);
|
||||
CREATE INDEX IDX_C5E47F7C727ACA70 ON plugin_ims_lti_tool (parent_id);
|
||||
```
|
||||
|
||||
## To v1.1
|
||||
|
||||
```sql
|
||||
ALTER TABLE plugin_ims_lti_tool
|
||||
ADD active_deep_linking TINYINT(1) DEFAULT '0' NOT NULL,
|
||||
CHANGE id id INT AUTO_INCREMENT NOT NULL,
|
||||
CHANGE launch_url launch_url VARCHAR(255) NOT NULL;
|
||||
|
||||
ALTER TABLE plugin_ims_lti_tool
|
||||
ADD gradebook_eval_id INT DEFAULT NULL;
|
||||
ALTER TABLE plugin_ims_lti_tool
|
||||
ADD CONSTRAINT FK_C5E47F7C82F80D8B FOREIGN KEY (gradebook_eval_id) REFERENCES gradebook_evaluation (id) ON DELETE SET NULL;
|
||||
CREATE INDEX IDX_C5E47F7C82F80D8B ON plugin_ims_lti_tool (gradebook_eval_id);
|
||||
```
|
||||
|
||||
[certification link]: https://site.imsglobal.org/certifications/asociacion-chamilo/156616/chamilo+lms
|
||||
56
plugin/ims_lti/admin.php
Normal file
56
plugin/ims_lti/admin.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/* For license terms, see /license.txt */
|
||||
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
|
||||
$cidReset = true;
|
||||
|
||||
require_once __DIR__.'/../../main/inc/global.inc.php';
|
||||
|
||||
api_protect_admin_script();
|
||||
|
||||
$plugin = ImsLtiPlugin::create();
|
||||
|
||||
if ($plugin->get('enabled') !== 'true') {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
$em = Database::getManager();
|
||||
|
||||
$criteria = Criteria::create()
|
||||
->where(
|
||||
Criteria::expr()->isNull('parent')
|
||||
);
|
||||
|
||||
$tools = $em->getRepository('ChamiloPluginBundle:ImsLti\ImsLtiTool')->matching($criteria);
|
||||
|
||||
$categoriesGradeBook = [];
|
||||
foreach ($tools as $tool) {
|
||||
foreach ($tool->getChildren() as $childTool) {
|
||||
$categories = [];
|
||||
if ($childTool->getSession() != null) {
|
||||
$categories = Category::load(null, null, $childTool->getCourse()->getCode(), null, null, $childTool->getSession()->getId());
|
||||
} else {
|
||||
$categories = Category::load(null, null, $childTool->getCourse()->getCode());
|
||||
}
|
||||
if (!empty($categories) && !in_array($categories[0], $categoriesGradeBook)) {
|
||||
$categoriesGradeBook[] = $categories[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$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).'ims_lti/assets/style.css'
|
||||
);
|
||||
|
||||
$template = new Template($plugin->get_title());
|
||||
$template->assign('tools', $tools);
|
||||
$template->assign('categories', $categoriesGradeBook);
|
||||
|
||||
$content = $template->fetch('ims_lti/view/admin.tpl');
|
||||
|
||||
$template->assign('header', $plugin->get_title());
|
||||
$template->assign('content', $content);
|
||||
$template->display_one_col_template();
|
||||
51
plugin/ims_lti/ags2.php
Normal file
51
plugin/ims_lti/ags2.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||
|
||||
require_once __DIR__.'/../../main/inc/global.inc.php';
|
||||
|
||||
$request = Request::createFromGlobals();
|
||||
|
||||
$response = new JsonResponse(
|
||||
null,
|
||||
Response::HTTP_OK,
|
||||
['content-type' => 'application/json']
|
||||
);
|
||||
|
||||
try {
|
||||
$pathInfo = $request->getPathInfo();
|
||||
|
||||
if (empty($pathInfo) || '/' === $pathInfo) {
|
||||
throw new BadRequestHttpException('Path info is missing.');
|
||||
}
|
||||
|
||||
$resource = LtiAssignmentGradesService::getResource($request, $response);
|
||||
$resource->validate();
|
||||
$resource->process();
|
||||
} catch (HttpExceptionInterface $exception) {
|
||||
foreach ($exception->getHeaders() as $headerKey => $headerValue) {
|
||||
$response->headers->set($headerKey, $headerValue);
|
||||
}
|
||||
|
||||
$response
|
||||
->setStatusCode($exception->getStatusCode())
|
||||
->setData(
|
||||
[
|
||||
'status' => $exception->getStatusCode(),
|
||||
'message' => $exception->getMessage(),
|
||||
'request' => [
|
||||
'method' => $request->getMethod(),
|
||||
'url' => $request->getRequestUri(),
|
||||
'accept' => $request->headers->get('accept'),
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$response->prepare($request);
|
||||
$response->send();
|
||||
37
plugin/ims_lti/assets/style.css
Normal file
37
plugin/ims_lti/assets/style.css
Normal file
@@ -0,0 +1,37 @@
|
||||
/* For license terms, see /license.txt */
|
||||
|
||||
table.admin-tools > tbody > tr.child > td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
table.admin-tools .child td:first-of-type {
|
||||
padding-left: 50px;
|
||||
position: relative;
|
||||
}
|
||||
table.admin-tools .child td:first-of-type:before {
|
||||
content: "";
|
||||
width: 12px;
|
||||
left: 24px;
|
||||
top: 0%;
|
||||
height: 50%;
|
||||
border-left: 2px solid #ddd;
|
||||
border-bottom: 2px solid #ddd;
|
||||
position: absolute;
|
||||
display: block;
|
||||
}
|
||||
table.admin-tools .child td:first-of-type:after {
|
||||
content: "";
|
||||
width: 12px;
|
||||
left: 24px;
|
||||
top: 50%;
|
||||
height: 50%;
|
||||
border-left: 2px solid #ddd;
|
||||
position: absolute;
|
||||
display: block;
|
||||
}
|
||||
table.admin-tools .child:last-child td:first-of-type:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.plugin-ims-lti-iframe {
|
||||
width: 100%;
|
||||
}
|
||||
314
plugin/ims_lti/auth.php
Normal file
314
plugin/ims_lti/auth.php
Normal file
@@ -0,0 +1,314 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\Platform;
|
||||
use Firebase\JWT\JWT;
|
||||
|
||||
require_once __DIR__.'/../../main/inc/global.inc.php';
|
||||
|
||||
api_protect_course_script();
|
||||
api_block_anonymous_users(false);
|
||||
|
||||
$scope = empty($_REQUEST['scope']) ? '' : trim($_REQUEST['scope']);
|
||||
$responseType = empty($_REQUEST['response_type']) ? '' : trim($_REQUEST['response_type']);
|
||||
$responseMode = empty($_REQUEST['response_mode']) ? '' : trim($_REQUEST['response_mode']);
|
||||
$prompt = empty($_REQUEST['prompt']) ? '' : trim($_REQUEST['prompt']);
|
||||
$clientId = empty($_REQUEST['client_id']) ? '' : trim($_REQUEST['client_id']);
|
||||
$redirectUri = empty($_REQUEST['redirect_uri']) ? '' : trim($_REQUEST['redirect_uri']);
|
||||
$state = empty($_REQUEST['state']) ? '' : trim($_REQUEST['state']);
|
||||
$nonce = empty($_REQUEST['nonce']) ? '' : trim($_REQUEST['nonce']);
|
||||
$loginHint = empty($_REQUEST['login_hint']) ? '' : trim($_REQUEST['login_hint']);
|
||||
$ltiMessageHint = empty($_REQUEST['lti_message_hint']) ? '' : trim($_REQUEST['lti_message_hint']);
|
||||
|
||||
$em = Database::getManager();
|
||||
|
||||
$webPath = api_get_path(WEB_PATH);
|
||||
$webPluginPath = api_get_path(WEB_PLUGIN_PATH);
|
||||
|
||||
$tool = null;
|
||||
|
||||
try {
|
||||
if (
|
||||
empty($scope) ||
|
||||
empty($responseType) ||
|
||||
empty($clientId) ||
|
||||
empty($redirectUri) ||
|
||||
empty($loginHint) ||
|
||||
empty($nonce)
|
||||
) {
|
||||
throw LtiAuthException::invalidRequest();
|
||||
}
|
||||
|
||||
if ($scope !== 'openid') {
|
||||
throw LtiAuthException::invalidScope();
|
||||
}
|
||||
|
||||
if ($responseType !== 'id_token') {
|
||||
throw LtiAuthException::unsupportedResponseType();
|
||||
}
|
||||
|
||||
if (empty($responseMode)) {
|
||||
throw LtiAuthException::missingResponseMode();
|
||||
}
|
||||
|
||||
if ($responseMode !== 'form_post') {
|
||||
throw LtiAuthException::invalidRespondeMode();
|
||||
}
|
||||
|
||||
if ($prompt !== 'none') {
|
||||
throw LtiAuthException::invalidPrompt();
|
||||
}
|
||||
|
||||
$ltiToolLogin = ChamiloSession::read('lti_tool_login');
|
||||
|
||||
if ($ltiToolLogin != $ltiMessageHint) {
|
||||
throw LtiAuthException::invalidRequest();
|
||||
}
|
||||
|
||||
try {
|
||||
/** @var ImsLtiTool $tool */
|
||||
$tool = $em
|
||||
->find('ChamiloPluginBundle:ImsLti\ImsLtiTool', $ltiToolLogin);
|
||||
} catch (\Exception $e) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
if ($tool->getClientId() != $clientId) {
|
||||
throw LtiAuthException::unauthorizedClient();
|
||||
}
|
||||
|
||||
$user = api_get_user_entity(api_get_user_id());
|
||||
|
||||
if (ImsLtiPlugin::getLaunchUserIdClaim($tool, $user) != $loginHint) {
|
||||
throw LtiAuthException::accessDenied();
|
||||
}
|
||||
|
||||
if ($redirectUri !== $tool->getRedirectUrl()) {
|
||||
throw LtiAuthException::unregisteredRedirectUri();
|
||||
}
|
||||
|
||||
/** @var Platform|null $platform */
|
||||
$platform = $em
|
||||
->getRepository('ChamiloPluginBundle:ImsLti\Platform')
|
||||
->findOneBy([]);
|
||||
$session = api_get_session_entity(api_get_session_id());
|
||||
$course = api_get_course_entity(api_get_course_int_id());
|
||||
|
||||
$platformDomain = str_replace(['https://', 'http://'], '', api_get_setting('InstitutionUrl'));
|
||||
|
||||
$jwtContent = [];
|
||||
$jwtContent['iss'] = ImsLtiPlugin::getIssuerUrl();
|
||||
$jwtContent['sub'] = ImsLtiPlugin::getLaunchUserIdClaim($tool, $user);
|
||||
$jwtContent['aud'] = $tool->getClientId();
|
||||
$jwtContent['iat'] = time();
|
||||
$jwtContent['exp'] = time() + 60;
|
||||
$jwtContent['nonce'] = $nonce;
|
||||
|
||||
if (empty($nonce)) {
|
||||
$jwtContent['nonce'] = md5(microtime().mt_rand());
|
||||
}
|
||||
|
||||
// User info
|
||||
if ($tool->isSharingName()) {
|
||||
$jwtContent['name'] = $user->getFullname();
|
||||
$jwtContent['given_name'] = $user->getFirstname();
|
||||
$jwtContent['family_name'] = $user->getLastname();
|
||||
}
|
||||
|
||||
if (DRH === $user->getStatus()) {
|
||||
$roleScopeMentor = ImsLtiPlugin::getRoleScopeMentor($user, $tool);
|
||||
|
||||
$jwtContent['https://purl.imsglobal.org/spec/lti/claim/role_scope_mentor'] = $roleScopeMentor;
|
||||
}
|
||||
|
||||
if ($tool->isSharingPicture()) {
|
||||
$jwtContent['picture'] = UserManager::getUserPicture($user->getId());
|
||||
}
|
||||
|
||||
if ($tool->isSharingEmail()) {
|
||||
$jwtContent['email'] = $user->getEmail();
|
||||
}
|
||||
|
||||
// Course (context) info
|
||||
$jwtContent['https://purl.imsglobal.org/spec/lti/claim/context'] = [
|
||||
'id' => (string) $course->getId(),
|
||||
'title' => $course->getTitle(),
|
||||
'label' => $course->getCode(),
|
||||
'type' => ['http://purl.imsglobal.org/vocab/lis/v2/course#CourseSection'],
|
||||
];
|
||||
|
||||
// Deployment info
|
||||
$jwtContent['https://purl.imsglobal.org/spec/lti/claim/deployment_id'] = $tool->getParent()
|
||||
? (string) $tool->getParent()->getId()
|
||||
: (string) $tool->getId();
|
||||
|
||||
// Platform info
|
||||
$jwtContent['https://purl.imsglobal.org/spec/lti/claim/tool_platform'] = [
|
||||
'guid' => $platformDomain,
|
||||
'contact_email' => api_get_setting('emailAdministrator'),
|
||||
'name' => api_get_setting('siteName'),
|
||||
'family_code' => 'Chamilo LMS',
|
||||
'version' => api_get_version(),
|
||||
'url' => $webPath,
|
||||
];
|
||||
|
||||
// Launch info
|
||||
$jwtContent['https://purl.imsglobal.org/spec/lti/claim/launch_presentation'] = [
|
||||
'locale' => api_get_language_isocode($user->getLanguage()),
|
||||
'document_target' => $tool->getDocumentTarget(),
|
||||
//'height' => 320,
|
||||
//'wdith' => 240,
|
||||
//'return_url' => api_get_course_url(),
|
||||
];
|
||||
|
||||
// LTI info
|
||||
$jwtContent['https://purl.imsglobal.org/spec/lti/claim/version'] = '1.3.0';
|
||||
|
||||
// Roles info
|
||||
$jwtContent['https://purl.imsglobal.org/spec/lti/claim/roles'] = ImsLtiPlugin::getRoles($user);
|
||||
|
||||
// Message type info
|
||||
$jwtContent['https://purl.imsglobal.org/spec/lti/claim/target_link_uri'] = $tool->getLaunchUrl();
|
||||
|
||||
if ($tool->isActiveDeepLinking()) {
|
||||
$jwtContent['https://purl.imsglobal.org/spec/lti/claim/message_type'] = 'LtiDeepLinkingRequest';
|
||||
$jwtContent['https://purl.imsglobal.org/spec/lti-dl/claim/deep_linking_settings'] = [
|
||||
'accept_types' => ['ltiResourceLink'],
|
||||
'accept_media_types' => implode(
|
||||
',',
|
||||
['*/*', ':::asterisk:::/:::asterisk:::']
|
||||
),
|
||||
'accept_presentation_document_targets' => ['iframe', 'window'],
|
||||
'accept_multiple' => true,
|
||||
'auto_create' => true,
|
||||
'title' => $tool->getName(),
|
||||
'text' => $tool->getDescription(),
|
||||
'data' => "tool:{$tool->getId()}",
|
||||
'deep_link_return_url' => $webPluginPath.'ims_lti/item_return2.php',
|
||||
];
|
||||
} else {
|
||||
$jwtContent['https://purl.imsglobal.org/spec/lti/claim/message_type'] = 'LtiResourceLinkRequest';
|
||||
|
||||
// Resource link
|
||||
$jwtContent['https://purl.imsglobal.org/spec/lti/claim/resource_link'] = [
|
||||
'id' => (string) $tool->getId(),
|
||||
'title' => $tool->getName(),
|
||||
'description' => $tool->getDescription(),
|
||||
];
|
||||
|
||||
// LIS info
|
||||
$jwtContent['https://purl.imsglobal.org/spec/lti/claim/lis'] = [
|
||||
'person_sourcedid' => ImsLti::getPersonSourcedId($platformDomain, $user),
|
||||
'course_section_sourcedid' => ImsLti::getCourseSectionSourcedId($platformDomain, $course, $session),
|
||||
];
|
||||
|
||||
$advServices = $tool->getAdvantageServices();
|
||||
|
||||
if (!empty($advServices)) {
|
||||
if (LtiAssignmentGradesService::AGS_NONE !== $advServices['ags']) {
|
||||
$agsClaim = [
|
||||
'scope' => [
|
||||
LtiAssignmentGradesService::SCOPE_LINE_ITEM_READ,
|
||||
LtiAssignmentGradesService::SCOPE_RESULT_READ,
|
||||
LtiAssignmentGradesService::SCOPE_SCORE_WRITE,
|
||||
],
|
||||
];
|
||||
|
||||
if (LtiAssignmentGradesService::AGS_FULL === $advServices['ags']) {
|
||||
$agsClaim['scope'][] = LtiAssignmentGradesService::SCOPE_LINE_ITEM;
|
||||
}
|
||||
|
||||
$agsClaim['lineitems'] = LtiAssignmentGradesService::getLineItemsUrl(
|
||||
$course->getId(),
|
||||
$tool->getId()
|
||||
);
|
||||
|
||||
if ($tool->getLineItems()->count() === 1) {
|
||||
$agsClaim['lineitem'] = LtiAssignmentGradesService::getLineItemUrl(
|
||||
$course->getId(),
|
||||
$tool->getLineItems()->first()->getId(),
|
||||
$tool->getId()
|
||||
);
|
||||
}
|
||||
|
||||
$jwtContent['https://purl.imsglobal.org/spec/lti-ags/claim/endpoint'] = $agsClaim;
|
||||
}
|
||||
|
||||
if (LtiNamesRoleProvisioningService::NRPS_NONE !== $advServices['nrps'] &&
|
||||
api_is_allowed_to_edit(false, false, true)
|
||||
) {
|
||||
$nrpsClaim = [
|
||||
'context_memberships_url' => LtiNamesRoleProvisioningService::getUrl(
|
||||
$tool->getId(),
|
||||
$course->getId(),
|
||||
$session ? $session->getId() : 0
|
||||
),
|
||||
'service_versions' => ['2.0'],
|
||||
];
|
||||
|
||||
$jwtContent['https://purl.imsglobal.org/spec/lti-nrps/claim/namesroleservice'] = $nrpsClaim;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Custom params info
|
||||
$customParams = $tool->getCustomParamsAsArray();
|
||||
|
||||
if (!empty($customParams)) {
|
||||
$jwtContent['https://purl.imsglobal.org/spec/lti/claim/custom'] = ImsLti::substituteVariablesInCustomParams(
|
||||
$jwtContent,
|
||||
$customParams,
|
||||
$user,
|
||||
$course,
|
||||
$session,
|
||||
$platformDomain,
|
||||
ImsLti::V_1P3,
|
||||
$tool
|
||||
);
|
||||
}
|
||||
|
||||
array_walk_recursive(
|
||||
$jwtContent,
|
||||
function (&$value) {
|
||||
if (gettype($value) === 'string') {
|
||||
$value = preg_replace('/\s+/', ' ', $value);
|
||||
$value = trim($value);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Sign
|
||||
$jwt = JWT::encode(
|
||||
$jwtContent,
|
||||
$platform->getPrivateKey(),
|
||||
'RS256',
|
||||
$platform->getKid()
|
||||
);
|
||||
|
||||
$params = [
|
||||
'id_token' => $jwt,
|
||||
'state' => $state,
|
||||
];
|
||||
} catch (LtiAuthException $authException) {
|
||||
$params = [
|
||||
'error' => $authException->getType(),
|
||||
'error_description' => $authException->getMessage(),
|
||||
];
|
||||
}
|
||||
|
||||
if (!$tool) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$formActionUrl = $tool->isActiveDeepLinking() ? $tool->getRedirectUrl() : $tool->getLaunchUrl();
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<form action="<?php echo $formActionUrl; ?>" name="ltiLaunchForm" method="post">
|
||||
<?php foreach ($params as $name => $value) { ?>
|
||||
<input type="hidden" name="<?php echo $name; ?>" value="<?php echo $value; ?>">
|
||||
<?php } ?>
|
||||
</form>
|
||||
<script>document.ltiLaunchForm.submit();</script>
|
||||
</html>
|
||||
274
plugin/ims_lti/configure.php
Normal file
274
plugin/ims_lti/configure.php
Normal file
@@ -0,0 +1,274 @@
|
||||
<?php
|
||||
/* For license terms, see /license.txt */
|
||||
|
||||
use Chamilo\CoreBundle\Entity\Course;
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
|
||||
|
||||
require_once __DIR__.'/../../main/inc/global.inc.php';
|
||||
|
||||
api_protect_course_script();
|
||||
api_protect_teacher_script();
|
||||
|
||||
$plugin = ImsLtiPlugin::create();
|
||||
$em = Database::getManager();
|
||||
$toolsRepo = $em->getRepository('ChamiloPluginBundle:ImsLti\ImsLtiTool');
|
||||
|
||||
/** @var ImsLtiTool $baseTool */
|
||||
$baseTool = isset($_REQUEST['type']) ? $toolsRepo->find(intval($_REQUEST['type'])) : null;
|
||||
$action = !empty($_REQUEST['action']) ? $_REQUEST['action'] : 'add';
|
||||
|
||||
$course = api_get_course_entity(api_get_course_int_id());
|
||||
$addedTools = $toolsRepo->findBy(['course' => $course]);
|
||||
$globalTools = $toolsRepo->findBy(['parent' => null, 'course' => null]);
|
||||
|
||||
if ($baseTool && !$baseTool->isGlobal()) {
|
||||
Display::addFlash(
|
||||
Display::return_message($plugin->get_lang('ToolNotAvailable'), 'warning')
|
||||
);
|
||||
|
||||
header('Location: '.api_get_self().'?'.api_get_cidreq());
|
||||
exit;
|
||||
}
|
||||
|
||||
$categories = Category::load(null, null, $course->getCode());
|
||||
|
||||
switch ($action) {
|
||||
case 'add':
|
||||
$form = new \Chamilo\PluginBundle\ImsLti\Form\FrmAdd('ims_lti_add_tool', [], $baseTool);
|
||||
$form->build();
|
||||
|
||||
if ($baseTool) {
|
||||
$form->addHidden('type', $baseTool->getId());
|
||||
}
|
||||
|
||||
if ($form->validate()) {
|
||||
$formValues = $form->getSubmitValues();
|
||||
|
||||
$tool = new ImsLtiTool();
|
||||
|
||||
if ($baseTool) {
|
||||
$tool = clone $baseTool;
|
||||
$tool->setParent($baseTool);
|
||||
}
|
||||
|
||||
$tool
|
||||
->setName($formValues['name'])
|
||||
->setDescription(
|
||||
empty($formValues['description']) ? null : $formValues['description']
|
||||
)
|
||||
->setCustomParams(
|
||||
empty($formValues['custom_params']) ? null : $formValues['custom_params']
|
||||
)
|
||||
->setDocumenTarget($formValues['document_target'])
|
||||
->setCourse($course)
|
||||
->setPrivacy(
|
||||
!empty($formValues['share_name']),
|
||||
!empty($formValues['share_email']),
|
||||
!empty($formValues['share_picture'])
|
||||
);
|
||||
|
||||
if (!empty($formValues['replacement_user_id'])) {
|
||||
$tool->setReplacementForUserId($formValues['replacement_user_id']);
|
||||
}
|
||||
|
||||
if (!$baseTool) {
|
||||
if (ImsLti::V_1P3 === $formValues['version']) {
|
||||
$tool
|
||||
->setVersion(ImsLti::V_1P3)
|
||||
->setLaunchUrl($formValues['launch_url'])
|
||||
->setClientId(
|
||||
ImsLti::generateClientId()
|
||||
)
|
||||
->setLoginUrl($formValues['login_url'])
|
||||
->setRedirectUrl($formValues['redirect_url'])
|
||||
->setAdvantageServices(
|
||||
[
|
||||
'ags' => $formValues['1p3_ags'] ?? LtiAssignmentGradesService::AGS_NONE,
|
||||
'nrps' => $formValues['1p3_nrps'],
|
||||
]
|
||||
)
|
||||
->setJwksUrl($formValues['jwks_url'])
|
||||
->publicKey = $formValues['public_key'];
|
||||
} elseif (ImsLti::V_1P1 === $formValues['version']) {
|
||||
if (empty($formValues['consumer_key']) && empty($formValues['shared_secret'])) {
|
||||
try {
|
||||
$launchUrl = $plugin->getLaunchUrlFromCartridge($formValues['launch_url']);
|
||||
} catch (Exception $e) {
|
||||
Display::addFlash(
|
||||
Display::return_message($e->getMessage(), 'error')
|
||||
);
|
||||
|
||||
header('Location: '.api_get_self().'?'.api_get_cidreq());
|
||||
exit;
|
||||
}
|
||||
|
||||
$tool->setLaunchUrl($launchUrl);
|
||||
} else {
|
||||
$tool
|
||||
->setLaunchUrl($formValues['launch_url'])
|
||||
->setConsumerKey($formValues['consumer_key'])
|
||||
->setSharedSecret($formValues['shared_secret']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (null === $baseTool ||
|
||||
($baseTool && !$baseTool->isActiveDeepLinking())
|
||||
) {
|
||||
$tool
|
||||
->setActiveDeepLinking(
|
||||
!empty($formValues['deep_linking'])
|
||||
);
|
||||
}
|
||||
|
||||
$em->persist($tool);
|
||||
$em->flush();
|
||||
|
||||
if ($tool->getVersion() === ImsLti::V_1P3) {
|
||||
$advServices = $tool->getAdvantageServices();
|
||||
|
||||
if (LtiAssignmentGradesService::AGS_NONE !== $advServices['ags']) {
|
||||
$lineItemResource = new LtiLineItemsResource(
|
||||
$tool->getId(),
|
||||
$course->getId()
|
||||
);
|
||||
$lineItemResource->createLineItem(
|
||||
['label' => $tool->getName(), 'scoreMaximum' => 100]
|
||||
);
|
||||
|
||||
Display::addFlash(
|
||||
Display::return_message($plugin->get_lang('GradebookEvaluationCreated'), 'success')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$tool->isActiveDeepLinking()) {
|
||||
$plugin->addCourseTool($course, $tool);
|
||||
}
|
||||
|
||||
Display::addFlash(
|
||||
Display::return_message($plugin->get_lang('ToolAdded'), 'success')
|
||||
);
|
||||
|
||||
header('Location: '.api_get_self().'?'.api_get_cidreq());
|
||||
exit;
|
||||
}
|
||||
|
||||
$form->setDefaultValues();
|
||||
break;
|
||||
case 'edit':
|
||||
/** @var ImsLtiTool|null $tool */
|
||||
$tool = null;
|
||||
|
||||
if (!empty($_REQUEST['id'])) {
|
||||
$tool = $em->find('ChamiloPluginBundle:ImsLti\ImsLtiTool', (int) $_REQUEST['id']);
|
||||
}
|
||||
|
||||
if (empty($tool) ||
|
||||
!ImsLtiPlugin::existsToolInCourse($tool->getId(), $course)
|
||||
) {
|
||||
api_not_allowed(
|
||||
true,
|
||||
Display::return_message($plugin->get_lang('ToolNotAvailable'), 'error')
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
$form = new \Chamilo\PluginBundle\Form\FrmEdit('ims_lti_edit_tool', [], $tool);
|
||||
$form->build(false);
|
||||
|
||||
if ($form->validate()) {
|
||||
$formValues = $form->getSubmitValues();
|
||||
|
||||
$tool
|
||||
->setName($formValues['name'])
|
||||
->setDescription(
|
||||
empty($formValues['description']) ? null : $formValues['description']
|
||||
)
|
||||
->setActiveDeepLinking(
|
||||
!empty($formValues['deep_linking'])
|
||||
)
|
||||
->setCustomParams(
|
||||
empty($formValues['custom_params']) ? null : $formValues['custom_params']
|
||||
)
|
||||
->setDocumenTarget($formValues['document_target'])
|
||||
->setPrivacy(
|
||||
!empty($formValues['share_name']),
|
||||
!empty($formValues['share_email']),
|
||||
!empty($formValues['share_picture'])
|
||||
);
|
||||
|
||||
if (!empty($formValues['replacement_user_id'])) {
|
||||
$tool->setReplacementForUserId($formValues['replacement_user_id']);
|
||||
}
|
||||
|
||||
if (null === $tool->getParent()) {
|
||||
if ($tool->getVersion() === ImsLti::V_1P3) {
|
||||
$tool
|
||||
->setLaunchUrl($formValues['launch_url'])
|
||||
->setLoginUrl($formValues['login_url'])
|
||||
->setRedirectUrl($formValues['redirect_url'])
|
||||
->setAdvantageServices(
|
||||
[
|
||||
'ags' => $formValues['1p3_ags'] ?? LtiAssignmentGradesService::AGS_NONE,
|
||||
'nrps' => $formValues['1p3_nrps'],
|
||||
]
|
||||
)
|
||||
->setJwksUrl($formValues['jwks_url'])
|
||||
->publicKey = $formValues['public_key'];
|
||||
} elseif ($tool->getVersion() === ImsLti::V_1P1) {
|
||||
$tool
|
||||
->setLaunchUrl($formValues['launch_url'])
|
||||
->setConsumerKey($formValues['consumer_key'])
|
||||
->setSharedSecret($formValues['shared_secret']);
|
||||
}
|
||||
}
|
||||
|
||||
$em->persist($tool);
|
||||
$em->flush();
|
||||
|
||||
$courseTool = $plugin->findCourseToolByLink($course, $tool);
|
||||
|
||||
if ($courseTool) {
|
||||
$plugin->updateCourseTool($courseTool, $tool);
|
||||
}
|
||||
|
||||
Display::addFlash(
|
||||
Display::return_message($plugin->get_lang('ToolEdited'), 'success')
|
||||
);
|
||||
|
||||
header('Location: '.api_get_self().'?'.api_get_cidreq());
|
||||
exit;
|
||||
}
|
||||
|
||||
$form->setDefaultValues();
|
||||
break;
|
||||
default:
|
||||
api_not_allowed(true);
|
||||
break;
|
||||
}
|
||||
|
||||
$template = new Template($plugin->get_lang('AddExternalTool'));
|
||||
$template->assign('type', $baseTool ? $baseTool->getId() : null);
|
||||
$template->assign('added_tools', $addedTools);
|
||||
$template->assign('global_tools', $globalTools);
|
||||
$template->assign('form', $form->returnForm());
|
||||
|
||||
$content = $template->fetch('ims_lti/view/add.tpl');
|
||||
|
||||
$actions = Display::url(
|
||||
Display::return_icon('add.png', $plugin->get_lang('AddExternalTool'), [], ICON_SIZE_MEDIUM),
|
||||
api_get_self().'?'.api_get_cidreq()
|
||||
);
|
||||
|
||||
if (!empty($categories)) {
|
||||
$actions .= Display::url(
|
||||
Display::return_icon('gradebook.png', get_lang('MakeQualifiable'), [], ICON_SIZE_MEDIUM),
|
||||
'./gradebook/add_eval.php?selectcat='.$categories[0]->get_id().'&'.api_get_cidreq()
|
||||
);
|
||||
}
|
||||
|
||||
$template->assign('actions', Display::toolbarAction('lti_toolbar', [$actions]));
|
||||
$template->assign('content', $content);
|
||||
$template->display_one_col_template();
|
||||
115
plugin/ims_lti/create.php
Normal file
115
plugin/ims_lti/create.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
/* For license terms, see /license.txt */
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
|
||||
use Chamilo\PluginBundle\ImsLti\Form\FrmAdd;
|
||||
|
||||
$cidReset = true;
|
||||
|
||||
require_once __DIR__.'/../../main/inc/global.inc.php';
|
||||
|
||||
api_protect_admin_script();
|
||||
|
||||
$plugin = ImsLtiPlugin::create();
|
||||
|
||||
$em = Database::getManager();
|
||||
|
||||
$form = new FrmAdd('ism_lti_create_tool');
|
||||
$form->build();
|
||||
|
||||
if ($form->validate()) {
|
||||
$formValues = $form->exportValues();
|
||||
|
||||
$externalTool = new ImsLtiTool();
|
||||
$externalTool
|
||||
->setName($formValues['name'])
|
||||
->setDescription(
|
||||
empty($formValues['description']) ? null : $formValues['description']
|
||||
)
|
||||
->setCustomParams(
|
||||
empty($formValues['custom_params']) ? null : $formValues['custom_params']
|
||||
)
|
||||
->setDocumenTarget($formValues['document_target'])
|
||||
->setCourse(null)
|
||||
->setActiveDeepLinking(
|
||||
isset($formValues['deep_linking'])
|
||||
)
|
||||
->setPrivacy(
|
||||
isset($formValues['share_name']),
|
||||
isset($formValues['share_email']),
|
||||
isset($formValues['share_picture'])
|
||||
);
|
||||
|
||||
if (!empty($formValues['replacement_user_id'])) {
|
||||
$externalTool->setReplacementForUserId($formValues['replacement_user_id']);
|
||||
}
|
||||
|
||||
if (ImsLti::V_1P3 === $formValues['version']) {
|
||||
$externalTool
|
||||
->setVersion(ImsLti::V_1P3)
|
||||
->setLaunchUrl($formValues['launch_url'])
|
||||
->setClientId(
|
||||
ImsLti::generateClientId()
|
||||
)
|
||||
->setLoginUrl($formValues['login_url'])
|
||||
->setRedirectUrl($formValues['redirect_url'])
|
||||
->setAdvantageServices(
|
||||
[
|
||||
'ags' => $formValues['1p3_ags'],
|
||||
]
|
||||
)
|
||||
->setJwksUrl($formValues['jwks_url'])
|
||||
->publicKey = $formValues['public_key'];
|
||||
} else {
|
||||
$externalTool->setVersion(ImsLti::V_1P1);
|
||||
|
||||
if (empty($formValues['consumer_key']) && empty($formValues['shared_secret'])) {
|
||||
try {
|
||||
$launchUrl = $plugin->getLaunchUrlFromCartridge($formValues['launch_url']);
|
||||
} catch (Exception $e) {
|
||||
Display::addFlash(
|
||||
Display::return_message($e->getMessage(), 'error')
|
||||
);
|
||||
|
||||
header('Location: '.api_get_path(WEB_PLUGIN_PATH).'ims_lti/admin.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$externalTool->setLaunchUrl($launchUrl);
|
||||
} else {
|
||||
$externalTool
|
||||
->setLaunchUrl($formValues['launch_url'])
|
||||
->setConsumerKey(
|
||||
empty($formValues['consumer_key']) ? null : $formValues['consumer_key']
|
||||
)
|
||||
->setSharedSecret(
|
||||
empty($formValues['shared_secret']) ? null : $formValues['shared_secret']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$em->persist($externalTool);
|
||||
$em->flush();
|
||||
|
||||
Display::addFlash(
|
||||
Display::return_message($plugin->get_lang('ToolAdded'), 'success')
|
||||
);
|
||||
|
||||
header('Location: '.api_get_path(WEB_PLUGIN_PATH).'ims_lti/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).'ims_lti/admin.php', 'name' => $plugin->get_title()];
|
||||
|
||||
$pageTitle = $plugin->get_lang('AddExternalTool');
|
||||
|
||||
$template = new Template($pageTitle);
|
||||
$template->assign('form', $form->returnForm());
|
||||
|
||||
$content = $template->fetch('ims_lti/view/add.tpl');
|
||||
|
||||
$template->assign('header', $pageTitle);
|
||||
$template->assign('content', $content);
|
||||
$template->display_one_col_template();
|
||||
43
plugin/ims_lti/delete.php
Normal file
43
plugin/ims_lti/delete.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/* For license terms, see /license.txt */
|
||||
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
|
||||
|
||||
require_once __DIR__.'/../../main/inc/global.inc.php';
|
||||
|
||||
$plugin = ImsLtiPlugin::create();
|
||||
|
||||
api_protect_admin_script();
|
||||
|
||||
$em = Database::getManager();
|
||||
|
||||
/** @var ImsLtiTool $tool */
|
||||
$tool = isset($_GET['id']) ? $em->find('ChamiloPluginBundle:ImsLti\ImsLtiTool', intval($_GET['id'])) : 0;
|
||||
|
||||
if (!$tool) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
$links = [];
|
||||
$links[] = 'ims_lti/start.php?id='.$tool->getId();
|
||||
|
||||
if (!$tool->getParent()) {
|
||||
/** @var ImsLtiTool $child */
|
||||
foreach ($tool->getChildren() as $child) {
|
||||
$links[] = "ims_lti/start.php?id=".$child->getId();
|
||||
}
|
||||
}
|
||||
|
||||
$em->remove($tool);
|
||||
$em->flush();
|
||||
|
||||
$em
|
||||
->createQuery("DELETE FROM ChamiloCourseBundle:CTool ct WHERE ct.category = :category AND ct.link IN (:links)")
|
||||
->execute(['category' => 'plugin', 'links' => $links]);
|
||||
|
||||
Display::addFlash(
|
||||
Display::return_message($plugin->get_lang('ToolDeleted'), 'success')
|
||||
);
|
||||
|
||||
header('Location: '.api_get_path(WEB_PLUGIN_PATH).'ims_lti/admin.php');
|
||||
exit;
|
||||
145
plugin/ims_lti/edit.php
Normal file
145
plugin/ims_lti/edit.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
/* For license terms, see /license.txt */
|
||||
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
|
||||
use Chamilo\PluginBundle\Form\FrmEdit;
|
||||
|
||||
$cidReset = true;
|
||||
|
||||
require_once __DIR__.'/../../main/inc/global.inc.php';
|
||||
|
||||
api_protect_admin_script();
|
||||
|
||||
if (!isset($_REQUEST['id'])) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
$toolId = intval($_REQUEST['id']);
|
||||
|
||||
$plugin = ImsLtiPlugin::create();
|
||||
$em = Database::getManager();
|
||||
|
||||
/** @var ImsLtiTool $tool */
|
||||
$tool = $em->find('ChamiloPluginBundle:ImsLti\ImsLtiTool', $toolId);
|
||||
|
||||
if (!$tool) {
|
||||
Display::addFlash(
|
||||
Display::return_message($plugin->get_lang('NoTool'), 'error')
|
||||
);
|
||||
|
||||
header('Location: '.api_get_path(WEB_PLUGIN_PATH).'ims_lti/admin.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$form = new FrmEdit('ims_lti_edit_tool', [], $tool);
|
||||
$form->build();
|
||||
|
||||
if ($form->validate()) {
|
||||
$formValues = $form->exportValues();
|
||||
|
||||
$tool
|
||||
->setName($formValues['name'])
|
||||
->setDescription(
|
||||
empty($formValues['description']) ? null : $formValues['description']
|
||||
)
|
||||
->setCustomParams(
|
||||
empty($formValues['custom_params']) ? null : $formValues['custom_params']
|
||||
)
|
||||
->setDocumenTarget($formValues['document_target'])
|
||||
->setPrivacy(
|
||||
!empty($formValues['share_name']),
|
||||
!empty($formValues['share_email']),
|
||||
!empty($formValues['share_picture'])
|
||||
);
|
||||
|
||||
if (null === $tool->getParent()) {
|
||||
$tool->setLaunchUrl($formValues['launch_url']);
|
||||
|
||||
if ($tool->getVersion() === ImsLti::V_1P1) {
|
||||
$tool
|
||||
->setConsumerKey(
|
||||
empty($formValues['consumer_key']) ? null : $formValues['consumer_key']
|
||||
)
|
||||
->setSharedSecret(
|
||||
empty($formValues['shared_secret']) ? null : $formValues['shared_secret']
|
||||
);
|
||||
} elseif ($tool->getVersion() === ImsLti::V_1P3) {
|
||||
$tool
|
||||
->setLoginUrl($formValues['login_url'])
|
||||
->setRedirectUrl($formValues['redirect_url'])
|
||||
->setAdvantageServices(
|
||||
[
|
||||
'ags' => $formValues['1p3_ags'] ?? LtiAssignmentGradesService::AGS_NONE,
|
||||
'nrps' => $formValues['1p3_nrps'],
|
||||
]
|
||||
)
|
||||
->setJwksUrl($formValues['jwks_url'])
|
||||
->publicKey = $formValues['public_key'];
|
||||
}
|
||||
|
||||
if (!empty($formValues['replacement_user_id'])) {
|
||||
$tool->setReplacementForUserId($formValues['replacement_user_id']);
|
||||
}
|
||||
}
|
||||
|
||||
if (null === $tool->getParent() ||
|
||||
(null !== $tool->getParent() && !$tool->getParent()->isActiveDeepLinking())
|
||||
) {
|
||||
$tool->setActiveDeepLinking(!empty($formValues['deep_linking']));
|
||||
}
|
||||
|
||||
if (null == $tool->getParent()) {
|
||||
/** @var ImsLtiTool $child */
|
||||
foreach ($tool->getChildren() as $child) {
|
||||
$child
|
||||
->setLaunchUrl($tool->getLaunchUrl())
|
||||
->setLoginUrl($tool->getLoginUrl())
|
||||
->setRedirectUrl($tool->getRedirectUrl())
|
||||
->setAdvantageServices(
|
||||
$tool->getAdvantageServices()
|
||||
)
|
||||
->setDocumenTarget($tool->getDocumentTarget())
|
||||
->publicKey = $tool->publicKey;
|
||||
|
||||
$em->persist($child);
|
||||
|
||||
$courseTool = $plugin->findCourseToolByLink(
|
||||
$child->getCourse(),
|
||||
$child
|
||||
);
|
||||
|
||||
$plugin->updateCourseTool($courseTool, $child);
|
||||
}
|
||||
} else {
|
||||
$courseTool = $plugin->findCourseToolByLink(
|
||||
$tool->getCourse(),
|
||||
$tool
|
||||
);
|
||||
|
||||
$plugin->updateCourseTool($courseTool, $tool);
|
||||
}
|
||||
|
||||
$em->persist($tool);
|
||||
$em->flush();
|
||||
|
||||
Display::addFlash(
|
||||
Display::return_message($plugin->get_lang('ToolEdited'), 'success')
|
||||
);
|
||||
|
||||
header('Location: '.api_get_path(WEB_PLUGIN_PATH).'ims_lti/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).'ims_lti/admin.php', 'name' => $plugin->get_title()];
|
||||
|
||||
$template = new Template($plugin->get_lang('EditExternalTool'));
|
||||
$template->assign('form', $form->returnForm());
|
||||
|
||||
$content = $template->fetch('ims_lti/view/add.tpl');
|
||||
|
||||
$template->assign('header', $plugin->get_title());
|
||||
$template->assign('content', $content);
|
||||
$template->display_one_col_template();
|
||||
156
plugin/ims_lti/form.php
Normal file
156
plugin/ims_lti/form.php
Normal file
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
/* For license terms, see /license.txt */
|
||||
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
|
||||
|
||||
require_once __DIR__.'/../../main/inc/global.inc.php';
|
||||
|
||||
api_protect_course_script();
|
||||
api_block_anonymous_users(false);
|
||||
|
||||
$em = Database::getManager();
|
||||
|
||||
/** @var ImsLtiTool $tool */
|
||||
$tool = isset($_GET['id'])
|
||||
? $em->find('ChamiloPluginBundle:ImsLti\ImsLtiTool', (int) $_GET['id'])
|
||||
: null;
|
||||
|
||||
if (!$tool) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
$imsLtiPlugin = ImsLtiPlugin::create();
|
||||
$session = api_get_session_entity();
|
||||
$course = api_get_course_entity();
|
||||
$user = api_get_user_entity(api_get_user_id());
|
||||
|
||||
$pluginPath = api_get_path(WEB_PLUGIN_PATH).'ims_lti/';
|
||||
$toolUserId = ImsLtiPlugin::getLaunchUserIdClaim($tool, $user);
|
||||
$platformDomain = str_replace(['https://', 'http://'], '', api_get_setting('InstitutionUrl'));
|
||||
|
||||
$params = [];
|
||||
$params['lti_version'] = 'LTI-1p0';
|
||||
|
||||
if ($tool->isActiveDeepLinking()) {
|
||||
$params['lti_message_type'] = 'ContentItemSelectionRequest';
|
||||
$params['content_item_return_url'] = $pluginPath.'item_return.php';
|
||||
$params['accept_media_types'] = '*/*';
|
||||
$params['accept_presentation_document_targets'] = 'iframe,window';
|
||||
//$params['accept_unsigned'];
|
||||
//$params['accept_multiple'];
|
||||
//$params['accept_copy_advice'];
|
||||
//$params['auto_create']';
|
||||
$params['title'] = $tool->getName();
|
||||
$params['text'] = $tool->getDescription();
|
||||
$params['data'] = 'tool:'.$tool->getId();
|
||||
} else {
|
||||
$params['lti_message_type'] = 'basic-lti-launch-request';
|
||||
$params['resource_link_id'] = $tool->getId();
|
||||
$params['resource_link_title'] = $tool->getName();
|
||||
$params['resource_link_description'] = $tool->getDescription();
|
||||
|
||||
$toolEval = $tool->getGradebookEval();
|
||||
|
||||
if (!empty($toolEval)) {
|
||||
$params['lis_result_sourcedid'] = json_encode(
|
||||
['e' => $toolEval->getId(), 'u' => $user->getId(), 'l' => uniqid(), 'lt' => time()]
|
||||
);
|
||||
$params['lis_outcome_service_url'] = api_get_path(WEB_PATH).'lti/os';
|
||||
$params['lis_person_sourcedid'] = "$platformDomain:$toolUserId";
|
||||
$params['lis_course_section_sourcedid'] = ImsLti::getCourseSectionSourcedId($platformDomain, $course, $session);
|
||||
}
|
||||
}
|
||||
|
||||
$params['user_id'] = $toolUserId;
|
||||
|
||||
if ($tool->isSharingPicture()) {
|
||||
$params['user_image'] = UserManager::getUserPicture($user->getId());
|
||||
}
|
||||
|
||||
$params['roles'] = ImsLtiPlugin::getUserRoles($user);
|
||||
|
||||
if ($tool->isSharingName()) {
|
||||
$params['lis_person_name_given'] = $user->getFirstname();
|
||||
$params['lis_person_name_family'] = $user->getLastname();
|
||||
$params['lis_person_name_full'] = $user->getFirstname().' '.$user->getLastname();
|
||||
}
|
||||
|
||||
if ($tool->isSharingEmail()) {
|
||||
$params['lis_person_contact_email_primary'] = $user->getEmail();
|
||||
}
|
||||
|
||||
if (DRH === $user->getStatus()) {
|
||||
$scopeMentor = ImsLtiPlugin::getRoleScopeMentor($user, $tool);
|
||||
|
||||
if (!empty($scopeMentor)) {
|
||||
$params['role_scope_mentor'] = $scopeMentor;
|
||||
}
|
||||
}
|
||||
|
||||
$params['context_id'] = $course->getId();
|
||||
$params['context_type'] = 'CourseSection';
|
||||
$params['context_label'] = $course->getCode();
|
||||
$params['context_title'] = $course->getTitle();
|
||||
$params['launch_presentation_locale'] = api_get_language_isocode();
|
||||
$params['launch_presentation_document_target'] = $tool->getDocumentTarget();
|
||||
$params['tool_consumer_info_product_family_code'] = 'Chamilo LMS';
|
||||
$params['tool_consumer_info_version'] = api_get_version();
|
||||
$params['tool_consumer_instance_guid'] = $platformDomain;
|
||||
$params['tool_consumer_instance_name'] = api_get_setting('siteName');
|
||||
$params['tool_consumer_instance_url'] = api_get_path(WEB_PATH);
|
||||
$params['tool_consumer_instance_contact_email'] = api_get_setting('emailAdministrator');
|
||||
$params['oauth_callback'] = 'about:blank';
|
||||
|
||||
$customParams = $tool->parseCustomParams();
|
||||
$imsLtiPlugin->trimParams($customParams);
|
||||
|
||||
$params += ImsLti::substituteVariablesInCustomParams(
|
||||
$params,
|
||||
$customParams,
|
||||
$user,
|
||||
$course,
|
||||
$session,
|
||||
$platformDomain,
|
||||
ImsLti::V_1P1,
|
||||
$tool
|
||||
);
|
||||
|
||||
$imsLtiPlugin->trimParams($params);
|
||||
|
||||
if (!empty($tool->getConsumerKey()) && !empty($tool->getSharedSecret())) {
|
||||
$consumer = new OAuthConsumer(
|
||||
$tool->getConsumerKey(),
|
||||
$tool->getSharedSecret(),
|
||||
null
|
||||
);
|
||||
$hmacMethod = new OAuthSignatureMethod_HMAC_SHA1();
|
||||
|
||||
$request = OAuthRequest::from_consumer_and_token(
|
||||
$consumer,
|
||||
'',
|
||||
'POST',
|
||||
$tool->getLaunchUrl(),
|
||||
$params
|
||||
);
|
||||
$request->sign_request($hmacMethod, $consumer, '');
|
||||
|
||||
$params = $request->get_parameters();
|
||||
}
|
||||
|
||||
$imsLtiPlugin->removeUrlParamsFromLaunchParams($tool, $params);
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>title</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="<?php echo $tool->getLaunchUrl(); ?>" name="ltiLaunchForm" method="post"
|
||||
encType="application/x-www-form-urlencoded">
|
||||
<?php foreach ($params as $key => $value) { ?>
|
||||
<input type="hidden" name="<?php echo $key; ?>" value="<?php echo htmlspecialchars($value); ?>">
|
||||
<?php } ?>
|
||||
</form>
|
||||
<script>document.ltiLaunchForm.submit();</script>
|
||||
</body>
|
||||
</html>
|
||||
190
plugin/ims_lti/gradebook/OutcomeForm.php
Normal file
190
plugin/ims_lti/gradebook/OutcomeForm.php
Normal file
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
class OutcomeForm extends EvalForm
|
||||
{
|
||||
/**
|
||||
* Builds a form containing form items based on a given parameter.
|
||||
*
|
||||
* @param int $form_type 1=add, 2=edit,3=move,4=result_add
|
||||
* @param Evaluation $evaluation_object the category object
|
||||
* @param obj $result_object the result object
|
||||
* @param string $form_name
|
||||
* @param string $method
|
||||
* @param string $action
|
||||
*/
|
||||
public function __construct(
|
||||
$evaluation_object,
|
||||
$result_object,
|
||||
$form_name,
|
||||
$method = 'post',
|
||||
$action = null,
|
||||
$extra1 = null,
|
||||
$extra2 = null
|
||||
) {
|
||||
parent::__construct(
|
||||
-1,
|
||||
$evaluation_object,
|
||||
$result_object,
|
||||
$form_name,
|
||||
$method,
|
||||
$action,
|
||||
$extra1,
|
||||
$extra2
|
||||
);
|
||||
|
||||
$this->build_add_form();
|
||||
$this->setDefaults();
|
||||
}
|
||||
|
||||
protected function build_add_form()
|
||||
{
|
||||
$this->setDefaults(
|
||||
[
|
||||
'hid_user_id' => $this->evaluation_object->get_user_id(),
|
||||
'hid_category_id' => $this->evaluation_object->get_category_id(),
|
||||
'hid_course_code' => $this->evaluation_object->get_course_code(),
|
||||
'created_at' => api_get_utc_datetime(),
|
||||
]
|
||||
);
|
||||
$this->build_basic_form();
|
||||
|
||||
$this->addButtonCreate(get_lang('AddAssessment'), 'submit');
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a basic form that is used in add and edit.
|
||||
*
|
||||
* @param int $edit
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function build_basic_form($edit = 0)
|
||||
{
|
||||
$this->addElement('header', get_plugin_lang('NewOutcomeFormTitle'));
|
||||
$this->addElement('hidden', 'hid_user_id');
|
||||
$this->addElement('hidden', 'hid_course_code');
|
||||
|
||||
$this->addText(
|
||||
'name',
|
||||
get_lang('EvaluationName'),
|
||||
true,
|
||||
[
|
||||
'maxlength' => '50',
|
||||
'id' => 'evaluation_title',
|
||||
]
|
||||
);
|
||||
|
||||
$cat_id = $this->evaluation_object->get_category_id();
|
||||
|
||||
$session_id = api_get_session_id();
|
||||
$course_code = api_get_course_id();
|
||||
$all_categories = Category:: load(null, null, $course_code, null, null, $session_id, false);
|
||||
|
||||
if (count($all_categories) == 1) {
|
||||
$this->addElement('hidden', 'hid_category_id', $cat_id);
|
||||
} else {
|
||||
$select_gradebook = $this->addElement(
|
||||
'select',
|
||||
'hid_category_id',
|
||||
get_lang('SelectGradebook'),
|
||||
[],
|
||||
['id' => 'hid_category_id']
|
||||
);
|
||||
$this->addRule('hid_category_id', get_lang('ThisFieldIsRequired'), 'nonzero');
|
||||
$default_weight = 0;
|
||||
if (!empty($all_categories)) {
|
||||
foreach ($all_categories as $my_cat) {
|
||||
if ($my_cat->get_course_code() == api_get_course_id()) {
|
||||
$grade_model_id = $my_cat->get_grade_model_id();
|
||||
if (empty($grade_model_id)) {
|
||||
if ($my_cat->get_parent_id() == 0) {
|
||||
$default_weight = $my_cat->get_weight();
|
||||
$select_gradebook->addOption(get_lang('Default'), $my_cat->get_id());
|
||||
$cats_added[] = $my_cat->get_id();
|
||||
} else {
|
||||
$select_gradebook->addOption($my_cat->get_name(), $my_cat->get_id());
|
||||
$cats_added[] = $my_cat->get_id();
|
||||
}
|
||||
} else {
|
||||
$select_gradebook->addOption(get_lang('Select'), 0);
|
||||
}
|
||||
if ($this->evaluation_object->get_category_id() == $my_cat->get_id()) {
|
||||
$default_weight = $my_cat->get_weight();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->addFloat(
|
||||
'weight_mask',
|
||||
[
|
||||
get_lang('Weight'),
|
||||
null,
|
||||
' [0 .. <span id="max_weight">'.$all_categories[0]->get_weight().'</span>] ',
|
||||
],
|
||||
true,
|
||||
[
|
||||
'size' => '4',
|
||||
'maxlength' => '5',
|
||||
]
|
||||
);
|
||||
|
||||
if ($edit) {
|
||||
if (!$this->evaluation_object->has_results()) {
|
||||
$this->addText(
|
||||
'max',
|
||||
get_lang('QualificationNumeric'),
|
||||
true,
|
||||
[
|
||||
'maxlength' => '5',
|
||||
]
|
||||
);
|
||||
} else {
|
||||
$this->addText(
|
||||
'max',
|
||||
[get_lang('QualificationNumeric'), get_lang('CannotChangeTheMaxNote')],
|
||||
false,
|
||||
[
|
||||
'maxlength' => '5',
|
||||
'disabled' => 'disabled',
|
||||
]
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$this->addText(
|
||||
'max',
|
||||
get_lang('QualificationNumeric'),
|
||||
true,
|
||||
[
|
||||
'maxlength' => '5',
|
||||
]
|
||||
);
|
||||
$default_max = api_get_setting('gradebook_default_weight');
|
||||
$defaults['max'] = isset($default_max) ? $default_max : 100;
|
||||
$this->setDefaults($defaults);
|
||||
}
|
||||
|
||||
$this->addElement('textarea', 'description', get_lang('Description'));
|
||||
$this->addRule('hid_category_id', get_lang('ThisFieldIsRequired'), 'required');
|
||||
$this->addElement('checkbox', 'visible', null, get_lang('Visible'));
|
||||
$this->addRule('max', get_lang('OnlyNumbers'), 'numeric');
|
||||
$this->addRule(
|
||||
'max',
|
||||
get_lang('NegativeValue'),
|
||||
'compare',
|
||||
'>=',
|
||||
'server',
|
||||
false,
|
||||
false,
|
||||
0
|
||||
);
|
||||
$setting = api_get_setting('tool_visible_by_default_at_creation');
|
||||
$visibility_default = 1;
|
||||
if (isset($setting['gradebook']) && $setting['gradebook'] == 'false') {
|
||||
$visibility_default = 0;
|
||||
}
|
||||
$this->setDefaults(['visible' => $visibility_default]);
|
||||
}
|
||||
}
|
||||
145
plugin/ims_lti/gradebook/add_eval.php
Normal file
145
plugin/ims_lti/gradebook/add_eval.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* @package plugin.ims_lti
|
||||
*/
|
||||
|
||||
use Chamilo\CoreBundle\Entity\GradebookEvaluation;
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
|
||||
|
||||
require_once __DIR__.'/../../../main/inc/global.inc.php';
|
||||
|
||||
$current_course_tool = TOOL_GRADEBOOK;
|
||||
|
||||
api_protect_course_script(true);
|
||||
api_block_anonymous_users();
|
||||
GradebookUtils::block_students();
|
||||
|
||||
$select_cat = isset($_GET['selectcat']) ? (int) $_GET['selectcat'] : 0;
|
||||
$sessionId = isset($_GET['id_session']) ? (int) $_GET['id_session'] : null;
|
||||
$is_allowedToEdit = $is_courseAdmin;
|
||||
|
||||
$em = Database::getManager();
|
||||
/** @var \Chamilo\CoreBundle\Entity\Course $course */
|
||||
$course = $em->find('ChamiloCoreBundle:Course', api_get_course_int_id());
|
||||
$ltiToolRepo = $em->getRepository('ChamiloPluginBundle:ImsLti\ImsLtiTool');
|
||||
|
||||
$categories = Category::load(null, null, $course->getCode(), null, null, $sessionId);
|
||||
|
||||
if (empty($categories)) {
|
||||
$message = Display::return_message(
|
||||
get_plugin_lang('GradebookNotSetWarning', 'ImsLtiPlugin'),
|
||||
'warning'
|
||||
);
|
||||
|
||||
api_not_allowed(true, $message);
|
||||
}
|
||||
|
||||
$evaladd = new Evaluation();
|
||||
$evaladd->set_user_id($_user['user_id']);
|
||||
|
||||
if (!empty($select_cat)) {
|
||||
$evaladd->set_category_id($_GET['selectcat']);
|
||||
$cat = Category::load($_GET['selectcat']);
|
||||
$evaladd->set_course_code($cat[0]->get_course_code());
|
||||
} else {
|
||||
$evaladd->set_category_id(0);
|
||||
}
|
||||
|
||||
$form = new EvalForm(
|
||||
EvalForm::TYPE_ADD,
|
||||
$evaladd,
|
||||
null,
|
||||
'add_eval_form',
|
||||
null,
|
||||
api_get_self().'?selectcat='.$select_cat.'&'.api_get_cidreq()
|
||||
);
|
||||
$form->removeElement('name');
|
||||
$form->removeElement('addresult');
|
||||
$slcLtiTools = $form->createElement('select', 'name', get_lang('Tool'));
|
||||
$form->insertElementBefore($slcLtiTools, 'hid_category_id');
|
||||
$form->addRule('name', get_lang('ThisFieldIsRequired'), 'required');
|
||||
|
||||
$ltiTools = $ltiToolRepo->findBy(['course' => $course, 'gradebookEval' => null]);
|
||||
|
||||
/** @var ImsLtiTool $ltiTool */
|
||||
foreach ($ltiTools as $ltiTool) {
|
||||
$slcLtiTools->addOption($ltiTool->getName(), $ltiTool->getId());
|
||||
}
|
||||
|
||||
if ($form->validate()) {
|
||||
$values = $form->exportValues();
|
||||
|
||||
/** @var ImsLtiTool $ltiTool */
|
||||
$ltiTool = $ltiToolRepo->find($values['name']);
|
||||
|
||||
if (!$ltiTool) {
|
||||
api_not_allowed();
|
||||
}
|
||||
|
||||
$eval = new Evaluation();
|
||||
$eval->set_name($ltiTool->getName());
|
||||
$eval->set_description($values['description']);
|
||||
$eval->set_user_id($values['hid_user_id']);
|
||||
|
||||
if (!empty($values['hid_course_code'])) {
|
||||
$eval->set_course_code($values['hid_course_code']);
|
||||
}
|
||||
|
||||
//Always add the gradebook to the course
|
||||
$eval->set_course_code(api_get_course_id());
|
||||
$eval->set_category_id($values['hid_category_id']);
|
||||
|
||||
$parent_cat = Category::load($values['hid_category_id']);
|
||||
$global_weight = $cat[0]->get_weight();
|
||||
//$values['weight'] = $values['weight_mask']/$global_weight*$parent_cat[0]->get_weight();
|
||||
$values['weight'] = $values['weight_mask'];
|
||||
|
||||
$eval->set_weight($values['weight']);
|
||||
$eval->set_max($values['max']);
|
||||
$eval->set_visible(empty($values['visible']) ? 0 : 1);
|
||||
$eval->add();
|
||||
|
||||
/** @var GradebookEvaluation $gradebookEval */
|
||||
$gradebookEval = $em->find('ChamiloCoreBundle:GradebookEvaluation', $eval->get_id());
|
||||
$ltiTool->setGradebookEval($gradebookEval);
|
||||
|
||||
$em->persist($ltiTool);
|
||||
$em->flush();
|
||||
|
||||
header('Location: '.Category::getUrl().'selectcat='.$eval->get_category_id());
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
$interbreadcrumb[] = [
|
||||
'url' => Category::getUrl().'selectcat='.$select_cat,
|
||||
'name' => get_lang('Gradebook'),
|
||||
];
|
||||
$this_section = SECTION_COURSES;
|
||||
|
||||
$htmlHeadXtra[] = '<script>
|
||||
$(document).ready( function() {
|
||||
$("#hid_category_id").change(function() {
|
||||
$("#hid_category_id option:selected").each(function () {
|
||||
var cat_id = $(this).val();
|
||||
$.ajax({
|
||||
url: "'.api_get_path(WEB_AJAX_PATH).'gradebook.ajax.php?a=get_gradebook_weight",
|
||||
data: "cat_id="+cat_id,
|
||||
success: function(return_value) {
|
||||
if (return_value != 0 ) {
|
||||
$("#max_weight").html(return_value);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>';
|
||||
|
||||
Display::display_header(get_lang('NewEvaluation'));
|
||||
|
||||
$form->display();
|
||||
|
||||
Display::display_footer();
|
||||
13
plugin/ims_lti/install.php
Normal file
13
plugin/ims_lti/install.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
/* For license terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Install the MSI/LTI Plugin.
|
||||
*
|
||||
* @package chamilo.plugin.ims_lti
|
||||
*/
|
||||
if (!api_is_platform_admin()) {
|
||||
exit('You must have admin permissions to install plugins');
|
||||
}
|
||||
|
||||
ImsLtiPlugin::create()->install();
|
||||
74
plugin/ims_lti/item_return.php
Normal file
74
plugin/ims_lti/item_return.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
/* For license terms, see /license.txt */
|
||||
|
||||
use Chamilo\CoreBundle\Entity\Course;
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
|
||||
|
||||
require_once __DIR__.'/../../main/inc/global.inc.php';
|
||||
|
||||
api_protect_course_script(false);
|
||||
api_block_anonymous_users(false);
|
||||
|
||||
if (empty($_POST['content_items']) || empty($_POST['data'])) {
|
||||
api_not_allowed(false);
|
||||
}
|
||||
|
||||
$toolId = str_replace('tool:', '', $_POST['data']);
|
||||
|
||||
$plugin = ImsLtiPlugin::create();
|
||||
$em = Database::getManager();
|
||||
/** @var Course $course */
|
||||
$course = $em->find('ChamiloCoreBundle:Course', api_get_course_int_id());
|
||||
/** @var ImsLtiTool|null $ltiTool */
|
||||
$ltiTool = $em->find('ChamiloPluginBundle:ImsLti\ImsLtiTool', $toolId);
|
||||
|
||||
if (!$ltiTool) {
|
||||
api_not_allowed();
|
||||
}
|
||||
|
||||
$consumer = new OAuthConsumer(
|
||||
$_POST['oauth_consumer_key'],
|
||||
$ltiTool->getSharedSecret()
|
||||
);
|
||||
$hmacMethod = new OAuthSignatureMethod_HMAC_SHA1();
|
||||
|
||||
$request = OAuthRequest::from_request('POST', api_get_path(WEB_PLUGIN_PATH).'ims_lti/item_return.php');
|
||||
$request->sign_request($hmacMethod, $consumer, '');
|
||||
$signature = $request->get_parameter('oauth_signature');
|
||||
|
||||
if ($signature !== $_POST['oauth_signature']) {
|
||||
api_not_allowed();
|
||||
}
|
||||
|
||||
$contentItems = json_decode($_POST['content_items'], true);
|
||||
$contentItems = $contentItems['@graph'];
|
||||
|
||||
foreach ($contentItems as $contentItem) {
|
||||
if ('LtiLinkItem' === $contentItem['@type']) {
|
||||
if ('application/vnd.ims.lti.v1.ltilink' === $contentItem['mediaType']) {
|
||||
$plugin->saveItemAsLtiLink($contentItem, $ltiTool, $course);
|
||||
|
||||
Display::addFlash(
|
||||
Display::return_message($plugin->get_lang('ToolAdded'), 'success')
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$currentUrl = api_get_path(WEB_PLUGIN_PATH).'ims_lti/start.php?id='.$ltiTool->getId();
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
window.parent.location.href = '<?php echo $currentUrl; ?>';
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
85
plugin/ims_lti/item_return2.php
Normal file
85
plugin/ims_lti/item_return2.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
|
||||
use Firebase\JWT\JWT;
|
||||
|
||||
require_once __DIR__.'/../../main/inc/global.inc.php';
|
||||
|
||||
api_protect_course_script();
|
||||
api_block_anonymous_users(false);
|
||||
|
||||
$jwt = empty($_REQUEST['JWT']) ? '' : $_REQUEST['JWT'];
|
||||
|
||||
$em = Database::getManager();
|
||||
$course = api_get_course_entity(api_get_course_int_id());
|
||||
|
||||
try {
|
||||
if (empty($jwt)) {
|
||||
throw new Exception('Token is missing');
|
||||
}
|
||||
|
||||
$jwtParts = explode('.', $jwt, 3);
|
||||
$payloadStr = JWT::urlsafeB64Decode($jwtParts[1]);
|
||||
$payload = json_decode($payloadStr, true);
|
||||
|
||||
if (empty($payload)) {
|
||||
throw new Exception('Token payload is empty');
|
||||
}
|
||||
|
||||
if (empty($payload['https://purl.imsglobal.org/spec/lti-dl/claim/data'])) {
|
||||
throw new Exception('Data claim is missing');
|
||||
}
|
||||
|
||||
if ($payload['aud'] !== ImsLtiPlugin::getIssuerUrl()) {
|
||||
throw new Exception('Audience not valid');
|
||||
}
|
||||
|
||||
$toolId = str_replace('tool:', '', $payload['https://purl.imsglobal.org/spec/lti-dl/claim/data']);
|
||||
/** @var ImsLtiTool $ltiTool */
|
||||
$ltiTool = $em->find('ChamiloPluginBundle:ImsLti\ImsLtiTool', $toolId);
|
||||
|
||||
if (empty($ltiTool)) {
|
||||
throw new Exception('LTI tool not found');
|
||||
}
|
||||
|
||||
if ($payload['iss'] !== $ltiTool->getClientId()) {
|
||||
throw new Exception('Consumer not valid');
|
||||
}
|
||||
|
||||
$decodedJwt = JWT::decode($jwt, $ltiTool->publicKey, ['RS256']);
|
||||
|
||||
if (empty($decodedJwt->{'https://purl.imsglobal.org/spec/lti-dl/claim/content_items'})) {
|
||||
throw new Exception('Content items are missing');
|
||||
}
|
||||
|
||||
foreach ($decodedJwt->{'https://purl.imsglobal.org/spec/lti-dl/claim/content_items'} as $contentItemClaim) {
|
||||
/** @var LtiContentItemType|null $contentItem */
|
||||
$contentItem = null;
|
||||
|
||||
switch ($contentItemClaim->type) {
|
||||
case 'ltiResourceLink':
|
||||
$contentItem = new LtiResourceLink($contentItemClaim);
|
||||
// no break
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
$contentItem->save($ltiTool, $course);
|
||||
}
|
||||
} catch (Exception $exception) {
|
||||
$message = Display::return_message($exception->getMessage(), 'error');
|
||||
|
||||
api_not_allowed(true, $message);
|
||||
}
|
||||
|
||||
$plugin = ImsLtiPlugin::create();
|
||||
|
||||
Display::addFlash(
|
||||
Display::return_message($plugin->get_lang('ToolAdded'), 'success')
|
||||
);
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<body>
|
||||
<script>window.parent.location.href = '<?php echo api_get_course_url(); ?>';</script>
|
||||
</body>
|
||||
47
plugin/ims_lti/jwks.php
Normal file
47
plugin/ims_lti/jwks.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\Platform;
|
||||
use Firebase\JWT\JWT;
|
||||
use phpseclib\Crypt\RSA;
|
||||
|
||||
$cidReset = true;
|
||||
|
||||
require_once __DIR__.'/../../main/inc/global.inc.php';
|
||||
|
||||
$plugin = ImsLtiPlugin::create();
|
||||
|
||||
if ($plugin->get('enabled') !== 'true') {
|
||||
exit;
|
||||
}
|
||||
|
||||
/** @var Platform $platform */
|
||||
$platform = Database::getManager()
|
||||
->getRepository('ChamiloPluginBundle:ImsLti\Platform')
|
||||
->findOneBy([]);
|
||||
|
||||
if (!$platform) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$jwks = [];
|
||||
|
||||
$key = new RSA();
|
||||
$key->setHash('sha256');
|
||||
$key->loadKey($platform->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' => $platform->getKid(),
|
||||
];
|
||||
}
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
echo json_encode(['keys' => [$jwks]]);
|
||||
77
plugin/ims_lti/lang/english.php
Normal file
77
plugin/ims_lti/lang/english.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
/* For license terms, see /license.txt */
|
||||
$strings['plugin_title'] = 'IMS/LTI client';
|
||||
$strings['plugin_comment'] = 'Adds support for IMS/LTI activities integration, where Chamilo acts as an IMS/LTI client';
|
||||
|
||||
$strings['enabled'] = 'Enabled';
|
||||
$strings['client_id'] = 'Client ID';
|
||||
$strings['client_id_help'] = 'Client ID to be used for tools by default. You can customize the client_id for each tool.';
|
||||
|
||||
$strings['ImsLtiDescription'] = '<p>Learning Tools Interoperability® (LTI®) is a specification developed by IMS Global Learning Consortium. The principal concept of LTI is to establish a standard way of integrating rich learning applications (often remotely hosted and provided through third-party services) with platforms like learning management systems, portals, learning object repositories, or other educational environments.</p>';
|
||||
$strings['ManageToolButton'] = '<p>To manage the tools go to <a href="%s">Tool list</a></p>';
|
||||
$strings['AddExternalTool'] = 'Add external tool';
|
||||
$strings['ProviderName'] = 'Provider name';
|
||||
$strings['LaunchUrl'] = 'Launch URL';
|
||||
$strings['ConsumerKey'] = 'Consumer key';
|
||||
$strings['SharedSecret'] = 'Shared secret';
|
||||
$strings['CustomParams'] = 'Custom params';
|
||||
$strings['CustomParamsHelp'] = 'Custom params required by the Tool Provider. Format: <code>name=value</code>';
|
||||
$strings['ToolName'] = 'Tool name';
|
||||
$strings['ToolNotAdded'] = 'Tool not added';
|
||||
$strings['AvailableTools'] = 'Available tools';
|
||||
$strings['ToolSettings'] = 'Tool settings';
|
||||
$strings['ToolNotAvailable'] = 'Tool not available';
|
||||
$strings['IsGlobal'] = 'Is global';
|
||||
$strings['EditExternalTool'] = 'Edit external tool';
|
||||
$strings['ToolDeleted'] = 'Tool deleted';
|
||||
$strings['ToolAdded'] = 'Tool added';
|
||||
$strings['PressToContinue'] = 'Press to continue to external tool';
|
||||
$strings['ConfigureExternalTool'] = 'Configure external tools';
|
||||
$strings['SupportDeepLinking'] = 'Support Deep-Linking';
|
||||
$strings['ScoreNotSet'] = 'Score not set';
|
||||
$strings['ScoreForXUserIsYScore'] = 'Score for %s is %s';
|
||||
$strings['ToolsAdded'] = 'Added tools';
|
||||
$strings['ToolEdited'] = 'Tool edited';
|
||||
$strings['ShareLauncherName'] = 'Share launcher\'s name';
|
||||
$strings['ShareLauncherEmail'] = 'Share launcher\'s email';
|
||||
$strings['ShareLauncherPicture'] = 'Share launcher\'s picture';
|
||||
$strings['NoTool'] = 'Tool not exists';
|
||||
$strings['ToolAddedOnCourseX'] = 'Tool added to course <strong>%s</strong>.';
|
||||
$strings['SupportDeppLinkingHelp'] = 'Contact your Tool Provider to verify if Deep Linking support is mandatory';
|
||||
$strings['NoAccessToUrl'] = 'No access to URL';
|
||||
$strings['LaunchUrlNotFound'] = 'Launch URL not found';
|
||||
$strings['GenerateKeyPairInfo'] = 'A new private and public key pair will be created when enabling.';
|
||||
$strings['PlatformKeys'] = 'Platform keys';
|
||||
$strings['ClientId'] = 'Client ID';
|
||||
$strings['Keys'] = 'Keys';
|
||||
$strings['KeyId'] = 'Key ID';
|
||||
$strings['PublicKey'] = 'Public key';
|
||||
$strings['PrivateKey'] = 'Private key';
|
||||
$strings['PlatformDateUpdated'] = 'Platform data updated';
|
||||
$strings['LtiVersion'] = 'LTI Version';
|
||||
$strings['PublicKeyType'] = 'Public key type';
|
||||
$strings['KeySetUrl'] = 'Keyset URL';
|
||||
$strings['RsaKey'] = 'RSA key';
|
||||
$strings['PublicKeyset'] = 'Jwks URL';
|
||||
$strings['LoginUrl'] = 'Login URL';
|
||||
$strings['RedirectUrl'] = 'Redirect URL';
|
||||
$strings['AssigmentAndGradesService'] = 'Assigment and Grades Service';
|
||||
$strings['DontUseService'] = 'Don\'t use service';
|
||||
$strings['UseService'] = 'Use service';
|
||||
$strings['AGServiceSimple'] = 'Synchronize grades only';
|
||||
$strings['AGServiceFull'] = 'Synchronize grades and column management';
|
||||
$strings['GradebookEvaluationCreated'] = 'New online activity was created for the LTI Tool.';
|
||||
$strings['GradebookEvaluationWithEmptyWeight'] = 'Online activity was created with empty weight.';
|
||||
$strings['ConfigSettingsForTool'] = 'Configuration settings for tool';
|
||||
$strings['PlatformId'] = 'Platform ID';
|
||||
$strings['DeploymentId'] = 'Deployment ID';
|
||||
$strings['AuthUrl'] = 'OIDC Auth URL';
|
||||
$strings['TokenUrl'] = 'OAuth2 Access Token URL';
|
||||
$strings['KeySetUrl'] = 'Keyset URL';
|
||||
$strings['NamesAndRoleProvisioningService'] = 'Names and Role Provisioning Service';
|
||||
$strings['YouNeedCreateTheGradebokInCourseFirst'] = 'You must first create the course gradebook to use this service by following this link %s';
|
||||
$strings['ReplacementUserId'] = 'Replacement for user_id (sub)';
|
||||
$strings['ReplacementUserIdHelp'] = 'The current user_id (sub) in launch params will be formed from platform settings and user ID.<br>You can set a replacement for it using some variables like <code>$User.id</code> or <code>$User.username</code>.';
|
||||
$strings['AddInCourses'] = 'Add in courses';
|
||||
$strings['AddInAllCourses'] = 'Add in all courses';
|
||||
$strings['AddInSessions'] = 'Add in sessions';
|
||||
77
plugin/ims_lti/lang/french.php
Normal file
77
plugin/ims_lti/lang/french.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
/* For license terms, see /license.txt */
|
||||
$strings['plugin_title'] = 'Client IMS/LTI';
|
||||
$strings['plugin_comment'] = 'Ajoute le support de IMS/LTI en tant que client. Des activités IMS/LTI peuvent être intégrées dans Chamilo depuis des sources extérieures.';
|
||||
|
||||
$strings['enabled'] = 'Activé';
|
||||
$strings['client_id'] = 'ID du client';
|
||||
$strings['client_id_help'] = 'ID Client à utiliser pour les outils par défaut. Vous pouvez personnaliser le client_id pour chaque outil.';
|
||||
|
||||
$strings['ImsLtiDescription'] = '<p>Learning Tools Interoperability® (LTI®) est une spécification développée par le IMS Global Learning Consortium. L\'objectif principal de LTI est d\'établir une forme standard d\'intégration d\'application riches d\'apprentissage (souvent hébergées de manière distante et fournies au travers de services d\'une tierce partie) dans des plateformes telles que les learning management systems, portails, learning object repositories, ou autres environnements éducatifs.</p>';
|
||||
$strings['ManageToolButton'] = '<p>Pour gérer les différents outils, dirigez-vous vers la <a href="%s">liste d\'outils</a></p>';
|
||||
$strings['AddExternalTool'] = 'Ajouter un outil externe';
|
||||
$strings['ProviderName'] = 'Nom du fournisseur';
|
||||
$strings['LaunchUrl'] = 'URL de démarrage (Launch URL)';
|
||||
$strings['ConsumerKey'] = 'Clef consommateur (Consumer key)';
|
||||
$strings['SharedSecret'] = 'Secret partagé (Shared secret)';
|
||||
$strings['CustomParams'] = 'Paramètres personnalisés';
|
||||
$strings['CustomParamsHelp'] = 'Paramètres personnalisés requis par le fournisseur d\'outils. Format: <code>nom=valeur</code>.';
|
||||
$strings['ToolName'] = 'Nom de l\'outil';
|
||||
$strings['ToolNotAdded'] = 'Outil non ajouté';
|
||||
$strings['AvailableTools'] = 'Outils disponibles';
|
||||
$strings['ToolSettings'] = 'Paramètres de l\'outil';
|
||||
$strings['ToolNotAvailable'] = 'Outil non disponible';
|
||||
$strings['IsGlobal'] = 'Est global';
|
||||
$strings['EditExternalTool'] = 'Éditer outil externe';
|
||||
$strings['ToolDeleted'] = 'Outil supprimé';
|
||||
$strings['ToolAdded'] = 'Outil ajouté';
|
||||
$strings['PressToContinue'] = 'Appuyez sur pour continuer à l\'outil externe';
|
||||
$strings['ConfigureExternalTool'] = 'Configure external tools';
|
||||
$strings['SupportDeepLinking'] = 'Support Deep-Linking';
|
||||
$strings['ScoreNotSet'] = 'Score non défini';
|
||||
$strings['ScoreForXUserIsYScore'] = 'Score pour %s est %s';
|
||||
$strings['ToolsAdded'] = 'Outils ajoutés';
|
||||
$strings['ToolEdited'] = 'Outil édité';
|
||||
$strings['ShareLauncherName'] = 'Partager le nom d\'utilisateur';
|
||||
$strings['ShareLauncherEmail'] = 'Partager l\'e-mail';
|
||||
$strings['ShareLauncherPicture'] = 'Partager la photo';
|
||||
$strings['NoTool'] = 'L\'outil n\'existe pas';
|
||||
$strings['ToolAddedOnCourseX'] = 'Outil ajouté dans le cours <strong>%s</strong>.';
|
||||
$strings['SupportDeppLinkingHelp'] = 'Contactez le fournisseur de votre outil pour vérifier si le support du Deep Linking est nécessaire.';
|
||||
$strings['NoAccessToUrl'] = 'Aucun accès à l\'URL';
|
||||
$strings['LaunchUrlNotFound'] = 'URL de démarrage non trouvée';
|
||||
$strings['GenerateKeyPairInfo'] = 'Une nouvelle paire de clefs publique/privée sera créée à l\'activation.';
|
||||
$strings['PlatformKeys'] = 'Clefs de la plateforme';
|
||||
$strings['ClientId'] = 'ID du client';
|
||||
$strings['Keys'] = 'Clefs';
|
||||
$strings['KeyId'] = 'ID de clef';
|
||||
$strings['PublicKey'] = 'Clef publique';
|
||||
$strings['PrivateKey'] = 'Clef privée';
|
||||
$strings['PlatformDateUpdated'] = 'Données de la plateforme mises à jour';
|
||||
$strings['LtiVersion'] = 'Version LTI';
|
||||
$strings['PublicKeyType'] = 'Type de clé publique';
|
||||
$strings['KeySetUrl'] = 'URL du jeu de clés';
|
||||
$strings['RsaKey'] = 'Clé RSA';
|
||||
$strings['PublicKeyset'] = 'URL de Jwks';
|
||||
$strings['LoginUrl'] = 'URL de connexion';
|
||||
$strings['RedirectUrl'] = 'URL de redirection';
|
||||
$strings['AssigmentAndGradesService'] = 'Service de travaux et notes';
|
||||
$strings['DontUseService'] = 'Ne pas utiliser le service';
|
||||
$strings['UseService'] = 'Utiliser le service';
|
||||
$strings['AGServiceSimple'] = 'Synchroniser les notes uniquement';
|
||||
$strings['AGServiceFull'] = 'Synchroniser les notes et la gestion des colonnes';
|
||||
$strings['GradebookEvaluationCreated'] = 'Une nouvelle activité en ligne a été créée pour l\'outil LTI.';
|
||||
$strings['GradebookEvaluationWithEmptyWeight'] = 'Une activité en ligne a été créée avec une pondération vide.';
|
||||
$strings['ConfigSettingsForTool'] = 'Paramètres de configuration pour l\'outil';
|
||||
$strings['PlatformId'] = 'ID de plateforme';
|
||||
$strings['DeploymentId'] = 'ID de déploiement';
|
||||
$strings['AuthUrl'] = 'URL de OIDC Auth';
|
||||
$strings['TokenUrl'] = 'URL de OAuth2 Access Token';
|
||||
$strings['KeySetUrl'] = 'URL de config des clefs (Keyset)';
|
||||
$strings['NamesAndRoleProvisioningService'] = 'Service de fourniture des noms et rôles';
|
||||
$strings['YouNeedCreateTheGradebokInCourseFirst'] = 'Vous devez d\'abord créer un cahier de notes dans le cours pour utiliser ce service, en suivant ce lien : %s';
|
||||
$strings['ReplacementUserId'] = 'Substitut pour user_id (sub)';
|
||||
$strings['ReplacementUserIdHelp'] = 'Le user_id (sub) actuel dans les paramètres de lancement sera composé depuis les paramètres et l\'ID de l\'utilisateur.<br>Vous pouvez configurer une substitution en utilisant des variables comme <code>$User.id</code> ou <code>$User.username</code>.';
|
||||
$strings['AddInCourses'] = 'Ajouter aux cours';
|
||||
$strings['AddInAllCourses'] = 'Ajouter à tous les cours';
|
||||
$strings['AddInSessions'] = 'Ajouter aux sessions';
|
||||
77
plugin/ims_lti/lang/spanish.php
Normal file
77
plugin/ims_lti/lang/spanish.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
/* For license terms, see /license.txt */
|
||||
$strings['plugin_title'] = 'Cliente IMS/LTI';
|
||||
$strings['plugin_comment'] = 'Añade el soporte de actividades IMS/LTI donde Chamilo actua como cliente.';
|
||||
|
||||
$strings['enabled'] = 'Activado';
|
||||
$strings['client_id'] = 'ID cliente';
|
||||
$strings['client_id_help'] = 'ID cliente a usar para las herramientas por defecto. Puede personalizar el client_id para cada herramienta.';
|
||||
|
||||
$strings['ImsLtiDescription'] = '<p>Learning Tools Interoperability® (LTI®) es una especificación desarrollada por el IMS Global Learning Consortium. El concepto principal de LTI es establecer una manera estándar de integrar aplicaciones de aprendizaje inteligentes (usualmente alojadas en servidores remotos y proveeidos a través de servicios de terceros) en plataformas como los LMS, portales, repositorios de objetos de aprendizaje, u otros ambientes educativos.</p>';
|
||||
$strings['ManageToolButton'] = '<p>Para gestionar las herramientas, ir a la <a href="%s">lista de herramientas</a></p>';
|
||||
$strings['AddExternalTool'] = 'Añadir herramienta externa';
|
||||
$strings['ProviderName'] = 'Proveedor de la herramienta';
|
||||
$strings['LaunchUrl'] = 'URL de arranque (launch URL)';
|
||||
$strings['ConsumerKey'] = 'Clave consumidor (consumer key)';
|
||||
$strings['SharedSecret'] = 'Secreto compartido (shared secret)';
|
||||
$strings['CustomParams'] = 'Parámetros personalizados';
|
||||
$strings['CustomParamsHelp'] = 'Parámetros personalizados requeridos por el Proveedor de la Herramienta. Formato: <code>name=value</code>.';
|
||||
$strings['ToolName'] = 'Nombre de la herramienta';
|
||||
$strings['ToolNotAdded'] = 'Herramienta no añadida';
|
||||
$strings['AvailableTools'] = 'Herramientas disponibles';
|
||||
$strings['ToolSettings'] = 'Parámetros de la herramienta';
|
||||
$strings['ToolNotAvailable'] = 'Herramienta no disponible';
|
||||
$strings['IsGlobal'] = 'Es global';
|
||||
$strings['EditExternalTool'] = 'Editar herramienta externa';
|
||||
$strings['ToolDeleted'] = 'Herramienta eliminada';
|
||||
$strings['ToolAdded'] = 'Herramienta agregada';
|
||||
$strings['PressToContinue'] = 'Presione para continuar con la herramienta externa';
|
||||
$strings['ConfigureExternalTool'] = 'Configure external tools';
|
||||
$strings['SupportDeepLinking'] = 'Soportar Deep-Linking';
|
||||
$strings['ScoreNotSet'] = 'Puntuación no establecida';
|
||||
$strings['ScoreForXUserIsYScore'] = 'Puntuación para %s es %s';
|
||||
$strings['ToolsAdded'] = 'Herramientas agregadas';
|
||||
$strings['ToolEdited'] = 'Herramienta editada';
|
||||
$strings['ShareLauncherName'] = 'Enviar el nombre del usuario';
|
||||
$strings['ShareLauncherEmail'] = 'Enviar el correo electrónico del usuario';
|
||||
$strings['ShareLauncherPicture'] = 'Enviar la foto del usuario';
|
||||
$strings['NoTool'] = 'La herramienta no existe';
|
||||
$strings['ToolAddedOnCourseX'] = 'Herramienta agregada en el curso <strong>%s</strong>.';
|
||||
$strings['SupportDeppLinkingHelp'] = 'Contacte a su Proveedor de Herramienta para verificar si el soporte a Deep Linking es obligatorio';
|
||||
$strings['NoAccessToUrl'] = 'Sin acceso a la URL';
|
||||
$strings['LaunchUrlNotFound'] = 'URL de lanzamiento no encontrada';
|
||||
$strings['GenerateKeyPairInfo'] = 'Un par de llaves privada y pública será creada al activar.';
|
||||
$strings['PlatformKeys'] = 'Clave plataforma';
|
||||
$strings['ClientId'] = 'ID cliente';
|
||||
$strings['Keys'] = 'Llaves';
|
||||
$strings['KeyId'] = 'ID de llave';
|
||||
$strings['PublicKey'] = 'Llave pública';
|
||||
$strings['PrivateKey'] = 'Llave privada';
|
||||
$strings['PlatformDateUpdated'] = 'Datos de la plataforma actualizados.';
|
||||
$strings['LtiVersion'] = 'Versión LTI';
|
||||
$strings['PublicKeyType'] = 'Tipo de clave pública';
|
||||
$strings['KeySetUrl'] = 'KeySet URL';
|
||||
$strings['RsaKey'] = 'Clave RSA';
|
||||
$strings['PublicKeyset'] = 'URL de Jwks';
|
||||
$strings['LoginUrl'] = 'URL de Login';
|
||||
$strings['RedirectUrl'] = 'URL de redirección';
|
||||
$strings['AssigmentAndGradesService'] = 'Servicio de tareas y notas';
|
||||
$strings['DontUseService'] = 'No usar el servicio';
|
||||
$strings['UseService'] = 'Usar el servicio';
|
||||
$strings['AGServiceSimple'] = 'Sincronizar notas únicamente';
|
||||
$strings['AGServiceFull'] = 'Sincronizar notas y gestión de columnas';
|
||||
$strings['GradebookEvaluationCreated'] = 'Una nueva actividad online ha sido creada para la herramienta LTI.';
|
||||
$strings['GradebookEvaluationWithEmptyWeight'] = 'La actividad online fue creada con una ponderación vacía.';
|
||||
$strings['ConfigSettingsForTool'] = 'Parametros de configuración de la herramienta';
|
||||
$strings['PlatformId'] = 'ID de plataforma';
|
||||
$strings['DeploymentId'] = 'ID de despliegue';
|
||||
$strings['AuthUrl'] = 'URL OIDC Auth';
|
||||
$strings['TokenUrl'] = 'URL OAuth2 Access Token';
|
||||
$strings['KeySetUrl'] = 'URL Keyset';
|
||||
$strings['NamesAndRoleProvisioningService'] = 'Servicio de provisión de nombres y roles';
|
||||
$strings['YouNeedCreateTheGradebokInCourseFirst'] = 'Debe crear el libro de calificaciones del curso para usar este servicio siguiendo este enlace %s';
|
||||
$strings['ReplacementUserId'] = 'Remplazo para user_id (sub)';
|
||||
$strings['ReplacementUserIdHelp'] = 'El user_id (sub) actual en los parametros de lanzamiento será compuesto en base a parametros de la plataforma y del user ID.<br>Puede configurarle un remplazo con variables como <code>$User.id</code> o <code>$User.username</code>.';
|
||||
$strings['AddInCourses'] = 'Añadir a cursos';
|
||||
$strings['AddInAllCourses'] = 'Aãndir a todos los cursos';
|
||||
$strings['AddInSessions'] = 'Añadir a sesiones';
|
||||
44
plugin/ims_lti/login.php
Normal file
44
plugin/ims_lti/login.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
|
||||
|
||||
require_once __DIR__.'/../../main/inc/global.inc.php';
|
||||
|
||||
api_protect_course_script(true);
|
||||
api_block_anonymous_users();
|
||||
|
||||
$em = Database::getManager();
|
||||
|
||||
/** @var ImsLtiTool $tool */
|
||||
$tool = isset($_GET['id'])
|
||||
? $em->find('ChamiloPluginBundle:ImsLti\ImsLtiTool', $_GET['id'])
|
||||
: null;
|
||||
|
||||
if (!$tool) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
$user = api_get_user_entity(api_get_user_id());
|
||||
|
||||
ChamiloSession::write('lti_tool_login', $tool->getId());
|
||||
|
||||
$params = [
|
||||
'iss' => ImsLtiPlugin::getIssuerUrl(),
|
||||
'target_link_uri' => $tool->getLaunchUrl(),
|
||||
'login_hint' => ImsLtiPlugin::getLaunchUserIdClaim($tool, $user),
|
||||
'lti_message_hint' => $tool->getId(),
|
||||
'client_id' => $tool->getClientId(),
|
||||
];
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<body>
|
||||
<form action="<?php echo $tool->getLoginUrl(); ?>" method="post" name="lti_1p3_login" id="lti_1p3_login"
|
||||
enctype="application/x-www-form-urlencoded" class="form-horizontal">
|
||||
<?php foreach ($params as $name => $value) { ?>
|
||||
<input type="hidden" name="<?php echo $name; ?>" value="<?php echo $value; ?>">
|
||||
<?php } ?>
|
||||
</form>
|
||||
|
||||
<script>document.lti_1p3_login.submit();</script>
|
||||
</body>
|
||||
149
plugin/ims_lti/multiply.php
Normal file
149
plugin/ims_lti/multiply.php
Normal file
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
$cidReset = true;
|
||||
|
||||
require_once __DIR__.'/../../main/inc/global.inc.php';
|
||||
|
||||
api_protect_admin_script();
|
||||
|
||||
$plugin = ImsLtiPlugin::create();
|
||||
$webPluginPath = api_get_path(WEB_PLUGIN_PATH).'ims_lti/';
|
||||
|
||||
$em = Database::getManager();
|
||||
|
||||
try {
|
||||
if ($plugin->get('enabled') !== 'true') {
|
||||
throw new Exception(get_lang('NotAllowed'));
|
||||
}
|
||||
|
||||
$request = Request::createFromGlobals();
|
||||
/** @var ImsLtiTool $tool */
|
||||
$tool = $em->find('ChamiloPluginBundle:ImsLti\ImsLtiTool', $request->query->getInt('id'));
|
||||
|
||||
if (!$tool) {
|
||||
throw new Exception($plugin->get_lang('NoTool'));
|
||||
}
|
||||
|
||||
if ($tool->getParent()) {
|
||||
throw new Exception($plugin->get_lang('NoAllowed'));
|
||||
}
|
||||
|
||||
$content = '';
|
||||
|
||||
$courses = ImsLtiPlugin::getCoursesForParentTool($tool);
|
||||
|
||||
$slctCourses = [];
|
||||
|
||||
/** @var \Chamilo\CoreBundle\Entity\Course $course */
|
||||
foreach ($courses as $course) {
|
||||
$slctCourses[$course->getId()] = $course->getName();
|
||||
}
|
||||
|
||||
$selectedCoursesIds = array_keys($slctCourses);
|
||||
|
||||
$form = new FormValidator('frm_multiply', 'post', api_get_self().'?id='.$tool->getId());
|
||||
$form->addLabel($plugin->get_lang('Tool'), $tool->getName());
|
||||
$form->addSelectAjax(
|
||||
'courses',
|
||||
get_lang('Courses'),
|
||||
$slctCourses,
|
||||
['url' => api_get_path(WEB_AJAX_PATH).'course.ajax.php?a=search_course', 'multiple' => true]
|
||||
);
|
||||
$form->addCheckBox('all_courses', '', $plugin->get_lang('AddInAllCourses'));
|
||||
$form->addCheckBox('tool_visible', get_lang('SetVisible'), get_lang('ToolIsNowVisible'));
|
||||
$form->addButtonExport(get_lang('Save'));
|
||||
|
||||
if ($form->validate()) {
|
||||
$em = Database::getManager();
|
||||
$formValues = $form->exportValues();
|
||||
$formValues['courses'] = empty($formValues['courses']) ? [] : $formValues['courses'];
|
||||
$formValues['tool_visible'] = !empty($formValues['tool_visible']);
|
||||
|
||||
if (!empty($formValues['all_courses'])) {
|
||||
$courseList = Database::select('id', Database::get_main_table(TABLE_MAIN_COURSE));
|
||||
$formValues['courses'] = array_keys($courseList);
|
||||
}
|
||||
|
||||
$courseIdsToDelete = array_diff($selectedCoursesIds, $formValues['courses']);
|
||||
$newSelectedCourseIds = array_diff($formValues['courses'], $selectedCoursesIds);
|
||||
|
||||
if ($courseIdsToDelete) {
|
||||
$toolLinks = [];
|
||||
|
||||
/** @var ImsLtiTool $childInCourse */
|
||||
foreach ($tool->getChildrenInCourses($courseIdsToDelete) as $childInCourse) {
|
||||
$toolLinks[] = "ims_lti/start.php?id={$childInCourse->getId()}";
|
||||
|
||||
$em->remove($childInCourse);
|
||||
}
|
||||
|
||||
$em->flush();
|
||||
|
||||
if (!empty($toolLinks)) {
|
||||
$em
|
||||
->createQuery(
|
||||
"DELETE FROM ChamiloCourseBundle:CTool ct WHERE ct.category = :category AND ct.link IN (:links)"
|
||||
)
|
||||
->execute(['category' => 'plugin', 'links' => $toolLinks]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($newSelectedCourseIds) {
|
||||
foreach ($newSelectedCourseIds as $newSelectedCourseId) {
|
||||
$newSelectedCourse = api_get_course_entity($newSelectedCourseId);
|
||||
|
||||
$newTool = clone $tool;
|
||||
$newTool->setParent($tool);
|
||||
$newTool->setCourse($newSelectedCourse);
|
||||
|
||||
$em->persist($newTool);
|
||||
$em->flush();
|
||||
|
||||
if ($tool->isActiveDeepLinking()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$plugin->addCourseTool(
|
||||
api_get_course_entity($newSelectedCourseId),
|
||||
$newTool,
|
||||
$formValues['tool_visible']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Display::addFlash(
|
||||
Display::return_message(get_lang('ItemUpdated'))
|
||||
);
|
||||
|
||||
header('Location: '.api_get_path(WEB_PLUGIN_PATH).'ims_lti/admin.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$form->setDefaults(
|
||||
[
|
||||
'courses' => $selectedCoursesIds,
|
||||
'tool_visible' => true,
|
||||
]
|
||||
);
|
||||
$form->protect();
|
||||
|
||||
$content = $form->returnForm();
|
||||
|
||||
$interbreadcrumb[] = ['url' => api_get_path(WEB_CODE_PATH).'admin/index.php', 'name' => get_lang('PlatformAdmin')];
|
||||
$interbreadcrumb[] = ['url' => api_get_path(WEB_PLUGIN_PATH).'ims_lti/admin.php', 'name' => $plugin->get_title()];
|
||||
|
||||
$template = new Template($plugin->get_lang('AddInCourses'));
|
||||
$template->assign('header', $plugin->get_lang('AddInCourses'));
|
||||
$template->assign('content', $content);
|
||||
$template->display_one_col_template();
|
||||
} catch (Exception $exception) {
|
||||
Display::addFlash(
|
||||
Display::return_message($exception->getMessage(), 'error')
|
||||
);
|
||||
|
||||
header('Location: '.api_get_path(WEB_PLUGIN_PATH).'ims_lti/admin.php');
|
||||
}
|
||||
163
plugin/ims_lti/multiply_session.php
Normal file
163
plugin/ims_lti/multiply_session.php
Normal file
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
$cidReset = true;
|
||||
|
||||
require_once __DIR__.'/../../main/inc/global.inc.php';
|
||||
|
||||
api_protect_admin_script();
|
||||
|
||||
$plugin = ImsLtiPlugin::create();
|
||||
$webPluginPath = api_get_path(WEB_PLUGIN_PATH).'ims_lti/';
|
||||
|
||||
$em = Database::getManager();
|
||||
|
||||
try {
|
||||
if ($plugin->get('enabled') !== 'true') {
|
||||
throw new Exception(get_lang('NotAllowed'));
|
||||
}
|
||||
|
||||
$request = Request::createFromGlobals();
|
||||
$ltiToolId = $request->query->getInt('id');
|
||||
$sessionId = $request->query->getInt('session_id');
|
||||
|
||||
/** @var ImsLtiTool $tool */
|
||||
$tool = $em->find('ChamiloPluginBundle:ImsLti\ImsLtiTool', $ltiToolId);
|
||||
|
||||
if (!$tool) {
|
||||
throw new Exception($plugin->get_lang('NoTool'));
|
||||
}
|
||||
|
||||
if ($tool->getParent()) {
|
||||
throw new Exception($plugin->get_lang('NoAllowed'));
|
||||
}
|
||||
|
||||
$session = api_get_session_entity($sessionId);
|
||||
|
||||
if (!$session) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
$content = '';
|
||||
|
||||
$courses = ImsLtiPlugin::getCoursesForParentTool($tool, $session);
|
||||
|
||||
$slctCourses = [];
|
||||
|
||||
/** @var \Chamilo\CoreBundle\Entity\Course $course */
|
||||
foreach ($courses as $course) {
|
||||
$slctCourses[$course->getId()] = $course->getName();
|
||||
}
|
||||
|
||||
$selectedCoursesIds = array_keys($slctCourses);
|
||||
|
||||
$form = new FormValidator('frm_multiply', 'post', api_get_self().'?id='.$tool->getId().'&session_id='.$sessionId);
|
||||
$form->addLabel(get_lang('SessionName'), $session);
|
||||
$form->addLabel($plugin->get_lang('Tool'), $tool->getName());
|
||||
$form->addSelectAjax(
|
||||
'courses',
|
||||
get_lang('Courses'),
|
||||
$slctCourses,
|
||||
[
|
||||
'url' => api_get_path(WEB_AJAX_PATH).'course.ajax.php?'.http_build_query(
|
||||
[
|
||||
'a' => 'search_course_by_session_all',
|
||||
'session_id' => $sessionId,
|
||||
]
|
||||
),
|
||||
'multiple' => true,
|
||||
]
|
||||
);
|
||||
$form->addCheckBox('tool_visible', get_lang('SetVisible'), get_lang('ToolIsNowVisible'));
|
||||
$form->addButtonExport(get_lang('Save'));
|
||||
|
||||
if ($form->validate()) {
|
||||
$em = Database::getManager();
|
||||
$formValues = $form->exportValues();
|
||||
$formValues['courses'] = empty($formValues['courses']) ? [] : $formValues['courses'];
|
||||
$formValues['tool_visible'] = !empty($formValues['tool_visible']);
|
||||
|
||||
$courseIdsToDelete = array_diff($selectedCoursesIds, $formValues['courses']);
|
||||
$newSelectedCourseIds = array_diff($formValues['courses'], $selectedCoursesIds);
|
||||
|
||||
if ($courseIdsToDelete) {
|
||||
$toolLinks = [];
|
||||
|
||||
/** @var ImsLtiTool $childInCourse */
|
||||
foreach ($tool->getChildrenInCourses($courseIdsToDelete) as $childInCourse) {
|
||||
$toolLinks[] = "ims_lti/start.php?id={$childInCourse->getId()}";
|
||||
|
||||
$em->remove($childInCourse);
|
||||
}
|
||||
|
||||
$em->flush();
|
||||
|
||||
if (!empty($toolLinks)) {
|
||||
$em
|
||||
->createQuery(
|
||||
"DELETE FROM ChamiloCourseBundle:CTool ct WHERE ct.category = :category AND ct.link IN (:links) AND ct.session_id = :sessionId"
|
||||
)
|
||||
->execute(['category' => 'plugin', 'links' => $toolLinks, 'sessionId' => $sessionId]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($newSelectedCourseIds) {
|
||||
foreach ($newSelectedCourseIds as $newSelectedCourseId) {
|
||||
$newSelectedCourse = api_get_course_entity($newSelectedCourseId);
|
||||
|
||||
$newTool = clone $tool;
|
||||
$newTool->setParent($tool);
|
||||
$newTool->setCourse($newSelectedCourse);
|
||||
$newTool->setSession($session);
|
||||
|
||||
$em->persist($newTool);
|
||||
$em->flush();
|
||||
|
||||
if ($tool->isActiveDeepLinking()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$plugin->addCourseSessionTool(
|
||||
$newSelectedCourse,
|
||||
$session,
|
||||
$newTool,
|
||||
$formValues['tool_visible']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Display::addFlash(
|
||||
Display::return_message(get_lang('ItemUpdated'))
|
||||
);
|
||||
|
||||
header('Location: '.api_get_path(WEB_PLUGIN_PATH).'ims_lti/admin.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$form->setDefaults(
|
||||
[
|
||||
'courses' => $selectedCoursesIds,
|
||||
'tool_visible' => true,
|
||||
]
|
||||
);
|
||||
$form->protect();
|
||||
|
||||
$content = $form->returnForm();
|
||||
|
||||
$interbreadcrumb[] = ['url' => api_get_path(WEB_CODE_PATH).'admin/index.php', 'name' => get_lang('PlatformAdmin')];
|
||||
$interbreadcrumb[] = ['url' => api_get_path(WEB_PLUGIN_PATH).'ims_lti/admin.php', 'name' => $plugin->get_title()];
|
||||
|
||||
$template = new Template($plugin->get_lang('AddInCourses'));
|
||||
$template->assign('header', $plugin->get_lang('AddInCourses'));
|
||||
$template->assign('content', $content);
|
||||
$template->display_one_col_template();
|
||||
} catch (Exception $exception) {
|
||||
Display::addFlash(
|
||||
Display::return_message($exception->getMessage(), 'error')
|
||||
);
|
||||
|
||||
header('Location: '.api_get_path(WEB_PLUGIN_PATH).'ims_lti/admin.php');
|
||||
}
|
||||
51
plugin/ims_lti/nrps2.php
Normal file
51
plugin/ims_lti/nrps2.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||
|
||||
require_once __DIR__.'/../../main/inc/global.inc.php';
|
||||
|
||||
$request = Request::createFromGlobals();
|
||||
|
||||
$response = new JsonResponse(
|
||||
null,
|
||||
Response::HTTP_OK,
|
||||
['content-type' => 'application/json']
|
||||
);
|
||||
|
||||
try {
|
||||
$pathInfo = $request->getPathInfo();
|
||||
|
||||
if (empty($pathInfo) || '/' === $pathInfo) {
|
||||
throw new BadRequestHttpException('Path info is missing.');
|
||||
}
|
||||
|
||||
$resource = LtiNamesRoleProvisioningService::getResource($request, $response);
|
||||
$resource->validate();
|
||||
$resource->process();
|
||||
} catch (HttpExceptionInterface $exception) {
|
||||
foreach ($exception->getHeaders() as $headerKey => $headerValue) {
|
||||
$response->headers->set($headerKey, $headerValue);
|
||||
}
|
||||
|
||||
$response
|
||||
->setStatusCode($exception->getStatusCode())
|
||||
->setData(
|
||||
[
|
||||
'status' => $exception->getStatusCode(),
|
||||
'message' => $exception->getMessage(),
|
||||
'request' => [
|
||||
'method' => $request->getMethod(),
|
||||
'url' => $request->getRequestUri(),
|
||||
'accept' => $request->headers->get('accept'),
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$response->prepare($request);
|
||||
$response->send();
|
||||
69
plugin/ims_lti/outcome_service.php
Normal file
69
plugin/ims_lti/outcome_service.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/* For license terms, see /license.txt */
|
||||
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
|
||||
|
||||
require_once __DIR__.'/../../main/inc/global.inc.php';
|
||||
|
||||
header('Content-Type: application/xml');
|
||||
|
||||
$url = api_get_path(WEB_PATH).'lti/os';
|
||||
|
||||
$em = Database::getManager();
|
||||
$toolRepo = $em->getRepository('ChamiloPluginBundle:ImsLti\ImsLtiTool');
|
||||
|
||||
$headers = OAuthUtil::get_headers();
|
||||
|
||||
if (empty($headers['Authorization'])) {
|
||||
error_log('Authorization header missed');
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
$authParams = OAuthUtil::split_header($headers['Authorization']);
|
||||
|
||||
if (empty($authParams) || empty($authParams['oauth_consumer_key']) || empty($authParams['oauth_signature'])) {
|
||||
error_log('Authorization params not found');
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
$tools = $toolRepo->findBy(['consumerKey' => $authParams['oauth_consumer_key']]);
|
||||
$toolIsFound = false;
|
||||
|
||||
/** @var ImsLtiTool $tool */
|
||||
foreach ($tools as $tool) {
|
||||
$consumer = new OAuthConsumer($tool->getConsumerKey(), $tool->getSharedSecret());
|
||||
$hmacMethod = new OAuthSignatureMethod_HMAC_SHA1();
|
||||
|
||||
$request = OAuthRequest::from_request('POST', $url);
|
||||
$request->sign_request($hmacMethod, $consumer, '');
|
||||
$signature = $request->get_parameter('oauth_signature');
|
||||
|
||||
if ($signature === $authParams['oauth_signature']) {
|
||||
$toolIsFound = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (false === $toolIsFound) {
|
||||
error_log('Tool not found. Signature is not valid');
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
$body = file_get_contents('php://input');
|
||||
$bodyHash = base64_encode(sha1($body, true));
|
||||
|
||||
if ($bodyHash !== $authParams['oauth_body_hash']) {
|
||||
error_log('Authorization request not valid');
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
$plugin = ImsLtiPlugin::create();
|
||||
|
||||
$process = $plugin->processServiceRequest();
|
||||
|
||||
echo $process;
|
||||
39
plugin/ims_lti/platform.php
Normal file
39
plugin/ims_lti/platform.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/* For license terms, see /license.txt */
|
||||
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\Platform;
|
||||
|
||||
$cidReset = true;
|
||||
|
||||
require_once __DIR__.'/../../main/inc/global.inc.php';
|
||||
|
||||
api_protect_admin_script();
|
||||
|
||||
$plugin = ImsLtiPlugin::create();
|
||||
|
||||
if ($plugin->get('enabled') !== 'true') {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
/** @var Platform $platform */
|
||||
$platform = Database::getManager()
|
||||
->getRepository('ChamiloPluginBundle:ImsLti\Platform')
|
||||
->findOneBy([]);
|
||||
|
||||
$table = new HTML_Table(['class' => 'table table-striped']);
|
||||
$table->setHeaderContents(0, 0, $plugin->get_lang('KeyId'));
|
||||
$table->setHeaderContents(0, 1, $plugin->get_lang('PublicKey'));
|
||||
$table->setHeaderContents(0, 2, $plugin->get_lang('PrivateKey'));
|
||||
$table->setCellContents(1, 0, $platform ? $platform->getKid() : '');
|
||||
$table->setCellContents(1, 1, $platform ? nl2br($platform->publicKey) : '');
|
||||
$table->setCellContents(1, 2, $platform ? nl2br($platform->getPrivateKey()) : '');
|
||||
$table->updateCellAttributes(1, 1, ['style' => 'font-family: monospace; font-size: 10px;']);
|
||||
$table->updateCellAttributes(1, 2, ['style' => 'font-family: monospace; font-size: 10px;']);
|
||||
|
||||
$interbreadcrumb[] = ['url' => api_get_path(WEB_CODE_PATH).'admin/index.php', 'name' => get_lang('PlatformAdmin')];
|
||||
$interbreadcrumb[] = ['url' => api_get_path(WEB_PLUGIN_PATH).'ims_lti/admin.php', 'name' => $plugin->get_title()];
|
||||
|
||||
$template = new Template($plugin->get_lang('PlatformKeys'));
|
||||
$template->assign('header', $plugin->get_lang('PlatformKeys'));
|
||||
$template->assign('content', $table->toHtml());
|
||||
$template->display_one_col_template();
|
||||
4
plugin/ims_lti/plugin.php
Normal file
4
plugin/ims_lti/plugin.php
Normal file
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
/* For license terms, see /license.txt */
|
||||
|
||||
$plugin_info = ImsLtiPlugin::create()->get_info();
|
||||
91
plugin/ims_lti/session.php
Normal file
91
plugin/ims_lti/session.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
$cidReset = true;
|
||||
|
||||
require_once __DIR__.'/../../main/inc/global.inc.php';
|
||||
|
||||
api_protect_admin_script();
|
||||
|
||||
$plugin = ImsLtiPlugin::create();
|
||||
$webPluginPath = api_get_path(WEB_PLUGIN_PATH).'ims_lti/';
|
||||
|
||||
$request = Request::createFromGlobals();
|
||||
$ltiToolId = $request->query->getInt('id');
|
||||
|
||||
$em = Database::getManager();
|
||||
|
||||
try {
|
||||
if ($plugin->get('enabled') !== 'true') {
|
||||
throw new Exception(get_lang('NotAllowed'));
|
||||
}
|
||||
|
||||
/** @var ImsLtiTool $tool */
|
||||
$tool = $em->find('ChamiloPluginBundle:ImsLti\ImsLtiTool', $ltiToolId);
|
||||
|
||||
if (!$tool) {
|
||||
throw new Exception($plugin->get_lang('NoTool'));
|
||||
}
|
||||
|
||||
if ($tool->getParent()) {
|
||||
throw new Exception($plugin->get_lang('NoAllowed'));
|
||||
}
|
||||
|
||||
$content = '';
|
||||
|
||||
$form = new FormValidator('frm_multiply', 'post', api_get_self().'?id='.$tool->getId());
|
||||
$form->addLabel($plugin->get_lang('Tool'), $tool->getName());
|
||||
$form->addSelectAjax(
|
||||
'sessions',
|
||||
get_lang('Sessions'),
|
||||
[],
|
||||
[
|
||||
'url' => api_get_path(WEB_AJAX_PATH).'session.ajax.php?'.http_build_query(
|
||||
[
|
||||
'a' => 'search_session',
|
||||
]
|
||||
),
|
||||
]
|
||||
);
|
||||
$form->addHidden('tool_id', $tool->getId());
|
||||
$form->addButtonExport(get_lang('Next'));
|
||||
|
||||
if ($form->validate()) {
|
||||
$em = Database::getManager();
|
||||
$formValues = $form->exportValues();
|
||||
$formValues['sessions'] = empty($formValues['sessions']) ? [] : $formValues['sessions'];
|
||||
|
||||
if (!$formValues['sessions']) {
|
||||
Display::addFlash(
|
||||
Display::return_message($plugin->get_lang('NeedToSelectASession'), 'error', false)
|
||||
);
|
||||
header('Location:'.api_get_self());
|
||||
exit;
|
||||
}
|
||||
|
||||
header('Location: '.api_get_path(WEB_PLUGIN_PATH).'ims_lti/multiply_session.php?id='.$formValues['tool_id'].'&session_id='.$formValues['sessions']);
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
$form->protect();
|
||||
|
||||
$content = $form->returnForm();
|
||||
|
||||
$interbreadcrumb[] = ['url' => api_get_path(WEB_CODE_PATH).'admin/index.php', 'name' => get_lang('PlatformAdmin')];
|
||||
$interbreadcrumb[] = ['url' => api_get_path(WEB_PLUGIN_PATH).'ims_lti/admin.php', 'name' => $plugin->get_title()];
|
||||
|
||||
$template = new Template($plugin->get_lang('AddInSessions'));
|
||||
$template->assign('header', $plugin->get_lang('AddInSessions'));
|
||||
$template->assign('content', $content);
|
||||
$template->display_one_col_template();
|
||||
} catch (Exception $exception) {
|
||||
Display::addFlash(
|
||||
Display::return_message($exception->getMessage(), 'error')
|
||||
);
|
||||
|
||||
header('Location: '.api_get_path(WEB_PLUGIN_PATH).'ims_lti/admin.php');
|
||||
}
|
||||
28
plugin/ims_lti/src/ContentItem/LtiContentItemType.php
Normal file
28
plugin/ims_lti/src/ContentItem/LtiContentItemType.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\CoreBundle\Entity\Course;
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
|
||||
|
||||
/**
|
||||
* Class LtiContentItemType.
|
||||
*/
|
||||
abstract class LtiContentItemType
|
||||
{
|
||||
/**
|
||||
* LtiContentItemType constructor.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct(stdClass $itemData)
|
||||
{
|
||||
$this->validateItemData($itemData);
|
||||
}
|
||||
|
||||
abstract public function save(ImsLtiTool $baseTool, Course $course);
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
abstract protected function validateItemData(stdClass $itemData);
|
||||
}
|
||||
150
plugin/ims_lti/src/ContentItem/LtiResourceLink.php
Normal file
150
plugin/ims_lti/src/ContentItem/LtiResourceLink.php
Normal file
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\CoreBundle\Entity\Course;
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
|
||||
|
||||
/**
|
||||
* Class LtiContentItem.
|
||||
*/
|
||||
class LtiResourceLink extends LtiContentItemType
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $url;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $title;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $text;
|
||||
/**
|
||||
* @var stdClass
|
||||
*/
|
||||
private $icon;
|
||||
/**
|
||||
* @var stdClass
|
||||
*/
|
||||
private $thumbnail;
|
||||
/**
|
||||
* @var stdClass
|
||||
*/
|
||||
private $iframe;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $custom;
|
||||
/**
|
||||
* @var stdClass
|
||||
*/
|
||||
private $lineItem;
|
||||
/**
|
||||
* @var stdClass
|
||||
*/
|
||||
private $available;
|
||||
/**
|
||||
* @var stdClass
|
||||
*/
|
||||
private $submission;
|
||||
|
||||
/**
|
||||
* @throws \Doctrine\ORM\OptimisticLockException
|
||||
*
|
||||
* @return ImsLtiTool
|
||||
*/
|
||||
public function save(ImsLtiTool $baseTool, Course $course)
|
||||
{
|
||||
$newTool = $this->createTool($baseTool);
|
||||
$newTool->setActiveDeepLinking(false);
|
||||
|
||||
$em = Database::getManager();
|
||||
|
||||
$em->persist($newTool);
|
||||
$em->flush();
|
||||
|
||||
ImsLtiPlugin::create()->addCourseTool($course, $newTool);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function validateItemData(stdClass $itemData)
|
||||
{
|
||||
$this->url = empty($itemData->url) ? '' : $itemData->url;
|
||||
$this->title = empty($itemData->title) ? '' : $itemData->title;
|
||||
$this->text = empty($itemData->text) ? '' : $itemData->text;
|
||||
$this->custom = empty($itemData->custom) || !is_array($itemData->custom) ? [] : (array) $itemData->custom;
|
||||
|
||||
$this->icon = empty($itemData->icon) ? null : $itemData->icon;
|
||||
|
||||
if ($this->icon
|
||||
&& (empty($this->icon->url) || empty($this->icon->width) || empty($this->icon->height))
|
||||
) {
|
||||
throw new Exception(sprintf("Icon properties are missing in data form content item: %s", print_r($itemData, true)));
|
||||
}
|
||||
|
||||
$this->thumbnail = empty($itemData->thumbnail) ? null : $itemData->thumbnail;
|
||||
|
||||
if ($this->thumbnail
|
||||
&& (empty($this->thumbnail->url) || empty($this->thumbnail->width) || empty($this->thumbnail->height))
|
||||
) {
|
||||
throw new Exception(sprintf("Thumbnail URL is missing in data form content item: %s", print_r($itemData, true)));
|
||||
}
|
||||
|
||||
$this->iframe = empty($itemData->iframe) ? null : $itemData->iframe;
|
||||
|
||||
if ($this->iframe && (empty($this->iframe->width) || empty($this->iframe->height))) {
|
||||
throw new Exception(sprintf("Iframe size is wrong in data form content item: %s", print_r($itemData, true)));
|
||||
}
|
||||
|
||||
$this->lineItem = empty($itemData->lineItem) ? null : $itemData->lineItem;
|
||||
|
||||
if ($this->lineItem && empty($this->lineItem->scoreMaximum)) {
|
||||
throw new Exception(sprintf("LineItem properties are missing in data form content item: %s", print_r($itemData, true)));
|
||||
}
|
||||
|
||||
$this->available = empty($itemData->available) ? null : $itemData->available;
|
||||
|
||||
if ($this->available && empty($this->available->startDateTime) && empty($this->available->endDateTime)) {
|
||||
throw new Exception(sprintf("LineItem properties are missing in data form content item: %s", print_r($itemData, true)));
|
||||
}
|
||||
|
||||
$this->submission = empty($itemData->submission) ? null : $itemData->submission;
|
||||
|
||||
if ($this->submission && empty($this->submission->startDateTime) && empty($this->submission->endDateTime)) {
|
||||
throw new Exception(sprintf("Submission properties are missing in data form content item: %s", print_r($itemData, true)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ImsLtiTool
|
||||
*/
|
||||
private function createTool(ImsLtiTool $baseTool)
|
||||
{
|
||||
$newTool = clone $baseTool;
|
||||
$newTool->setParent($baseTool);
|
||||
|
||||
if (!empty($this->url)) {
|
||||
$newTool->setLaunchUrl($this->url);
|
||||
}
|
||||
|
||||
if (!empty($this->title)) {
|
||||
$newTool->setName($this->title);
|
||||
}
|
||||
|
||||
if (!empty($this->text)) {
|
||||
$newTool->setDescription($this->text);
|
||||
}
|
||||
|
||||
if (!empty($this->custom)) {
|
||||
$newTool->setCustomParams(
|
||||
$newTool->encodeCustomParams($this->custom)
|
||||
);
|
||||
}
|
||||
|
||||
return $newTool;
|
||||
}
|
||||
}
|
||||
239
plugin/ims_lti/src/Form/FrmAdd.php
Normal file
239
plugin/ims_lti/src/Form/FrmAdd.php
Normal file
@@ -0,0 +1,239 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
namespace Chamilo\PluginBundle\ImsLti\Form;
|
||||
|
||||
use Category;
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
|
||||
use Display;
|
||||
use FormValidator;
|
||||
use ImsLti;
|
||||
use ImsLtiPlugin;
|
||||
use LtiAssignmentGradesService;
|
||||
use LtiNamesRoleProvisioningService;
|
||||
|
||||
/**
|
||||
* Class FrmAdd.
|
||||
*/
|
||||
class FrmAdd extends FormValidator
|
||||
{
|
||||
/**
|
||||
* @var ImsLtiTool|null
|
||||
*/
|
||||
private $baseTool;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $toolIsV1p3;
|
||||
|
||||
/**
|
||||
* FrmAdd constructor.
|
||||
*
|
||||
* @param string $name
|
||||
* @param array $attributes
|
||||
*/
|
||||
public function __construct(
|
||||
$name,
|
||||
$attributes = [],
|
||||
ImsLtiTool $tool = null
|
||||
) {
|
||||
parent::__construct($name, 'POST', '', '', $attributes, self::LAYOUT_HORIZONTAL, true);
|
||||
|
||||
$this->baseTool = $tool;
|
||||
$this->toolIsV1p3 = $this->baseTool
|
||||
&& !empty($this->baseTool->publicKey)
|
||||
&& !empty($this->baseTool->getClientId())
|
||||
&& !empty($this->baseTool->getLoginUrl())
|
||||
&& !empty($this->baseTool->getRedirectUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the form.
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
$plugin = ImsLtiPlugin::create();
|
||||
|
||||
$this->addHeader($plugin->get_lang('ToolSettings'));
|
||||
$this->addText('name', get_lang('Name'));
|
||||
$this->addTextarea('description', get_lang('Description'));
|
||||
|
||||
if (null === $this->baseTool) {
|
||||
$this->addUrl('launch_url', $plugin->get_lang('LaunchUrl'), true);
|
||||
$this->addRadio(
|
||||
'version',
|
||||
$plugin->get_lang('LtiVersion'),
|
||||
[
|
||||
ImsLti::V_1P1 => 'LTI 1.0 / 1.1',
|
||||
ImsLti::V_1P3 => 'LTI 1.3.0',
|
||||
]
|
||||
);
|
||||
$this->addHtml('<div class="'.ImsLti::V_1P1.'" style="display: none;">');
|
||||
$this->addText('consumer_key', $plugin->get_lang('ConsumerKey'), false);
|
||||
$this->addText('shared_secret', $plugin->get_lang('SharedSecret'), false);
|
||||
$this->addHtml('</div>');
|
||||
$this->addHtml('<div class="'.ImsLti::V_1P3.'" style="display: block;">');
|
||||
$this->addRadio(
|
||||
'public_key_type',
|
||||
$plugin->get_lang('PublicKeyType'),
|
||||
[
|
||||
ImsLti::LTI_JWK_KEYSET => $plugin->get_lang('KeySetUrl'),
|
||||
ImsLti::LTI_RSA_KEY => $plugin->get_lang('RsaKey'),
|
||||
]
|
||||
);
|
||||
$this->addHtml('<div class="'.ImsLti::LTI_JWK_KEYSET.'" style="display: block;">');
|
||||
$this->addUrl('jwks_url', $plugin->get_lang('PublicKeyset'), false);
|
||||
$this->addHtml('</div>');
|
||||
$this->addHtml('<div class="'.ImsLti::LTI_RSA_KEY.'" style="display: none;">');
|
||||
$this->addTextarea(
|
||||
'public_key',
|
||||
$plugin->get_lang('PublicKey'),
|
||||
['style' => 'font-family: monospace;', 'rows' => 5]
|
||||
);
|
||||
$this->addHtml('</div>');
|
||||
$this->addUrl('login_url', $plugin->get_lang('LoginUrl'), false);
|
||||
$this->addUrl('redirect_url', $plugin->get_lang('RedirectUrl'), false);
|
||||
$this->addHtml('</div>');
|
||||
}
|
||||
|
||||
$this->addButtonAdvancedSettings('lti_adv');
|
||||
$this->addHtml('<div id="lti_adv_options" style="display:none;">');
|
||||
$this->addTextarea(
|
||||
'custom_params',
|
||||
[$plugin->get_lang('CustomParams'), $plugin->get_lang('CustomParamsHelp')]
|
||||
);
|
||||
$this->addSelect(
|
||||
'document_target',
|
||||
get_lang('LinkTarget'),
|
||||
['iframe' => 'iframe', 'window' => 'window']
|
||||
);
|
||||
|
||||
if (null === $this->baseTool
|
||||
|| ($this->baseTool && !$this->baseTool->isActiveDeepLinking())
|
||||
) {
|
||||
$this->addCheckBox(
|
||||
'deep_linking',
|
||||
[null, $plugin->get_lang('SupportDeppLinkingHelp'), null],
|
||||
$plugin->get_lang('SupportDeepLinking')
|
||||
);
|
||||
}
|
||||
|
||||
$showAGS = false;
|
||||
|
||||
if (api_get_course_int_id()) {
|
||||
$caterories = Category::load(null, null, api_get_course_id());
|
||||
|
||||
if (!empty($caterories)) {
|
||||
$showAGS = true;
|
||||
}
|
||||
} else {
|
||||
$showAGS = true;
|
||||
}
|
||||
|
||||
$this->addHtml('<div class="'.ImsLti::V_1P3.'" style="display: none;">');
|
||||
|
||||
if ($showAGS) {
|
||||
$this->addRadio(
|
||||
'1p3_ags',
|
||||
$plugin->get_lang('AssigmentAndGradesService'),
|
||||
[
|
||||
LtiAssignmentGradesService::AGS_NONE => $plugin->get_lang('DontUseService'),
|
||||
LtiAssignmentGradesService::AGS_SIMPLE => $plugin->get_lang('AGServiceSimple'),
|
||||
LtiAssignmentGradesService::AGS_FULL => $plugin->get_lang('AGServiceFull'),
|
||||
]
|
||||
);
|
||||
} else {
|
||||
$gradebookUrl = api_get_path(WEB_CODE_PATH).'gradebook/index.php?'.api_get_cidreq();
|
||||
|
||||
$this->addLabel(
|
||||
$plugin->get_lang('AssigmentAndGradesService'),
|
||||
sprintf(
|
||||
$plugin->get_lang('YouNeedCreateTheGradebokInCourseFirst'),
|
||||
Display::url($gradebookUrl, $gradebookUrl)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$this->addRadio(
|
||||
'1p3_nrps',
|
||||
$plugin->get_lang('NamesAndRoleProvisioningService'),
|
||||
[
|
||||
LtiNamesRoleProvisioningService::NRPS_NONE => $plugin->get_lang('DontUseService'),
|
||||
LtiNamesRoleProvisioningService::NRPS_CONTEXT_MEMBERSHIP => $plugin->get_lang('UseService'),
|
||||
]
|
||||
);
|
||||
$this->addHtml('</div>');
|
||||
|
||||
if (!$this->baseTool) {
|
||||
$this->addText(
|
||||
'replacement_user_id',
|
||||
[
|
||||
$plugin->get_lang('ReplacementUserId'),
|
||||
$plugin->get_lang('ReplacementUserIdHelp'),
|
||||
],
|
||||
false
|
||||
);
|
||||
$this->applyFilter('replacement_user_id', 'trim');
|
||||
}
|
||||
|
||||
$this->addHtml('</div>');
|
||||
$this->addButtonAdvancedSettings('lti_privacy', get_lang('Privacy'));
|
||||
$this->addHtml('<div id="lti_privacy_options" style="display:none;">');
|
||||
$this->addCheckBox('share_name', null, $plugin->get_lang('ShareLauncherName'));
|
||||
$this->addCheckBox('share_email', null, $plugin->get_lang('ShareLauncherEmail'));
|
||||
$this->addCheckBox('share_picture', null, $plugin->get_lang('ShareLauncherPicture'));
|
||||
$this->addHtml('</div>');
|
||||
$this->addButtonCreate($plugin->get_lang('AddExternalTool'));
|
||||
$this->applyFilter('__ALL__', 'trim');
|
||||
}
|
||||
|
||||
public function setDefaultValues()
|
||||
{
|
||||
$defaults = [];
|
||||
$defaults['version'] = ImsLti::V_1P3;
|
||||
$defaults['public_key_type'] = ImsLti::LTI_JWK_KEYSET;
|
||||
|
||||
if ($this->baseTool) {
|
||||
$defaults['name'] = $this->baseTool->getName();
|
||||
$defaults['description'] = $this->baseTool->getDescription();
|
||||
$defaults['custom_params'] = $this->baseTool->getCustomParams();
|
||||
$defaults['document_target'] = $this->baseTool->getDocumentTarget();
|
||||
$defaults['share_name'] = $this->baseTool->isSharingName();
|
||||
$defaults['share_email'] = $this->baseTool->isSharingEmail();
|
||||
$defaults['share_picture'] = $this->baseTool->isSharingPicture();
|
||||
$defaults['public_key'] = $this->baseTool->publicKey;
|
||||
$defaults['login_url'] = $this->baseTool->getLoginUrl();
|
||||
$defaults['redirect_url'] = $this->baseTool->getRedirectUrl();
|
||||
|
||||
if ($this->toolIsV1p3) {
|
||||
$advServices = $this->baseTool->getAdvantageServices();
|
||||
|
||||
$defaults['1p3_ags'] = $advServices['ags'];
|
||||
$defaults['1p3_nrps'] = $advServices['nrps'];
|
||||
}
|
||||
}
|
||||
|
||||
$this->setDefaults($defaults);
|
||||
}
|
||||
|
||||
public function returnForm(): string
|
||||
{
|
||||
$js = "<script>
|
||||
\$(function () {
|
||||
\$('[name=\"version\"]').on('change', function () {
|
||||
$('.".ImsLti::V_1P1.", .".ImsLti::V_1P3."').hide();
|
||||
|
||||
$('.' + this.value).show();
|
||||
})
|
||||
\$('[name=\"public_key_type\"]').on('change', function () {
|
||||
$('.".ImsLti::LTI_JWK_KEYSET.", .".ImsLti::LTI_RSA_KEY."').hide();
|
||||
$('[name=\"public_key\"], [name=\"jwks_url\"]').val('');
|
||||
|
||||
$('.' + this.value).show();
|
||||
})
|
||||
});
|
||||
</script>";
|
||||
|
||||
return $js.parent::returnForm(); // TODO: Change the autogenerated stub
|
||||
}
|
||||
}
|
||||
224
plugin/ims_lti/src/Form/FrmEdit.php
Normal file
224
plugin/ims_lti/src/Form/FrmEdit.php
Normal file
@@ -0,0 +1,224 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
namespace Chamilo\PluginBundle\Form;
|
||||
|
||||
use Category;
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
|
||||
use Display;
|
||||
use Exception;
|
||||
use FormValidator;
|
||||
use ImsLti;
|
||||
use ImsLtiPlugin;
|
||||
use LtiAssignmentGradesService;
|
||||
use LtiNamesRoleProvisioningService;
|
||||
|
||||
/**
|
||||
* Class FrmAdd.
|
||||
*/
|
||||
class FrmEdit extends FormValidator
|
||||
{
|
||||
/**
|
||||
* @var ImsLtiTool|null
|
||||
*/
|
||||
private $tool;
|
||||
|
||||
/**
|
||||
* FrmAdd constructor.
|
||||
*
|
||||
* @param string $name
|
||||
* @param array $attributes
|
||||
*/
|
||||
public function __construct(
|
||||
$name,
|
||||
$attributes = [],
|
||||
ImsLtiTool $tool = null
|
||||
) {
|
||||
parent::__construct($name, 'POST', '', '', $attributes, self::LAYOUT_HORIZONTAL, true);
|
||||
|
||||
$this->tool = $tool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the form.
|
||||
*
|
||||
* @param bool $globalMode
|
||||
*/
|
||||
public function build($globalMode = true)
|
||||
{
|
||||
$plugin = ImsLtiPlugin::create();
|
||||
$course = $this->tool->getCourse();
|
||||
$parent = $this->tool->getParent();
|
||||
|
||||
$this->addHeader($plugin->get_lang('ToolSettings'));
|
||||
|
||||
if (null !== $course && $globalMode) {
|
||||
$this->addHtml(
|
||||
Display::return_message(
|
||||
sprintf($plugin->get_lang('ToolAddedOnCourseX'), $course->getTitle()),
|
||||
'normal',
|
||||
false
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$this->addText('name', get_lang('Name'));
|
||||
$this->addTextarea('description', get_lang('Description'));
|
||||
$this->addRadio(
|
||||
'version',
|
||||
$plugin->get_lang('LtiVersion'),
|
||||
[
|
||||
ImsLti::V_1P1 => 'LTI 1.0 / 1.1',
|
||||
ImsLti::V_1P3 => 'LTI 1.3.0',
|
||||
]
|
||||
);
|
||||
$this->freeze(['version']);
|
||||
|
||||
if (null === $parent) {
|
||||
$this->addUrl('launch_url', $plugin->get_lang('LaunchUrl'), true);
|
||||
if ($this->tool->getVersion() === ImsLti::V_1P1) {
|
||||
$this->addText('consumer_key', $plugin->get_lang('ConsumerKey'), false);
|
||||
$this->addText('shared_secret', $plugin->get_lang('SharedSecret'), false);
|
||||
} elseif ($this->tool->getVersion() === ImsLti::V_1P3) {
|
||||
$this->addText('client_id', $plugin->get_lang('ClientId'), true);
|
||||
$this->freeze(['client_id']);
|
||||
if (!empty($this->tool->getJwksUrl())) {
|
||||
$this->addUrl('jwks_url', $plugin->get_lang('PublicKeyset'));
|
||||
} else {
|
||||
$this->addTextarea(
|
||||
'public_key',
|
||||
$plugin->get_lang('PublicKey'),
|
||||
['style' => 'font-family: monospace;', 'rows' => 5],
|
||||
true
|
||||
);
|
||||
}
|
||||
$this->addUrl('login_url', $plugin->get_lang('LoginUrl'));
|
||||
$this->addUrl('redirect_url', $plugin->get_lang('RedirectUrl'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->addButtonAdvancedSettings('lti_adv');
|
||||
$this->addHtml('<div id="lti_adv_options" style="display:none;">');
|
||||
$this->addTextarea(
|
||||
'custom_params',
|
||||
[$plugin->get_lang('CustomParams'), $plugin->get_lang('CustomParamsHelp')]
|
||||
);
|
||||
$this->addSelect(
|
||||
'document_target',
|
||||
get_lang('LinkTarget'),
|
||||
['iframe' => 'iframe', 'window' => 'window']
|
||||
);
|
||||
|
||||
if (null === $parent
|
||||
|| (null !== $parent && !$parent->isActiveDeepLinking())
|
||||
) {
|
||||
$this->addCheckBox(
|
||||
'deep_linking',
|
||||
[null, $plugin->get_lang('SupportDeppLinkingHelp'), null],
|
||||
$plugin->get_lang('SupportDeepLinking')
|
||||
);
|
||||
}
|
||||
|
||||
if (null === $parent && $this->tool->getVersion() === ImsLti::V_1P3) {
|
||||
$showAGS = false;
|
||||
|
||||
if (api_get_course_int_id()) {
|
||||
$caterories = Category::load(null, null, api_get_course_id());
|
||||
|
||||
if (!empty($caterories)) {
|
||||
$showAGS = true;
|
||||
}
|
||||
} else {
|
||||
$showAGS = true;
|
||||
}
|
||||
|
||||
if ($showAGS) {
|
||||
$this->addRadio(
|
||||
'1p3_ags',
|
||||
$plugin->get_lang('AssigmentAndGradesService'),
|
||||
[
|
||||
LtiAssignmentGradesService::AGS_NONE => $plugin->get_lang('DontUseService'),
|
||||
LtiAssignmentGradesService::AGS_SIMPLE => $plugin->get_lang('AGServiceSimple'),
|
||||
LtiAssignmentGradesService::AGS_FULL => $plugin->get_lang('AGServiceFull'),
|
||||
]
|
||||
);
|
||||
} else {
|
||||
$gradebookUrl = api_get_path(WEB_CODE_PATH).'gradebook/index.php?'.api_get_cidreq();
|
||||
|
||||
$this->addLabel(
|
||||
$plugin->get_lang('AssigmentAndGradesService'),
|
||||
sprintf(
|
||||
$plugin->get_lang('YouNeedCreateTheGradebokInCourseFirst'),
|
||||
Display::url($gradebookUrl, $gradebookUrl)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$this->addRadio(
|
||||
'1p3_nrps',
|
||||
$plugin->get_lang('NamesAndRoleProvisioningService'),
|
||||
[
|
||||
LtiNamesRoleProvisioningService::NRPS_NONE => $plugin->get_lang('DontUseService'),
|
||||
LtiNamesRoleProvisioningService::NRPS_CONTEXT_MEMBERSHIP => $plugin->get_lang('UseService'),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if (!$parent) {
|
||||
$this->addText(
|
||||
'replacement_user_id',
|
||||
[
|
||||
$plugin->get_lang('ReplacementUserId'),
|
||||
$plugin->get_lang('ReplacementUserIdHelp'),
|
||||
],
|
||||
false
|
||||
);
|
||||
$this->applyFilter('replacement_user_id', 'trim');
|
||||
}
|
||||
|
||||
$this->addHtml('</div>');
|
||||
$this->addButtonAdvancedSettings('lti_privacy', get_lang('Privacy'));
|
||||
$this->addHtml('<div id="lti_privacy_options" style="display:none;">');
|
||||
$this->addCheckBox('share_name', null, $plugin->get_lang('ShareLauncherName'));
|
||||
$this->addCheckBox('share_email', null, $plugin->get_lang('ShareLauncherEmail'));
|
||||
$this->addCheckBox('share_picture', null, $plugin->get_lang('ShareLauncherPicture'));
|
||||
$this->addHtml('</div>');
|
||||
$this->addButtonUpdate($plugin->get_lang('EditExternalTool'));
|
||||
$this->addHidden('id', $this->tool->getId());
|
||||
$this->addHidden('action', 'edit');
|
||||
$this->applyFilter('__ALL__', 'trim');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function setDefaultValues()
|
||||
{
|
||||
$advServices = $this->tool->getAdvantageServices();
|
||||
|
||||
$this->setDefaults(
|
||||
[
|
||||
'name' => $this->tool->getName(),
|
||||
'description' => $this->tool->getDescription(),
|
||||
'launch_url' => $this->tool->getLaunchUrl(),
|
||||
'consumer_key' => $this->tool->getConsumerKey(),
|
||||
'shared_secret' => $this->tool->getSharedSecret(),
|
||||
'custom_params' => $this->tool->getCustomParams(),
|
||||
'deep_linking' => $this->tool->isActiveDeepLinking(),
|
||||
'share_name' => $this->tool->isSharingName(),
|
||||
'share_email' => $this->tool->isSharingEmail(),
|
||||
'share_picture' => $this->tool->isSharingPicture(),
|
||||
'version' => $this->tool->getVersion(),
|
||||
'client_id' => $this->tool->getClientId(),
|
||||
'public_key' => $this->tool->publicKey,
|
||||
'jwks_url' => $this->tool->getJwksUrl(),
|
||||
'login_url' => $this->tool->getLoginUrl(),
|
||||
'redirect_url' => $this->tool->getRedirectUrl(),
|
||||
'1p3_ags' => $advServices['ags'],
|
||||
'1p3_nrps' => $advServices['nrps'],
|
||||
'document_target' => $this->tool->getDocumentTarget(),
|
||||
'replacement_user_id' => $this->tool->getReplacementForUserId(),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
240
plugin/ims_lti/src/ImsLti.php
Normal file
240
plugin/ims_lti/src/ImsLti.php
Normal file
@@ -0,0 +1,240 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\CoreBundle\Entity\Course;
|
||||
use Chamilo\CoreBundle\Entity\Session;
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
|
||||
use Chamilo\UserBundle\Entity\User;
|
||||
|
||||
/**
|
||||
* Class ImsLti.
|
||||
*/
|
||||
class ImsLti
|
||||
{
|
||||
const V_1P1 = 'lti1p1';
|
||||
const V_1P3 = 'lti1p3';
|
||||
const LTI_RSA_KEY = 'rsa_key';
|
||||
const LTI_JWK_KEYSET = 'jwk_keyset';
|
||||
|
||||
/**
|
||||
* @param Session|null $session Optional.
|
||||
* @param string $domain Optional. Institution domain.
|
||||
* @param string $ltiVersion Optional. Default is lti1p1.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getSubstitutableVariables(
|
||||
User $user,
|
||||
Course $course,
|
||||
Session $session = null,
|
||||
$domain = '',
|
||||
$ltiVersion = self::V_1P1,
|
||||
ImsLtiTool $tool
|
||||
) {
|
||||
$isLti1p3 = $ltiVersion === self::V_1P3;
|
||||
|
||||
return [
|
||||
'$User.id' => $user->getId(),
|
||||
'$User.image' => $isLti1p3 ? ['claim' => 'sub'] : ['user_image'],
|
||||
'$User.username' => $user->getUsername(),
|
||||
'$User.org' => false,
|
||||
'$User.scope.mentor' => $isLti1p3 ? ['claim' => '/claim/role_scope_mentor'] : ['role_scope_mentor'],
|
||||
|
||||
'$Person.sourcedId' => $isLti1p3
|
||||
? self::getPersonSourcedId($domain, $user)
|
||||
: "$domain:".ImsLtiPlugin::getLaunchUserIdClaim($tool, $user),
|
||||
'$Person.name.full' => $user->getFullname(),
|
||||
'$Person.name.family' => $user->getLastname(),
|
||||
'$Person.name.given' => $user->getFirstname(),
|
||||
'$Person.address.street1' => $user->getAddress(),
|
||||
'$Person.phone.primary' => $user->getPhone(),
|
||||
'$Person.email.primary' => $user->getEmail(),
|
||||
|
||||
'$CourseSection.sourcedId' => $isLti1p3
|
||||
? ['claim' => '/claim/lis', 'property' => 'course_section_sourcedid']
|
||||
: ['lis_course_section_sourcedid'],
|
||||
'$CourseSection.label' => $course->getCode(),
|
||||
'$CourseSection.title' => $course->getTitle(),
|
||||
'$CourseSection.longDescription' => $session && $session->getShowDescription()
|
||||
? $session->getDescription()
|
||||
: false,
|
||||
'$CourseSection.timeFrame.begin' => $session && $session->getDisplayStartDate()
|
||||
? $session->getDisplayStartDate()->format(DateTime::ATOM)
|
||||
: '$CourseSection.timeFrame.begin',
|
||||
'$CourseSection.timeFrame.end' => $session && $session->getDisplayEndDate()
|
||||
? $session->getDisplayEndDate()->format(DateTime::ATOM)
|
||||
: '$CourseSection.timeFrame.end',
|
||||
|
||||
'$Membership.role' => $isLti1p3 ? ['claim' => '/claim/roles'] : ['roles'],
|
||||
|
||||
'$Result.sourcedGUID' => $isLti1p3 ? ['claim' => 'sub'] : ['lis_result_sourcedid'],
|
||||
'$Result.sourcedId' => $isLti1p3 ? ['claim' => 'sub'] : ['lis_result_sourcedid'],
|
||||
|
||||
'$ResourceLink.id' => $isLti1p3
|
||||
? ['claim' => '/claim/resource_link', 'property' => 'id']
|
||||
: ['resource_link_id'],
|
||||
'$ResourceLink.title' => $isLti1p3
|
||||
? ['claim' => '/claim/resource_link', 'property' => 'title']
|
||||
: ['resource_link_title'],
|
||||
'$ResourceLink.description' => $isLti1p3
|
||||
? ['claim' => '/claim/resource_link', 'property' => 'description']
|
||||
: ['resource_link_description'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $launchParams All params for launch.
|
||||
* @param array $customParams Custom params where search variables to substitute.
|
||||
* @param Session|null $session Optional.
|
||||
* @param string $domain Optional. Institution domain.
|
||||
* @param string $ltiVersion Optional. Default is lti1p1.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function substituteVariablesInCustomParams(
|
||||
array $launchParams,
|
||||
array $customParams,
|
||||
User $user,
|
||||
Course $course,
|
||||
Session $session = null,
|
||||
$domain = '',
|
||||
$ltiVersion = self::V_1P1,
|
||||
ImsLtiTool $tool
|
||||
) {
|
||||
$substitutables = self::getSubstitutableVariables($user, $course, $session, $domain, $ltiVersion, $tool);
|
||||
$variables = array_keys($substitutables);
|
||||
|
||||
foreach ($customParams as $customKey => $customValue) {
|
||||
if (!in_array($customValue, $variables)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$substitute = $substitutables[$customValue];
|
||||
|
||||
if (is_array($substitute)) {
|
||||
if ($ltiVersion === self::V_1P1) {
|
||||
$substitute = current($substitute);
|
||||
|
||||
$substitute = $launchParams[$substitute];
|
||||
} elseif ($ltiVersion === self::V_1P3) {
|
||||
$claim = array_key_exists($substitute['claim'], $launchParams)
|
||||
? $substitute['claim']
|
||||
: "https://purl.imsglobal.org/spec/lti{$substitute['claim']}";
|
||||
|
||||
$substitute = empty($substitute['property'])
|
||||
? $launchParams[$claim]
|
||||
: $launchParams[$claim][$substitute['property']];
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$customParams[$customKey] = $substitute;
|
||||
}
|
||||
|
||||
array_walk_recursive(
|
||||
$customParams,
|
||||
function (&$value) {
|
||||
if (gettype($value) !== 'array') {
|
||||
$value = (string) $value;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return $customParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a user sourced ID for LIS.
|
||||
*
|
||||
* @param string $domain
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getPersonSourcedId($domain, User $user)
|
||||
{
|
||||
$sourceId = [$domain, $user->getId()];
|
||||
|
||||
return implode(':', $sourceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a course sourced ID for LIS.
|
||||
*
|
||||
* @param string $domain
|
||||
* @param Session $session Optional.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getCourseSectionSourcedId($domain, Course $course, Session $session = null)
|
||||
{
|
||||
$sourceId = [$domain, $course->getId()];
|
||||
|
||||
if ($session) {
|
||||
$sourceId[] = $session->getId();
|
||||
}
|
||||
|
||||
return implode(':', $sourceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get instances for LTI Advantage services.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getAdvantageServices(ImsLtiTool $tool)
|
||||
{
|
||||
return [
|
||||
new LtiAssignmentGradesService($tool),
|
||||
new LtiNamesRoleProvisioningService($tool),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $length
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function generateClientId($length = 20)
|
||||
{
|
||||
$hash = md5(mt_rand().time());
|
||||
|
||||
$clientId = '';
|
||||
|
||||
for ($p = 0; $p < $length; $p++) {
|
||||
$op = mt_rand(1, 3);
|
||||
|
||||
if ($op === 1) {
|
||||
$char = chr(mt_rand(97, 97 + 25));
|
||||
} elseif ($op === 2) {
|
||||
$char = chr(mt_rand(65, 65 + 25));
|
||||
} else {
|
||||
$char = substr($hash, mt_rand(0, strlen($hash) - 1), 1);
|
||||
}
|
||||
|
||||
$clientId .= $char;
|
||||
}
|
||||
|
||||
return $clientId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the format ISO 8601 for date strings coming from JSON or JavaScript.
|
||||
*
|
||||
* @see https://www.myintervals.com/blog/2009/05/20/iso-8601-date-validation-that-doesnt-suck/ Pattern source.
|
||||
*
|
||||
* @param string $strDate
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function validateFormatDateIso8601($strDate)
|
||||
{
|
||||
$pattern = '/^([\+-]?\d{4}(?!\d{2}\b))((-?)('
|
||||
.'(0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W'
|
||||
.'([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))('
|
||||
.'[T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?'
|
||||
.'([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/';
|
||||
|
||||
return preg_match($pattern, $strDate) !== false;
|
||||
}
|
||||
}
|
||||
72
plugin/ims_lti/src/ImsLtiServiceDeleteRequest.php
Normal file
72
plugin/ims_lti/src/ImsLtiServiceDeleteRequest.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\CoreBundle\Entity\GradebookEvaluation;
|
||||
use Chamilo\UserBundle\Entity\User;
|
||||
|
||||
/**
|
||||
* Class ImsLtiDeleteServiceRequest.
|
||||
*/
|
||||
class ImsLtiServiceDeleteRequest extends ImsLtiServiceRequest
|
||||
{
|
||||
/**
|
||||
* ImsLtiDeleteServiceRequest constructor.
|
||||
*/
|
||||
public function __construct(SimpleXMLElement $xml)
|
||||
{
|
||||
parent::__construct($xml);
|
||||
|
||||
$this->responseType = ImsLtiServiceResponse::TYPE_DELETE;
|
||||
$this->xmlRequest = $this->xmlRequest->deleteResultRequest;
|
||||
}
|
||||
|
||||
protected function processBody()
|
||||
{
|
||||
$resultRecord = $this->xmlRequest->resultRecord;
|
||||
$sourcedId = (string) $resultRecord->sourcedGUID->sourcedId;
|
||||
$sourcedId = htmlspecialchars_decode($sourcedId);
|
||||
|
||||
$sourcedParts = json_decode($sourcedId, true);
|
||||
|
||||
if (empty($sourcedParts)) {
|
||||
$this->statusInfo
|
||||
->setSeverity(ImsLtiServiceResponseStatus::SEVERITY_ERROR)
|
||||
->setCodeMajor(ImsLtiServiceResponseStatus::CODEMAJOR_FAILURE);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$em = Database::getManager();
|
||||
/** @var GradebookEvaluation $evaluation */
|
||||
$evaluation = $em->find('ChamiloCoreBundle:GradebookEvaluation', $sourcedParts['e']);
|
||||
/** @var User $user */
|
||||
$user = $em->find('ChamiloUserBundle:User', $sourcedParts['u']);
|
||||
|
||||
if (empty($evaluation) || empty($user)) {
|
||||
$this->statusInfo
|
||||
->setSeverity(ImsLtiServiceResponseStatus::SEVERITY_STATUS)
|
||||
->setCodeMajor(ImsLtiServiceResponseStatus::CODEMAJOR_FAILURE);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$results = Result::load(null, $user->getId(), $evaluation->getId());
|
||||
|
||||
if (empty($results)) {
|
||||
$this->statusInfo
|
||||
->setSeverity(ImsLtiServiceResponseStatus::SEVERITY_STATUS)
|
||||
->setCodeMajor(ImsLtiServiceResponseStatus::CODEMAJOR_FAILURE);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var Result $result */
|
||||
$result = $results[0];
|
||||
$result->addResultLog($user->getId(), $evaluation->getId());
|
||||
$result->delete();
|
||||
|
||||
$this->statusInfo
|
||||
->setSeverity(ImsLtiServiceResponseStatus::SEVERITY_STATUS)
|
||||
->setCodeMajor(ImsLtiServiceResponseStatus::CODEMAJOR_SUCCESS);
|
||||
}
|
||||
}
|
||||
25
plugin/ims_lti/src/ImsLtiServiceDeleteResponse.php
Normal file
25
plugin/ims_lti/src/ImsLtiServiceDeleteResponse.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Class ImsLtiServiceDeleteResponse.
|
||||
*/
|
||||
class ImsLtiServiceDeleteResponse extends ImsLtiServiceResponse
|
||||
{
|
||||
/**
|
||||
* ImsLtiServiceDeleteResponse constructor.
|
||||
*
|
||||
* @param mixed|null $bodyParam
|
||||
*/
|
||||
public function __construct(ImsLtiServiceResponseStatus $statusInfo, $bodyParam = null)
|
||||
{
|
||||
$statusInfo->setOperationRefIdentifier('deleteResult');
|
||||
|
||||
parent::__construct($statusInfo, $bodyParam);
|
||||
}
|
||||
|
||||
protected function generateBody(SimpleXMLElement $xmlBody)
|
||||
{
|
||||
$xmlBody->addChild('deleteResultResponse');
|
||||
}
|
||||
}
|
||||
81
plugin/ims_lti/src/ImsLtiServiceReadRequest.php
Normal file
81
plugin/ims_lti/src/ImsLtiServiceReadRequest.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\CoreBundle\Entity\GradebookEvaluation;
|
||||
use Chamilo\UserBundle\Entity\User;
|
||||
|
||||
/**
|
||||
* Class ImsLtiServiceReadRequest.
|
||||
*/
|
||||
class ImsLtiServiceReadRequest extends ImsLtiServiceRequest
|
||||
{
|
||||
/**
|
||||
* ImsLtiServiceReadRequest constructor.
|
||||
*/
|
||||
public function __construct(SimpleXMLElement $xml)
|
||||
{
|
||||
parent::__construct($xml);
|
||||
|
||||
$this->responseType = ImsLtiServiceResponse::TYPE_READ;
|
||||
$this->xmlRequest = $this->xmlRequest->readResultRequest;
|
||||
}
|
||||
|
||||
protected function processBody()
|
||||
{
|
||||
$resultRecord = $this->xmlRequest->resultRecord;
|
||||
$sourcedId = (string) $resultRecord->sourcedGUID->sourcedId;
|
||||
$sourcedId = htmlspecialchars_decode($sourcedId);
|
||||
|
||||
$sourcedParts = json_decode($sourcedId, true);
|
||||
|
||||
if (empty($sourcedParts)) {
|
||||
$this->statusInfo
|
||||
->setSeverity(ImsLtiServiceResponseStatus::SEVERITY_ERROR)
|
||||
->setCodeMajor(ImsLtiServiceResponseStatus::CODEMAJOR_FAILURE);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$em = Database::getManager();
|
||||
/** @var GradebookEvaluation $evaluation */
|
||||
$evaluation = $em->find('ChamiloCoreBundle:GradebookEvaluation', $sourcedParts['e']);
|
||||
/** @var User $user */
|
||||
$user = $em->find('ChamiloUserBundle:User', $sourcedParts['u']);
|
||||
|
||||
if (empty($evaluation) || empty($user)) {
|
||||
$this->statusInfo
|
||||
->setSeverity(ImsLtiServiceResponseStatus::SEVERITY_STATUS)
|
||||
->setCodeMajor(ImsLtiServiceResponseStatus::CODEMAJOR_FAILURE);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$results = Result::load(null, $user->getId(), $evaluation->getId());
|
||||
|
||||
$ltiScore = '';
|
||||
$responseDescription = get_plugin_lang('ScoreNotSet', 'ImsLtiPlugin');
|
||||
|
||||
if (!empty($results)) {
|
||||
/** @var Result $result */
|
||||
$result = $results[0];
|
||||
$ltiScore = 0;
|
||||
|
||||
if (!empty($result->get_score())) {
|
||||
$ltiScore = $result->get_score() / $evaluation->getMax();
|
||||
}
|
||||
|
||||
$responseDescription = sprintf(
|
||||
get_plugin_lang('ScoreForXUserIsYScore', 'ImsLtiPlugin'),
|
||||
$user->getId(),
|
||||
$ltiScore
|
||||
);
|
||||
}
|
||||
|
||||
$this->statusInfo
|
||||
->setSeverity(ImsLtiServiceResponseStatus::SEVERITY_STATUS)
|
||||
->setCodeMajor(ImsLtiServiceResponseStatus::CODEMAJOR_SUCCESS)
|
||||
->setDescription($responseDescription);
|
||||
|
||||
$this->responseBodyParam = (string) $ltiScore;
|
||||
}
|
||||
}
|
||||
31
plugin/ims_lti/src/ImsLtiServiceReadResponse.php
Normal file
31
plugin/ims_lti/src/ImsLtiServiceReadResponse.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Class ImsLtiReadServiceResponse.
|
||||
*/
|
||||
class ImsLtiServiceReadResponse extends ImsLtiServiceResponse
|
||||
{
|
||||
/**
|
||||
* ImsLtiServiceReadResponse constructor.
|
||||
*
|
||||
* @param mixed|null $bodyParam
|
||||
*/
|
||||
public function __construct(ImsLtiServiceResponseStatus $statusInfo, $bodyParam = null)
|
||||
{
|
||||
$statusInfo->setOperationRefIdentifier('readResult');
|
||||
|
||||
parent::__construct($statusInfo, $bodyParam);
|
||||
}
|
||||
|
||||
protected function generateBody(SimpleXMLElement $xmlBody)
|
||||
{
|
||||
$resultResponse = $xmlBody->addChild('readResultResponse');
|
||||
|
||||
$xmlResultScore = $resultResponse->addChild('result')
|
||||
->addChild('resultScore');
|
||||
|
||||
$xmlResultScore->addChild('language', 'en');
|
||||
$xmlResultScore->addChild('textString', $this->bodyParams);
|
||||
}
|
||||
}
|
||||
101
plugin/ims_lti/src/ImsLtiServiceReplaceRequest.php
Normal file
101
plugin/ims_lti/src/ImsLtiServiceReplaceRequest.php
Normal file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\CoreBundle\Entity\GradebookEvaluation;
|
||||
use Chamilo\UserBundle\Entity\User;
|
||||
|
||||
/**
|
||||
* Class ImsLtiReplaceServiceRequest.
|
||||
*/
|
||||
class ImsLtiServiceReplaceRequest extends ImsLtiServiceRequest
|
||||
{
|
||||
/**
|
||||
* ImsLtiReplaceServiceRequest constructor.
|
||||
*/
|
||||
public function __construct(SimpleXMLElement $xml)
|
||||
{
|
||||
parent::__construct($xml);
|
||||
|
||||
$this->responseType = ImsLtiServiceResponse::TYPE_REPLACE;
|
||||
$this->xmlRequest = $this->xmlRequest->replaceResultRequest;
|
||||
}
|
||||
|
||||
protected function processBody()
|
||||
{
|
||||
$resultRecord = $this->xmlRequest->resultRecord;
|
||||
$sourcedId = (string) $resultRecord->sourcedGUID->sourcedId;
|
||||
$sourcedId = htmlspecialchars_decode($sourcedId);
|
||||
$resultScore = (string) $resultRecord->result->resultScore->textString;
|
||||
|
||||
if (!is_numeric($resultScore)) {
|
||||
$this->statusInfo
|
||||
->setSeverity(ImsLtiServiceResponseStatus::SEVERITY_ERROR)
|
||||
->setCodeMajor(ImsLtiServiceResponseStatus::CODEMAJOR_FAILURE);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$resultScore = (float) $resultScore;
|
||||
|
||||
if (0 > $resultScore || 1 < $resultScore) {
|
||||
$this->statusInfo
|
||||
->setSeverity(ImsLtiServiceResponseStatus::SEVERITY_WARNING)
|
||||
->setCodeMajor(ImsLtiServiceResponseStatus::CODEMAJOR_FAILURE);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$sourcedParts = json_decode($sourcedId, true);
|
||||
|
||||
if (empty($sourcedParts)) {
|
||||
$this->statusInfo
|
||||
->setSeverity(ImsLtiServiceResponseStatus::SEVERITY_ERROR)
|
||||
->setCodeMajor(ImsLtiServiceResponseStatus::CODEMAJOR_FAILURE);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$em = Database::getManager();
|
||||
/** @var GradebookEvaluation $evaluation */
|
||||
$evaluation = $em->find('ChamiloCoreBundle:GradebookEvaluation', $sourcedParts['e']);
|
||||
/** @var User $user */
|
||||
$user = $em->find('ChamiloUserBundle:User', $sourcedParts['u']);
|
||||
|
||||
if (empty($evaluation) || empty($user)) {
|
||||
$this->statusInfo
|
||||
->setSeverity(ImsLtiServiceResponseStatus::SEVERITY_STATUS)
|
||||
->setCodeMajor(ImsLtiServiceResponseStatus::CODEMAJOR_FAILURE);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$score = $evaluation->getMax() * $resultScore;
|
||||
|
||||
$results = Result::load(null, $user->getId(), $evaluation->getId());
|
||||
|
||||
if (empty($results)) {
|
||||
$result = new Result();
|
||||
$result->set_evaluation_id($evaluation->getId());
|
||||
$result->set_user_id($user->getId());
|
||||
$result->set_score($score);
|
||||
$result->add();
|
||||
} else {
|
||||
/** @var Result $result */
|
||||
$result = $results[0];
|
||||
$result->addResultLog($user->getId(), $evaluation->getId());
|
||||
$result->set_score($score);
|
||||
$result->save();
|
||||
}
|
||||
|
||||
$this->statusInfo
|
||||
->setSeverity(ImsLtiServiceResponseStatus::SEVERITY_STATUS)
|
||||
->setCodeMajor(ImsLtiServiceResponseStatus::CODEMAJOR_SUCCESS)
|
||||
->setDescription(
|
||||
sprintf(
|
||||
get_plugin_lang('ScoreForXUserIsYScore', 'ImsLtiPlugin'),
|
||||
$user->getId(),
|
||||
$resultScore
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
25
plugin/ims_lti/src/ImsLtiServiceReplaceResponse.php
Normal file
25
plugin/ims_lti/src/ImsLtiServiceReplaceResponse.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Class ImsLtiReplaceServiceResponse.
|
||||
*/
|
||||
class ImsLtiServiceReplaceResponse extends ImsLtiServiceResponse
|
||||
{
|
||||
/**
|
||||
* ImsLtiServiceReplaceResponse constructor.
|
||||
*
|
||||
* @param mixed|null $bodyParam
|
||||
*/
|
||||
public function __construct(ImsLtiServiceResponseStatus $statusInfo, $bodyParam = null)
|
||||
{
|
||||
$statusInfo->setOperationRefIdentifier('replaceResult');
|
||||
|
||||
parent::__construct($statusInfo, $bodyParam);
|
||||
}
|
||||
|
||||
protected function generateBody(SimpleXMLElement $xmlBody)
|
||||
{
|
||||
$xmlBody->addChild('replaceResultResponse');
|
||||
}
|
||||
}
|
||||
80
plugin/ims_lti/src/ImsLtiServiceRequest.php
Normal file
80
plugin/ims_lti/src/ImsLtiServiceRequest.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Class ImsLtiServiceRequest.
|
||||
*/
|
||||
abstract class ImsLtiServiceRequest
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $responseType;
|
||||
|
||||
/**
|
||||
* @var SimpleXMLElement
|
||||
*/
|
||||
protected $xmlHeaderInfo;
|
||||
|
||||
/**
|
||||
* @var SimpleXMLElement
|
||||
*/
|
||||
protected $xmlRequest;
|
||||
|
||||
/**
|
||||
* @var ImsLtiServiceResponseStatus
|
||||
*/
|
||||
protected $statusInfo;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
protected $responseBodyParam;
|
||||
|
||||
/**
|
||||
* ImsLtiServiceRequest constructor.
|
||||
*/
|
||||
public function __construct(SimpleXMLElement $xml)
|
||||
{
|
||||
$this->statusInfo = new ImsLtiServiceResponseStatus();
|
||||
|
||||
$this->xmlHeaderInfo = $xml->imsx_POXHeader->imsx_POXRequestHeaderInfo;
|
||||
$this->xmlRequest = $xml->imsx_POXBody->children();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ImsLtiServiceResponse|null
|
||||
*/
|
||||
public function process()
|
||||
{
|
||||
$this->processHeader();
|
||||
$this->processBody();
|
||||
|
||||
return $this->generateResponse();
|
||||
}
|
||||
|
||||
protected function processHeader()
|
||||
{
|
||||
$info = $this->xmlHeaderInfo;
|
||||
|
||||
$this->statusInfo->setMessageRefIdentifier($info->imsx_messageIdentifier);
|
||||
|
||||
error_log("Service Request: tool version {$info->imsx_version} message ID {$info->imsx_messageIdentifier}");
|
||||
}
|
||||
|
||||
abstract protected function processBody();
|
||||
|
||||
/**
|
||||
* @return ImsLtiServiceResponse|null
|
||||
*/
|
||||
private function generateResponse()
|
||||
{
|
||||
$response = ImsLtiServiceResponseFactory::create(
|
||||
$this->responseType,
|
||||
$this->statusInfo,
|
||||
$this->responseBodyParam
|
||||
);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
35
plugin/ims_lti/src/ImsLtiServiceRequestFactory.php
Normal file
35
plugin/ims_lti/src/ImsLtiServiceRequestFactory.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Class ImsLtiServiceRequestFactory.
|
||||
*/
|
||||
class ImsLtiServiceRequestFactory
|
||||
{
|
||||
/**
|
||||
* @return ImsLtiServiceRequest|null
|
||||
*/
|
||||
public static function create(SimpleXMLElement $xml)
|
||||
{
|
||||
$bodyChildren = $xml->imsx_POXBody->children();
|
||||
|
||||
if (!empty($bodyChildren)) {
|
||||
$name = $bodyChildren->getName();
|
||||
|
||||
switch ($name) {
|
||||
case 'replaceResultRequest':
|
||||
return new ImsLtiServiceReplaceRequest($xml);
|
||||
case 'readResultRequest':
|
||||
return new ImsLtiServiceReadRequest($xml);
|
||||
case 'deleteResultRequest':
|
||||
return new ImsLtiServiceDeleteRequest($xml);
|
||||
default:
|
||||
$name = str_replace(['ResultRequest', 'Request'], '', $name);
|
||||
|
||||
return new ImsLtiServiceUnsupportedRequest($xml, $name);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
60
plugin/ims_lti/src/ImsLtiServiceResponse.php
Normal file
60
plugin/ims_lti/src/ImsLtiServiceResponse.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Class ImsLtiServiceResponse.
|
||||
*/
|
||||
abstract class ImsLtiServiceResponse
|
||||
{
|
||||
const TYPE_REPLACE = 'replace';
|
||||
const TYPE_READ = 'read';
|
||||
const TYPE_DELETE = 'delete';
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
protected $bodyParams;
|
||||
/**
|
||||
* @var ImsLtiServiceResponseStatus
|
||||
*/
|
||||
private $statusInfo;
|
||||
|
||||
/**
|
||||
* ImsLtiServiceResponse constructor.
|
||||
*
|
||||
* @param mixed|null $bodyParam
|
||||
*/
|
||||
public function __construct(ImsLtiServiceResponseStatus $statusInfo, $bodyParam = null)
|
||||
{
|
||||
$this->statusInfo = $statusInfo;
|
||||
$this->bodyParams = $bodyParam;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
$xml = new SimpleXMLElement('<imsx_POXEnvelopeResponse></imsx_POXEnvelopeResponse>');
|
||||
$xml->addAttribute('xmlns', 'http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0');
|
||||
|
||||
$headerInfo = $xml->addChild('imsx_POXHeader')->addChild('imsx_POXResponseHeaderInfo');
|
||||
$headerInfo->addChild('imsx_version', 'V1.0');
|
||||
$headerInfo->addChild('imsx_messageIdentifier', time());
|
||||
|
||||
$statusInfo = $headerInfo->addChild('imsx_statusInfo');
|
||||
$statusInfo->addChild('imsx_codeMajor', $this->statusInfo->getCodeMajor());
|
||||
$statusInfo->addChild('imsx_severity', $this->statusInfo->getSeverity());
|
||||
$statusInfo->addChild('imsx_description', $this->statusInfo->getDescription());
|
||||
$statusInfo->addChild('imsx_messageRefIdentifier', $this->statusInfo->getMessageRefIdentifier());
|
||||
$statusInfo->addChild('imsx_operationRefIdentifier', $this->statusInfo->getOperationRefIdentifier());
|
||||
|
||||
$body = $xml->addChild('imsx_POXBody');
|
||||
|
||||
$this->generateBody($body);
|
||||
|
||||
return $xml->asXML();
|
||||
}
|
||||
|
||||
abstract protected function generateBody(SimpleXMLElement $xmlBody);
|
||||
}
|
||||
30
plugin/ims_lti/src/ImsLtiServiceResponseFactory.php
Normal file
30
plugin/ims_lti/src/ImsLtiServiceResponseFactory.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Class ImsLtiServiceResponseFactory.
|
||||
*/
|
||||
class ImsLtiServiceResponseFactory
|
||||
{
|
||||
/**
|
||||
* @param string $type
|
||||
* @param mixed $bodyParam
|
||||
*
|
||||
* @return ImsLtiServiceResponse|null
|
||||
*/
|
||||
public static function create($type, ImsLtiServiceResponseStatus $statusInfo, $bodyParam = null)
|
||||
{
|
||||
switch ($type) {
|
||||
case ImsLtiServiceResponse::TYPE_REPLACE:
|
||||
return new ImsLtiServiceReplaceResponse($statusInfo, $bodyParam);
|
||||
case ImsLtiServiceResponse::TYPE_READ:
|
||||
return new ImsLtiServiceReadResponse($statusInfo, $bodyParam);
|
||||
case ImsLtiServiceResponse::TYPE_DELETE:
|
||||
return new ImsLtiServiceDeleteResponse($statusInfo, $bodyParam);
|
||||
default:
|
||||
return new ImsLtiServiceUnsupportedResponse($statusInfo, $type);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
162
plugin/ims_lti/src/ImsLtiServiceResponseStatus.php
Normal file
162
plugin/ims_lti/src/ImsLtiServiceResponseStatus.php
Normal file
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Class ImsLtiResponseStatus.
|
||||
*/
|
||||
class ImsLtiServiceResponseStatus
|
||||
{
|
||||
const SEVERITY_STATUS = 'status';
|
||||
const SEVERITY_WARNING = 'warning';
|
||||
const SEVERITY_ERROR = 'error';
|
||||
|
||||
const CODEMAJOR_SUCCESS = 'success';
|
||||
const CODEMAJOR_PROCESSING = 'processing';
|
||||
const CODEMAJOR_FAILURE = 'failure';
|
||||
const CODEMAJOR_UNSUPPORTED = 'unsupported';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $codeMajor = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $severity = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $messageRefIdentifier = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $operationRefIdentifier = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $description = '';
|
||||
|
||||
/**
|
||||
* Get codeMajor.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCodeMajor()
|
||||
{
|
||||
return $this->codeMajor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set codeMajor.
|
||||
*
|
||||
* @param string $codeMajor
|
||||
*
|
||||
* @return ImsLtiServiceResponseStatus
|
||||
*/
|
||||
public function setCodeMajor($codeMajor)
|
||||
{
|
||||
$this->codeMajor = $codeMajor;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get severity.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSeverity()
|
||||
{
|
||||
return $this->severity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set severity.
|
||||
*
|
||||
* @param string $severity
|
||||
*
|
||||
* @return ImsLtiServiceResponseStatus
|
||||
*/
|
||||
public function setSeverity($severity)
|
||||
{
|
||||
$this->severity = $severity;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get messageRefIdentifier.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getMessageRefIdentifier()
|
||||
{
|
||||
return $this->messageRefIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set messageRefIdentifier.
|
||||
*
|
||||
* @param int $messageRefIdentifier
|
||||
*
|
||||
* @return ImsLtiServiceResponseStatus
|
||||
*/
|
||||
public function setMessageRefIdentifier($messageRefIdentifier)
|
||||
{
|
||||
$this->messageRefIdentifier = $messageRefIdentifier;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get operationRefIdentifier.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getOperationRefIdentifier()
|
||||
{
|
||||
return $this->operationRefIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set operationRefIdentifier.
|
||||
*
|
||||
* @param int $operationRefIdentifier
|
||||
*
|
||||
* @return ImsLtiServiceResponseStatus
|
||||
*/
|
||||
public function setOperationRefIdentifier($operationRefIdentifier)
|
||||
{
|
||||
$this->operationRefIdentifier = $operationRefIdentifier;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get description.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set description.
|
||||
*
|
||||
* @param string $description
|
||||
*
|
||||
* @return ImsLtiServiceResponseStatus
|
||||
*/
|
||||
public function setDescription($description)
|
||||
{
|
||||
$this->description = $description;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
30
plugin/ims_lti/src/ImsLtiServiceUnsupportedRequest.php
Normal file
30
plugin/ims_lti/src/ImsLtiServiceUnsupportedRequest.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Class ImsLtiServiceUnsupportedRequest.
|
||||
*/
|
||||
class ImsLtiServiceUnsupportedRequest extends ImsLtiServiceRequest
|
||||
{
|
||||
/**
|
||||
* ImsLtiDeleteServiceRequest constructor.
|
||||
*
|
||||
* @param string $name
|
||||
*/
|
||||
public function __construct(SimpleXMLElement $xml, $name)
|
||||
{
|
||||
parent::__construct($xml);
|
||||
|
||||
$this->responseType = $name;
|
||||
}
|
||||
|
||||
protected function processBody()
|
||||
{
|
||||
$this->statusInfo
|
||||
->setSeverity(ImsLtiServiceResponseStatus::SEVERITY_STATUS)
|
||||
->setCodeMajor(ImsLtiServiceResponseStatus::CODEMAJOR_UNSUPPORTED)
|
||||
->setDescription(
|
||||
$this->responseType.' is not supported'
|
||||
);
|
||||
}
|
||||
}
|
||||
24
plugin/ims_lti/src/ImsLtiServiceUnsupportedResponse.php
Normal file
24
plugin/ims_lti/src/ImsLtiServiceUnsupportedResponse.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Class ImsLtiServiceUnsupportedResponse.
|
||||
*/
|
||||
class ImsLtiServiceUnsupportedResponse extends ImsLtiServiceResponse
|
||||
{
|
||||
/**
|
||||
* ImsLtiServiceUnsupportedResponse constructor.
|
||||
*
|
||||
* @param string $type
|
||||
*/
|
||||
public function __construct(ImsLtiServiceResponseStatus $statusInfo, $type)
|
||||
{
|
||||
$statusInfo->setOperationRefIdentifier($type);
|
||||
|
||||
parent::__construct($statusInfo);
|
||||
}
|
||||
|
||||
protected function generateBody(SimpleXMLElement $xmlBody)
|
||||
{
|
||||
}
|
||||
}
|
||||
149
plugin/ims_lti/src/LtiAuthException.php
Normal file
149
plugin/ims_lti/src/LtiAuthException.php
Normal file
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Class LtiAuthException.
|
||||
*/
|
||||
class LtiAuthException extends Exception
|
||||
{
|
||||
const INVALID_REQUEST = 1;
|
||||
const INVALID_SCOPE = 2;
|
||||
const UNSUPPORTED_RESPONSE_TYPE = 3;
|
||||
const UNAUTHORIZED_CLIENT = 4;
|
||||
const ACCESS_DENIED = 5;
|
||||
const UNREGISTERED_REDIRECT_URI = 6;
|
||||
const INVALID_RESPONSE_MODE = 7;
|
||||
const MISSING_RESPONSE_MODE = 8;
|
||||
const INVALID_PROMPT = 9;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $type;
|
||||
|
||||
/**
|
||||
* LtiAuthException constructor.
|
||||
*
|
||||
* @param int $code
|
||||
*/
|
||||
public function __construct($code = 0, Throwable $previous = null)
|
||||
{
|
||||
switch ($code) {
|
||||
case self::INVALID_SCOPE:
|
||||
$this->type = 'invalid_scope';
|
||||
$message = 'Invalid scope';
|
||||
break;
|
||||
case self::UNSUPPORTED_RESPONSE_TYPE:
|
||||
$this->type = 'unsupported_response_type';
|
||||
$message = 'Unsupported responde type';
|
||||
break;
|
||||
case self::UNAUTHORIZED_CLIENT:
|
||||
$this->type = 'unauthorized_client';
|
||||
$message = 'Unauthorized client';
|
||||
break;
|
||||
case self::ACCESS_DENIED:
|
||||
$this->type = 'access_denied';
|
||||
$message = 'Access denied';
|
||||
break;
|
||||
case self::UNREGISTERED_REDIRECT_URI:
|
||||
$message = 'Unregistered redirect_uri';
|
||||
break;
|
||||
case self::INVALID_RESPONSE_MODE:
|
||||
$message = 'Invalid response_mode';
|
||||
break;
|
||||
case self::MISSING_RESPONSE_MODE:
|
||||
$message = 'Missing response_mode';
|
||||
break;
|
||||
case self::INVALID_PROMPT:
|
||||
$message = 'Invalid prompt';
|
||||
break;
|
||||
case self::INVALID_REQUEST:
|
||||
default:
|
||||
$this->type = 'invalid_request';
|
||||
$message = 'Invalid request';
|
||||
break;
|
||||
}
|
||||
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LtiAuthException
|
||||
*/
|
||||
public static function invalidRequest(Throwable $previous = null)
|
||||
{
|
||||
return new self(self::INVALID_REQUEST, $previous);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LtiAuthException
|
||||
*/
|
||||
public static function invalidScope(Throwable $previous = null)
|
||||
{
|
||||
return new self(self::INVALID_SCOPE, $previous);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LtiAuthException
|
||||
*/
|
||||
public static function unsupportedResponseType(Throwable $previous = null)
|
||||
{
|
||||
return new self(self::UNSUPPORTED_RESPONSE_TYPE, $previous);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LtiAuthException
|
||||
*/
|
||||
public static function unauthorizedClient(Throwable $previous = null)
|
||||
{
|
||||
return new self(self::UNAUTHORIZED_CLIENT, $previous);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LtiAuthException
|
||||
*/
|
||||
public static function accessDenied(Throwable $previous = null)
|
||||
{
|
||||
return new self(self::ACCESS_DENIED, $previous);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LtiAuthException
|
||||
*/
|
||||
public static function unregisteredRedirectUri(Throwable $previous = null)
|
||||
{
|
||||
return new self(self::UNREGISTERED_REDIRECT_URI, $previous);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LtiAuthException
|
||||
*/
|
||||
public static function invalidRespondeMode(Throwable $previous = null)
|
||||
{
|
||||
return new self(self::INVALID_RESPONSE_MODE, $previous);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LtiAuthException
|
||||
*/
|
||||
public static function missingResponseMode(Throwable $previous = null)
|
||||
{
|
||||
return new self(self::MISSING_RESPONSE_MODE, $previous);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LtiAuthException
|
||||
*/
|
||||
public static function invalidPrompt(Throwable $previous = null)
|
||||
{
|
||||
return new self(self::INVALID_PROMPT, $previous);
|
||||
}
|
||||
}
|
||||
117
plugin/ims_lti/src/Request/LtiTokenRequest.php
Normal file
117
plugin/ims_lti/src/Request/LtiTokenRequest.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\Token;
|
||||
use Firebase\JWT\JWT;
|
||||
|
||||
/**
|
||||
* Class LtiTokenRequest.
|
||||
*/
|
||||
class LtiTokenRequest
|
||||
{
|
||||
/**
|
||||
* @var ImsLtiTool
|
||||
*/
|
||||
private $tool;
|
||||
|
||||
/**
|
||||
* Validate the request's client assertion. Return the right tool.
|
||||
*
|
||||
* @param string $clientAssertion
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return ImsLtiTool
|
||||
*/
|
||||
public function validateClientAssertion($clientAssertion)
|
||||
{
|
||||
$parts = explode('.', $clientAssertion);
|
||||
|
||||
if (count($parts) !== 3) {
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
$payload = JWT::urlsafeB64Decode($parts[1]);
|
||||
$claims = json_decode($payload, true);
|
||||
|
||||
if (empty($claims) || empty($claims['sub'])) {
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
$this->tool = Database::getManager()
|
||||
->getRepository('ChamiloPluginBundle:ImsLti\ImsLtiTool')
|
||||
->findOneBy(['clientId' => $claims['sub']]);
|
||||
|
||||
if (!$this->tool ||
|
||||
$this->tool->getVersion() !== ImsLti::V_1P3 ||
|
||||
empty($this->tool->publicKey)
|
||||
) {
|
||||
throw new Exception();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the request' scope. Return the allowed scopes in services.
|
||||
*
|
||||
* @param string $scope
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function validateScope($scope)
|
||||
{
|
||||
if (empty($scope)) {
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
$services = ImsLti::getAdvantageServices($this->tool);
|
||||
|
||||
$requested = explode(' ', $scope);
|
||||
$allowed = [];
|
||||
|
||||
/** @var LtiAdvantageService $service */
|
||||
foreach ($services as $service) {
|
||||
$allowed = array_merge($allowed, $service->getAllowedScopes());
|
||||
}
|
||||
|
||||
$intersect = array_intersect($requested, $allowed);
|
||||
|
||||
if (empty($intersect)) {
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
return $intersect;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $clientAssertion
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public function decodeJwt($clientAssertion)
|
||||
{
|
||||
return JWT::decode($clientAssertion, $this->tool->publicKey, ['RS256']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Token
|
||||
*/
|
||||
public function generateToken(array $allowedScopes)
|
||||
{
|
||||
$now = api_get_utc_datetime(null, false, true)->getTimestamp();
|
||||
|
||||
$token = new Token();
|
||||
$token
|
||||
->generateHash()
|
||||
->setTool($this->tool)
|
||||
->setScope($allowedScopes)
|
||||
->setCreatedAt($now)
|
||||
->setExpiresAt($now + Token::TOKEN_LIFETIME);
|
||||
|
||||
return $token;
|
||||
}
|
||||
}
|
||||
49
plugin/ims_lti/src/Service/LtiAdvantageService.php
Normal file
49
plugin/ims_lti/src/Service/LtiAdvantageService.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Class LtiAdvantageService.
|
||||
*/
|
||||
abstract class LtiAdvantageService
|
||||
{
|
||||
/**
|
||||
* @var ImsLtiTool
|
||||
*/
|
||||
protected $tool;
|
||||
|
||||
/**
|
||||
* LtiAdvantageService constructor.
|
||||
*/
|
||||
public function __construct(ImsLtiTool $tool)
|
||||
{
|
||||
$this->tool = $tool;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LtiAdvantageService
|
||||
*/
|
||||
public function setTool(ImsLtiTool $tool)
|
||||
{
|
||||
$this->tool = $tool;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
abstract public function getAllowedScopes();
|
||||
|
||||
/**
|
||||
* @throws \Doctrine\ORM\ORMException
|
||||
* @throws \Doctrine\ORM\OptimisticLockException
|
||||
* @throws \Doctrine\ORM\TransactionRequiredException
|
||||
*
|
||||
* @return LtiServiceResource
|
||||
*/
|
||||
abstract public static function getResource(Request $request, JsonResponse $response);
|
||||
}
|
||||
155
plugin/ims_lti/src/Service/LtiAssignmentGradesService.php
Normal file
155
plugin/ims_lti/src/Service/LtiAssignmentGradesService.php
Normal file
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* Class LtiAssignmentGradesService.
|
||||
*/
|
||||
class LtiAssignmentGradesService extends LtiAdvantageService
|
||||
{
|
||||
const AGS_NONE = 'none';
|
||||
const AGS_SIMPLE = 'simple';
|
||||
const AGS_FULL = 'full';
|
||||
|
||||
const SCOPE_LINE_ITEM = 'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem';
|
||||
const SCOPE_LINE_ITEM_READ = 'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem.readonly';
|
||||
const SCOPE_RESULT_READ = 'https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly';
|
||||
const SCOPE_SCORE_WRITE = 'https://purl.imsglobal.org/spec/lti-ags/scope/score';
|
||||
|
||||
const TYPE_LINE_ITEM_CONTAINER = 'application/vnd.ims.lis.v2.lineitemcontainer+json';
|
||||
const TYPE_LINE_ITEM = 'application/vnd.ims.lis.v2.lineitem+json';
|
||||
const TYPE_RESULT_CONTAINER = 'application/vnd.ims.lis.v2.resultcontainer+json';
|
||||
const TYPE_SCORE = 'application/vnd.ims.lis.v1.score+json';
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getAllowedScopes()
|
||||
{
|
||||
$scopes = [
|
||||
self::SCOPE_LINE_ITEM_READ,
|
||||
self::SCOPE_RESULT_READ,
|
||||
self::SCOPE_SCORE_WRITE,
|
||||
];
|
||||
|
||||
$toolServices = $this->tool->getAdvantageServices();
|
||||
|
||||
if (self::AGS_FULL === $toolServices['ags']) {
|
||||
$scopes[] = self::SCOPE_LINE_ITEM;
|
||||
}
|
||||
|
||||
return $scopes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Doctrine\ORM\ORMException
|
||||
* @throws \Doctrine\ORM\OptimisticLockException
|
||||
* @throws \Doctrine\ORM\TransactionRequiredException
|
||||
*
|
||||
* @return LtiAdvantageServiceResource
|
||||
*/
|
||||
public static function getResource(Request $request, JsonResponse $response)
|
||||
{
|
||||
$parts = explode('/', $request->getPathInfo());
|
||||
$parts = array_filter($parts);
|
||||
|
||||
$resource = null;
|
||||
|
||||
if (count($parts) === 2 && 'lineitems' === $parts[2]) {
|
||||
$resource = new LtiLineItemsResource(
|
||||
$request->query->get('t'),
|
||||
$parts[1]
|
||||
);
|
||||
}
|
||||
|
||||
if (count($parts) === 3 && 'lineitems' === $parts[2]) {
|
||||
$resource = new LtiLineItemResource(
|
||||
$request->query->get('t'),
|
||||
$parts[1],
|
||||
$parts[3]
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($parts[4]) && 'results' === $parts[4]) {
|
||||
$resource = new LtiResultsResource(
|
||||
$request->query->get('t'),
|
||||
$parts[1],
|
||||
$parts[3]
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($parts[4]) && 'scores' === $parts[4]) {
|
||||
$resource = new LtiScoresResource(
|
||||
$request->query->get('t'),
|
||||
$parts[1],
|
||||
$parts[3]
|
||||
);
|
||||
}
|
||||
|
||||
if (!$resource) {
|
||||
throw new NotFoundHttpException('Line item resource not found.');
|
||||
}
|
||||
|
||||
return $resource
|
||||
->setRequest($request)
|
||||
->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $contextId
|
||||
* @param int $toolId
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getLineItemsUrl($contextId, $toolId, array $extraParams = [])
|
||||
{
|
||||
$base = api_get_path(WEB_PLUGIN_PATH).'ims_lti/ags2.php';
|
||||
$resource = str_replace(
|
||||
'context_id',
|
||||
$contextId,
|
||||
LtiLineItemsResource::URL_TEMPLATE
|
||||
);
|
||||
$params = array_merge($extraParams, ['t' => $toolId]);
|
||||
$query = http_build_query($params);
|
||||
|
||||
return "$base$resource?$query";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $contextId
|
||||
* @param int $lineItemId
|
||||
* @param int $toolId
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getLineItemUrl($contextId, $lineItemId, $toolId)
|
||||
{
|
||||
$base = api_get_path(WEB_PLUGIN_PATH).'ims_lti/ags2.php';
|
||||
$resource = str_replace(
|
||||
['context_id', 'line_item_id'],
|
||||
[$contextId, $lineItemId],
|
||||
LtiLineItemResource::URL_TEMPLATE
|
||||
);
|
||||
$query = http_build_query(['t' => $toolId]);
|
||||
|
||||
return "$base$resource?$query";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $contextId
|
||||
* @param int $lineItemId
|
||||
* @param int $toolId
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getResultsUrl($contextId, $lineItemId, $toolId, array $extraParams = [])
|
||||
{
|
||||
$lineItemUrl = self::getLineItemUrl($contextId, $lineItemId, $toolId);
|
||||
$query = http_build_query($extraParams);
|
||||
|
||||
return "$lineItemUrl/results?$query";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* Class LtiNamesRoleProvisioningService.
|
||||
*/
|
||||
class LtiNamesRoleProvisioningService extends LtiAdvantageService
|
||||
{
|
||||
const NRPS_NONE = 'none';
|
||||
const NRPS_CONTEXT_MEMBERSHIP = 'simple';
|
||||
|
||||
const SCOPE_CONTEXT_MEMBERSHIP_READ = 'https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly';
|
||||
|
||||
const TYPE_MEMBERSHIP_CONTAINER = 'application/vnd.ims.lti-nrps.v2.membershipcontainer+json';
|
||||
|
||||
const USER_STATUS_ACTIVE = 'Active';
|
||||
const USER_STATUS_INACTIVE = 'Inactive';
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getAllowedScopes()
|
||||
{
|
||||
return [
|
||||
self::SCOPE_CONTEXT_MEMBERSHIP_READ,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function getResource(Request $request, JsonResponse $response)
|
||||
{
|
||||
$parts = explode('/', $request->getPathInfo());
|
||||
$parts = array_filter($parts);
|
||||
|
||||
$resource = null;
|
||||
|
||||
if (isset($parts[1], $parts[2]) &&
|
||||
(int) $parts[1] > 0 && 'memberships' === $parts[2]
|
||||
) {
|
||||
$resource = new LtiContextMembershipResource(
|
||||
$request->query->getInt('t'),
|
||||
$parts[1],
|
||||
$request->query->getInt('s')
|
||||
);
|
||||
}
|
||||
|
||||
if (!$resource) {
|
||||
throw new NotFoundHttpException('Resource not found for Name and Role Provisioning.');
|
||||
}
|
||||
|
||||
return $resource
|
||||
->setRequest($request)
|
||||
->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $toolId
|
||||
* @param int $courseId
|
||||
* @param int $sessionId
|
||||
* @param array $extraParams
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getUrl($toolId, $courseId, $sessionId = 0, $extraParams = [])
|
||||
{
|
||||
$base = api_get_path(WEB_PLUGIN_PATH).'ims_lti/nrps2.php';
|
||||
$resource = str_replace(
|
||||
'context_id',
|
||||
$courseId,
|
||||
LtiContextMembershipResource::URL_TEMPLATE
|
||||
);
|
||||
$query = http_build_query(['s' => $sessionId, 't' => $toolId]);
|
||||
|
||||
if ($extraParams) {
|
||||
$query .= '&'.http_build_query($extraParams);
|
||||
}
|
||||
|
||||
return "$base$resource?$query";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\CoreBundle\Entity\Course;
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\Token;
|
||||
use Doctrine\ORM\OptimisticLockException;
|
||||
use Doctrine\ORM\ORMException;
|
||||
use Doctrine\ORM\TransactionRequiredException;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
|
||||
|
||||
/**
|
||||
* Class LtiAdvantageServiceResource.
|
||||
*/
|
||||
abstract class LtiAdvantageServiceResource
|
||||
{
|
||||
const URL_TEMPLATE = '/';
|
||||
|
||||
/**
|
||||
* @var Request
|
||||
*/
|
||||
protected $request;
|
||||
/**
|
||||
* @var JsonResponse
|
||||
*/
|
||||
protected $response;
|
||||
/**
|
||||
* @var Course
|
||||
*/
|
||||
protected $course;
|
||||
/**
|
||||
* @var ImsLtiTool
|
||||
*/
|
||||
protected $tool;
|
||||
|
||||
/**
|
||||
* LtiAdvantageServiceResource constructor.
|
||||
*
|
||||
* @param int $toolId
|
||||
* @param int $courseId
|
||||
*
|
||||
* @throws ORMException
|
||||
* @throws OptimisticLockException
|
||||
* @throws TransactionRequiredException
|
||||
*/
|
||||
public function __construct($toolId, $courseId)
|
||||
{
|
||||
$this->course = api_get_course_entity((int) $courseId);
|
||||
$this->tool = Database::getManager()->find('ChamiloPluginBundle:ImsLti\ImsLtiTool', (int) $toolId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LtiAdvantageServiceResource
|
||||
*/
|
||||
public function setRequest(Request $request)
|
||||
{
|
||||
$this->request = $request;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LtiAdvantageServiceResource
|
||||
*/
|
||||
public function setResponse(JsonResponse $response)
|
||||
{
|
||||
$this->response = $response;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws HttpExceptionInterface
|
||||
*/
|
||||
abstract public function validate();
|
||||
|
||||
/**
|
||||
* @throws MethodNotAllowedHttpException
|
||||
*/
|
||||
abstract public function process();
|
||||
|
||||
/**
|
||||
* @throws HttpException
|
||||
*/
|
||||
protected function validateToken(array $allowedScopes)
|
||||
{
|
||||
$headers = getallheaders();
|
||||
$authorization = isset($headers['Authorization']) ? $headers['Authorization'] : '';
|
||||
|
||||
if (substr($authorization, 0, 7) !== 'Bearer ') {
|
||||
throw new BadRequestHttpException('Authorization is missing.');
|
||||
}
|
||||
|
||||
$hash = trim(substr($authorization, 7));
|
||||
|
||||
/** @var Token $token */
|
||||
$token = Database::getManager()
|
||||
->getRepository('ChamiloPluginBundle:ImsLti\Token')
|
||||
->findOneBy(['hash' => $hash]);
|
||||
|
||||
if (!$token) {
|
||||
throw new BadRequestHttpException('Authorization token invalid.');
|
||||
}
|
||||
|
||||
if ($token->getExpiresAt() < api_get_utc_datetime(null, false, true)->getTimestamp()) {
|
||||
throw new BadRequestHttpException('Authorization token expired.');
|
||||
}
|
||||
|
||||
$intersect = array_intersect(
|
||||
$token->getScope(),
|
||||
$allowedScopes
|
||||
);
|
||||
|
||||
if (empty($intersect)) {
|
||||
throw new BadRequestHttpException('Authorization token invalid for the scope.');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\CoreBundle\Entity\CourseRelUser;
|
||||
use Chamilo\CoreBundle\Entity\Session;
|
||||
use Chamilo\CoreBundle\Entity\SessionRelCourseRelUser;
|
||||
use Chamilo\UserBundle\Entity\User;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
|
||||
|
||||
/**
|
||||
* Class LtiContextMembershipResource.
|
||||
*/
|
||||
class LtiContextMembershipResource extends LtiAdvantageServiceResource
|
||||
{
|
||||
const URL_TEMPLATE = '/context_id/memberships';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $inSession = false;
|
||||
/**
|
||||
* @var Session
|
||||
*/
|
||||
private $session;
|
||||
|
||||
/**
|
||||
* LtiContextMembershipResorce constructor.
|
||||
*
|
||||
* @param int $toolId
|
||||
* @param int $courseId
|
||||
* @param int $sessionId
|
||||
*
|
||||
* @throws \Doctrine\ORM\ORMException
|
||||
* @throws \Doctrine\ORM\OptimisticLockException
|
||||
* @throws \Doctrine\ORM\TransactionRequiredException
|
||||
*/
|
||||
public function __construct($toolId, $courseId, $sessionId = 0)
|
||||
{
|
||||
$this->session = api_get_session_entity($sessionId);
|
||||
|
||||
parent::__construct($toolId, $courseId);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function validate()
|
||||
{
|
||||
if ($this->request->server->get('HTTP_ACCEPT') !== LtiNamesRoleProvisioningService::TYPE_MEMBERSHIP_CONTAINER) {
|
||||
throw new UnsupportedMediaTypeHttpException('Unsupported media type.');
|
||||
}
|
||||
|
||||
if (!$this->course) {
|
||||
throw new BadRequestHttpException('Course not found.');
|
||||
}
|
||||
|
||||
if (!$this->tool) {
|
||||
throw new BadRequestHttpException('Tool not found.');
|
||||
}
|
||||
|
||||
if (null === $this->tool->getCourse()) {
|
||||
throw new BadRequestHttpException('Tool not enabled.');
|
||||
}
|
||||
|
||||
if ($this->tool->getCourse()->getId() !== $this->course->getId()) {
|
||||
throw new AccessDeniedHttpException('Tool not found in course.');
|
||||
}
|
||||
|
||||
$sessionId = (int) $this->request->query->get('s');
|
||||
$this->inSession = $sessionId > 0;
|
||||
|
||||
if ($this->inSession && !$this->session) {
|
||||
throw new BadRequestHttpException('Session not found');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function process()
|
||||
{
|
||||
switch ($this->request->getMethod()) {
|
||||
case Request::METHOD_GET:
|
||||
$this->validateToken(
|
||||
[LtiNamesRoleProvisioningService::SCOPE_CONTEXT_MEMBERSHIP_READ]
|
||||
);
|
||||
$this->processGet();
|
||||
break;
|
||||
default:
|
||||
throw new MethodNotAllowedHttpException([Request::METHOD_GET]);
|
||||
}
|
||||
}
|
||||
|
||||
private function processGet()
|
||||
{
|
||||
$role = str_replace(
|
||||
'http://purl.imsglobal.org/vocab/lis/v2/membership#',
|
||||
'',
|
||||
$this->request->query->get('role')
|
||||
);
|
||||
$limit = $this->request->query->getInt('limit');
|
||||
$page = $this->request->query->getInt('page');
|
||||
$status = -1;
|
||||
|
||||
if ('Instructor' === $role) {
|
||||
$status = $this->session ? Session::COACH : User::COURSE_MANAGER;
|
||||
} elseif ('Learner' === $role) {
|
||||
$status = $this->session ? Session::STUDENT : User::STUDENT;
|
||||
}
|
||||
|
||||
$members = $this->getMembers($status, $limit, $page);
|
||||
|
||||
$data = $this->getGetData($members);
|
||||
|
||||
$this->setLinkHeaderToGet($status, $limit, $page);
|
||||
|
||||
$this->response->headers->set('Content-Type', LtiNamesRoleProvisioningService::TYPE_MEMBERSHIP_CONTAINER);
|
||||
$this->response->setData($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $status If $status = -1 then get all statuses.
|
||||
* @param int $limit
|
||||
* @param int $page
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getMembers($status, $limit, $page = 0)
|
||||
{
|
||||
if ($this->session) {
|
||||
$subscriptions = $this->session->getUsersSubscriptionsInCourse($this->course);
|
||||
|
||||
// Add session admin as teacher in course
|
||||
$adminSubscription = new SessionRelCourseRelUser();
|
||||
$adminSubscription->setCourse($this->course);
|
||||
$adminSubscription->setSession($this->session);
|
||||
$adminSubscription->setStatus(Session::COACH);
|
||||
$adminSubscription->setUser(
|
||||
api_get_user_entity($this->session->getSessionAdminId())
|
||||
);
|
||||
|
||||
$subscriptions->add($adminSubscription);
|
||||
} else {
|
||||
$subscriptions = $this->course->getUsers();
|
||||
}
|
||||
|
||||
$criteria = Criteria::create();
|
||||
|
||||
if ($status > -1) {
|
||||
$criteria->where(
|
||||
Criteria::expr()->eq('status', $status)
|
||||
);
|
||||
}
|
||||
|
||||
if ($limit > 0) {
|
||||
$criteria->setMaxResults($limit);
|
||||
|
||||
if ($page > 0) {
|
||||
$criteria->setFirstResult($page * $limit);
|
||||
}
|
||||
}
|
||||
|
||||
return $subscriptions->matching($criteria)->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getGetData(array $members)
|
||||
{
|
||||
$platformDomain = str_replace(['https://', 'http://'], '', api_get_setting('InstitutionUrl'));
|
||||
$dataMembers = [];
|
||||
|
||||
$isSharingName = $this->tool->isSharingName();
|
||||
$isSharingEmail = $this->tool->isSharingEmail();
|
||||
$isSharingPicture = $this->tool->isSharingPicture();
|
||||
|
||||
foreach ($members as $member) {
|
||||
/** @var User $user */
|
||||
$user = $member->getUser();
|
||||
|
||||
$dataMember = [
|
||||
'status' => $user->isActive()
|
||||
? LtiNamesRoleProvisioningService::USER_STATUS_ACTIVE
|
||||
: LtiNamesRoleProvisioningService::USER_STATUS_INACTIVE,
|
||||
'user_id' => ImsLtiPlugin::getLaunchUserIdClaim($this->tool, $user),
|
||||
'lis_person_sourcedid' => ImsLti::getPersonSourcedId($platformDomain, $user),
|
||||
'lti11_legacy_user_id' => ImsLtiPlugin::generateToolUserId($user->getId()),
|
||||
];
|
||||
|
||||
if ($isSharingName) {
|
||||
$dataMember['name'] = $user->getFullname();
|
||||
$dataMember['given_name'] = $user->getFirstname();
|
||||
$dataMember['family_name'] = $user->getLastname();
|
||||
}
|
||||
|
||||
if ($isSharingEmail) {
|
||||
$dataMember['email'] = $user->getEmail();
|
||||
}
|
||||
|
||||
if ($isSharingPicture) {
|
||||
$dataMember['picture'] = UserManager::getUserPicture($user->getId());
|
||||
}
|
||||
|
||||
if ($member instanceof CourseRelUser) {
|
||||
$dataMember['roles'] = $member->getStatus() === User::COURSE_MANAGER
|
||||
? ['http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor']
|
||||
: ['http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'];
|
||||
} elseif ($member instanceof SessionRelCourseRelUser) {
|
||||
$dataMember['roles'] = $member->getStatus() === Session::STUDENT
|
||||
? ['http://purl.imsglobal.org/vocab/lis/v2/membership#Learner']
|
||||
: ['http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor'];
|
||||
}
|
||||
|
||||
$dataMembers[] = $dataMember;
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => api_get_path(WEB_PLUGIN_PATH)
|
||||
."ims_lti/nrps2.php/{$this->course->getId()}/memberships?"
|
||||
.http_build_query(
|
||||
[
|
||||
't' => $this->tool->getId(),
|
||||
's' => $this->session ? $this->session->getId() : null,
|
||||
]
|
||||
),
|
||||
'context' => [
|
||||
'id' => (string) $this->course->getId(),
|
||||
'label' => $this->course->getCode(),
|
||||
'title' => $this->course->getTitle(),
|
||||
],
|
||||
'members' => $dataMembers,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $status
|
||||
* @param int $limit
|
||||
* @param int $page
|
||||
*/
|
||||
private function setLinkHeaderToGet($status, $limit, $page = 0)
|
||||
{
|
||||
if (!$limit) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->session) {
|
||||
$subscriptions = $this->session->getUsersSubscriptionsInCourse($this->course);
|
||||
} else {
|
||||
$subscriptions = $this->course->getUsers();
|
||||
}
|
||||
|
||||
$criteria = Criteria::create();
|
||||
|
||||
if ($status > -1) {
|
||||
$criteria->where(
|
||||
Criteria::expr()->eq('status', $status)
|
||||
);
|
||||
}
|
||||
|
||||
$count = $subscriptions->matching($criteria)->count();
|
||||
|
||||
if ($this->session) {
|
||||
// +1 for session admin
|
||||
$count++;
|
||||
}
|
||||
|
||||
if ($page + 1 < ceil($count / $limit)) {
|
||||
$url = LtiNamesRoleProvisioningService::getUrl(
|
||||
$this->tool->getId(),
|
||||
$this->course->getId(),
|
||||
$this->session ? $this->session->getId() : 0,
|
||||
[
|
||||
'role' => $this->request->query->get('role'),
|
||||
'limit' => $limit,
|
||||
'page' => $page + 1,
|
||||
]
|
||||
);
|
||||
|
||||
$this->response->headers->set(
|
||||
'Link',
|
||||
'<'.$url.'>; rel="next"'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
230
plugin/ims_lti/src/Service/Resource/LtiLineItemResource.php
Normal file
230
plugin/ims_lti/src/Service/Resource/LtiLineItemResource.php
Normal file
@@ -0,0 +1,230 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\LineItem;
|
||||
use Doctrine\ORM\OptimisticLockException;
|
||||
use Doctrine\ORM\ORMException;
|
||||
use Doctrine\ORM\TransactionRequiredException;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
|
||||
|
||||
/**
|
||||
* Class LtiLineItemResource.
|
||||
*/
|
||||
class LtiLineItemResource extends LtiAdvantageServiceResource
|
||||
{
|
||||
const URL_TEMPLATE = '/context_id/lineitems/line_item_id';
|
||||
|
||||
/**
|
||||
* @var LineItem|null
|
||||
*/
|
||||
private $lineItem;
|
||||
|
||||
/**
|
||||
* LtiLineItemResource constructor.
|
||||
*
|
||||
* @param int $toolId
|
||||
* @param int $courseId
|
||||
* @param int $lineItemId
|
||||
*
|
||||
* @throws ORMException
|
||||
* @throws OptimisticLockException
|
||||
* @throws TransactionRequiredException
|
||||
*/
|
||||
public function __construct($toolId, $courseId, $lineItemId)
|
||||
{
|
||||
parent::__construct($toolId, $courseId);
|
||||
|
||||
$this->lineItem = Database::getManager()->find('ChamiloPluginBundle:ImsLti\LineItem', (int) $lineItemId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws OptimisticLockException
|
||||
*/
|
||||
public function process()
|
||||
{
|
||||
switch ($this->request->getMethod()) {
|
||||
case Request::METHOD_GET:
|
||||
$this->validateToken(
|
||||
[LtiAssignmentGradesService::SCOPE_LINE_ITEM]
|
||||
);
|
||||
$this->processGet();
|
||||
break;
|
||||
case Request::METHOD_PUT:
|
||||
if (LtiAssignmentGradesService::AGS_FULL !== $this->tool->getAdvantageServices()['ags']) {
|
||||
throw new MethodNotAllowedHttpException([Request::METHOD_GET]);
|
||||
}
|
||||
|
||||
$this->validateToken(
|
||||
[LtiAssignmentGradesService::SCOPE_LINE_ITEM]
|
||||
);
|
||||
$this->processPut();
|
||||
break;
|
||||
case Request::METHOD_DELETE:
|
||||
if (LtiAssignmentGradesService::AGS_FULL !== $this->tool->getAdvantageServices()['ags']) {
|
||||
throw new MethodNotAllowedHttpException([Request::METHOD_GET]);
|
||||
}
|
||||
|
||||
$this->validateToken(
|
||||
[LtiAssignmentGradesService::SCOPE_LINE_ITEM]
|
||||
);
|
||||
$this->processDelete();
|
||||
break;
|
||||
default:
|
||||
throw new MethodNotAllowedHttpException([Request::METHOD_GET, Request::METHOD_PUT, Request::METHOD_DELETE]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the values for the resource URL.
|
||||
*/
|
||||
public function validate()
|
||||
{
|
||||
if (!$this->course) {
|
||||
throw new BadRequestHttpException('Course not found.');
|
||||
}
|
||||
|
||||
if (!$this->tool) {
|
||||
throw new BadRequestHttpException('Tool not found.');
|
||||
}
|
||||
|
||||
if ($this->tool->getCourse()->getId() !== $this->course->getId()) {
|
||||
throw new AccessDeniedHttpException('Tool not found in course.');
|
||||
}
|
||||
|
||||
if ($this->request->server->get('HTTP_ACCEPT') !== LtiAssignmentGradesService::TYPE_LINE_ITEM) {
|
||||
throw new UnsupportedMediaTypeHttpException('Unsupported media type.');
|
||||
}
|
||||
|
||||
$parentTool = $this->tool->getParent();
|
||||
|
||||
if ($parentTool) {
|
||||
$advServices = $parentTool->getAdvantageServices();
|
||||
|
||||
if (LtiAssignmentGradesService::AGS_NONE === $advServices['ags']) {
|
||||
throw new AccessDeniedHttpException('Assigment and grade service is not enabled for this tool.');
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->lineItem) {
|
||||
throw new NotFoundHttpException('Line item not found');
|
||||
}
|
||||
|
||||
if ($this->lineItem->getTool()->getId() !== $this->tool->getId()) {
|
||||
throw new AccessDeniedHttpException('Line item not found for the tool.');
|
||||
}
|
||||
}
|
||||
|
||||
private function processGet()
|
||||
{
|
||||
$data = $this->lineItem->toArray();
|
||||
$data['id'] = LtiAssignmentGradesService::getLineItemUrl(
|
||||
$this->course->getId(),
|
||||
$this->lineItem->getId(),
|
||||
$this->tool->getId()
|
||||
);
|
||||
|
||||
$this->response->headers->set('Content-Type', LtiAssignmentGradesService::TYPE_LINE_ITEM);
|
||||
$this->response->setData($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws OptimisticLockException
|
||||
*/
|
||||
private function processPut()
|
||||
{
|
||||
$data = json_decode($this->request->getContent(), true);
|
||||
|
||||
if (empty($data) || empty($data['label']) || empty($data['scoreMaximum'])) {
|
||||
throw new BadRequestHttpException('Missing data to update line item.');
|
||||
}
|
||||
|
||||
$this->updateLineItem($data);
|
||||
|
||||
$data['id'] = LtiAssignmentGradesService::getLineItemUrl(
|
||||
$this->course->getId(),
|
||||
$this->lineItem->getId(),
|
||||
$this->tool->getId()
|
||||
);
|
||||
$data['scoreMaximum'] = $this->lineItem->getEvaluation()->getMax();
|
||||
|
||||
$this->response->headers->set('Content-Type', LtiAssignmentGradesService::TYPE_LINE_ITEM);
|
||||
$this->response->setEncodingOptions(JSON_UNESCAPED_SLASHES);
|
||||
$this->response->setData($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws OptimisticLockException
|
||||
*/
|
||||
private function updateLineItem(array $data)
|
||||
{
|
||||
$lineItemEvaluation = $this->lineItem->getEvaluation();
|
||||
$evaluations = Evaluation::load($lineItemEvaluation->getId());
|
||||
/** @var Evaluation $evaluation */
|
||||
$evaluation = $evaluations[0];
|
||||
|
||||
$lineItemEvaluation->setName($data['label']);
|
||||
|
||||
if (isset($data['resourceId'])) {
|
||||
$this->lineItem->setResourceId($data['resourceId']);
|
||||
}
|
||||
|
||||
if (isset($data['tag'])) {
|
||||
$this->lineItem->setTag($data['tag']);
|
||||
}
|
||||
|
||||
if (!empty($data['startDateTime'])) {
|
||||
$startDate = new DateTime($data['startDateTime']);
|
||||
$this->lineItem->setStartDate($startDate);
|
||||
}
|
||||
|
||||
if (!empty($data['endDateTime'])) {
|
||||
$endDate = new DateTime($data['endDateTime']);
|
||||
$this->lineItem->setEndDate($endDate);
|
||||
}
|
||||
|
||||
if (!$evaluation->has_results()) {
|
||||
$lineItemEvaluation->setMax($data['scoreMaximum']);
|
||||
}
|
||||
|
||||
$em = Database::getManager();
|
||||
$em->persist($this->lineItem);
|
||||
$em->persist($lineItemEvaluation);
|
||||
|
||||
$em->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws OptimisticLockException
|
||||
*/
|
||||
private function processDelete()
|
||||
{
|
||||
$this->deleteLineItem();
|
||||
|
||||
$this->response->setStatusCode(Response::HTTP_NO_CONTENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws OptimisticLockException
|
||||
*/
|
||||
private function deleteLineItem()
|
||||
{
|
||||
$lineItemEvaluation = $this->lineItem->getEvaluation();
|
||||
$evaluations = Evaluation::load($lineItemEvaluation->getId());
|
||||
|
||||
/** @var Evaluation $evaluation */
|
||||
$evaluation = $evaluations[0];
|
||||
|
||||
$em = Database::getManager();
|
||||
|
||||
$em->remove($this->lineItem);
|
||||
$em->flush();
|
||||
|
||||
$evaluation->delete_with_results();
|
||||
}
|
||||
}
|
||||
265
plugin/ims_lti/src/Service/Resource/LtiLineItemsResource.php
Normal file
265
plugin/ims_lti/src/Service/Resource/LtiLineItemsResource.php
Normal file
@@ -0,0 +1,265 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\LineItem;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\ORM\OptimisticLockException;
|
||||
use Doctrine\ORM\ORMException;
|
||||
use Doctrine\ORM\TransactionRequiredException;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
|
||||
|
||||
/**
|
||||
* Class LtiLineItemsResource.
|
||||
*/
|
||||
class LtiLineItemsResource extends LtiAdvantageServiceResource
|
||||
{
|
||||
const URL_TEMPLATE = '/context_id/lineitems';
|
||||
|
||||
public function validate()
|
||||
{
|
||||
if (!$this->course) {
|
||||
throw new BadRequestHttpException('Course not found.');
|
||||
}
|
||||
|
||||
if (!$this->tool) {
|
||||
throw new BadRequestHttpException('Tool not found.');
|
||||
}
|
||||
|
||||
if ($this->tool->getCourse()->getId() !== $this->course->getId()) {
|
||||
throw new AccessDeniedHttpException('Tool not found in course.');
|
||||
}
|
||||
|
||||
$isMediaTypeAllowed = in_array(
|
||||
$this->request->server->get('HTTP_ACCEPT'),
|
||||
[
|
||||
LtiAssignmentGradesService::TYPE_LINE_ITEM_CONTAINER,
|
||||
LtiAssignmentGradesService::TYPE_LINE_ITEM,
|
||||
]
|
||||
);
|
||||
|
||||
if (!$isMediaTypeAllowed) {
|
||||
throw new UnsupportedMediaTypeHttpException('Unsupported media type.');
|
||||
}
|
||||
|
||||
$parentTool = $this->tool->getParent();
|
||||
|
||||
if ($parentTool) {
|
||||
$advServices = $parentTool->getAdvantageServices();
|
||||
|
||||
if (LtiAssignmentGradesService::AGS_NONE === $advServices['ags']) {
|
||||
throw new AccessDeniedHttpException('Assigment and grade service is not enabled for this tool.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ORMException
|
||||
* @throws OptimisticLockException
|
||||
* @throws TransactionRequiredException
|
||||
* @throws MethodNotAllowedHttpException
|
||||
*/
|
||||
public function process()
|
||||
{
|
||||
switch ($this->request->getMethod()) {
|
||||
case Request::METHOD_POST:
|
||||
if (LtiAssignmentGradesService::AGS_FULL !== $this->tool->getAdvantageServices()['ags']) {
|
||||
throw new MethodNotAllowedHttpException([Request::METHOD_GET]);
|
||||
}
|
||||
|
||||
$this->validateToken(
|
||||
[
|
||||
LtiAssignmentGradesService::SCOPE_LINE_ITEM,
|
||||
]
|
||||
);
|
||||
$this->processPost();
|
||||
break;
|
||||
case Request::METHOD_GET:
|
||||
$this->validateToken(
|
||||
[
|
||||
LtiAssignmentGradesService::SCOPE_LINE_ITEM,
|
||||
LtiAssignmentGradesService::SCOPE_LINE_ITEM_READ,
|
||||
]
|
||||
);
|
||||
$this->processGet();
|
||||
break;
|
||||
default:
|
||||
throw new MethodNotAllowedHttpException([Request::METHOD_GET, Request::METHOD_POST]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ORMException
|
||||
* @throws OptimisticLockException
|
||||
* @throws TransactionRequiredException
|
||||
*
|
||||
* @return LineItem
|
||||
*/
|
||||
public function createLineItem(array $data)
|
||||
{
|
||||
$caterories = Category::load(null, null, $this->course->getCode());
|
||||
/** @var Category $gradebookCategory */
|
||||
$gradebookCategory = $caterories[0];
|
||||
|
||||
$em = Database::getManager();
|
||||
|
||||
$userId = 1;
|
||||
|
||||
$eval = new Evaluation();
|
||||
$eval->set_user_id($userId);
|
||||
$eval->set_category_id($gradebookCategory->get_id());
|
||||
$eval->set_course_code($this->tool->getCourse()->getCode());
|
||||
$eval->set_name($data['label']);
|
||||
$eval->set_description(null);
|
||||
$eval->set_weight(
|
||||
$gradebookCategory->getRemainingWeight()
|
||||
);
|
||||
$eval->set_max($data['scoreMaximum']);
|
||||
$eval->set_visible(1);
|
||||
$eval->add();
|
||||
|
||||
$evaluation = $em->find('ChamiloCoreBundle:GradebookEvaluation', $eval->get_id());
|
||||
|
||||
$lineItem = new LineItem();
|
||||
$lineItem
|
||||
->setTool($this->tool)
|
||||
->setEvaluation($evaluation)
|
||||
->setResourceId(!empty($data['resourceId']) ? $data['resourceId'] : null)
|
||||
->setTag(!empty($data['tag']) ? $data['tag'] : null);
|
||||
|
||||
if (!empty($data['startDateTime'])) {
|
||||
$startDate = new DateTime($data['startDateTime']);
|
||||
$lineItem->setStartDate($startDate);
|
||||
}
|
||||
|
||||
if (!empty($data['endDateTime'])) {
|
||||
$endDate = new DateTime($data['endDateTime']);
|
||||
$lineItem->setEndDate($endDate);
|
||||
}
|
||||
|
||||
$em->persist($lineItem);
|
||||
$em->flush();
|
||||
|
||||
return $lineItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ORMException
|
||||
* @throws OptimisticLockException
|
||||
* @throws TransactionRequiredException
|
||||
*/
|
||||
private function processPost()
|
||||
{
|
||||
$data = json_decode($this->request->getContent(), true);
|
||||
|
||||
if (empty($data) || empty($data['label']) || empty($data['scoreMaximum'])) {
|
||||
throw new BadRequestHttpException('Missing data to create line item.');
|
||||
}
|
||||
|
||||
$lineItem = $this->createLineItem($data);
|
||||
|
||||
$data['id'] = LtiAssignmentGradesService::getLineItemUrl(
|
||||
$this->course->getId(),
|
||||
$lineItem->getId(),
|
||||
$this->tool->getId()
|
||||
);
|
||||
|
||||
$this->response->headers->set('Content-Type', LtiAssignmentGradesService::TYPE_LINE_ITEM);
|
||||
$this->response->setEncodingOptions(JSON_UNESCAPED_SLASHES);
|
||||
$this->response->setStatusCode(Response::HTTP_CREATED);
|
||||
$this->response->setData($data);
|
||||
}
|
||||
|
||||
private function processGet()
|
||||
{
|
||||
$resourceLinkId = $this->request->query->get('resource_link_id');
|
||||
$resourceId = $this->request->query->get('resource_id');
|
||||
$tag = $this->request->query->get('tag');
|
||||
$limit = $this->request->query->get('limit');
|
||||
$page = $this->request->query->get('page');
|
||||
|
||||
$lineItems = $this->tool->getLineItems($resourceLinkId, $resourceId, $tag, $limit, $page);
|
||||
|
||||
$this->setLinkHeaderToGet($lineItems, $resourceLinkId, $resourceId, $tag, $limit, $page);
|
||||
|
||||
$data = $this->getGetData($lineItems);
|
||||
|
||||
$this->response->headers->set('Content-Type', LtiAssignmentGradesService::TYPE_LINE_ITEM);
|
||||
$this->response->setData($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $resourceLinkId
|
||||
* @param int $resourceId
|
||||
* @param int $tag
|
||||
* @param int $limit
|
||||
* @param int $page
|
||||
*/
|
||||
private function setLinkHeaderToGet(ArrayCollection $lineItems, $resourceLinkId, $resourceId, $tag, $limit, $page)
|
||||
{
|
||||
if (!$limit) {
|
||||
return;
|
||||
}
|
||||
|
||||
$links = [];
|
||||
|
||||
$links['first'] = 0;
|
||||
$links['last'] = ceil($lineItems->count() / $limit);
|
||||
$links['canonical'] = $page;
|
||||
|
||||
if ($page > 1) {
|
||||
$links['prev'] = $page - 1;
|
||||
}
|
||||
|
||||
if ($page + 1 <= $lineItems->count() / $limit) {
|
||||
$links['next'] = $page + 1;
|
||||
}
|
||||
|
||||
foreach ($links as $rel => $linkPage) {
|
||||
$url = LtiAssignmentGradesService::getLineItemsUrl(
|
||||
$this->course->getId(),
|
||||
$this->tool->getId(),
|
||||
[
|
||||
'resource_link_id' => $resourceLinkId,
|
||||
'resource_id' => $resourceId,
|
||||
'tag' => $tag,
|
||||
'limit' => $limit,
|
||||
'page' => $linkPage,
|
||||
]
|
||||
);
|
||||
|
||||
$links[$rel] = '<'.$url.'>; rel="'.$rel.'"';
|
||||
}
|
||||
|
||||
$this->response->headers->set(
|
||||
'Link',
|
||||
implode(', ', $links)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getGetData(ArrayCollection $lineItems)
|
||||
{
|
||||
$data = [];
|
||||
|
||||
/** @var LineItem $lineItem */
|
||||
foreach ($lineItems as $lineItem) {
|
||||
$item = $lineItem->toArray();
|
||||
$item['id'] = LtiAssignmentGradesService::getLineItemUrl(
|
||||
$this->course->getId(),
|
||||
$lineItem->getId(),
|
||||
$this->tool->getId()
|
||||
);
|
||||
|
||||
$data[] = $item;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
235
plugin/ims_lti/src/Service/Resource/LtiResultsResource.php
Normal file
235
plugin/ims_lti/src/Service/Resource/LtiResultsResource.php
Normal file
@@ -0,0 +1,235 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\CoreBundle\Entity\GradebookResult;
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\LineItem;
|
||||
use Doctrine\ORM\OptimisticLockException;
|
||||
use Doctrine\ORM\ORMException;
|
||||
use Doctrine\ORM\TransactionRequiredException;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
|
||||
|
||||
/**
|
||||
* Class LtiResultsResource.
|
||||
*/
|
||||
class LtiResultsResource extends LtiAdvantageServiceResource
|
||||
{
|
||||
const URL_TEMPLATE = '/context_id/lineitems/line_item_id/results';
|
||||
|
||||
/**
|
||||
* @var LineItem|null
|
||||
*/
|
||||
private $lineItem;
|
||||
|
||||
/**
|
||||
* LtiResultsResource constructor.
|
||||
*
|
||||
* @param int $toolId
|
||||
* @param int $courseId
|
||||
* @param int $lineItemId
|
||||
*
|
||||
* @throws ORMException
|
||||
* @throws OptimisticLockException
|
||||
* @throws TransactionRequiredException
|
||||
*/
|
||||
public function __construct($toolId, $courseId, $lineItemId)
|
||||
{
|
||||
parent::__construct($toolId, $courseId);
|
||||
|
||||
$this->lineItem = Database::getManager()->find('ChamiloPluginBundle:ImsLti\LineItem', (int) $lineItemId);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function validate()
|
||||
{
|
||||
if (!$this->course) {
|
||||
throw new BadRequestHttpException('Course not found.');
|
||||
}
|
||||
|
||||
if (!$this->tool) {
|
||||
throw new BadRequestHttpException('Tool not found.');
|
||||
}
|
||||
|
||||
if ($this->tool->getCourse()->getId() !== $this->course->getId()) {
|
||||
throw new AccessDeniedHttpException('Tool not found in course.');
|
||||
}
|
||||
|
||||
if ($this->request->server->get('HTTP_ACCEPT') !== LtiAssignmentGradesService::TYPE_RESULT_CONTAINER) {
|
||||
throw new UnsupportedMediaTypeHttpException('Unsupported media type.');
|
||||
}
|
||||
|
||||
$parentTool = $this->tool->getParent();
|
||||
|
||||
if ($parentTool) {
|
||||
$advServices = $parentTool->getAdvantageServices();
|
||||
|
||||
if (LtiAssignmentGradesService::AGS_NONE === $advServices['ags']) {
|
||||
throw new AccessDeniedHttpException('Assigment and grade service is not enabled for this tool.');
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->lineItem) {
|
||||
throw new NotFoundHttpException('Line item not found');
|
||||
}
|
||||
|
||||
if ($this->lineItem->getTool()->getId() !== $this->tool->getId()) {
|
||||
throw new AccessDeniedHttpException('Line item not found for the tool.');
|
||||
}
|
||||
}
|
||||
|
||||
public function process()
|
||||
{
|
||||
switch ($this->request->getMethod()) {
|
||||
case Request::METHOD_GET:
|
||||
$this->validateToken(
|
||||
[LtiAssignmentGradesService::SCOPE_RESULT_READ]
|
||||
);
|
||||
$this->processGet();
|
||||
break;
|
||||
default:
|
||||
throw new MethodNotAllowedHttpException([Request::METHOD_GET]);
|
||||
}
|
||||
}
|
||||
|
||||
private function processGet()
|
||||
{
|
||||
$limit = $this->request->query->get('limit');
|
||||
$page = $this->request->query->get('page');
|
||||
$userId = $this->request->query->get('user_id');
|
||||
|
||||
$results = $this->getResults($limit, $userId, $page);
|
||||
|
||||
$data = $this->getGetData($results);
|
||||
|
||||
$this->setLinkHeaderToGet($userId, $limit, $page);
|
||||
|
||||
$this->response->headers->set('Content-Type', LtiAssignmentGradesService::TYPE_RESULT_CONTAINER);
|
||||
$this->response->setData($data);
|
||||
}
|
||||
|
||||
private function getResults($limit, $userId, $page = 0)
|
||||
{
|
||||
$em = Database::getManager();
|
||||
|
||||
$limit = (int) $limit;
|
||||
$page = (int) $page;
|
||||
|
||||
$dql = 'SELECT r FROM ChamiloCoreBundle:GradebookResult r WHERE r.evaluationId = :id';
|
||||
$parameters = ['id' => $this->lineItem->getEvaluation()->getId()];
|
||||
|
||||
if ($userId) {
|
||||
$dql .= ' AND r.userId = :user';
|
||||
$parameters['user'] = (int) $userId;
|
||||
}
|
||||
|
||||
$query = $em->createQuery($dql);
|
||||
|
||||
if ($limit > 0) {
|
||||
$query->setMaxResults($limit);
|
||||
|
||||
if ($page > 0) {
|
||||
$query->setFirstResult($page * $limit);
|
||||
}
|
||||
}
|
||||
|
||||
return $query
|
||||
->setParameters($parameters)
|
||||
->getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|GradebookResult[] $results
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getGetData(array $results)
|
||||
{
|
||||
$data = [];
|
||||
|
||||
foreach ($results as $result) {
|
||||
$lineItemEndPoint = LtiAssignmentGradesService::getLineItemUrl(
|
||||
$this->course->getId(),
|
||||
$this->lineItem->getId(),
|
||||
$this->tool->getId()
|
||||
);
|
||||
|
||||
$data[] = [
|
||||
'id' => "$lineItemEndPoint/results/{$result->getId()}",
|
||||
'scoreOf' => $lineItemEndPoint,
|
||||
'userId' => (string) $result->getUserId(),
|
||||
'resultScore' => $result->getScore(),
|
||||
'resultMaximum' => $this->lineItem->getEvaluation()->getMax(),
|
||||
'comment' => null,
|
||||
];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $userId
|
||||
* @param int $limit
|
||||
* @param int $page
|
||||
*
|
||||
* @throws \Doctrine\ORM\Query\QueryException
|
||||
*/
|
||||
private function setLinkHeaderToGet($userId, $limit, $page = 0)
|
||||
{
|
||||
$limit = (int) $limit;
|
||||
$page = (int) $page;
|
||||
|
||||
if (!$limit) {
|
||||
return;
|
||||
}
|
||||
|
||||
$em = Database::getManager();
|
||||
|
||||
$dql = 'SELECT COUNT(r) FROM ChamiloCoreBundle:GradebookResult r WHERE r.evaluationId = :id';
|
||||
$parameters = ['id' => $this->lineItem->getEvaluation()->getId()];
|
||||
|
||||
if ($userId) {
|
||||
$dql .= ' AND r.userId = :user';
|
||||
$parameters['user'] = (int) $userId;
|
||||
}
|
||||
|
||||
$count = $em
|
||||
->createQuery($dql)
|
||||
->setParameters($parameters)
|
||||
->getSingleScalarResult();
|
||||
|
||||
$links = [];
|
||||
$links['first'] = 0;
|
||||
$links['last'] = ceil($count / $limit);
|
||||
$links['canonical'] = $page;
|
||||
|
||||
if ($page > 1) {
|
||||
$links['prev'] = $page - 1;
|
||||
}
|
||||
|
||||
if ($page + 1 < $links['last']) {
|
||||
$links['next'] = $page + 1;
|
||||
}
|
||||
|
||||
foreach ($links as $rel => $linkPage) {
|
||||
$url = LtiAssignmentGradesService::getResultsUrl(
|
||||
$this->course->getId(),
|
||||
$this->lineItem->getId(),
|
||||
$this->tool->getId(),
|
||||
['user_id' => $userId, 'limit' => $limit, 'page' => $linkPage]
|
||||
);
|
||||
|
||||
$links[$rel] = '<'.$url.'>; rel="'.$rel.'"';
|
||||
}
|
||||
|
||||
$this->response->headers->set(
|
||||
'Link',
|
||||
implode(', ', $links)
|
||||
);
|
||||
}
|
||||
}
|
||||
214
plugin/ims_lti/src/Service/Resource/LtiScoresResource.php
Normal file
214
plugin/ims_lti/src/Service/Resource/LtiScoresResource.php
Normal file
@@ -0,0 +1,214 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\CoreBundle\Entity\GradebookResult;
|
||||
use Chamilo\CoreBundle\Entity\GradebookResultLog;
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\LineItem;
|
||||
use Chamilo\UserBundle\Entity\User;
|
||||
use Doctrine\ORM\OptimisticLockException;
|
||||
use Doctrine\ORM\ORMException;
|
||||
use Doctrine\ORM\TransactionRequiredException;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
|
||||
|
||||
/**
|
||||
* Class LtiScoresResource.
|
||||
*/
|
||||
class LtiScoresResource extends LtiAdvantageServiceResource
|
||||
{
|
||||
const URL_TEMPLATE = '/context_id/lineitems/line_item_id/results';
|
||||
|
||||
const ACTIVITY_INITIALIZED = 'Initialized';
|
||||
const ACTIVITY_STARTED = 'Started';
|
||||
const ACTIVITY_IN_PROGRESS = 'InProgress';
|
||||
const ACTIVITY_SUBMITTED = 'Submitted';
|
||||
const ACTIVITY_COMPLETED = 'Completed';
|
||||
|
||||
const GRADING_FULLY_GRADED = 'FullyGraded';
|
||||
const GRADING_PENDING = 'Pending';
|
||||
const GRADING_PENDING_MANUAL = 'PendingManual';
|
||||
const GRADING_FAILED = 'Failed';
|
||||
const GRADING_NOT_READY = 'NotReady';
|
||||
|
||||
/**
|
||||
* @var LineItem|null
|
||||
*/
|
||||
private $lineItem;
|
||||
|
||||
/**
|
||||
* LtiScoresResource constructor.
|
||||
*
|
||||
* @param int $toolId
|
||||
* @param int $courseId
|
||||
* @param int $lineItemId
|
||||
*
|
||||
* @throws ORMException
|
||||
* @throws OptimisticLockException
|
||||
* @throws TransactionRequiredException
|
||||
*/
|
||||
public function __construct($toolId, $courseId, $lineItemId)
|
||||
{
|
||||
parent::__construct($toolId, $courseId);
|
||||
|
||||
$this->lineItem = Database::getManager()->find('ChamiloPluginBundle:ImsLti\LineItem', (int) $lineItemId);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function validate()
|
||||
{
|
||||
if (!$this->course) {
|
||||
throw new NotFoundHttpException('Course not found.');
|
||||
}
|
||||
|
||||
if (!$this->lineItem) {
|
||||
throw new NotFoundHttpException('Line item not found');
|
||||
}
|
||||
|
||||
if ($this->lineItem->getTool()->getId() !== $this->tool->getId()) {
|
||||
throw new AccessDeniedHttpException('Line item not found for the tool.');
|
||||
}
|
||||
|
||||
if (!$this->tool) {
|
||||
throw new BadRequestHttpException('Tool not found.');
|
||||
}
|
||||
|
||||
if ($this->tool->getCourse()->getId() !== $this->course->getId()) {
|
||||
throw new AccessDeniedHttpException('Tool not found in course.');
|
||||
}
|
||||
|
||||
if ($this->request->server->get('HTTP_ACCEPT') !== LtiAssignmentGradesService::TYPE_SCORE) {
|
||||
throw new UnsupportedMediaTypeHttpException('Unsupported media type.');
|
||||
}
|
||||
|
||||
$parentTool = $this->tool->getParent();
|
||||
|
||||
if ($parentTool) {
|
||||
$advServices = $parentTool->getAdvantageServices();
|
||||
|
||||
if (LtiAssignmentGradesService::AGS_NONE === $advServices['ags']) {
|
||||
throw new AccessDeniedHttpException('Assigment and grade service is not enabled for this tool.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function process()
|
||||
{
|
||||
switch ($this->request->getMethod()) {
|
||||
case Request::METHOD_POST:
|
||||
$this->validateToken(
|
||||
[LtiAssignmentGradesService::SCOPE_SCORE_WRITE]
|
||||
);
|
||||
$this->processPost();
|
||||
break;
|
||||
default:
|
||||
throw new MethodNotAllowedHttpException([Request::METHOD_POST]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function processPost()
|
||||
{
|
||||
$data = json_decode($this->request->getContent(), true);
|
||||
|
||||
if (empty($data) ||
|
||||
!isset($data['userId']) ||
|
||||
!isset($data['gradingProgress']) ||
|
||||
!isset($data['activityProgress']) ||
|
||||
!isset($data['timestamp']) ||
|
||||
(isset($data['timestamp']) && !ImsLti::validateFormatDateIso8601($data['timestamp'])) ||
|
||||
(isset($data['scoreGiven']) && !is_numeric($data['scoreGiven'])) ||
|
||||
(isset($data['scoreGiven']) && !isset($data['scoreMaximum'])) ||
|
||||
(isset($data['scoreMaximum']) && !is_numeric($data['scoreMaximum']))
|
||||
) {
|
||||
throw new BadRequestHttpException('Missing data to create score.');
|
||||
}
|
||||
|
||||
$student = api_get_user_entity($data['userId']);
|
||||
|
||||
if (!$student) {
|
||||
throw new BadRequestHttpException("User (id: {$data['userId']}) not found.");
|
||||
}
|
||||
|
||||
$data['scoreMaximum'] = isset($data['scoreMaximum']) ? $data['scoreMaximum'] : 1;
|
||||
|
||||
$evaluation = $this->lineItem->getEvaluation();
|
||||
|
||||
$result = Database::getManager()
|
||||
->getRepository('ChamiloCoreBundle:GradebookResult')
|
||||
->findOneBy(
|
||||
[
|
||||
'userId' => $data['userId'],
|
||||
'evaluationId' => $evaluation->getId(),
|
||||
]
|
||||
);
|
||||
|
||||
if ($result && $result->getCreatedAt() >= new DateTime($data['timestamp'])) {
|
||||
throw new ConflictHttpException('The timestamp on record is later than the incoming score.');
|
||||
}
|
||||
|
||||
if (isset($data['scoreGiven'])) {
|
||||
if (self::GRADING_FULLY_GRADED !== $data['gradingProgress']) {
|
||||
$data['scoreGiven'] = null;
|
||||
} else {
|
||||
$data['scoreGiven'] = (float) $data['scoreGiven'];
|
||||
|
||||
if ($data['scoreMaximum'] > 0 && $data['scoreMaximum'] != $evaluation->getMax()) {
|
||||
$data['scoreGiven'] = $data['scoreGiven'] * $evaluation->getMax() / $data['scoreMaximum'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$result) {
|
||||
$this->response->setStatusCode(Response::HTTP_CREATED);
|
||||
}
|
||||
|
||||
$this->saveScore($data, $student, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param GradebookResult $result
|
||||
*
|
||||
* @throws OptimisticLockException
|
||||
*/
|
||||
private function saveScore(array $data, User $student, GradebookResult $result = null)
|
||||
{
|
||||
$em = Database::getManager();
|
||||
|
||||
$evaluation = $this->lineItem->getEvaluation();
|
||||
|
||||
if ($result) {
|
||||
$resultLog = new GradebookResultLog();
|
||||
$resultLog
|
||||
->setCreatedAt(api_get_utc_datetime(null, false, true))
|
||||
->setUserId($student->getId())
|
||||
->setEvaluationId($evaluation->getId())
|
||||
->setIdResult($result->getId())
|
||||
->setScore($result->getScore());
|
||||
|
||||
$em->persist($resultLog);
|
||||
} else {
|
||||
$result = new GradebookResult();
|
||||
$result
|
||||
->setUserId($student->getId())
|
||||
->setEvaluationId($evaluation->getId());
|
||||
}
|
||||
|
||||
$result
|
||||
->setCreatedAt(new DateTime($data['timestamp']))
|
||||
->setScore($data['scoreGiven']);
|
||||
|
||||
$em->persist($result);
|
||||
|
||||
$em->flush();
|
||||
}
|
||||
}
|
||||
50
plugin/ims_lti/start.php
Normal file
50
plugin/ims_lti/start.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/* For license terms, see /license.txt */
|
||||
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
|
||||
|
||||
require_once __DIR__.'/../../main/inc/global.inc.php';
|
||||
|
||||
api_protect_course_script();
|
||||
|
||||
$em = Database::getManager();
|
||||
|
||||
/** @var ImsLtiTool $tool */
|
||||
$tool = isset($_GET['id']) ? $em->find('ChamiloPluginBundle:ImsLti\ImsLtiTool', intval($_GET['id'])) : null;
|
||||
$user = api_get_user_entity(
|
||||
api_get_user_id()
|
||||
);
|
||||
|
||||
if (!$tool) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
$imsLtiPlugin = ImsLtiPlugin::create();
|
||||
|
||||
$pageTitle = Security::remove_XSS($tool->getName());
|
||||
$publicKey = ImsLtiPlugin::getToolPublicKey($tool);
|
||||
|
||||
$is1p3 = !empty($publicKey) && !empty($tool->getClientId()) &&
|
||||
!empty($tool->getLoginUrl()) && !empty($tool->getRedirectUrl());
|
||||
|
||||
if ($is1p3) {
|
||||
$launchUrl = api_get_path(WEB_PLUGIN_PATH).'ims_lti/login.php?id='.$tool->getId();
|
||||
} else {
|
||||
$launchUrl = api_get_path(WEB_PLUGIN_PATH).'ims_lti/form.php?'.http_build_query(['id' => $tool->getId()]);
|
||||
}
|
||||
|
||||
if ($tool->getDocumentTarget() == 'window') {
|
||||
header("Location: $launchUrl");
|
||||
exit;
|
||||
}
|
||||
|
||||
$template = new Template($pageTitle);
|
||||
$template->assign('tool', $tool);
|
||||
|
||||
$template->assign('launch_url', $launchUrl);
|
||||
|
||||
$content = $template->fetch('ims_lti/view/start.tpl');
|
||||
|
||||
$template->assign('header', $pageTitle);
|
||||
$template->assign('content', $content);
|
||||
$template->display_one_col_template();
|
||||
72
plugin/ims_lti/token.php
Normal file
72
plugin/ims_lti/token.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\Token;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
$cidReset = true;
|
||||
|
||||
require_once __DIR__.'/../../main/inc/global.inc.php';
|
||||
|
||||
$plugin = ImsLtiPlugin::create();
|
||||
|
||||
$request = Request::createFromGlobals();
|
||||
|
||||
$response = new JsonResponse();
|
||||
|
||||
try {
|
||||
if ($plugin->get('enabled') !== 'true' ||
|
||||
$request->getMethod() !== Request::METHOD_POST ||
|
||||
$request->server->get('CONTENT_TYPE') !== 'application/x-www-form-urlencoded'
|
||||
) {
|
||||
throw new Exception('invalid_request');
|
||||
}
|
||||
|
||||
$clientAssertion = $request->request->get('client_assertion');
|
||||
$clientAssertionType = $request->request->get('client_assertion_type');
|
||||
$grantType = $request->request->get('grant_type');
|
||||
$scope = $request->request->get('scope');
|
||||
|
||||
if ('urn:ietf:params:oauth:client-assertion-type:jwt-bearer' !== $clientAssertionType
|
||||
|| $grantType !== 'client_credentials'
|
||||
) {
|
||||
throw new Exception('unsupported_grant_type');
|
||||
}
|
||||
|
||||
$tokenRequest = new LtiTokenRequest();
|
||||
|
||||
try {
|
||||
$tokenRequest->validateClientAssertion($clientAssertion);
|
||||
$tokenRequest->decodeJwt($clientAssertion);
|
||||
} catch (Exception $exception) {
|
||||
throw new Exception('invalid_client');
|
||||
}
|
||||
|
||||
try {
|
||||
$allowedScopes = $tokenRequest->validateScope($scope);
|
||||
} catch (Exception $exception) {
|
||||
throw new Exception('invalid_scope');
|
||||
}
|
||||
|
||||
$token = $tokenRequest->generateToken($allowedScopes);
|
||||
|
||||
$em = Database::getManager();
|
||||
$em->persist($token);
|
||||
$em->flush();
|
||||
|
||||
$data = [
|
||||
'access_token' => $token->getHash(),
|
||||
'token_type' => 'Bearer',
|
||||
'expires_in' => Token::TOKEN_LIFETIME,
|
||||
'scope' => $token->getScopeInString(),
|
||||
];
|
||||
} catch (Exception $exception) {
|
||||
$response->setStatusCode(Response::HTTP_BAD_REQUEST);
|
||||
|
||||
$data = ['error' => $exception->getMessage()];
|
||||
}
|
||||
|
||||
$response->setData($data);
|
||||
$response->send();
|
||||
66
plugin/ims_lti/tool_settings.php
Normal file
66
plugin/ims_lti/tool_settings.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
$cidReset = true;
|
||||
|
||||
require_once __DIR__.'/../../main/inc/global.inc.php';
|
||||
|
||||
api_block_anonymous_users(false);
|
||||
|
||||
$plugin = ImsLtiPlugin::create();
|
||||
$webPluginPath = api_get_path(WEB_PLUGIN_PATH).'ims_lti/';
|
||||
|
||||
$request = Request::createFromGlobals();
|
||||
$response = new Response();
|
||||
|
||||
$em = Database::getManager();
|
||||
|
||||
try {
|
||||
if ($plugin->get('enabled') !== 'true') {
|
||||
throw new Exception(get_lang('Forbidden'));
|
||||
}
|
||||
|
||||
/** @var ImsLtiTool $tool */
|
||||
$tool = $em->find('ChamiloPluginBundle:ImsLti\ImsLtiTool', $request->query->get('id'));
|
||||
|
||||
if (!$tool) {
|
||||
throw new Exception($plugin->get_lang('NoTool'));
|
||||
}
|
||||
|
||||
$html = '<div class="row">'
|
||||
.'<div class="col-xs-5 text-right"><strong>'.$plugin->get_lang('PlatformId').'</strong></div>'
|
||||
.'<div class="col-xs-7">'.ImsLtiPlugin::getIssuerUrl().'</div>'
|
||||
.'</div>'
|
||||
.'<div class="row">'
|
||||
.'<div class="col-xs-5 text-right"><strong>'.$plugin->get_lang('DeploymentId').'</strong></div>'
|
||||
.'<div class="col-xs-7">'.($tool->getParent() ? $tool->getParent()->getId() : $tool->getId()).'</div>'
|
||||
.'</div>'
|
||||
.'<div class="row">'
|
||||
.'<div class="col-xs-5 text-right"><strong>'.$plugin->get_lang('ClientId').'</strong></div>'
|
||||
.'<div class="col-xs-7">'.$tool->getClientId().'</div>'
|
||||
.'</div>'
|
||||
.'<div class="row">'
|
||||
.'<div class="col-xs-5 text-right"><strong>'.$plugin->get_lang('AuthUrl').'</strong></div>'
|
||||
.'<div class="col-xs-7">'.$webPluginPath.'auth.php</div>'
|
||||
.'</div>'
|
||||
.'<div class="row">'
|
||||
.'<div class="col-xs-5 text-right"><strong>'.$plugin->get_lang('TokenUrl').'</strong></div>'
|
||||
.'<div class="col-xs-7">'.$webPluginPath.'token.php</div>'
|
||||
.'</div>'
|
||||
.'<div class="row">'
|
||||
.'<div class="col-xs-5 text-right"><strong>'.$plugin->get_lang('KeySetUrl').'</strong></div>'
|
||||
.'<div class="col-xs-7">'.$webPluginPath.'jwks.php</div>'
|
||||
.'</div>';
|
||||
|
||||
$response->setContent($html);
|
||||
} catch (Exception $exception) {
|
||||
$response->setContent(
|
||||
Display::return_message($exception->getMessage(), 'error')
|
||||
);
|
||||
}
|
||||
|
||||
$response->send();
|
||||
13
plugin/ims_lti/uninstall.php
Normal file
13
plugin/ims_lti/uninstall.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
/* For license terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* Uninstall the MSI/LTI Plugin.
|
||||
*
|
||||
* @package chamilo.plugin.ims_lti
|
||||
*/
|
||||
if (!api_is_platform_admin()) {
|
||||
exit('You must have admin permissions to uninstall plugins');
|
||||
}
|
||||
|
||||
ImsLtiPlugin::create()->uninstall();
|
||||
42
plugin/ims_lti/vendor/oauth1/.gitignore
vendored
Normal file
42
plugin/ims_lti/vendor/oauth1/.gitignore
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
|
||||
# Created by https://www.gitignore.io/api/osx,svn,composer
|
||||
|
||||
### OSX ###
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
|
||||
### SVN ###
|
||||
.svn/
|
||||
|
||||
|
||||
### Composer ###
|
||||
composer.phar
|
||||
/vendor/
|
||||
|
||||
# Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file
|
||||
# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
|
||||
# composer.lock
|
||||
44
plugin/ims_lti/vendor/oauth1/CHANGELOG.md
vendored
Normal file
44
plugin/ims_lti/vendor/oauth1/CHANGELOG.md
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
### 2016.02.10
|
||||
----
|
||||
* Added composer.json
|
||||
* Separated classes to separate files
|
||||
|
||||
### 2008.08.04
|
||||
----
|
||||
* Added LICENSE.txt file with MIT license, copyright owner is perhaps
|
||||
dubious however.
|
||||
|
||||
### 2008.07.22
|
||||
----
|
||||
* Change to encoding to fix last change to encoding of spaces
|
||||
|
||||
### 2008.07.15
|
||||
----
|
||||
* Another change to encoding per:
|
||||
[http://groups.google.com/group/oauth/browse_thread/thread/d39931d39b4af4bd](http://groups.google.com/group/oauth/browse_thread/thread/d39931d39b4af4bd)
|
||||
* A change to port handling to better deal with https and the like per:
|
||||
[http://groups.google.com/group/oauth/browse_thread/thread/1b203a51d9590226](http://groups.google.com/group/oauth/browse_thread/thread/1b203a51d9590226)
|
||||
* Fixed a small bug per:
|
||||
[https://github.com/jtsternberg/oauth/issues/26](https://github.com/jtsternberg/oauth/issues/26)
|
||||
* Added missing base_string debug info when using RSA-SHA1
|
||||
* Increased size of example endpoint input field and added note about
|
||||
query strings
|
||||
|
||||
### 2009-2011.03.28
|
||||
----
|
||||
* Heaps of bug-fixes
|
||||
* Introduction of PHPUnit testcases (which aided in above mentioned bug-fixes)
|
||||
* Added support Revision A auth flows.
|
||||
* Possibly more, I've lost track..
|
||||
|
||||
### 2001.03.29
|
||||
----
|
||||
* Fixed issue with hosts not being normalized correctly
|
||||
[http://tools.ietf.org/html/rfc5849#section-3.4.1.2](http://tools.ietf.org/html/rfc5849#section-3.4.1.2)
|
||||
[https://github.com/jtsternberg/oauth/issues/176](https://github.com/jtsternberg/oauth/issues/176)
|
||||
[https://github.com/jtsternberg/oauth/issues/187](https://github.com/jtsternberg/oauth/issues/187)
|
||||
* Changed signature comparing to be timing insensitive
|
||||
[https://github.com/jtsternberg/oauth/issues/178](https://github.com/jtsternberg/oauth/issues/178)
|
||||
* Fixed issue with Host header on some servers on non-standard port includes the port-number
|
||||
[https://github.com/jtsternberg/oauth/issues/170](https://github.com/jtsternberg/oauth/issues/170)
|
||||
[https://github.com/jtsternberg/oauth/issues/192](https://github.com/jtsternberg/oauth/issues/192)
|
||||
22
plugin/ims_lti/vendor/oauth1/LICENSE.txt
vendored
Normal file
22
plugin/ims_lti/vendor/oauth1/LICENSE.txt
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2007 Andy Smith
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
6
plugin/ims_lti/vendor/oauth1/README.md
vendored
Normal file
6
plugin/ims_lti/vendor/oauth1/README.md
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
# OAuth1 PHP
|
||||
[Andy Smith](http://term.ie/)'s basic php library for OAuth. Original code taken from google code. For reference (and since google code is going away), I have ported the entire project to github. [You can find it here](https://github.com/jtsternberg/oauth).
|
||||
|
||||
To use this, be sure to run `composer install` in the root of the lib.
|
||||
|
||||
I have also split out the spec portion of the google code repo to [its own repo](https://github.com/jtsternberg/oauth-spec).
|
||||
16
plugin/ims_lti/vendor/oauth1/code/OAuthConsumer.php
vendored
Normal file
16
plugin/ims_lti/vendor/oauth1/code/OAuthConsumer.php
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
class OAuthConsumer {
|
||||
public $key;
|
||||
public $secret;
|
||||
|
||||
function __construct($key, $secret, $callback_url=NULL) {
|
||||
$this->key = $key;
|
||||
$this->secret = $secret;
|
||||
$this->callback_url = $callback_url;
|
||||
}
|
||||
|
||||
function __toString() {
|
||||
return "OAuthConsumer[key=$this->key,secret=$this->secret]";
|
||||
}
|
||||
}
|
||||
27
plugin/ims_lti/vendor/oauth1/code/OAuthDataStore.php
vendored
Normal file
27
plugin/ims_lti/vendor/oauth1/code/OAuthDataStore.php
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
class OAuthDataStore {
|
||||
function lookup_consumer($consumer_key) {
|
||||
// implement me
|
||||
}
|
||||
|
||||
function lookup_token($consumer, $token_type, $token) {
|
||||
// implement me
|
||||
}
|
||||
|
||||
function lookup_nonce($consumer, $token, $nonce, $timestamp) {
|
||||
// implement me
|
||||
}
|
||||
|
||||
function new_request_token($consumer, $callback = null) {
|
||||
// return a new token attached to this consumer
|
||||
}
|
||||
|
||||
function new_access_token($token, $consumer, $verifier = null) {
|
||||
// return a new access token attached to this consumer
|
||||
// for the user associated with this token if the request token
|
||||
// is authorized
|
||||
// should also invalidate the request token
|
||||
}
|
||||
|
||||
}
|
||||
7
plugin/ims_lti/vendor/oauth1/code/OAuthException.php
vendored
Normal file
7
plugin/ims_lti/vendor/oauth1/code/OAuthException.php
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
/* Generic exception class
|
||||
*/
|
||||
class OAuthException extends Exception {
|
||||
// pass
|
||||
}
|
||||
260
plugin/ims_lti/vendor/oauth1/code/OAuthRequest.php
vendored
Normal file
260
plugin/ims_lti/vendor/oauth1/code/OAuthRequest.php
vendored
Normal file
@@ -0,0 +1,260 @@
|
||||
<?php
|
||||
class OAuthRequest {
|
||||
protected $parameters;
|
||||
protected $http_method;
|
||||
protected $http_url;
|
||||
// for debug purposes
|
||||
public $base_string;
|
||||
public static $version = '1.0';
|
||||
public static $POST_INPUT = 'php://input';
|
||||
|
||||
function __construct($http_method, $http_url, $parameters=NULL) {
|
||||
$parameters = ($parameters) ? $parameters : array();
|
||||
$parameters = array_merge( OAuthUtil::parse_parameters(parse_url($http_url, PHP_URL_QUERY)), $parameters);
|
||||
$this->parameters = $parameters;
|
||||
$this->http_method = $http_method;
|
||||
$this->http_url = $http_url;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* attempt to build up a request from what was passed to the server
|
||||
*/
|
||||
public static function from_request($http_method=NULL, $http_url=NULL, $parameters=NULL) {
|
||||
$scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on")
|
||||
? 'http'
|
||||
: 'https';
|
||||
$http_url = ($http_url) ? $http_url : $scheme .
|
||||
'://' . $_SERVER['SERVER_NAME'] .
|
||||
':' .
|
||||
$_SERVER['SERVER_PORT'] .
|
||||
$_SERVER['REQUEST_URI'];
|
||||
$http_method = ($http_method) ? $http_method : $_SERVER['REQUEST_METHOD'];
|
||||
|
||||
// We weren't handed any parameters, so let's find the ones relevant to
|
||||
// this request.
|
||||
// If you run XML-RPC or similar you should use this to provide your own
|
||||
// parsed parameter-list
|
||||
if (!$parameters) {
|
||||
// Find request headers
|
||||
$request_headers = OAuthUtil::get_headers();
|
||||
|
||||
// Parse the query-string to find GET parameters
|
||||
$parameters = OAuthUtil::parse_parameters($_SERVER['QUERY_STRING']);
|
||||
|
||||
// It's a POST request of the proper content-type, so parse POST
|
||||
// parameters and add those overriding any duplicates from GET
|
||||
if ($http_method == "POST"
|
||||
&& isset($request_headers['Content-Type'])
|
||||
&& strstr($request_headers['Content-Type'],
|
||||
'application/x-www-form-urlencoded')
|
||||
) {
|
||||
$post_data = OAuthUtil::parse_parameters(
|
||||
file_get_contents(self::$POST_INPUT)
|
||||
);
|
||||
$parameters = array_merge($parameters, $post_data);
|
||||
}
|
||||
|
||||
// We have a Authorization-header with OAuth data. Parse the header
|
||||
// and add those overriding any duplicates from GET or POST
|
||||
if (isset($request_headers['Authorization']) && substr($request_headers['Authorization'], 0, 6) == 'OAuth ') {
|
||||
$header_parameters = OAuthUtil::split_header(
|
||||
$request_headers['Authorization']
|
||||
);
|
||||
$parameters = array_merge($parameters, $header_parameters);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return new OAuthRequest($http_method, $http_url, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* pretty much a helper function to set up the request
|
||||
*/
|
||||
public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters=NULL) {
|
||||
$parameters = ($parameters) ? $parameters : array();
|
||||
$defaults = array("oauth_version" => OAuthRequest::$version,
|
||||
"oauth_nonce" => OAuthRequest::generate_nonce(),
|
||||
"oauth_timestamp" => OAuthRequest::generate_timestamp(),
|
||||
"oauth_consumer_key" => $consumer->key);
|
||||
if ($token)
|
||||
$defaults['oauth_token'] = $token->key;
|
||||
|
||||
$parameters = array_merge($defaults, $parameters);
|
||||
|
||||
return new OAuthRequest($http_method, $http_url, $parameters);
|
||||
}
|
||||
|
||||
public function set_parameter($name, $value, $allow_duplicates = true) {
|
||||
if ($allow_duplicates && isset($this->parameters[$name])) {
|
||||
// We have already added parameter(s) with this name, so add to the list
|
||||
if (is_scalar($this->parameters[$name])) {
|
||||
// This is the first duplicate, so transform scalar (string)
|
||||
// into an array so we can add the duplicates
|
||||
$this->parameters[$name] = array($this->parameters[$name]);
|
||||
}
|
||||
|
||||
$this->parameters[$name][] = $value;
|
||||
} else {
|
||||
$this->parameters[$name] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
public function get_parameter($name) {
|
||||
return isset($this->parameters[$name]) ? $this->parameters[$name] : null;
|
||||
}
|
||||
|
||||
public function get_parameters() {
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
public function unset_parameter($name) {
|
||||
unset($this->parameters[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* The request parameters, sorted and concatenated into a normalized string.
|
||||
* @return string
|
||||
*/
|
||||
public function get_signable_parameters() {
|
||||
// Grab all parameters
|
||||
$params = $this->parameters;
|
||||
|
||||
// Remove oauth_signature if present
|
||||
// Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.")
|
||||
if (isset($params['oauth_signature'])) {
|
||||
unset($params['oauth_signature']);
|
||||
}
|
||||
|
||||
return OAuthUtil::build_http_query($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base string of this request
|
||||
*
|
||||
* The base string defined as the method, the url
|
||||
* and the parameters (normalized), each urlencoded
|
||||
* and the concated with &.
|
||||
*/
|
||||
public function get_signature_base_string() {
|
||||
$parts = array(
|
||||
$this->get_normalized_http_method(),
|
||||
$this->get_normalized_http_url(),
|
||||
$this->get_signable_parameters()
|
||||
);
|
||||
|
||||
$parts = OAuthUtil::urlencode_rfc3986($parts);
|
||||
|
||||
return implode('&', $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* just uppercases the http method
|
||||
*/
|
||||
public function get_normalized_http_method() {
|
||||
return strtoupper($this->http_method);
|
||||
}
|
||||
|
||||
/**
|
||||
* parses the url and rebuilds it to be
|
||||
* scheme://host/path
|
||||
*/
|
||||
public function get_normalized_http_url() {
|
||||
$parts = parse_url($this->http_url);
|
||||
|
||||
$scheme = (isset($parts['scheme'])) ? $parts['scheme'] : 'http';
|
||||
$port = (isset($parts['port'])) ? $parts['port'] : (($scheme == 'https') ? '443' : '80');
|
||||
$host = (isset($parts['host'])) ? strtolower($parts['host']) : '';
|
||||
$path = (isset($parts['path'])) ? $parts['path'] : '';
|
||||
|
||||
if (($scheme == 'https' && $port != '443')
|
||||
|| ($scheme == 'http' && $port != '80')) {
|
||||
$host = "$host:$port";
|
||||
}
|
||||
return "$scheme://$host$path";
|
||||
}
|
||||
|
||||
/**
|
||||
* builds a url usable for a GET request
|
||||
*/
|
||||
public function to_url() {
|
||||
$post_data = $this->to_postdata();
|
||||
$out = $this->get_normalized_http_url();
|
||||
if ($post_data) {
|
||||
$out .= '?'.$post_data;
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* builds the data one would send in a POST request
|
||||
*/
|
||||
public function to_postdata() {
|
||||
return OAuthUtil::build_http_query($this->parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* builds the Authorization: header
|
||||
*/
|
||||
public function to_header($realm=null) {
|
||||
$first = true;
|
||||
if($realm) {
|
||||
$out = 'Authorization: OAuth realm="' . OAuthUtil::urlencode_rfc3986($realm) . '"';
|
||||
$first = false;
|
||||
} else
|
||||
$out = 'Authorization: OAuth';
|
||||
|
||||
$total = array();
|
||||
foreach ($this->parameters as $k => $v) {
|
||||
if (substr($k, 0, 5) != "oauth") continue;
|
||||
if (is_array($v)) {
|
||||
throw new OAuthException('Arrays not supported in headers');
|
||||
}
|
||||
$out .= ($first) ? ' ' : ',';
|
||||
$out .= OAuthUtil::urlencode_rfc3986($k) .
|
||||
'="' .
|
||||
OAuthUtil::urlencode_rfc3986($v) .
|
||||
'"';
|
||||
$first = false;
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
return $this->to_url();
|
||||
}
|
||||
|
||||
|
||||
public function sign_request($signature_method, $consumer, $token) {
|
||||
$this->set_parameter(
|
||||
"oauth_signature_method",
|
||||
$signature_method->get_name(),
|
||||
false
|
||||
);
|
||||
$signature = $this->build_signature($signature_method, $consumer, $token);
|
||||
$this->set_parameter("oauth_signature", $signature, false);
|
||||
}
|
||||
|
||||
public function build_signature($signature_method, $consumer, $token) {
|
||||
$signature = $signature_method->build_signature($this, $consumer, $token);
|
||||
return $signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* util function: current timestamp
|
||||
*/
|
||||
private static function generate_timestamp() {
|
||||
return time();
|
||||
}
|
||||
|
||||
/**
|
||||
* util function: current nonce
|
||||
*/
|
||||
private static function generate_nonce() {
|
||||
$mt = microtime();
|
||||
$rand = mt_rand();
|
||||
|
||||
return md5($mt . $rand); // md5s look nicer than numbers
|
||||
}
|
||||
}
|
||||
223
plugin/ims_lti/vendor/oauth1/code/OAuthServer.php
vendored
Normal file
223
plugin/ims_lti/vendor/oauth1/code/OAuthServer.php
vendored
Normal file
@@ -0,0 +1,223 @@
|
||||
<?php
|
||||
|
||||
class OAuthServer {
|
||||
protected $timestamp_threshold = 300; // in seconds, five minutes
|
||||
protected $version = '1.0'; // hi blaine
|
||||
protected $signature_methods = array();
|
||||
|
||||
protected $data_store;
|
||||
|
||||
function __construct($data_store) {
|
||||
$this->data_store = $data_store;
|
||||
}
|
||||
|
||||
public function add_signature_method($signature_method) {
|
||||
$this->signature_methods[$signature_method->get_name()] =
|
||||
$signature_method;
|
||||
}
|
||||
|
||||
// high level functions
|
||||
|
||||
/**
|
||||
* process a request_token request
|
||||
* returns the request token on success
|
||||
*/
|
||||
public function fetch_request_token(&$request) {
|
||||
$this->get_version($request);
|
||||
|
||||
$consumer = $this->get_consumer($request);
|
||||
|
||||
// no token required for the initial token request
|
||||
$token = NULL;
|
||||
|
||||
$this->check_signature($request, $consumer, $token);
|
||||
|
||||
// Rev A change
|
||||
$callback = $request->get_parameter('oauth_callback');
|
||||
$new_token = $this->data_store->new_request_token($consumer, $callback);
|
||||
|
||||
return $new_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* process an access_token request
|
||||
* returns the access token on success
|
||||
*/
|
||||
public function fetch_access_token(&$request) {
|
||||
$this->get_version($request);
|
||||
|
||||
$consumer = $this->get_consumer($request);
|
||||
|
||||
// requires authorized request token
|
||||
$token = $this->get_token($request, $consumer, "request");
|
||||
|
||||
$this->check_signature($request, $consumer, $token);
|
||||
|
||||
// Rev A change
|
||||
$verifier = $request->get_parameter('oauth_verifier');
|
||||
$new_token = $this->data_store->new_access_token($token, $consumer, $verifier);
|
||||
|
||||
return $new_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* verify an api call, checks all the parameters
|
||||
*/
|
||||
public function verify_request(&$request) {
|
||||
$this->get_version($request);
|
||||
$consumer = $this->get_consumer($request);
|
||||
$token = $this->get_token($request, $consumer, "access");
|
||||
$this->check_signature($request, $consumer, $token);
|
||||
return array($consumer, $token);
|
||||
}
|
||||
|
||||
// Internals from here
|
||||
/**
|
||||
* version 1
|
||||
*/
|
||||
private function get_version(&$request) {
|
||||
$version = $request->get_parameter("oauth_version");
|
||||
if (!$version) {
|
||||
// Service Providers MUST assume the protocol version to be 1.0 if this parameter is not present.
|
||||
// Chapter 7.0 ("Accessing Protected Ressources")
|
||||
$version = '1.0';
|
||||
}
|
||||
if ($version !== $this->version) {
|
||||
throw new OAuthException("OAuth version '$version' not supported");
|
||||
}
|
||||
return $version;
|
||||
}
|
||||
|
||||
/**
|
||||
* figure out the signature with some defaults
|
||||
*/
|
||||
private function get_signature_method($request) {
|
||||
$signature_method = $request instanceof OAuthRequest
|
||||
? $request->get_parameter("oauth_signature_method")
|
||||
: NULL;
|
||||
|
||||
if (!$signature_method) {
|
||||
// According to chapter 7 ("Accessing Protected Ressources") the signature-method
|
||||
// parameter is required, and we can't just fallback to PLAINTEXT
|
||||
throw new OAuthException('No signature method parameter. This parameter is required');
|
||||
}
|
||||
|
||||
if (!in_array($signature_method,
|
||||
array_keys($this->signature_methods))) {
|
||||
throw new OAuthException(
|
||||
"Signature method '$signature_method' not supported " .
|
||||
"try one of the following: " .
|
||||
implode(", ", array_keys($this->signature_methods))
|
||||
);
|
||||
}
|
||||
return $this->signature_methods[$signature_method];
|
||||
}
|
||||
|
||||
/**
|
||||
* try to find the consumer for the provided request's consumer key
|
||||
*/
|
||||
private function get_consumer($request) {
|
||||
$consumer_key = $request instanceof OAuthRequest
|
||||
? $request->get_parameter("oauth_consumer_key")
|
||||
: NULL;
|
||||
|
||||
if (!$consumer_key) {
|
||||
throw new OAuthException("Invalid consumer key");
|
||||
}
|
||||
|
||||
$consumer = $this->data_store->lookup_consumer($consumer_key);
|
||||
if (!$consumer) {
|
||||
throw new OAuthException("Invalid consumer");
|
||||
}
|
||||
|
||||
return $consumer;
|
||||
}
|
||||
|
||||
/**
|
||||
* try to find the token for the provided request's token key
|
||||
*/
|
||||
private function get_token($request, $consumer, $token_type="access") {
|
||||
$token_field = $request instanceof OAuthRequest
|
||||
? $request->get_parameter('oauth_token')
|
||||
: NULL;
|
||||
|
||||
$token = $this->data_store->lookup_token(
|
||||
$consumer, $token_type, $token_field
|
||||
);
|
||||
if (!$token) {
|
||||
throw new OAuthException("Invalid $token_type token: $token_field");
|
||||
}
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* all-in-one function to check the signature on a request
|
||||
* should guess the signature method appropriately
|
||||
*/
|
||||
private function check_signature($request, $consumer, $token) {
|
||||
// this should probably be in a different method
|
||||
$timestamp = $request instanceof OAuthRequest
|
||||
? $request->get_parameter('oauth_timestamp')
|
||||
: NULL;
|
||||
$nonce = $request instanceof OAuthRequest
|
||||
? $request->get_parameter('oauth_nonce')
|
||||
: NULL;
|
||||
|
||||
$this->check_timestamp($timestamp);
|
||||
$this->check_nonce($consumer, $token, $nonce, $timestamp);
|
||||
|
||||
$signature_method = $this->get_signature_method($request);
|
||||
|
||||
$signature = $request->get_parameter('oauth_signature');
|
||||
$valid_sig = $signature_method->check_signature(
|
||||
$request,
|
||||
$consumer,
|
||||
$token,
|
||||
$signature
|
||||
);
|
||||
|
||||
if (!$valid_sig) {
|
||||
throw new OAuthException("Invalid signature");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check that the timestamp is new enough
|
||||
*/
|
||||
private function check_timestamp($timestamp) {
|
||||
if( ! $timestamp )
|
||||
throw new OAuthException(
|
||||
'Missing timestamp parameter. The parameter is required'
|
||||
);
|
||||
|
||||
// verify that timestamp is recentish
|
||||
$now = time();
|
||||
if (abs($now - $timestamp) > $this->timestamp_threshold) {
|
||||
throw new OAuthException(
|
||||
"Expired timestamp, yours $timestamp, ours $now"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check that the nonce is not repeated
|
||||
*/
|
||||
private function check_nonce($consumer, $token, $nonce, $timestamp) {
|
||||
if( ! $nonce )
|
||||
throw new OAuthException(
|
||||
'Missing nonce parameter. The parameter is required'
|
||||
);
|
||||
|
||||
// verify that the nonce is uniqueish
|
||||
$found = $this->data_store->lookup_nonce(
|
||||
$consumer,
|
||||
$token,
|
||||
$nonce,
|
||||
$timestamp
|
||||
);
|
||||
if ($found) {
|
||||
throw new OAuthException("Nonce already used: $nonce");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
55
plugin/ims_lti/vendor/oauth1/code/OAuthSignatureMethod.php
vendored
Normal file
55
plugin/ims_lti/vendor/oauth1/code/OAuthSignatureMethod.php
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* A class for implementing a Signature Method
|
||||
* See section 9 ("Signing Requests") in the spec
|
||||
*/
|
||||
abstract class OAuthSignatureMethod {
|
||||
/**
|
||||
* Needs to return the name of the Signature Method (ie HMAC-SHA1)
|
||||
* @return string
|
||||
*/
|
||||
abstract public function get_name();
|
||||
|
||||
/**
|
||||
* Build up the signature
|
||||
* NOTE: The output of this function MUST NOT be urlencoded.
|
||||
* the encoding is handled in OAuthRequest when the final
|
||||
* request is serialized
|
||||
* @param OAuthRequest $request
|
||||
* @param OAuthConsumer $consumer
|
||||
* @param OAuthToken $token
|
||||
* @return string
|
||||
*/
|
||||
abstract public function build_signature($request, $consumer, $token);
|
||||
|
||||
/**
|
||||
* Verifies that a given signature is correct
|
||||
* @param OAuthRequest $request
|
||||
* @param OAuthConsumer $consumer
|
||||
* @param OAuthToken $token
|
||||
* @param string $signature
|
||||
* @return bool
|
||||
*/
|
||||
public function check_signature($request, $consumer, $token, $signature) {
|
||||
$built = $this->build_signature($request, $consumer, $token);
|
||||
|
||||
// Check for zero length, although unlikely here
|
||||
if (strlen($built) == 0 || strlen($signature) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strlen($built) != strlen($signature)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Avoid a timing leak with a (hopefully) time insensitive compare
|
||||
$result = 0;
|
||||
for ($i = 0; $i < strlen($signature); $i++) {
|
||||
$result |= ord($built[$i]) ^ ord($signature[$i]);
|
||||
}
|
||||
|
||||
return $result == 0;
|
||||
}
|
||||
}
|
||||
|
||||
30
plugin/ims_lti/vendor/oauth1/code/OAuthSignatureMethod_HMAC_SHA1.php
vendored
Normal file
30
plugin/ims_lti/vendor/oauth1/code/OAuthSignatureMethod_HMAC_SHA1.php
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* The HMAC-SHA1 signature method uses the HMAC-SHA1 signature algorithm as
|
||||
* defined in [RFC2104] where the Signature Base String is the text and the
|
||||
* key is the concatenated values (each first encoded per Parameter Encoding)
|
||||
* of the Consumer Secret and Token Secret, separated by an '&' character
|
||||
* (ASCII code 38) even if empty.
|
||||
* - Chapter 9.2 ("HMAC-SHA1")
|
||||
*/
|
||||
class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod {
|
||||
function get_name() {
|
||||
return "HMAC-SHA1";
|
||||
}
|
||||
|
||||
public function build_signature($request, $consumer, $token) {
|
||||
$base_string = $request->get_signature_base_string();
|
||||
$request->base_string = $base_string;
|
||||
|
||||
$key_parts = array(
|
||||
$consumer->secret,
|
||||
($token) ? $token->secret : ""
|
||||
);
|
||||
|
||||
$key_parts = OAuthUtil::urlencode_rfc3986($key_parts);
|
||||
$key = implode('&', $key_parts);
|
||||
|
||||
return base64_encode(hash_hmac('sha1', $base_string, $key, true));
|
||||
}
|
||||
}
|
||||
34
plugin/ims_lti/vendor/oauth1/code/OAuthSignatureMethod_PLAINTEXT.php
vendored
Normal file
34
plugin/ims_lti/vendor/oauth1/code/OAuthSignatureMethod_PLAINTEXT.php
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* The PLAINTEXT method does not provide any security protection and SHOULD only be used
|
||||
* over a secure channel such as HTTPS. It does not use the Signature Base String.
|
||||
* - Chapter 9.4 ("PLAINTEXT")
|
||||
*/
|
||||
class OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod {
|
||||
public function get_name() {
|
||||
return "PLAINTEXT";
|
||||
}
|
||||
|
||||
/**
|
||||
* oauth_signature is set to the concatenated encoded values of the Consumer Secret and
|
||||
* Token Secret, separated by a '&' character (ASCII code 38), even if either secret is
|
||||
* empty. The result MUST be encoded again.
|
||||
* - Chapter 9.4.1 ("Generating Signatures")
|
||||
*
|
||||
* Please note that the second encoding MUST NOT happen in the SignatureMethod, as
|
||||
* OAuthRequest handles this!
|
||||
*/
|
||||
public function build_signature($request, $consumer, $token) {
|
||||
$key_parts = array(
|
||||
$consumer->secret,
|
||||
($token) ? $token->secret : ""
|
||||
);
|
||||
|
||||
$key_parts = OAuthUtil::urlencode_rfc3986($key_parts);
|
||||
$key = implode('&', $key_parts);
|
||||
$request->base_string = $key;
|
||||
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
69
plugin/ims_lti/vendor/oauth1/code/OAuthSignatureMethod_RSA_SHA1.php
vendored
Normal file
69
plugin/ims_lti/vendor/oauth1/code/OAuthSignatureMethod_RSA_SHA1.php
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* The RSA-SHA1 signature method uses the RSASSA-PKCS1-v1_5 signature algorithm as defined in
|
||||
* [RFC3447] section 8.2 (more simply known as PKCS#1), using SHA-1 as the hash function for
|
||||
* EMSA-PKCS1-v1_5. It is assumed that the Consumer has provided its RSA public key in a
|
||||
* verified way to the Service Provider, in a manner which is beyond the scope of this
|
||||
* specification.
|
||||
* - Chapter 9.3 ("RSA-SHA1")
|
||||
*/
|
||||
abstract class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod {
|
||||
public function get_name() {
|
||||
return "RSA-SHA1";
|
||||
}
|
||||
|
||||
// Up to the SP to implement this lookup of keys. Possible ideas are:
|
||||
// (1) do a lookup in a table of trusted certs keyed off of consumer
|
||||
// (2) fetch via http using a url provided by the requester
|
||||
// (3) some sort of specific discovery code based on request
|
||||
//
|
||||
// Either way should return a string representation of the certificate
|
||||
protected abstract function fetch_public_cert(&$request);
|
||||
|
||||
// Up to the SP to implement this lookup of keys. Possible ideas are:
|
||||
// (1) do a lookup in a table of trusted certs keyed off of consumer
|
||||
//
|
||||
// Either way should return a string representation of the certificate
|
||||
protected abstract function fetch_private_cert(&$request);
|
||||
|
||||
public function build_signature($request, $consumer, $token) {
|
||||
$base_string = $request->get_signature_base_string();
|
||||
$request->base_string = $base_string;
|
||||
|
||||
// Fetch the private key cert based on the request
|
||||
$cert = $this->fetch_private_cert($request);
|
||||
|
||||
// Pull the private key ID from the certificate
|
||||
$privatekeyid = openssl_get_privatekey($cert);
|
||||
|
||||
// Sign using the key
|
||||
$ok = openssl_sign($base_string, $signature, $privatekeyid);
|
||||
|
||||
// Release the key resource
|
||||
openssl_free_key($privatekeyid);
|
||||
|
||||
return base64_encode($signature);
|
||||
}
|
||||
|
||||
public function check_signature($request, $consumer, $token, $signature) {
|
||||
$decoded_sig = base64_decode($signature);
|
||||
|
||||
$base_string = $request->get_signature_base_string();
|
||||
|
||||
// Fetch the public key cert based on the request
|
||||
$cert = $this->fetch_public_cert($request);
|
||||
|
||||
// Pull the public key ID from the certificate
|
||||
$publickeyid = openssl_get_publickey($cert);
|
||||
|
||||
// Check the computed signature against the one passed in the query
|
||||
$ok = openssl_verify($base_string, $decoded_sig, $publickeyid);
|
||||
|
||||
// Release the key resource
|
||||
openssl_free_key($publickeyid);
|
||||
|
||||
return $ok == 1;
|
||||
}
|
||||
}
|
||||
|
||||
13
plugin/ims_lti/vendor/oauth1/code/OAuthTests.xml
vendored
Normal file
13
plugin/ims_lti/vendor/oauth1/code/OAuthTests.xml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
<phpunit colors="true">
|
||||
<testsuite name="OAuth">
|
||||
<directory>tests</directory>
|
||||
</testsuite>
|
||||
<filter>
|
||||
<blacklist>
|
||||
<directory>tests</directory>
|
||||
</blacklist>
|
||||
</filter>
|
||||
<logging>
|
||||
<log type="coverage-html" target="./report" charset="UTF-8" yui="true" highlight="false" lowUpperBound="35" highLowerBound="70"/>
|
||||
</logging>
|
||||
</phpunit>
|
||||
31
plugin/ims_lti/vendor/oauth1/code/OAuthToken.php
vendored
Normal file
31
plugin/ims_lti/vendor/oauth1/code/OAuthToken.php
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
class OAuthToken {
|
||||
// access tokens and request tokens
|
||||
public $key;
|
||||
public $secret;
|
||||
|
||||
/**
|
||||
* key = the token
|
||||
* secret = the token secret
|
||||
*/
|
||||
function __construct($key, $secret) {
|
||||
$this->key = $key;
|
||||
$this->secret = $secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* generates the basic string serialization of a token that a server
|
||||
* would respond to request_token and access_token calls with
|
||||
*/
|
||||
function to_string() {
|
||||
return "oauth_token=" .
|
||||
oauthutil::urlencode_rfc3986($this->key) .
|
||||
"&oauth_token_secret=" .
|
||||
oauthutil::urlencode_rfc3986($this->secret);
|
||||
}
|
||||
|
||||
function __tostring() {
|
||||
return $this->to_string();
|
||||
}
|
||||
}
|
||||
153
plugin/ims_lti/vendor/oauth1/code/OAuthUtil.php
vendored
Normal file
153
plugin/ims_lti/vendor/oauth1/code/OAuthUtil.php
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
class OAuthUtil {
|
||||
public static function urlencode_rfc3986($input) {
|
||||
if (is_array($input)) {
|
||||
return array_map(array('OAuthUtil', 'urlencode_rfc3986'), $input);
|
||||
} else if (is_scalar($input)) {
|
||||
return str_replace(
|
||||
'+',
|
||||
' ',
|
||||
str_replace('%7E', '~', rawurlencode($input))
|
||||
);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// This decode function isn't taking into consideration the above
|
||||
// modifications to the encoding process. However, this method doesn't
|
||||
// seem to be used anywhere so leaving it as is.
|
||||
public static function urldecode_rfc3986($string) {
|
||||
return urldecode($string);
|
||||
}
|
||||
|
||||
// Utility function for turning the Authorization: header into
|
||||
// parameters, has to do some unescaping
|
||||
// Can filter out any non-oauth parameters if needed (default behaviour)
|
||||
// May 28th, 2010 - method updated to tjerk.meesters for a speed improvement.
|
||||
// see http://code.google.com/p/oauth/issues/detail?id=163
|
||||
public static function split_header($header, $only_allow_oauth_parameters = true) {
|
||||
$params = array();
|
||||
if (preg_match_all('/('.($only_allow_oauth_parameters ? 'oauth_' : '').'[a-z_-]*)=(:?"([^"]*)"|([^,]*))/', $header, $matches)) {
|
||||
foreach ($matches[1] as $i => $h) {
|
||||
$params[$h] = OAuthUtil::urldecode_rfc3986(empty($matches[3][$i]) ? $matches[4][$i] : $matches[3][$i]);
|
||||
}
|
||||
if (isset($params['realm'])) {
|
||||
unset($params['realm']);
|
||||
}
|
||||
}
|
||||
return $params;
|
||||
}
|
||||
|
||||
// helper to try to sort out headers for people who aren't running apache
|
||||
public static function get_headers() {
|
||||
if (function_exists('apache_request_headers')) {
|
||||
// we need this to get the actual Authorization: header
|
||||
// because apache tends to tell us it doesn't exist
|
||||
$headers = apache_request_headers();
|
||||
|
||||
// sanitize the output of apache_request_headers because
|
||||
// we always want the keys to be Cased-Like-This and arh()
|
||||
// returns the headers in the same case as they are in the
|
||||
// request
|
||||
$out = array();
|
||||
foreach ($headers AS $key => $value) {
|
||||
$key = str_replace(
|
||||
" ",
|
||||
"-",
|
||||
ucwords(strtolower(str_replace("-", " ", $key)))
|
||||
);
|
||||
$out[$key] = $value;
|
||||
}
|
||||
} else {
|
||||
// otherwise we don't have apache and are just going to have to hope
|
||||
// that $_SERVER actually contains what we need
|
||||
$out = array();
|
||||
if( isset($_SERVER['CONTENT_TYPE']) )
|
||||
$out['Content-Type'] = $_SERVER['CONTENT_TYPE'];
|
||||
if( isset($_ENV['CONTENT_TYPE']) )
|
||||
$out['Content-Type'] = $_ENV['CONTENT_TYPE'];
|
||||
|
||||
foreach ($_SERVER as $key => $value) {
|
||||
if (substr($key, 0, 5) == "HTTP_") {
|
||||
// this is chaos, basically it is just there to capitalize the first
|
||||
// letter of every word that is not an initial HTTP and strip HTTP
|
||||
// code from przemek
|
||||
$key = str_replace(
|
||||
" ",
|
||||
"-",
|
||||
ucwords(strtolower(str_replace("_", " ", substr($key, 5))))
|
||||
);
|
||||
$out[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
// This function takes a input like a=b&a=c&d=e and returns the parsed
|
||||
// parameters like this
|
||||
// array('a' => array('b','c'), 'd' => 'e')
|
||||
public static function parse_parameters( $input ) {
|
||||
if (!isset($input) || !$input) return array();
|
||||
|
||||
$pairs = explode('&', $input);
|
||||
|
||||
$parsed_parameters = array();
|
||||
foreach ($pairs as $pair) {
|
||||
$split = explode('=', $pair, 2);
|
||||
$parameter = OAuthUtil::urldecode_rfc3986($split[0]);
|
||||
$value = isset($split[1]) ? OAuthUtil::urldecode_rfc3986($split[1]) : '';
|
||||
|
||||
if (isset($parsed_parameters[$parameter])) {
|
||||
// We have already recieved parameter(s) with this name, so add to the list
|
||||
// of parameters with this name
|
||||
|
||||
if (is_scalar($parsed_parameters[$parameter])) {
|
||||
// This is the first duplicate, so transform scalar (string) into an array
|
||||
// so we can add the duplicates
|
||||
$parsed_parameters[$parameter] = array($parsed_parameters[$parameter]);
|
||||
}
|
||||
|
||||
$parsed_parameters[$parameter][] = $value;
|
||||
} else {
|
||||
$parsed_parameters[$parameter] = $value;
|
||||
}
|
||||
}
|
||||
return $parsed_parameters;
|
||||
}
|
||||
|
||||
public static function build_http_query($params) {
|
||||
if (!$params) return '';
|
||||
|
||||
// Urlencode both keys and values
|
||||
$keys = OAuthUtil::urlencode_rfc3986(array_keys($params));
|
||||
$values = OAuthUtil::urlencode_rfc3986(array_values($params));
|
||||
$params = array_combine($keys, $values);
|
||||
|
||||
// Parameters are sorted by name, using lexicographical byte value ordering.
|
||||
// Ref: Spec: 9.1.1 (1)
|
||||
uksort($params, 'strcmp');
|
||||
|
||||
$pairs = array();
|
||||
foreach ($params as $parameter => $value) {
|
||||
if (is_array($value)) {
|
||||
// If two or more parameters share the same name, they are sorted by their value
|
||||
// Ref: Spec: 9.1.1 (1)
|
||||
// June 12th, 2010 - changed to sort because of issue 164 by hidetaka
|
||||
sort($value, SORT_STRING);
|
||||
foreach ($value as $duplicate_value) {
|
||||
$pairs[] = $parameter . '=' . $duplicate_value;
|
||||
}
|
||||
} else {
|
||||
$pairs[] = $parameter . '=' . $value;
|
||||
}
|
||||
}
|
||||
// For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61)
|
||||
// Each name-value pair is separated by an '&' character (ASCII code 38)
|
||||
return implode('&', $pairs);
|
||||
}
|
||||
}
|
||||
|
||||
106
plugin/ims_lti/vendor/oauth1/code/OAuth_TestServer.php
vendored
Normal file
106
plugin/ims_lti/vendor/oauth1/code/OAuth_TestServer.php
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
<?php //vim: foldmethod=marker
|
||||
//require_once("../init.php");
|
||||
|
||||
class TestOAuthServer extends OAuthServer {
|
||||
public function get_signature_methods() {
|
||||
return $this->signature_methods;
|
||||
}
|
||||
}
|
||||
|
||||
class TestOAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod_RSA_SHA1 {
|
||||
public function fetch_private_cert(&$request) {
|
||||
$cert = <<<EOD
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALRiMLAh9iimur8V
|
||||
A7qVvdqxevEuUkW4K+2KdMXmnQbG9Aa7k7eBjK1S+0LYmVjPKlJGNXHDGuy5Fw/d
|
||||
7rjVJ0BLB+ubPK8iA/Tw3hLQgXMRRGRXXCn8ikfuQfjUS1uZSatdLB81mydBETlJ
|
||||
hI6GH4twrbDJCR2Bwy/XWXgqgGRzAgMBAAECgYBYWVtleUzavkbrPjy0T5FMou8H
|
||||
X9u2AC2ry8vD/l7cqedtwMPp9k7TubgNFo+NGvKsl2ynyprOZR1xjQ7WgrgVB+mm
|
||||
uScOM/5HVceFuGRDhYTCObE+y1kxRloNYXnx3ei1zbeYLPCHdhxRYW7T0qcynNmw
|
||||
rn05/KO2RLjgQNalsQJBANeA3Q4Nugqy4QBUCEC09SqylT2K9FrrItqL2QKc9v0Z
|
||||
zO2uwllCbg0dwpVuYPYXYvikNHHg+aCWF+VXsb9rpPsCQQDWR9TT4ORdzoj+Nccn
|
||||
qkMsDmzt0EfNaAOwHOmVJ2RVBspPcxt5iN4HI7HNeG6U5YsFBb+/GZbgfBT3kpNG
|
||||
WPTpAkBI+gFhjfJvRw38n3g/+UeAkwMI2TJQS4n8+hid0uus3/zOjDySH3XHCUno
|
||||
cn1xOJAyZODBo47E+67R4jV1/gzbAkEAklJaspRPXP877NssM5nAZMU0/O/NGCZ+
|
||||
3jPgDUno6WbJn5cqm8MqWhW1xGkImgRk+fkDBquiq4gPiT898jusgQJAd5Zrr6Q8
|
||||
AO/0isr/3aa6O6NLQxISLKcPDk2NOccAfS/xOtfOz4sJYM3+Bs4Io9+dZGSDCA54
|
||||
Lw03eHTNQghS0A==
|
||||
-----END PRIVATE KEY-----
|
||||
EOD;
|
||||
return $cert;
|
||||
}
|
||||
|
||||
public function fetch_public_cert(&$request) {
|
||||
$cert = <<<EOD
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBpjCCAQ+gAwIBAgIBATANBgkqhkiG9w0BAQUFADAZMRcwFQYDVQQDDA5UZXN0
|
||||
IFByaW5jaXBhbDAeFw03MDAxMDEwODAwMDBaFw0zODEyMzEwODAwMDBaMBkxFzAV
|
||||
BgNVBAMMDlRlc3QgUHJpbmNpcGFsMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
|
||||
gQC0YjCwIfYoprq/FQO6lb3asXrxLlJFuCvtinTF5p0GxvQGu5O3gYytUvtC2JlY
|
||||
zypSRjVxwxrsuRcP3e641SdASwfrmzyvIgP08N4S0IFzEURkV1wp/IpH7kH41Etb
|
||||
mUmrXSwfNZsnQRE5SYSOhh+LcK2wyQkdgcMv11l4KoBkcwIDAQABMA0GCSqGSIb3
|
||||
DQEBBQUAA4GBAGZLPEuJ5SiJ2ryq+CmEGOXfvlTtEL2nuGtr9PewxkgnOjZpUy+d
|
||||
4TvuXJbNQc8f4AMWL/tO9w0Fk80rWKp9ea8/df4qMq5qlFWlx6yOLQxumNOmECKb
|
||||
WpkUQDIDJEoFUzKMVuJf4KO/FJ345+BNLGgbJ6WujreoM1X/gYfdnJ/J
|
||||
-----END CERTIFICATE-----
|
||||
EOD;
|
||||
return $cert;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A mock store for testing
|
||||
*/
|
||||
class MockOAuthDataStore extends OAuthDataStore {/*{{{*/
|
||||
private $consumer;
|
||||
private $request_token;
|
||||
private $access_token;
|
||||
private $nonce;
|
||||
|
||||
function __construct() {/*{{{*/
|
||||
$this->consumer = new OAuthConsumer("key", "secret", NULL);
|
||||
$this->request_token = new OAuthToken("requestkey", "requestsecret", 1);
|
||||
$this->access_token = new OAuthToken("accesskey", "accesssecret", 1);
|
||||
$this->nonce = "nonce";
|
||||
}/*}}}*/
|
||||
|
||||
function lookup_consumer($consumer_key) {/*{{{*/
|
||||
if ($consumer_key == $this->consumer->key) return $this->consumer;
|
||||
return NULL;
|
||||
}/*}}}*/
|
||||
|
||||
function lookup_token($consumer, $token_type, $token) {/*{{{*/
|
||||
$token_attrib = $token_type . "_token";
|
||||
if ($consumer->key == $this->consumer->key
|
||||
&& $token == $this->$token_attrib->key) {
|
||||
return $this->$token_attrib;
|
||||
}
|
||||
return NULL;
|
||||
}/*}}}*/
|
||||
|
||||
function lookup_nonce($consumer, $token, $nonce, $timestamp) {/*{{{*/
|
||||
if ($consumer->key == $this->consumer->key
|
||||
&& (($token && $token->key == $this->request_token->key)
|
||||
|| ($token && $token->key == $this->access_token->key))
|
||||
&& $nonce == $this->nonce) {
|
||||
return $this->nonce;
|
||||
}
|
||||
return NULL;
|
||||
}/*}}}*/
|
||||
|
||||
function new_request_token($consumer) {/*{{{*/
|
||||
if ($consumer->key == $this->consumer->key) {
|
||||
return $this->request_token;
|
||||
}
|
||||
return NULL;
|
||||
}/*}}}*/
|
||||
|
||||
function new_access_token($token, $consumer) {/*{{{*/
|
||||
if ($consumer->key == $this->consumer->key
|
||||
&& $token->key == $this->request_token->key) {
|
||||
return $this->access_token;
|
||||
}
|
||||
return NULL;
|
||||
}/*}}}*/
|
||||
}/*}}}*/
|
||||
?>
|
||||
30
plugin/ims_lti/vendor/oauth1/composer.json
vendored
Normal file
30
plugin/ims_lti/vendor/oauth1/composer.json
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "jtsternberg/oauth1-php",
|
||||
"description": "Andy Smith's basic php library for OAuth.",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Andy Smith",
|
||||
"homepage": "http://term.ie/",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Justin Sternberg",
|
||||
"email": "justin@dsgnwrks.pro",
|
||||
"homepage": "https://dsgnwrks.pro",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"keywords": ["oauth", "oauth1", "php"],
|
||||
"homepage": "https://github.com/jtsternberg/oauth1-php",
|
||||
"support": {
|
||||
"issues": "https://github.com/jtsternberg/oauth1-php/issues",
|
||||
"source": "https://github.com/jtsternberg/oauth1-php"
|
||||
},
|
||||
"require": {
|
||||
"php": ">5.3"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": ["code"]
|
||||
}
|
||||
}
|
||||
20
plugin/ims_lti/vendor/oauth1/composer.lock
generated
vendored
Normal file
20
plugin/ims_lti/vendor/oauth1/composer.lock
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"hash": "9ec16f80a40b9ed7b9501632e2ab33c3",
|
||||
"content-hash": "17d848aa0b54718b6bf5334fd7e94bcc",
|
||||
"packages": [],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"php": ">5.3"
|
||||
},
|
||||
"platform-dev": []
|
||||
}
|
||||
73
plugin/ims_lti/vendor/oauth1/doc/design.md
vendored
Normal file
73
plugin/ims_lti/vendor/oauth1/doc/design.md
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
## Interfaces:
|
||||
|
||||
**OAuthConsumer** is a data type that represents the identity of the Consumer via its shared secret with the Service Provider.
|
||||
|
||||
OAuthConsumer
|
||||
- `key : str`
|
||||
- `secret : str`
|
||||
|
||||
**OAuthToken** is a data type that represents an End User via either an access or request token
|
||||
|
||||
OAuthToken
|
||||
- `token : str`
|
||||
- `secret : str`
|
||||
- `to_string() -> str`
|
||||
- `(static) from_string() -> OAuthToken`
|
||||
|
||||
**OAuthSignatureMethod** is a strategy class that implements a signature method
|
||||
|
||||
OAuthSignatureMethod
|
||||
- `get_name() -> str`
|
||||
- `build_signature (OAuthRequest, OAuthConsumer, OAuthToken) -> str`
|
||||
|
||||
**OAuthRequest** represents the request and can be seriali
|
||||
|
||||
OAuthRequest:
|
||||
- `OAuthRequest(str http_method, str http_url, [dict parameters]) -> constructor`
|
||||
- `set_parameter(str parameter, str value) -> void`
|
||||
- `example parameters: oauth_consumer_key, foo`
|
||||
- `get_parameter(str parameter) -> str`
|
||||
- `get_parameters() -> dict`
|
||||
|
||||
- `get_normalized_http_method() -> str`
|
||||
- `get_normalized_http_url() -> str`
|
||||
- `get_signable_params() -> dict`
|
||||
|
||||
- `to_header () -> str // serialize as a header for an HTTPAuth request`
|
||||
- `to_postdata () -> str // serialize as post data for a POST request`
|
||||
- `to_url () -> str // serialize as a url for a GET request`
|
||||
- `sign_request(OAuthSignatureMethod, OAuthConsumer, OAuthToken) -> void`
|
||||
- `build_signature(OAuthSignatureMethod, OAuthConsumer, OAuthToken) -> str`
|
||||
- `(static) from_request([str http_method, str http_url, dict parameters])`
|
||||
- `(static) from_consumer_and_token(OAuthConsumer, OAuthToken, str http_method, str http_url, [dict parameters]) -> OAuthRequest`
|
||||
|
||||
|
||||
**OAuthServer** is a worker to check a requests validity against a data store
|
||||
|
||||
OAuthServer:
|
||||
- `OAuthServer(OAuthDataStore) -> constructor`
|
||||
- `set_data_store(OAuthDataStore) -> void`
|
||||
- `get_data_store() -> OAuthDataStore`
|
||||
|
||||
- `fetch_request_token (OAuthRequest) -> OAuthToken`
|
||||
- `fetch_access_token (OAuthRequest) -> OAuthToken`
|
||||
- `verify_request (OAuthRequest) -> OAuthToken`
|
||||
|
||||
**OAuthClient** is a worker to attempt to execute a request
|
||||
|
||||
OAuthClient:
|
||||
- `OAuthClient(OAuthConsumer, OAuthToken) -> constructor`
|
||||
- `get_consumer() -> OAuthConsumer`
|
||||
- `get_token() -> OAuthToken`
|
||||
|
||||
- `fetch_request_token (OAuthRequest) -> OAuthToken`
|
||||
- `fetch_access_token (OAuthRequest) -> OAuthToken`
|
||||
|
||||
**OAuthDataStore** is a database abstraction used to lookup consumers and tokens
|
||||
|
||||
OAuthDataStore:
|
||||
- `lookup_consumer(str key) -> OAuthConsumer`
|
||||
- `lookup_token(OAuthConsumer, str token_type, str token_token) -> OAuthToken`
|
||||
- `lookup_nonce(OAuthConsumer, OAuthToken, str nonce, int timestamp) -> OAuthToken`
|
||||
- `fetch_request_token(OAuthConsumer) -> OAuthToken`
|
||||
- `fetch_access_token(OAuthConsumer, OAuthToken) -> OAuthToken`
|
||||
74
plugin/ims_lti/vendor/oauth1/example/SimpleOAuthDataStore.php
vendored
Normal file
74
plugin/ims_lti/vendor/oauth1/example/SimpleOAuthDataStore.php
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
/* A very naive dbm-based oauth storage
|
||||
*
|
||||
* NOTE: This is for reference ONLY,
|
||||
* and contains, amongst others, a hole
|
||||
* where you can get the token secret
|
||||
* easily..
|
||||
*/
|
||||
class SimpleOAuthDataStore extends OAuthDataStore {/*{{{*/
|
||||
private $dbh;
|
||||
|
||||
function __construct($path = "oauth.gdbm") {/*{{{*/
|
||||
$this->dbh = dba_popen($path, 'c', 'gdbm');
|
||||
}/*}}}*/
|
||||
|
||||
function __destruct() {/*{{{*/
|
||||
dba_close($this->dbh);
|
||||
}/*}}}*/
|
||||
|
||||
function lookup_consumer($consumer_key) {/*{{{*/
|
||||
$rv = dba_fetch("consumer_$consumer_key", $this->dbh);
|
||||
if ($rv === FALSE) {
|
||||
return NULL;
|
||||
}
|
||||
$obj = unserialize($rv);
|
||||
if (!($obj instanceof OAuthConsumer)) {
|
||||
return NULL;
|
||||
}
|
||||
return $obj;
|
||||
}/*}}}*/
|
||||
|
||||
function lookup_token($consumer, $token_type, $token) {/*{{{*/
|
||||
$rv = dba_fetch("${token_type}_${token}", $this->dbh);
|
||||
if ($rv === FALSE) {
|
||||
return NULL;
|
||||
}
|
||||
$obj = unserialize($rv);
|
||||
if (!($obj instanceof OAuthToken)) {
|
||||
return NULL;
|
||||
}
|
||||
return $obj;
|
||||
}/*}}}*/
|
||||
|
||||
function lookup_nonce($consumer, $token, $nonce, $timestamp) {/*{{{*/
|
||||
if (dba_exists("nonce_$nonce", $this->dbh)) {
|
||||
return TRUE;
|
||||
} else {
|
||||
dba_insert("nonce_$nonce", "1", $this->dbh);
|
||||
return FALSE;
|
||||
}
|
||||
}/*}}}*/
|
||||
|
||||
function new_token($consumer, $type="request") {/*{{{*/
|
||||
$key = md5(time());
|
||||
$secret = time() + time();
|
||||
$token = new OAuthToken($key, md5(md5($secret)));
|
||||
if (!dba_insert("${type}_$key", serialize($token), $this->dbh)) {
|
||||
throw new OAuthException("doooom!");
|
||||
}
|
||||
return $token;
|
||||
}/*}}}*/
|
||||
|
||||
function new_request_token($consumer) {/*{{{*/
|
||||
return $this->new_token($consumer, "request");
|
||||
}/*}}}*/
|
||||
|
||||
function new_access_token($token, $consumer) {/*{{{*/
|
||||
|
||||
$token = $this->new_token($consumer, 'access');
|
||||
dba_delete("request_" . $token->key, $this->dbh);
|
||||
return $token;
|
||||
}/*}}}*/
|
||||
}/*}}}*/
|
||||
14
plugin/ims_lti/vendor/oauth1/example/access_token.php
vendored
Normal file
14
plugin/ims_lti/vendor/oauth1/example/access_token.php
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
require_once("common.inc.php");
|
||||
|
||||
try {
|
||||
$req = OAuthRequest::from_request();
|
||||
$token = $test_server->fetch_access_token($req);
|
||||
print $token;
|
||||
} catch (OAuthException $e) {
|
||||
print($e->getMessage() . "\n<hr />\n");
|
||||
print_r($req);
|
||||
die();
|
||||
}
|
||||
|
||||
?>
|
||||
133
plugin/ims_lti/vendor/oauth1/example/client.php
vendored
Normal file
133
plugin/ims_lti/vendor/oauth1/example/client.php
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
require_once("common.inc.php");
|
||||
|
||||
$key = @$_REQUEST['key'];
|
||||
$secret = @$_REQUEST['secret'];
|
||||
$token = @$_REQUEST['token'];
|
||||
$token_secret = @$_REQUEST['token_secret'];
|
||||
$endpoint = @$_REQUEST['endpoint'];
|
||||
$action = @$_REQUEST['action'];
|
||||
$dump_request = @$_REQUEST['dump_request'];
|
||||
$user_sig_method = @$_REQUEST['sig_method'];
|
||||
$sig_method = $hmac_method;
|
||||
if ($user_sig_method) {
|
||||
$sig_method = $sig_methods[$user_sig_method];
|
||||
}
|
||||
|
||||
$test_consumer = new OAuthConsumer($key, $secret, NULL);
|
||||
|
||||
$test_token = NULL;
|
||||
if ($token) {
|
||||
$test_token = new OAuthConsumer($token, $token_secret);
|
||||
}
|
||||
|
||||
|
||||
if ($action == "request_token") {
|
||||
$parsed = parse_url($endpoint);
|
||||
$params = array();
|
||||
parse_str($parsed['query'], $params);
|
||||
|
||||
$req_req = OAuthRequest::from_consumer_and_token($test_consumer, NULL, "GET", $endpoint, $params);
|
||||
$req_req->sign_request($sig_method, $test_consumer, NULL);
|
||||
if ($dump_request) {
|
||||
Header('Content-type: text/plain');
|
||||
print "request url: " . $req_req->to_url(). "\n";
|
||||
print_r($req_req);
|
||||
exit;
|
||||
}
|
||||
Header("Location: $req_req");
|
||||
}
|
||||
else if ($action == "authorize") {
|
||||
$callback_url = "$base_url/client.php?key=$key&secret=$secret&token=$token&token_secret=$token_secret&endpoint=" . urlencode($endpoint);
|
||||
$auth_url = $endpoint . "?oauth_token=$token&oauth_callback=".urlencode($callback_url);
|
||||
if ($dump_request) {
|
||||
Header('Content-type: text/plain');
|
||||
print("auth_url: " . $auth_url);
|
||||
exit;
|
||||
}
|
||||
Header("Location: $auth_url");
|
||||
}
|
||||
else if ($action == "access_token") {
|
||||
$parsed = parse_url($endpoint);
|
||||
$params = array();
|
||||
parse_str($parsed['query'], $params);
|
||||
|
||||
$acc_req = OAuthRequest::from_consumer_and_token($test_consumer, $test_token, "GET", $endpoint, $params);
|
||||
$acc_req->sign_request($sig_method, $test_consumer, $test_token);
|
||||
if ($dump_request) {
|
||||
Header('Content-type: text/plain');
|
||||
print "request url: " . $acc_req->to_url() . "\n";
|
||||
print_r($acc_req);
|
||||
exit;
|
||||
}
|
||||
Header("Location: $acc_req");
|
||||
}
|
||||
|
||||
?>
|
||||
<html>
|
||||
<head>
|
||||
<title>OAuth Test Client</title>
|
||||
</head>
|
||||
<body>
|
||||
<div><a href="index.php">server</a> | <a href="client.php">client</a></div>
|
||||
<h1>OAuth Test Client</h1>
|
||||
<h2>Instructions for Use</h2>
|
||||
<p>This is a test client that will let you test your OAuth server code. Enter the appropriate information below to test.</p>
|
||||
<p>Note: we don't store any of the information you type in.</p>
|
||||
|
||||
<form method="POST" name="oauth_client">
|
||||
<h3>Choose a Signature Method</h3>
|
||||
<select name="sig_method">
|
||||
<?php
|
||||
foreach ($sig_methods as $name=> $method) {
|
||||
$selected = "";
|
||||
if ($name == $sig_method->get_name()) {
|
||||
$selected = " selected='selected'";
|
||||
}
|
||||
print "<option value='$name'$selected>$name</option>\n";
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
<h3>Enter The Endpoint to Test</h3>
|
||||
endpoint: <input type="text" name="endpoint" value="<?php echo $endpoint; ?>" size="100"/><br />
|
||||
<small style="color: green">Note: You can include query parameters in there to have them parsed in and signed too</small>
|
||||
<h3>Enter Your Consumer Key / Secret</h3>
|
||||
consumer key: <input type="text" name="key" value="<?php echo $key; ?>" /><br />
|
||||
consumer secret: <input type="text" name="secret" value="<?php echo $secret;?>" /><br />
|
||||
dump request, don't redirect: <input type="checkbox" name="dump_request" value="1" <?php if ($dump_request) echo 'checked="checked"'; ?>/><br />
|
||||
make a token request (don't forget to copy down the values you get)
|
||||
<input type="submit" name="action" value="request_token" />
|
||||
<h3>Enter Your Request Token / Secret</h3>
|
||||
token: <input type="text" name="token" value="<?php echo $token; ?>" /><br />
|
||||
token secret: <input type="text" name="token_secret" value="<?php echo $token_secret; ?>" /><br />
|
||||
<p><strong>Don't forget to update your endpoint to point at the auth or access token url</strong></p>
|
||||
try to authorize this token: <input type="submit" name="action" value="authorize" /><br />
|
||||
try to get an access token: <input type="submit" name="action" value="access_token" /><br />
|
||||
|
||||
<h3>Currently Supported Signature Methods</h3>
|
||||
<p>Current signing method is: <?php echo $sig_method->get_name() ?></p>
|
||||
<ul>
|
||||
<?php
|
||||
foreach ($sig_methods as $key => $method) {
|
||||
|
||||
print "<li>$key";
|
||||
if ($key != $sig_method->get_name()) {
|
||||
print "(<a href='?sig_method=$key'>switch</a>)";
|
||||
}
|
||||
print "</li>\n";
|
||||
}
|
||||
?>
|
||||
</ul>
|
||||
|
||||
<?php
|
||||
if ("RSA-SHA1" == $sig_method->get_name()) {
|
||||
// passing test_server as a dummy referecne
|
||||
print "<pre>" . $sig_method->fetch_private_cert($test_server). "</pre>\n";
|
||||
print "<pre>" . $sig_method->fetch_public_cert($test_server) . "</pre>\n";
|
||||
}
|
||||
?>
|
||||
|
||||
<h3>Further Resources</h3>
|
||||
<p>There is also a <a href="index.php">test server</a> implementation in here.</p>
|
||||
<p>The code running this example can be downloaded from the PHP section of the OAuth google code project: <a href="http://code.google.com/p/oauth/">http://code.google.com/p/oauth/</a>
|
||||
</body>
|
||||
26
plugin/ims_lti/vendor/oauth1/example/common.inc.php
vendored
Normal file
26
plugin/ims_lti/vendor/oauth1/example/common.inc.php
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
require_once("../init.php");
|
||||
require_once("../code/OAuth_TestServer.php");
|
||||
|
||||
/*
|
||||
* Config Section
|
||||
*/
|
||||
$domain = $_SERVER['HTTP_HOST'];
|
||||
$base = "/oauth/example";
|
||||
$base_url = "http://$domain$base";
|
||||
|
||||
/**
|
||||
* Some default objects
|
||||
*/
|
||||
|
||||
$test_server = new TestOAuthServer(new MockOAuthDataStore());
|
||||
$hmac_method = new OAuthSignatureMethod_HMAC_SHA1();
|
||||
$plaintext_method = new OAuthSignatureMethod_PLAINTEXT();
|
||||
$rsa_method = new TestOAuthSignatureMethod_RSA_SHA1();
|
||||
|
||||
$test_server->add_signature_method($hmac_method);
|
||||
$test_server->add_signature_method($plaintext_method);
|
||||
$test_server->add_signature_method($rsa_method);
|
||||
|
||||
$sig_methods = $test_server->get_signature_methods();
|
||||
?>
|
||||
21
plugin/ims_lti/vendor/oauth1/example/echo_api.php
vendored
Normal file
21
plugin/ims_lti/vendor/oauth1/example/echo_api.php
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
require_once("common.inc.php");
|
||||
|
||||
try {
|
||||
$req = OAuthRequest::from_request();
|
||||
list($consumer, $token) = $test_server->verify_request($req);
|
||||
|
||||
// lsit back the non-OAuth params
|
||||
$total = array();
|
||||
foreach($req->get_parameters() as $k => $v) {
|
||||
if (substr($k, 0, 5) == "oauth") continue;
|
||||
$total[] = urlencode($k) . "=" . urlencode($v);
|
||||
}
|
||||
print implode("&", $total);
|
||||
} catch (OAuthException $e) {
|
||||
print($e->getMessage() . "\n<hr />\n");
|
||||
print_r($req);
|
||||
die();
|
||||
}
|
||||
|
||||
?>
|
||||
108
plugin/ims_lti/vendor/oauth1/example/index.php
vendored
Normal file
108
plugin/ims_lti/vendor/oauth1/example/index.php
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
require_once("common.inc.php");
|
||||
|
||||
|
||||
$test_consumer = new OAuthConsumer("key", "secret", NULL);
|
||||
$req_token = new OAuthConsumer("requestkey", "requestsecret", 1);
|
||||
$acc_token = new OAuthConsumer("accesskey", "accesssecret", 1);
|
||||
|
||||
$sig_method = $hmac_method;
|
||||
$user_sig_method = @$_GET['sig_method'];
|
||||
if ($user_sig_method) {
|
||||
$sig_method = $sig_methods[$user_sig_method];
|
||||
}
|
||||
|
||||
$req_req = OAuthRequest::from_consumer_and_token($test_consumer, NULL, "GET", $base_url . "/request_token.php");
|
||||
$req_req->sign_request($sig_method, $test_consumer, NULL);
|
||||
|
||||
$acc_req = OAuthRequest::from_consumer_and_token($test_consumer, $req_token, "GET", $base_url . "/access_token.php");
|
||||
$acc_req->sign_request($sig_method, $test_consumer, $req_token);
|
||||
|
||||
$echo_req = OAuthRequest::from_consumer_and_token($test_consumer, $acc_token, "GET", $base_url . "/echo_api.php", array("method"=> "foo%20bar", "bar" => "baz"));
|
||||
$echo_req->sign_request($sig_method, $test_consumer, $acc_token);
|
||||
|
||||
?>
|
||||
<html>
|
||||
<head>
|
||||
<title>OAuth Test Server</title>
|
||||
</head>
|
||||
<body>
|
||||
<div><a href="index.php">server</a> | <a href="client.php">client</a></div>
|
||||
<h1>OAuth Test Server</h1>
|
||||
<h2>Instructions for Use</h2>
|
||||
<p>This is a test server with a predefined static set of keys and tokens, you can make your requests using them to test your code (and mine ;)).</p>
|
||||
<h3>Your Consumer Key / Secret</h3>
|
||||
<ul>
|
||||
<li>consumer key: <code><strong>key</strong></code></li>
|
||||
<li>consumer secret: <code><strong>secret</strong></code></li>
|
||||
</ul>
|
||||
<p>Use this key and secret for all your requests.</p>
|
||||
<h3>Getting a Request Token</h3>
|
||||
|
||||
<ul>
|
||||
<li>request token endpoint: <code><strong><?php echo $base_url . "/request_token.php"; ?></strong></code></li>
|
||||
</ul>
|
||||
|
||||
<p>A successful request will return the following:</p>
|
||||
<p><code>oauth_token=requestkey&oauth_token_secret=requestsecret</code></p>
|
||||
|
||||
<p>An unsuccessful request will attempt to describe what went wrong.</p>
|
||||
|
||||
<h4>Example</h4>
|
||||
<a href="<?php echo $req_req; ?>"><?php echo $req_req; ?></a>
|
||||
|
||||
<h3>Getting an Access Token</h3>
|
||||
<p>The Request Token provided above is already authorized, you may use it to request an Access Token right away.</p>
|
||||
|
||||
<ul>
|
||||
<li>access token endpoint: <code><strong><?php echo $base_url . "/access_token.php"; ?></strong></code></li>
|
||||
</ul>
|
||||
|
||||
<p>A successful request will return the following:</p>
|
||||
<p><code>oauth_token=accesskey&oauth_token_secret=accesssecret</code></p>
|
||||
|
||||
<p>An unsuccessful request will attempt to describe what went wrong.</p>
|
||||
|
||||
<h4>Example</h4>
|
||||
<a href="<?php echo $acc_req; ?>"><?php echo $acc_req; ?></a>
|
||||
|
||||
<h3>Making Authenticated Calls</h3>
|
||||
<p>Using your Access Token you can make authenticated calls.</p>
|
||||
|
||||
<ul>
|
||||
<li>api endpoint: <code><strong><?php echo $base_url . "/echo_api.php"; ?></strong></code></li>
|
||||
</ul>
|
||||
<p>
|
||||
A successful request will echo the non-OAuth parameters sent to it, for example:</p>
|
||||
<p><code>method=foo&bar=baz</code></p>
|
||||
<p>An unsuccessful request will attempt to describe what went wrong.</p>
|
||||
|
||||
<h4>Example</h4>
|
||||
<a href="<?php echo $echo_req; ?>"><?php echo $echo_req; ?></a>
|
||||
|
||||
<h3>Currently Supported Signature Methods</h3>
|
||||
<p>Current signing method is: <?php echo $user_sig_method ?></p>
|
||||
<ul>
|
||||
<?php
|
||||
$sig_methods = $test_server->get_signature_methods();
|
||||
foreach ($sig_methods as $key => $method) {
|
||||
print "<li>$key";
|
||||
if ($key != $sig_method->get_name()) {
|
||||
print "(<a href='?sig_method=$key'>switch</a>)";
|
||||
}
|
||||
print "</li>\n";
|
||||
}
|
||||
?>
|
||||
</ul>
|
||||
|
||||
<?php
|
||||
if ("RSA-SHA1" == $sig_method->get_name()) {
|
||||
print "<pre>" . $sig_method->fetch_private_cert($req_req) . "</pre>\n";
|
||||
print "<pre>" . $sig_method->fetch_public_cert($req_req) . "</pre>\n";
|
||||
}
|
||||
?>
|
||||
|
||||
<h3>Further Resources</h3>
|
||||
<p>There is also a <a href="client.php">test client</a> implementation in here.</p>
|
||||
<p>The code running this example can be downloaded from the PHP section of the OAuth google code project: <a href="http://code.google.com/p/oauth/">http://code.google.com/p/oauth/</a>
|
||||
</body>
|
||||
14
plugin/ims_lti/vendor/oauth1/example/request_token.php
vendored
Normal file
14
plugin/ims_lti/vendor/oauth1/example/request_token.php
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
require_once("common.inc.php");
|
||||
|
||||
try {
|
||||
$req = OAuthRequest::from_request();
|
||||
$token = $test_server->fetch_request_token($req);
|
||||
print $token;
|
||||
} catch (OAuthException $e) {
|
||||
print($e->getMessage() . "\n<hr />\n");
|
||||
print_r($req);
|
||||
die();
|
||||
}
|
||||
|
||||
?>
|
||||
5
plugin/ims_lti/vendor/oauth1/init.php
vendored
Normal file
5
plugin/ims_lti/vendor/oauth1/init.php
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
// Include the autoloader to get started.
|
||||
// (make sure to run `composer install`!)
|
||||
require_once dirname( __FILE__ ) . '/vendor/autoload.php';
|
||||
12
plugin/ims_lti/vendor/oauth1/tests/Mock_OAuthBaseStringRequest.php
vendored
Normal file
12
plugin/ims_lti/vendor/oauth1/tests/Mock_OAuthBaseStringRequest.php
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* A very simple class that you can pass a base-string, and then have it returned again.
|
||||
* Used for testing the signature-methods
|
||||
*/
|
||||
class Mock_OAuthBaseStringRequest {
|
||||
private $provided_base_string;
|
||||
public $base_string; // legacy
|
||||
public function __construct($bs) { $this->provided_base_string = $bs; }
|
||||
public function get_signature_base_string() { return $this->provided_base_string; }
|
||||
}
|
||||
57
plugin/ims_lti/vendor/oauth1/tests/Mock_OAuthDataStore.php
vendored
Normal file
57
plugin/ims_lti/vendor/oauth1/tests/Mock_OAuthDataStore.php
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* A mock store for testing
|
||||
*/
|
||||
class Mock_OAuthDataStore extends OAuthDataStore {
|
||||
private $consumer;
|
||||
private $request_token;
|
||||
private $access_token;
|
||||
private $nonce;
|
||||
|
||||
function __construct() {
|
||||
$this->consumer = new OAuthConsumer("key", "secret", NULL);
|
||||
$this->request_token = new OAuthToken("requestkey", "requestsecret", 1);
|
||||
$this->access_token = new OAuthToken("accesskey", "accesssecret", 1);
|
||||
$this->nonce = "nonce";
|
||||
}
|
||||
|
||||
function lookup_consumer($consumer_key) {
|
||||
if ($consumer_key == $this->consumer->key) return $this->consumer;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
function lookup_token($consumer, $token_type, $token) {
|
||||
$token_attrib = $token_type . "_token";
|
||||
if ($consumer->key == $this->consumer->key
|
||||
&& $token == $this->$token_attrib->key) {
|
||||
return $this->$token_attrib;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
function lookup_nonce($consumer, $token, $nonce, $timestamp) {
|
||||
if ($consumer->key == $this->consumer->key
|
||||
&& (($token && $token->key == $this->request_token->key)
|
||||
|| ($token && $token->key == $this->access_token->key))
|
||||
&& $nonce == $this->nonce) {
|
||||
return $this->nonce;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
function new_request_token($consumer, $callback = null) {
|
||||
if ($consumer->key == $this->consumer->key) {
|
||||
return $this->request_token;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
function new_access_token($token, $consumer, $verifier = null) {
|
||||
if ($consumer->key == $this->consumer->key
|
||||
&& $token->key == $this->request_token->key) {
|
||||
return $this->access_token;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
47
plugin/ims_lti/vendor/oauth1/tests/Mock_OAuthSignatureMethod_RSA_SHA1.php
vendored
Normal file
47
plugin/ims_lti/vendor/oauth1/tests/Mock_OAuthSignatureMethod_RSA_SHA1.php
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* A mock implementation of OAuthSignatureMethod_RSA_SHA1
|
||||
* Always returns the signatures described in
|
||||
* http://wiki.oauth.net/TestCases section 9.3 ("RSA-SHA1")
|
||||
*/
|
||||
class Mock_OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod_RSA_SHA1 {
|
||||
public function fetch_private_cert(&$request) {
|
||||
$cert = <<<EOD
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALRiMLAh9iimur8V
|
||||
A7qVvdqxevEuUkW4K+2KdMXmnQbG9Aa7k7eBjK1S+0LYmVjPKlJGNXHDGuy5Fw/d
|
||||
7rjVJ0BLB+ubPK8iA/Tw3hLQgXMRRGRXXCn8ikfuQfjUS1uZSatdLB81mydBETlJ
|
||||
hI6GH4twrbDJCR2Bwy/XWXgqgGRzAgMBAAECgYBYWVtleUzavkbrPjy0T5FMou8H
|
||||
X9u2AC2ry8vD/l7cqedtwMPp9k7TubgNFo+NGvKsl2ynyprOZR1xjQ7WgrgVB+mm
|
||||
uScOM/5HVceFuGRDhYTCObE+y1kxRloNYXnx3ei1zbeYLPCHdhxRYW7T0qcynNmw
|
||||
rn05/KO2RLjgQNalsQJBANeA3Q4Nugqy4QBUCEC09SqylT2K9FrrItqL2QKc9v0Z
|
||||
zO2uwllCbg0dwpVuYPYXYvikNHHg+aCWF+VXsb9rpPsCQQDWR9TT4ORdzoj+Nccn
|
||||
qkMsDmzt0EfNaAOwHOmVJ2RVBspPcxt5iN4HI7HNeG6U5YsFBb+/GZbgfBT3kpNG
|
||||
WPTpAkBI+gFhjfJvRw38n3g/+UeAkwMI2TJQS4n8+hid0uus3/zOjDySH3XHCUno
|
||||
cn1xOJAyZODBo47E+67R4jV1/gzbAkEAklJaspRPXP877NssM5nAZMU0/O/NGCZ+
|
||||
3jPgDUno6WbJn5cqm8MqWhW1xGkImgRk+fkDBquiq4gPiT898jusgQJAd5Zrr6Q8
|
||||
AO/0isr/3aa6O6NLQxISLKcPDk2NOccAfS/xOtfOz4sJYM3+Bs4Io9+dZGSDCA54
|
||||
Lw03eHTNQghS0A==
|
||||
-----END PRIVATE KEY-----
|
||||
EOD;
|
||||
return $cert;
|
||||
}
|
||||
|
||||
public function fetch_public_cert(&$request) {
|
||||
$cert = <<<EOD
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBpjCCAQ+gAwIBAgIBATANBgkqhkiG9w0BAQUFADAZMRcwFQYDVQQDDA5UZXN0
|
||||
IFByaW5jaXBhbDAeFw03MDAxMDEwODAwMDBaFw0zODEyMzEwODAwMDBaMBkxFzAV
|
||||
BgNVBAMMDlRlc3QgUHJpbmNpcGFsMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
|
||||
gQC0YjCwIfYoprq/FQO6lb3asXrxLlJFuCvtinTF5p0GxvQGu5O3gYytUvtC2JlY
|
||||
zypSRjVxwxrsuRcP3e641SdASwfrmzyvIgP08N4S0IFzEURkV1wp/IpH7kH41Etb
|
||||
mUmrXSwfNZsnQRE5SYSOhh+LcK2wyQkdgcMv11l4KoBkcwIDAQABMA0GCSqGSIb3
|
||||
DQEBBQUAA4GBAGZLPEuJ5SiJ2ryq+CmEGOXfvlTtEL2nuGtr9PewxkgnOjZpUy+d
|
||||
4TvuXJbNQc8f4AMWL/tO9w0Fk80rWKp9ea8/df4qMq5qlFWlx6yOLQxumNOmECKb
|
||||
WpkUQDIDJEoFUzKMVuJf4KO/FJ345+BNLGgbJ6WujreoM1X/gYfdnJ/J
|
||||
-----END CERTIFICATE-----
|
||||
EOD;
|
||||
return $cert;
|
||||
}
|
||||
}
|
||||
10
plugin/ims_lti/vendor/oauth1/tests/OAuthConsumerTest.php
vendored
Normal file
10
plugin/ims_lti/vendor/oauth1/tests/OAuthConsumerTest.php
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
require 'common.php';
|
||||
|
||||
class OAuthConsumerTest extends PHPUnit_Framework_TestCase {
|
||||
public function testConvertToString() {
|
||||
$consumer = new OAuthConsumer('key', 'secret');
|
||||
$this->assertEquals('OAuthConsumer[key=key,secret=secret]', (string) $consumer);
|
||||
}
|
||||
}
|
||||
329
plugin/ims_lti/vendor/oauth1/tests/OAuthRequestTest.php
vendored
Normal file
329
plugin/ims_lti/vendor/oauth1/tests/OAuthRequestTest.php
vendored
Normal file
@@ -0,0 +1,329 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Tests of OAuthRequest
|
||||
*
|
||||
* The tests works by using OAuthTestUtils::build_request
|
||||
* to populare $_SERVER, $_GET & $_POST.
|
||||
*
|
||||
* Most of the base string and signature tests
|
||||
* are either very simple or based upon
|
||||
* http://wiki.oauth.net/TestCases
|
||||
*/
|
||||
|
||||
require_once dirname(__FILE__) . '/common.php';
|
||||
|
||||
class OAuthRequestTest extends PHPUnit_Framework_TestCase {
|
||||
public function testCanGetSingleParameter() {
|
||||
// Yes, a awesomely boring test.. But if this doesn't work, the other tests is unreliable
|
||||
$request = new OAuthRequest('', '', array('test'=>'foo'));
|
||||
$this->assertEquals( 'foo', $request->get_parameter('test'), 'Failed to read back parameter');
|
||||
|
||||
$request = new OAuthRequest('', '', array('test'=>array('foo', 'bar')));
|
||||
$this->assertEquals( array('foo', 'bar'), $request->get_parameter('test'), 'Failed to read back parameter');
|
||||
|
||||
|
||||
$request = new OAuthRequest('', '', array('test'=>'foo', 'bar'=>'baz'));
|
||||
$this->assertEquals( 'foo', $request->get_parameter('test'), 'Failed to read back parameter');
|
||||
$this->assertEquals( 'baz', $request->get_parameter('bar'), 'Failed to read back parameter');
|
||||
}
|
||||
|
||||
public function testGetAllParameters() {
|
||||
// Yes, a awesomely boring test.. But if this doesn't work, the other tests is unreliable
|
||||
$request = new OAuthRequest('', '', array('test'=>'foo'));
|
||||
$this->assertEquals( array('test'=>'foo'), $request->get_parameters(), 'Failed to read back parameters');
|
||||
|
||||
$request = new OAuthRequest('', '', array('test'=>'foo', 'bar'=>'baz'));
|
||||
$this->assertEquals( array('test'=>'foo', 'bar'=>'baz'), $request->get_parameters(), 'Failed to read back parameters');
|
||||
|
||||
$request = new OAuthRequest('', '', array('test'=>array('foo', 'bar')));
|
||||
$this->assertEquals( array('test'=>array('foo', 'bar')), $request->get_parameters(), 'Failed to read back parameters');
|
||||
}
|
||||
|
||||
public function testSetParameters() {
|
||||
$request = new OAuthRequest('', '');
|
||||
$this->assertEquals( NULL, $request->get_parameter('test'), 'Failed to assert that non-existing parameter is NULL');
|
||||
|
||||
$request->set_parameter('test', 'foo');
|
||||
$this->assertEquals( 'foo', $request->get_parameter('test'), 'Failed to set single-entry parameter');
|
||||
|
||||
$request->set_parameter('test', 'bar');
|
||||
$this->assertEquals( array('foo', 'bar'), $request->get_parameter('test'), 'Failed to set single-entry parameter');
|
||||
|
||||
$request->set_parameter('test', 'bar', false);
|
||||
$this->assertEquals( 'bar', $request->get_parameter('test'), 'Failed to set single-entry parameter');
|
||||
}
|
||||
|
||||
public function testUnsetParameter() {
|
||||
$request = new OAuthRequest('', '');
|
||||
$this->assertEquals( NULL, $request->get_parameter('test'));
|
||||
|
||||
$request->set_parameter('test', 'foo');
|
||||
$this->assertEquals( 'foo', $request->get_parameter('test'));
|
||||
|
||||
$request->unset_parameter('test');
|
||||
$this->assertEquals( NULL, $request->get_parameter('test'), 'Failed to unset parameter');
|
||||
}
|
||||
|
||||
public function testCreateRequestFromConsumerAndToken() {
|
||||
$cons = new OAuthConsumer('key', 'kd94hf93k423kf44');
|
||||
$token = new OAuthToken('token', 'pfkkdhi9sl3r4s00');
|
||||
|
||||
$request = OAuthRequest::from_consumer_and_token($cons, $token, 'POST', 'http://example.com');
|
||||
$this->assertEquals('POST', $request->get_normalized_http_method());
|
||||
$this->assertEquals('http://example.com', $request->get_normalized_http_url());
|
||||
$this->assertEquals('1.0', $request->get_parameter('oauth_version'));
|
||||
$this->assertEquals($cons->key, $request->get_parameter('oauth_consumer_key'));
|
||||
$this->assertEquals($token->key, $request->get_parameter('oauth_token'));
|
||||
$this->assertEquals(time(), $request->get_parameter('oauth_timestamp'));
|
||||
$this->assertRegExp('/[0-9a-f]{32}/', $request->get_parameter('oauth_nonce'));
|
||||
// We don't know what the nonce will be, except it'll be md5 and hence 32 hexa digits
|
||||
|
||||
$request = OAuthRequest::from_consumer_and_token($cons, $token, 'POST', 'http://example.com', array('oauth_nonce'=>'foo'));
|
||||
$this->assertEquals('foo', $request->get_parameter('oauth_nonce'));
|
||||
|
||||
$request = OAuthRequest::from_consumer_and_token($cons, NULL, 'POST', 'http://example.com', array('oauth_nonce'=>'foo'));
|
||||
$this->assertNull($request->get_parameter('oauth_token'));
|
||||
|
||||
// Test that parameters given in the $http_url instead of in the $parameters-parameter
|
||||
// will still be picked up
|
||||
$request = OAuthRequest::from_consumer_and_token($cons, $token, 'POST', 'http://example.com/?foo=bar');
|
||||
$this->assertEquals('http://example.com/', $request->get_normalized_http_url());
|
||||
$this->assertEquals('bar', $request->get_parameter('foo'));
|
||||
}
|
||||
|
||||
public function testBuildRequestFromPost() {
|
||||
OAuthTestUtils::build_request('POST', 'http://testbed/test', 'foo=bar&baz=blargh');
|
||||
$this->assertEquals(array('foo'=>'bar','baz'=>'blargh'), OAuthRequest::from_request()->get_parameters(), 'Failed to parse POST parameters');
|
||||
}
|
||||
|
||||
public function testBuildRequestFromGet() {
|
||||
OAuthTestUtils::build_request('GET', 'http://testbed/test?foo=bar&baz=blargh');
|
||||
$this->assertEquals(array('foo'=>'bar','baz'=>'blargh'), OAuthRequest::from_request()->get_parameters(), 'Failed to parse GET parameters');
|
||||
}
|
||||
|
||||
public function testBuildRequestFromHeader() {
|
||||
$test_header = 'OAuth realm="",oauth_foo=bar,oauth_baz="bla,rgh"';
|
||||
OAuthTestUtils::build_request('POST', 'http://testbed/test', '', $test_header);
|
||||
$this->assertEquals(array('oauth_foo'=>'bar','oauth_baz'=>'bla,rgh'), OAuthRequest::from_request()->get_parameters(), 'Failed to split auth-header correctly');
|
||||
}
|
||||
|
||||
public function testHasProperParameterPriority() {
|
||||
$test_header = 'OAuth realm="",oauth_foo=header';
|
||||
OAuthTestUtils::build_request('POST', 'http://testbed/test?oauth_foo=get', 'oauth_foo=post', $test_header);
|
||||
$this->assertEquals('header', OAuthRequest::from_request()->get_parameter('oauth_foo'), 'Loaded parameters in with the wrong priorities');
|
||||
|
||||
OAuthTestUtils::build_request('POST', 'http://testbed/test?oauth_foo=get', 'oauth_foo=post');
|
||||
$this->assertEquals('post', OAuthRequest::from_request()->get_parameter('oauth_foo'), 'Loaded parameters in with the wrong priorities');
|
||||
|
||||
OAuthTestUtils::build_request('POST', 'http://testbed/test?oauth_foo=get');
|
||||
$this->assertEquals('get', OAuthRequest::from_request()->get_parameter('oauth_foo'), 'Loaded parameters in with the wrong priorities');
|
||||
}
|
||||
|
||||
public function testNormalizeHttpMethod() {
|
||||
OAuthTestUtils::build_request('POST', 'http://testbed/test');
|
||||
$this->assertEquals('POST', OAuthRequest::from_request()->get_normalized_http_method(), 'Failed to normalize HTTP method: POST');
|
||||
|
||||
OAuthTestUtils::build_request('post', 'http://testbed/test');
|
||||
$this->assertEquals('POST', OAuthRequest::from_request()->get_normalized_http_method(), 'Failed to normalize HTTP method: post');
|
||||
|
||||
OAuthTestUtils::build_request('GET', 'http://testbed/test');
|
||||
$this->assertEquals('GET', OAuthRequest::from_request()->get_normalized_http_method(), 'Failed to normalize HTTP method: GET');
|
||||
|
||||
OAuthTestUtils::build_request('PUT', 'http://testbed/test');
|
||||
$this->assertEquals('PUT', OAuthRequest::from_request()->get_normalized_http_method(), 'Failed to normalize HTTP method: PUT');
|
||||
}
|
||||
|
||||
public function testNormalizeParameters() {
|
||||
// This is mostly repeats of OAuthUtilTest::testParseParameters & OAuthUtilTest::TestBuildHttpQuery
|
||||
|
||||
// Tests taken from
|
||||
// http://wiki.oauth.net/TestCases ("Normalize Request Parameters")
|
||||
OAuthTestUtils::build_request('POST', 'http://testbed/test', 'name');
|
||||
$this->assertEquals( 'name=', OAuthRequest::from_request()->get_signable_parameters());
|
||||
|
||||
OAuthTestUtils::build_request('POST', 'http://testbed/test', 'a=b');
|
||||
$this->assertEquals( 'a=b', OAuthRequest::from_request()->get_signable_parameters());
|
||||
|
||||
OAuthTestUtils::build_request('POST', 'http://testbed/test', 'a=b&c=d');
|
||||
$this->assertEquals( 'a=b&c=d', OAuthRequest::from_request()->get_signable_parameters());
|
||||
|
||||
OAuthTestUtils::build_request('POST', 'http://testbed/test', 'a=x%21y&a=x+y');
|
||||
$this->assertEquals( 'a=x%20y&a=x%21y', OAuthRequest::from_request()->get_signable_parameters());
|
||||
|
||||
OAuthTestUtils::build_request('POST', 'http://testbed/test', 'x%21y=a&x=a');
|
||||
$this->assertEquals( 'x=a&x%21y=a', OAuthRequest::from_request()->get_signable_parameters());
|
||||
|
||||
OAuthTestUtils::build_request('POST', 'http://testbed/test', 'a=1&c=hi there&f=25&f=50&f=a&z=p&z=t');
|
||||
$this->assertEquals( 'a=1&c=hi%20there&f=25&f=50&f=a&z=p&z=t', OAuthRequest::from_request()->get_signable_parameters());
|
||||
}
|
||||
|
||||
public function testNormalizeHttpUrl() {
|
||||
OAuthTestUtils::build_request('POST', 'http://example.com');
|
||||
$this->assertEquals('http://example.com', OAuthRequest::from_request()->get_normalized_http_url());
|
||||
|
||||
OAuthTestUtils::build_request('POST', 'https://example.com');
|
||||
$this->assertEquals('https://example.com', OAuthRequest::from_request()->get_normalized_http_url());
|
||||
|
||||
// Tests that http on !80 and https on !443 keeps the port
|
||||
OAuthTestUtils::build_request('POST', 'http://example.com:8080');
|
||||
$this->assertEquals('http://example.com:8080', OAuthRequest::from_request()->get_normalized_http_url());
|
||||
|
||||
OAuthTestUtils::build_request('POST', 'https://example.com:80');
|
||||
$this->assertEquals('https://example.com:80', OAuthRequest::from_request()->get_normalized_http_url());
|
||||
|
||||
OAuthTestUtils::build_request('POST', 'http://example.com:443');
|
||||
$this->assertEquals('http://example.com:443', OAuthRequest::from_request()->get_normalized_http_url());
|
||||
|
||||
OAuthTestUtils::build_request('POST', 'http://Example.COM');
|
||||
$this->assertEquals('http://example.com', OAuthRequest::from_request()->get_normalized_http_url());
|
||||
|
||||
// Emulate silly behavior by some clients, where there Host header includes the port
|
||||
OAuthTestUtils::build_request('POST', 'http://example.com');
|
||||
$_SERVER['HTTP_HOST'] = $_SERVER['HTTP_HOST'] . ':' . $_SERVER['SERVER_PORT'];
|
||||
$this->assertEquals('http://example.com', OAuthRequest::from_request()->get_normalized_http_url());
|
||||
}
|
||||
|
||||
public function testBuildPostData() {
|
||||
OAuthTestUtils::build_request('POST', 'http://example.com');
|
||||
$this->assertEquals('', OAuthRequest::from_request()->to_postdata());
|
||||
|
||||
OAuthTestUtils::build_request('POST', 'http://example.com', 'foo=bar');
|
||||
$this->assertEquals('foo=bar', OAuthRequest::from_request()->to_postdata());
|
||||
|
||||
OAuthTestUtils::build_request('GET', 'http://example.com?foo=bar');
|
||||
$this->assertEquals('foo=bar', OAuthRequest::from_request()->to_postdata());
|
||||
}
|
||||
|
||||
public function testBuildUrl() {
|
||||
OAuthTestUtils::build_request('POST', 'http://example.com');
|
||||
$this->assertEquals('http://example.com', OAuthRequest::from_request()->to_url());
|
||||
|
||||
OAuthTestUtils::build_request('POST', 'http://example.com', 'foo=bar');
|
||||
$this->assertEquals('http://example.com?foo=bar', OAuthRequest::from_request()->to_url());
|
||||
|
||||
OAuthTestUtils::build_request('GET', 'http://example.com?foo=bar');
|
||||
$this->assertEquals('http://example.com?foo=bar', OAuthRequest::from_request()->to_url());
|
||||
}
|
||||
|
||||
public function testConvertToString() {
|
||||
OAuthTestUtils::build_request('POST', 'http://example.com');
|
||||
$this->assertEquals('http://example.com', (string) OAuthRequest::from_request());
|
||||
|
||||
OAuthTestUtils::build_request('POST', 'http://example.com', 'foo=bar');
|
||||
$this->assertEquals('http://example.com?foo=bar', (string) OAuthRequest::from_request());
|
||||
|
||||
OAuthTestUtils::build_request('GET', 'http://example.com?foo=bar');
|
||||
$this->assertEquals('http://example.com?foo=bar', (string) OAuthRequest::from_request());
|
||||
}
|
||||
|
||||
public function testBuildHeader() {
|
||||
OAuthTestUtils::build_request('POST', 'http://example.com');
|
||||
$this->assertEquals('Authorization: OAuth', OAuthRequest::from_request()->to_header());
|
||||
$this->assertEquals('Authorization: OAuth realm="test"', OAuthRequest::from_request()->to_header('test'));
|
||||
|
||||
OAuthTestUtils::build_request('POST', 'http://example.com', 'foo=bar');
|
||||
$this->assertEquals('Authorization: OAuth', OAuthRequest::from_request()->to_header());
|
||||
$this->assertEquals('Authorization: OAuth realm="test"', OAuthRequest::from_request()->to_header('test'));
|
||||
|
||||
OAuthTestUtils::build_request('POST', 'http://example.com', 'oauth_test=foo');
|
||||
$this->assertEquals('Authorization: OAuth oauth_test="foo"', OAuthRequest::from_request()->to_header());
|
||||
$this->assertEquals('Authorization: OAuth realm="test",oauth_test="foo"', OAuthRequest::from_request()->to_header('test'));
|
||||
|
||||
// Is headers supposted to be Urlencoded. More to the point:
|
||||
// Should it be baz = bla,rgh or baz = bla%2Crgh ??
|
||||
// - morten.fangel
|
||||
OAuthTestUtils::build_request('POST', 'http://example.com', '', 'OAuth realm="",oauth_foo=bar,oauth_baz="bla,rgh"');
|
||||
$this->assertEquals('Authorization: OAuth oauth_foo="bar",oauth_baz="bla%2Crgh"', OAuthRequest::from_request()->to_header());
|
||||
$this->assertEquals('Authorization: OAuth realm="test",oauth_foo="bar",oauth_baz="bla%2Crgh"', OAuthRequest::from_request()->to_header('test'));
|
||||
}
|
||||
|
||||
public function testWontBuildHeaderWithArrayInput() {
|
||||
$this->setExpectedException('OAuthException');
|
||||
OAuthTestUtils::build_request('POST', 'http://example.com', 'oauth_foo=bar&oauth_foo=baz');
|
||||
OAuthRequest::from_request()->to_header();
|
||||
}
|
||||
|
||||
public function testBuildBaseString() {
|
||||
OAuthTestUtils::build_request('POST', 'http://testbed/test', 'n=v');
|
||||
$this->assertEquals('POST&http%3A%2F%2Ftestbed%2Ftest&n%3Dv', OAuthRequest::from_request()->get_signature_base_string());
|
||||
|
||||
OAuthTestUtils::build_request('POST', 'http://testbed/test', 'n=v&n=v2');
|
||||
$this->assertEquals('POST&http%3A%2F%2Ftestbed%2Ftest&n%3Dv%26n%3Dv2', OAuthRequest::from_request()->get_signature_base_string());
|
||||
|
||||
OAuthTestUtils::build_request('GET', 'http://example.com?n=v');
|
||||
$this->assertEquals('GET&http%3A%2F%2Fexample.com&n%3Dv', OAuthRequest::from_request()->get_signature_base_string());
|
||||
|
||||
$params = 'oauth_version=1.0&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_timestamp=1191242090';
|
||||
$params .= '&oauth_nonce=hsu94j3884jdopsl&oauth_signature_method=PLAINTEXT&oauth_signature=ignored';
|
||||
OAuthTestUtils::build_request('POST', 'https://photos.example.net/request_token', $params);
|
||||
$this->assertEquals('POST&https%3A%2F%2Fphotos.example.net%2Frequest_token&oauth_'
|
||||
.'consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dhsu94j3884j'
|
||||
.'dopsl%26oauth_signature_method%3DPLAINTEXT%26oauth_timestam'
|
||||
.'p%3D1191242090%26oauth_version%3D1.0',
|
||||
OAuthRequest::from_request()->get_signature_base_string());
|
||||
|
||||
$params = 'file=vacation.jpg&size=original&oauth_version=1.0&oauth_consumer_key=dpf43f3p2l4k3l03';
|
||||
$params .= '&oauth_token=nnch734d00sl2jdk&oauth_timestamp=1191242096&oauth_nonce=kllo9940pd9333jh';
|
||||
$params .= '&oauth_signature=ignored&oauth_signature_method=HMAC-SHA1';
|
||||
OAuthTestUtils::build_request('GET', 'http://photos.example.net/photos?'.$params);
|
||||
$this->assertEquals('GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation'
|
||||
.'.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%'
|
||||
.'3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26o'
|
||||
.'auth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jd'
|
||||
.'k%26oauth_version%3D1.0%26size%3Doriginal',
|
||||
OAuthRequest::from_request()->get_signature_base_string());
|
||||
}
|
||||
|
||||
public function testBuildSignature() {
|
||||
$params = 'file=vacation.jpg&size=original&oauth_version=1.0&oauth_consumer_key=dpf43f3p2l4k3l03';
|
||||
$params .= '&oauth_token=nnch734d00sl2jdk&oauth_timestamp=1191242096&oauth_nonce=kllo9940pd9333jh';
|
||||
$params .= '&oauth_signature=ignored&oauth_signature_method=HMAC-SHA1';
|
||||
OAuthTestUtils::build_request('GET', 'http://photos.example.net/photos?'.$params);
|
||||
$r = OAuthRequest::from_request();
|
||||
|
||||
$cons = new OAuthConsumer('key', 'kd94hf93k423kf44');
|
||||
$token = new OAuthToken('token', 'pfkkdhi9sl3r4s00');
|
||||
|
||||
$hmac = new OAuthSignatureMethod_HMAC_SHA1();
|
||||
$plaintext = new OAuthSignatureMethod_PLAINTEXT();
|
||||
|
||||
$this->assertEquals('tR3+Ty81lMeYAr/Fid0kMTYa/WM=', $r->build_signature($hmac, $cons, $token));
|
||||
$this->assertEquals('kd94hf93k423kf44&pfkkdhi9sl3r4s00', $r->build_signature($plaintext, $cons, $token));
|
||||
}
|
||||
|
||||
public function testSign() {
|
||||
$params = 'file=vacation.jpg&size=original&oauth_version=1.0&oauth_consumer_key=dpf43f3p2l4k3l03';
|
||||
$params .= '&oauth_token=nnch734d00sl2jdk&oauth_timestamp=1191242096&oauth_nonce=kllo9940pd9333jh';
|
||||
$params .= '&oauth_signature=__ignored__&oauth_signature_method=HMAC-SHA1';
|
||||
OAuthTestUtils::build_request('GET', 'http://photos.example.net/photos?'.$params);
|
||||
$r = OAuthRequest::from_request();
|
||||
|
||||
$cons = new OAuthConsumer('key', 'kd94hf93k423kf44');
|
||||
$token = new OAuthToken('token', 'pfkkdhi9sl3r4s00');
|
||||
|
||||
$hmac = new OAuthSignatureMethod_HMAC_SHA1();
|
||||
$plaintext = new OAuthSignatureMethod_PLAINTEXT();
|
||||
|
||||
// We need to test both what the parameter is, and how the serialized request is..
|
||||
|
||||
$r->sign_request($hmac, $cons, $token);
|
||||
$this->assertEquals('HMAC-SHA1', $r->get_parameter('oauth_signature_method'));
|
||||
$this->assertEquals('tR3+Ty81lMeYAr/Fid0kMTYa/WM=', $r->get_parameter('oauth_signature'));
|
||||
$expectedPostdata = 'file=vacation.jpg&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_nonce=kllo9940pd9333jh&'
|
||||
. 'oauth_signature=tR3%2BTy81lMeYAr%2FFid0kMTYa%2FWM%3D&oauth_signature_method=HMAC-SHA1&'
|
||||
. 'oauth_timestamp=1191242096&oauth_token=nnch734d00sl2jdk&oauth_version=1.0&size=original';
|
||||
$this->assertEquals( $expectedPostdata, $r->to_postdata());
|
||||
|
||||
$r->sign_request($plaintext, $cons, $token);
|
||||
$this->assertEquals('PLAINTEXT', $r->get_parameter('oauth_signature_method'));
|
||||
$this->assertEquals('kd94hf93k423kf44&pfkkdhi9sl3r4s00', $r->get_parameter('oauth_signature'));
|
||||
$expectedPostdata = 'file=vacation.jpg&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_nonce=kllo9940pd9333jh&'
|
||||
. 'oauth_signature=kd94hf93k423kf44%26pfkkdhi9sl3r4s00&oauth_signature_method=PLAINTEXT&'
|
||||
. 'oauth_timestamp=1191242096&oauth_token=nnch734d00sl2jdk&oauth_version=1.0&size=original';
|
||||
$this->assertEquals( $expectedPostdata, $r->to_postdata());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
225
plugin/ims_lti/vendor/oauth1/tests/OAuthServerTest.php
vendored
Normal file
225
plugin/ims_lti/vendor/oauth1/tests/OAuthServerTest.php
vendored
Normal file
@@ -0,0 +1,225 @@
|
||||
<?php
|
||||
|
||||
require_once dirname(__FILE__) . '/common.php';
|
||||
require_once dirname(__FILE__) . '/Mock_OAuthDataStore.php';
|
||||
|
||||
/**
|
||||
* Tests of OAuthUtil
|
||||
*/
|
||||
class OAuthServerTest extends PHPUnit_Framework_TestCase {
|
||||
private $consumer;
|
||||
private $request_token;
|
||||
private $access_token;
|
||||
private $hmac_sha1;
|
||||
private $plaintext;
|
||||
private $server;
|
||||
|
||||
public function setUp() {
|
||||
$this->consumer = new OAuthConsumer('key', 'secret');
|
||||
$this->request_token = new OAuthToken('requestkey', 'requestsecret');
|
||||
$this->access_token = new OAuthToken('accesskey', 'accesssecret');
|
||||
|
||||
$this->hmac_sha1 = new OAuthSignatureMethod_HMAC_SHA1();
|
||||
$this->plaintext = new OAuthSignatureMethod_PLAINTEXT();
|
||||
|
||||
$this->server = new OAuthServer( new Mock_OAuthDataStore() );
|
||||
$this->server->add_signature_method( $this->hmac_sha1 );
|
||||
$this->server->add_signature_method( $this->plaintext );
|
||||
}
|
||||
|
||||
public function testAcceptValidRequest() {
|
||||
$request = OAuthRequest::from_consumer_and_token( $this->consumer, $this->access_token, 'POST', 'http://example.com');
|
||||
$request->sign_request( $this->plaintext, $this->consumer, $this->access_token );
|
||||
list($consumer, $token) = $this->server->verify_request( $request );
|
||||
$this->assertEquals( $this->consumer, $consumer );
|
||||
$this->assertEquals( $this->access_token, $token );
|
||||
|
||||
$request->sign_request( $this->hmac_sha1, $this->consumer, $this->access_token );
|
||||
list($consumer, $token) = $this->server->verify_request( $request );
|
||||
$this->assertEquals( $this->consumer, $consumer );
|
||||
$this->assertEquals( $this->access_token, $token );
|
||||
}
|
||||
|
||||
public function testAcceptRequestWithoutVersion() {
|
||||
$request = OAuthRequest::from_consumer_and_token( $this->consumer, $this->access_token, 'POST', 'http://example.com');
|
||||
$request->unset_parameter('oauth_version');
|
||||
$request->sign_request( $this->hmac_sha1, $this->consumer, $this->access_token );
|
||||
|
||||
$this->server->verify_request( $request );
|
||||
}
|
||||
|
||||
public function testRejectRequestSignedWithRequestToken() {
|
||||
$request = OAuthRequest::from_consumer_and_token( $this->consumer, $this->request_token, 'POST', 'http://example.com');
|
||||
$request->sign_request( $this->plaintext, $this->consumer, $this->request_token );
|
||||
|
||||
$this->setExpectedException('OAuthException');
|
||||
$this->server->verify_request( $request );
|
||||
}
|
||||
|
||||
public function testRejectRequestWithMissingParameters() {
|
||||
// The list of required parameters is taken from
|
||||
// Chapter 7 ("Accessing Protected Resources")
|
||||
|
||||
$required_parameters = array(
|
||||
'oauth_consumer_key',
|
||||
'oauth_token',
|
||||
'oauth_signature_method',
|
||||
'oauth_signature',
|
||||
'oauth_timestamp',
|
||||
'oauth_nonce'
|
||||
);
|
||||
|
||||
foreach( $required_parameters AS $required ) {
|
||||
$request = OAuthRequest::from_consumer_and_token( $this->consumer, $this->access_token, 'POST', 'http://example.com');
|
||||
$request->sign_request( $this->plaintext, $this->consumer, $this->access_token );
|
||||
try {
|
||||
$request->unset_parameter( $required );
|
||||
$this->server->verify_request($request);
|
||||
$this->fail('Allowed a request without `' . $required . '`');
|
||||
} catch( OAuthException $e ) { /* expected */ }
|
||||
}
|
||||
}
|
||||
|
||||
public function testRejectPastTimestamp() {
|
||||
// We change the timestamp to be 10 hours ago, it should throw an exception
|
||||
|
||||
$request = OAuthRequest::from_consumer_and_token( $this->consumer, $this->access_token, 'POST', 'http://example.com');
|
||||
$request->set_parameter( 'oauth_timestamp', $request->get_parameter('oauth_timestamp') - 10*60*60, false);
|
||||
$request->sign_request( $this->plaintext, $this->consumer, $this->access_token );
|
||||
|
||||
$this->setExpectedException('OAuthException');
|
||||
$this->server->verify_request($request);
|
||||
}
|
||||
|
||||
public function testRejectFutureTimestamp() {
|
||||
// We change the timestamp to be 10 hours in the future, it should throw an exception
|
||||
|
||||
$request = OAuthRequest::from_consumer_and_token( $this->consumer, $this->access_token, 'POST', 'http://example.com');
|
||||
$request->set_parameter( 'oauth_timestamp', $request->get_parameter('oauth_timestamp') + 10*60*60, false);
|
||||
$request->sign_request( $this->plaintext, $this->consumer, $this->access_token );
|
||||
|
||||
$this->setExpectedException('OAuthException');
|
||||
$this->server->verify_request($request);
|
||||
}
|
||||
|
||||
public function testRejectUsedNonce() {
|
||||
// We give a known nonce and should see an exception
|
||||
|
||||
$request = OAuthRequest::from_consumer_and_token( $this->consumer, $this->access_token, 'POST', 'http://example.com');
|
||||
// The Mock datastore is set to say that the `nonce` nonce is known
|
||||
$request->set_parameter( 'oauth_nonce', 'nonce', false);
|
||||
$request->sign_request( $this->plaintext, $this->consumer, $this->access_token );
|
||||
|
||||
$this->setExpectedException('OAuthException');
|
||||
$this->server->verify_request($request);
|
||||
}
|
||||
|
||||
public function testRejectInvalidSignature() {
|
||||
// We change the signature post-signing to be something invalid
|
||||
|
||||
$request = OAuthRequest::from_consumer_and_token( $this->consumer, $this->access_token, 'POST', 'http://example.com');
|
||||
$request->sign_request( $this->plaintext, $this->consumer, $this->access_token );
|
||||
$request->set_parameter( 'oauth_signature', '__whatever__', false);
|
||||
|
||||
$this->setExpectedException('OAuthException');
|
||||
$this->server->verify_request($request);
|
||||
}
|
||||
|
||||
public function testRejectInvalidConsumer() {
|
||||
// We use the consumer-key "unknown", which isn't known by the datastore.
|
||||
|
||||
$unknown_consumer = new OAuthConsumer('unknown', '__unused__');
|
||||
|
||||
$request = OAuthRequest::from_consumer_and_token( $unknown_consumer, $this->access_token, 'POST', 'http://example.com');
|
||||
$request->sign_request( $this->plaintext, $unknown_consumer, $this->access_token );
|
||||
|
||||
$this->setExpectedException('OAuthException');
|
||||
$this->server->verify_request( $request );
|
||||
}
|
||||
|
||||
public function testRejectInvalidToken() {
|
||||
// We use the access-token "unknown" which isn't known by the datastore
|
||||
|
||||
$unknown_token = new OAuthToken('unknown', '__unused__');
|
||||
|
||||
$request = OAuthRequest::from_consumer_and_token( $this->consumer, $unknown_token, 'POST', 'http://example.com');
|
||||
$request->sign_request( $this->plaintext, $this->consumer, $unknown_token );
|
||||
|
||||
$this->setExpectedException('OAuthException');
|
||||
$this->server->verify_request( $request );
|
||||
}
|
||||
|
||||
public function testRejectUnknownSignatureMethod() {
|
||||
// We use a server that only supports HMAC-SHA1, but requests with PLAINTEXT signature
|
||||
|
||||
$request = OAuthRequest::from_consumer_and_token( $this->consumer, $this->access_token, 'POST', 'http://example.com');
|
||||
$request->sign_request( $this->plaintext, $this->consumer, $this->access_token );
|
||||
|
||||
$server = new OAuthServer( new Mock_OAuthDataStore() );
|
||||
$server->add_signature_method( $this->hmac_sha1 );
|
||||
|
||||
$this->setExpectedException('OAuthException');
|
||||
$server->verify_request( $request );
|
||||
}
|
||||
|
||||
public function testRejectUnknownVersion() {
|
||||
// We use the version "1.0a" which isn't "1.0", so reject the request
|
||||
|
||||
$request = OAuthRequest::from_consumer_and_token( $this->consumer, $this->access_token, 'POST', 'http://example.com');
|
||||
$request->sign_request( $this->plaintext, $this->consumer, $this->access_token );
|
||||
$request->set_parameter('oauth_version', '1.0a', false);
|
||||
|
||||
$this->setExpectedException('OAuthException');
|
||||
$this->server->verify_request( $request );
|
||||
}
|
||||
|
||||
public function testCreateRequestToken() {
|
||||
// We request a new Request Token
|
||||
|
||||
$request = OAuthRequest::from_consumer_and_token( $this->consumer, NULL, 'POST', 'http://example.com');
|
||||
$request->sign_request( $this->plaintext, $this->consumer, NULL );
|
||||
|
||||
$token = $this->server->fetch_request_token($request);
|
||||
$this->assertEquals($this->request_token, $token);
|
||||
}
|
||||
|
||||
public function testRejectSignedRequestTokenRequest() {
|
||||
// We request a new Request Token, but the request is signed with a token which should fail
|
||||
|
||||
$request = OAuthRequest::from_consumer_and_token( $this->consumer, $this->request_token, 'POST', 'http://example.com');
|
||||
$request->sign_request( $this->plaintext, $this->consumer, $this->request_token );
|
||||
|
||||
$this->setExpectedException('OAuthException');
|
||||
$token = $this->server->fetch_request_token($request);
|
||||
}
|
||||
|
||||
public function testCreateAccessToken() {
|
||||
// We request a new Access Token
|
||||
|
||||
$request = OAuthRequest::from_consumer_and_token( $this->consumer, $this->request_token, 'POST', 'http://example.com');
|
||||
$request->sign_request( $this->plaintext, $this->consumer, $this->request_token );
|
||||
|
||||
$token = $this->server->fetch_access_token($request);
|
||||
$this->assertEquals($this->access_token, $token);
|
||||
}
|
||||
|
||||
public function testRejectUnsignedAccessTokenRequest() {
|
||||
// We request a new Access Token, but we didn't sign the request with a Access Token
|
||||
|
||||
$request = OAuthRequest::from_consumer_and_token( $this->consumer, NULL, 'POST', 'http://example.com');
|
||||
$request->sign_request( $this->plaintext, $this->consumer, NULL );
|
||||
|
||||
$this->setExpectedException('OAuthException');
|
||||
$token = $this->server->fetch_access_token($request);
|
||||
}
|
||||
|
||||
public function testRejectAccessTokenSignedAccessTokenRequest() {
|
||||
// We request a new Access Token, but the request is signed with an access token, so fail!
|
||||
|
||||
$request = OAuthRequest::from_consumer_and_token( $this->consumer, $this->access_token, 'POST', 'http://example.com');
|
||||
$request->sign_request( $this->plaintext, $this->consumer, $this->access_token );
|
||||
|
||||
$this->setExpectedException('OAuthException');
|
||||
$token = $this->server->fetch_access_token($request);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user