Skip to content

Commit

Permalink
XAPI: Launch tincan tools - refs BT#16742
Browse files Browse the repository at this point in the history
  • Loading branch information
AngelFQC committed Nov 27, 2020
1 parent ae04bed commit 6c3c413
Show file tree
Hide file tree
Showing 4 changed files with 306 additions and 3 deletions.
4 changes: 4 additions & 0 deletions plugin/xapi/lang/english.php
Expand Up @@ -25,3 +25,7 @@
$strings['ActivityId'] = 'Activity ID';
$strings['ActivityType'] = 'Activity type';
$strings['ActivityDeleted'] = 'Activity deleted';
$strings['ActivityLaunch'] = 'Launch';
$strings['ActivityFirstLaunch'] = 'First launch at';
$strings['ActivityLastLaunch'] = 'Last launch at';
$strings['LaunchNewAttempt'] = 'Launch new attempt';
131 changes: 131 additions & 0 deletions plugin/xapi/launch/launch.php
@@ -0,0 +1,131 @@
<?php
/* For licensing terms, see /license.txt */

use Chamilo\PluginBundle\Entity\XApi\ToolLaunch;
use Symfony\Component\HttpFoundation\Request as HttpRequest;
use Xabbuh\XApi\Common\Exception\NotFoundException;
use Xabbuh\XApi\Model\Activity;
use Xabbuh\XApi\Model\Agent;
use Xabbuh\XApi\Model\DocumentData;
use Xabbuh\XApi\Model\InverseFunctionalIdentifier;
use Xabbuh\XApi\Model\IRI;
use Xabbuh\XApi\Model\State;
use Xabbuh\XApi\Model\StateDocument;
use Xabbuh\XApi\Serializer\Symfony\Serializer;

require_once __DIR__.'/../../../main/inc/global.inc.php';

api_block_anonymous_users();
api_protect_course_script(true);

$request = HttpRequest::createFromGlobals();

$user = api_get_user_entity(api_get_user_id());

$em = Database::getManager();

$attemptId = $request->request->get('attempt_id');
$toolLaunch = $em->find(
ToolLaunch::class,
$request->request->getInt('id')
);

if (empty($attemptId)
|| null === $toolLaunch
|| $toolLaunch->getCourse()->getId() !== api_get_course_entity()->getId()
) {
api_not_allowed(true);
}

$plugin = XApiPlugin::create();

$activity = new Activity(
IRI::fromString($toolLaunch->getActivityId())
);
$actor = new Agent(
InverseFunctionalIdentifier::withMbox(
IRI::fromString('mailto:'.$user->getEmail())
),
$user->getCompleteName()
);
$state = new State(
$activity,
$actor,
$plugin->generateIri('tool-'.$toolLaunch->getId(), 'state')
);

$nowDate = api_get_utc_datetime(null, false, true)->format('c');

try {
$stateDocument = $plugin->getXApiStateClient()->getDocument($state);

$data = $stateDocument->getData()->getData();

if ($stateDocument->offsetExists($attemptId)) {
$data[$attemptId][XApiPlugin::STATE_LAST_LAUNCH] = $nowDate;
} else {
$data[$attemptId] = [
XApiPlugin::STATE_FIRST_LAUNCH => $nowDate,
XApiPlugin::STATE_LAST_LAUNCH => $nowDate,
];
}

uasort($data, function ($attemptA, $attemptB) {
$timeA = strtotime($attemptA[XApiPlugin::STATE_LAST_LAUNCH]);
$timeB = strtotime($attemptB[XApiPlugin::STATE_LAST_LAUNCH]);

return $timeB - $timeA;
});

$documentData = new DocumentData($data);
} catch (NotFoundException $notFoundException) {
$documentData = new DocumentData(
[
$attemptId => [
XApiPlugin::STATE_FIRST_LAUNCH => $nowDate,
XApiPlugin::STATE_LAST_LAUNCH => $nowDate,
],
]
);
} catch (Exception $exception) {
Display::addFlash(
Display::return_message($exception->getMessage(), 'error')
);

header('Location: '.api_get_course_url());
exit;
}

try {
$plugin
->getXApiStateClient()
->createOrReplaceDocument(
new StateDocument($state, $documentData)
);
} catch (Exception $exception) {
Display::addFlash(
Display::return_message($exception->getMessage(), 'error')
);

header('Location: '.api_get_course_url());
exit;
}

$authString = $plugin->get(XApiPlugin::SETTING_LRS_AUTH);
$parts = explode(':', $authString);

$activityLaunchUrl = $toolLaunch->getLaunchUrl().'?'
.http_build_query(
[
'endpoint' => $plugin->get(XApiPlugin::SETTING_LRS_URL),
'auth' => 'Basic '.base64_encode("{$parts[1]}:{$parts[2]}"),
'actor' => Serializer::createSerializer()->serialize($actor, 'json'),
'registration' => $attemptId,
'activity_id' => $toolLaunch->getActivityId(),
],
'',
'&',
PHP_QUERY_RFC3986
);

header("Location: $activityLaunchUrl");
157 changes: 157 additions & 0 deletions plugin/xapi/launch/tool.php
@@ -0,0 +1,157 @@
<?php
/* For licensing terms, see /license.txt */

use Chamilo\PluginBundle\Entity\XApi\ToolLaunch;
use Symfony\Component\HttpFoundation\Request as HttpRequest;
use Xabbuh\XApi\Common\Exception\NotFoundException;
use Xabbuh\XApi\Model\Activity;
use Xabbuh\XApi\Model\Agent;
use Xabbuh\XApi\Model\InverseFunctionalIdentifier;
use Xabbuh\XApi\Model\IRI;
use Xabbuh\XApi\Model\State;
use Xabbuh\XApi\Model\Uuid;

require_once __DIR__.'/../../../main/inc/global.inc.php';

api_block_anonymous_users();
api_protect_course_script(true);

$request = HttpRequest::createFromGlobals();

$user = api_get_user_entity(api_get_user_id());

$em = Database::getManager();

$toolLaunch = $em->find(
ToolLaunch::class,
$request->query->getInt('id')
);

if (null === $toolLaunch
|| $toolLaunch->getCourse()->getId() !== api_get_course_entity()->getId()
) {
api_not_allowed(true);
}

$plugin = XApiPlugin::create();

$activity = new Activity(
IRI::fromString($toolLaunch->getActivityId())
);
$actor = new Agent(
InverseFunctionalIdentifier::withMbox(
IRI::fromString('mailto:'.$user->getEmail())
),
$user->getCompleteName()
);
$state = new State(
$activity,
$actor,
$plugin->generateIri('tool-'.$toolLaunch->getId(), 'state')
);

$cidReq = api_get_cidreq();

try {
$stateDocument = $plugin->getXApiStateClient()->getDocument($state);
} catch (NotFoundException $notFoundException) {
$stateDocument = null;
} catch (Exception $exception) {
Display::addFlash(
Display::return_message($exception->getMessage(), 'error')
);

header('Location: '.api_get_course_url());
exit;
}

$frmNewRegistration = new FormValidator(
'launch_new',
'post',
"launch.php?$cidReq",
'',
['target' => '_blank'],
FormValidator::LAYOUT_INLINE
);
$frmNewRegistration->addHidden('attempt_id', Uuid::uuid4());
$frmNewRegistration->addHidden('id', $toolLaunch->getId());
$frmNewRegistration->addButton(
'submit',
$plugin->get_lang('LaunchNewAttempt'),
'external-link fa-fw',
'success'
);

if ($stateDocument) {
$row = 0;

$table = new HTML_Table(['class' => 'table table-hover table-striped']);
$table->setHeaderContents($row, 0, $plugin->get_lang('ActivityFirstLaunch'));
$table->setHeaderContents($row, 1, $plugin->get_lang('ActivityLastLaunch'));
$table->setHeaderContents($row, 2, get_lang('Actions'));

$row++;

$langActivityLaunch = $plugin->get_lang('ActivityLaunch');

foreach ($stateDocument->getData()->getData() as $attemptId => $attempt) {
$firstLaunch = api_convert_and_format_date(
$attempt[XApiPlugin::STATE_FIRST_LAUNCH],
DATE_TIME_FORMAT_LONG
);
$lastLaunch = api_convert_and_format_date(
$attempt[XApiPlugin::STATE_LAST_LAUNCH],
DATE_TIME_FORMAT_LONG
);

$frmLaunch = new FormValidator(
"launch_$row",
'post',
"launch.php?$cidReq",
'',
['target' => '_blank'],
FormValidator::LAYOUT_INLINE
);
$frmLaunch->addHidden('attempt_id', $attemptId);
$frmLaunch->addHidden('id', $toolLaunch->getId());
$frmLaunch->addButton(
'submit',
$langActivityLaunch,
'external-link fa-fw',
'default'
);

$table->setCellContents($row, 0, $firstLaunch);
$table->setCellContents($row, 1, $lastLaunch);
$table->setCellContents($row, 2, $frmLaunch->returnForm());

$row++;
}

$table->setColAttributes(0, ['class' => 'text-center']);
$table->setColAttributes(1, ['class' => 'text-center']);
$table->setColAttributes(2, ['class' => 'text-center']);
}

$pageTitle = $toolLaunch->getTitle();
$pageContent = '';

if ($toolLaunch->getDescription()) {
$pageContent .= PHP_EOL;
$pageContent .= "<p class='lead'>{$toolLaunch->getDescription()}</p>";
}

$pageContent .= Display::div(
$frmNewRegistration->returnForm(),
['class' => 'exercise_overview_options']
);

if ($stateDocument) {
$pageContent .= $table->toHtml();
}

$view = new Template($pageTitle);
$view->assign('header', $pageTitle);
$view->assign('content', $pageContent);
$view->display_one_col_template();

17 changes: 14 additions & 3 deletions plugin/xapi/src/XApiPlugin.php
Expand Up @@ -45,6 +45,9 @@ class XApiPlugin extends Plugin implements HookPluginInterface
const TYPE_LP = 'lp';
const TYPE_LP_ITEM = 'lp_item';

const STATE_FIRST_LAUNCH = 'first_launch';
const STATE_LAST_LAUNCH = 'last_launch';

/**
* XApiPlugin constructor.
*/
Expand Down Expand Up @@ -184,6 +187,14 @@ public function uninstallPluginDbTables()
);
}

/**
* @return \Xabbuh\XApi\Client\Api\StateApiClientInterface
*/
public function getXApiStateClient()
{
return $this->createXApiClient()->getStateApiClient();
}

/**
* @return \Xabbuh\XApi\Client\Api\StatementsApiClientInterface
*/
Expand Down Expand Up @@ -353,7 +364,7 @@ private function deleteCourseTools()

Database::getManager()
->createQuery('DELETE FROM ChamiloCourseBundle:CTool t WHERE t.category = :category AND t.link LIKE :link')
->execute(['category' => 'plugin', 'link' => 'xapi/launch.php%']);
->execute(['category' => 'plugin', 'link' => 'xapi/launch/tool.php%']);
}

/**
Expand All @@ -363,7 +374,7 @@ private function deleteCourseTools()
*/
public function createLaunchCourseTool(ToolLaunch $toolLaunch)
{
$link ='xapi/launch.php?'.http_build_query(
$link ='xapi/launch/tool.php?'.http_build_query(
[
'id' => $toolLaunch->getId(),
]
Expand All @@ -388,7 +399,7 @@ public function getCourseToolFromLaunchTool(ToolLaunch $toolLaunch)
$tool = Database::getManager()
->getRepository(CTool::class)
->findOneBy([
'link' => 'xapi/launch.php?id='.$toolLaunch->getId(),
'link' => 'xapi/tool.php?id='.$toolLaunch->getId(),
'cId' => $toolLaunch->getCourse()->getId(),
]);

Expand Down

0 comments on commit 6c3c413

Please sign in to comment.