upgrade
This commit is contained in:
3
plugin/exercisemonitoring/admin.php
Normal file
3
plugin/exercisemonitoring/admin.php
Normal file
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
BIN
plugin/exercisemonitoring/assets/images/idcard.png
Normal file
BIN
plugin/exercisemonitoring/assets/images/idcard.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.2 KiB |
BIN
plugin/exercisemonitoring/assets/images/user.png
Normal file
BIN
plugin/exercisemonitoring/assets/images/user.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.7 KiB |
97
plugin/exercisemonitoring/cron/cleanup.php
Normal file
97
plugin/exercisemonitoring/cron/cleanup.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\CoreBundle\Entity\TrackEExercises;
|
||||
use Chamilo\PluginBundle\ExerciseMonitoring\Entity\Log;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
|
||||
require_once __DIR__.'/../../../main/inc/global.inc.php';
|
||||
|
||||
if ('cli' !== PHP_SAPI) {
|
||||
exit('For security reasons, this script can only be launched from cron or from the command line');
|
||||
}
|
||||
|
||||
exit;
|
||||
|
||||
$plugin = ExerciseMonitoringPlugin::create();
|
||||
$em = Database::getManager();
|
||||
$repo = $em->getRepository(Log::class);
|
||||
$trackExeRepo = $em->getRepository(TrackEExercises::class);
|
||||
|
||||
$lifetimeDays = (int) $plugin->get(ExerciseMonitoringPlugin::SETTING_SNAPSHOTS_LIFETIME);
|
||||
|
||||
if (empty($lifetimeDays)) {
|
||||
logging("There is no set time limit");
|
||||
exit;
|
||||
}
|
||||
|
||||
$timeLimit = api_get_utc_datetime(null, false, true);
|
||||
$timeLimit->modify("-$lifetimeDays day");
|
||||
|
||||
logging(
|
||||
sprintf("Deleting snapshots taken before than %s", $timeLimit->format('Y-m-d H:i:s'))
|
||||
);
|
||||
|
||||
$fs = new Filesystem();
|
||||
|
||||
$logs = findLogsBeforeThan($timeLimit);
|
||||
|
||||
foreach ($logs as $log) {
|
||||
$sysPath = ExerciseMonitoringPlugin::generateSnapshotUrl(
|
||||
$log['exe_user_id'],
|
||||
$log['image_filename'],
|
||||
SYS_UPLOAD_PATH
|
||||
);
|
||||
|
||||
if (!file_exists($sysPath)) {
|
||||
logging(
|
||||
sprintf("File %s not exists", $sysPath)
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$fs->remove($sysPath);
|
||||
|
||||
Database::update(
|
||||
'plugin_exercisemonitoring_log',
|
||||
['removed' => true],
|
||||
['id = ?' => $log['log_id']]
|
||||
);
|
||||
|
||||
logging(
|
||||
sprintf(
|
||||
"From exe_id %s; deleting filename %s created at %s",
|
||||
$log['exe_id'],
|
||||
$sysPath,
|
||||
$log['created_at']
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function findLogsBeforeThan(DateTime $timeLimit): array
|
||||
{
|
||||
$sql = "SELECT tee.exe_id, l.id AS log_id, l.image_filename, tee.exe_user_id
|
||||
FROM plugin_exercisemonitoring_log l
|
||||
INNER JOIN chamilo.track_e_exercises tee on l.exe_id = tee.exe_id
|
||||
WHERE l.created_at <= '".$timeLimit->format('Y-m-d H:i:s')."'
|
||||
AND l.removed IS FALSE";
|
||||
|
||||
$result = Database::query($sql);
|
||||
|
||||
$rows = [];
|
||||
|
||||
while ($row = Database::fetch_assoc($result)) {
|
||||
$rows[] = $row;
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
function logging(string $message)
|
||||
{
|
||||
$time = time();
|
||||
|
||||
printf("[%s] %s \n", $time, $message);
|
||||
}
|
||||
61
plugin/exercisemonitoring/index.php
Normal file
61
plugin/exercisemonitoring/index.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
$plugin = ExerciseMonitoringPlugin::create();
|
||||
$em = Database::getManager();
|
||||
|
||||
$isEnabled = $plugin->isEnabled(true);
|
||||
$showOverviewRegion = $isEnabled && strpos($_SERVER['SCRIPT_NAME'], '/main/exercise/overview.php') !== false;
|
||||
$showSubmitRegion = $isEnabled && strpos($_SERVER['SCRIPT_NAME'], '/main/exercise/exercise_submit.php') !== false;
|
||||
|
||||
$_template['enabled'] = false;
|
||||
$_template['show_overview_region'] = $showOverviewRegion;
|
||||
$_template['show_submit_region'] = $showSubmitRegion;
|
||||
|
||||
if ($showOverviewRegion || $showSubmitRegion) {
|
||||
$exerciseId = (int) $_GET['exerciseId'];
|
||||
|
||||
$objFieldValue = new ExtraFieldValue('exercise');
|
||||
$values = $objFieldValue->get_values_by_handler_and_field_variable(
|
||||
$exerciseId,
|
||||
ExerciseMonitoringPlugin::FIELD_SELECTED
|
||||
);
|
||||
|
||||
$_template['enabled'] = $values && (bool) $values['value'];
|
||||
$_template['exercise_id'] = $exerciseId;
|
||||
}
|
||||
|
||||
$_template['enable_snapshots'] = true;
|
||||
|
||||
$isAdult = $plugin->isAdult();
|
||||
|
||||
if ($showOverviewRegion && $_template['enabled']) {
|
||||
$_template['instructions'] = $plugin->get(ExerciseMonitoringPlugin::SETTING_INSTRUCTIONS);
|
||||
|
||||
if ('true' === $plugin->get(ExerciseMonitoringPlugin::SETTING_INSTRUCTION_AGE_DISTINCTION_ENABLE)) {
|
||||
$_template['instructions'] = $plugin->get(ExerciseMonitoringPlugin::SETTING_INSTRUCTIONS_MINORS);
|
||||
|
||||
if ($isAdult) {
|
||||
$_template['instructions'] = $plugin->get(ExerciseMonitoringPlugin::SETTING_INSTRUCTIONS_ADULTS);
|
||||
} else {
|
||||
$_template['enable_snapshots'] = false;
|
||||
}
|
||||
}
|
||||
|
||||
$_template['instructions'] = Security::remove_XSS($_template['instructions']);
|
||||
}
|
||||
|
||||
if ($showSubmitRegion && $_template['enabled']) {
|
||||
$exercise = new Exercise(api_get_course_int_id());
|
||||
|
||||
if ($exercise->read($_template['exercise_id'])) {
|
||||
$_template['exercise_type'] = (int) $exercise->selectType();
|
||||
|
||||
if ('true' === $plugin->get(ExerciseMonitoringPlugin::SETTING_INSTRUCTION_AGE_DISTINCTION_ENABLE)
|
||||
&& !$isAdult
|
||||
) {
|
||||
$_template['enable_snapshots'] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
5
plugin/exercisemonitoring/install.php
Normal file
5
plugin/exercisemonitoring/install.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
ExerciseMonitoringPlugin::create()->install();
|
||||
32
plugin/exercisemonitoring/lang/english.php
Normal file
32
plugin/exercisemonitoring/lang/english.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
$strings['plugin_title'] = "Exercise monitoring";
|
||||
$strings['plugin_comment'] = "Random photo taking mechanism during an exercise";
|
||||
|
||||
$strings['tool_enable'] = "Enable tool";
|
||||
$strings['intructions'] = 'Instructions';
|
||||
$strings['age_distinction_enable'] = 'Enable age distinction';
|
||||
$strings['legal_age'] = 'Legal age';
|
||||
$strings['extrafield_birtdate'] = 'Extra field for birthdate';
|
||||
$strings['extrafield_birtdate_help'] = 'The name of the field with which age will be calculated, e.g. <code>birthdate</code>';
|
||||
$strings['instructions_adults'] = 'Intructions for adults students';
|
||||
$strings['instructions_minors'] = 'Intrucctions for minors students';
|
||||
$strings['snapshots_lifetime'] = 'Life time of photos taken';
|
||||
$strings['snapshots_lifetime_help'] = 'Number of days that taken photos can remain stored on the server.<br>The cleanup script is located in <code>plugin/exercisemonitoring/cron/cleanup.php</code>';
|
||||
|
||||
$strings['ExerciseMonitored'] = "Exercise monitored";
|
||||
$strings['Retry'] = "Retry";
|
||||
$strings['IdDocumentSnapshot'] = "Validated photo of the ID document";
|
||||
$strings['StudentSnapshot'] = "Validated photo of the student";
|
||||
|
||||
$strings['ImageIdDocumentCameraInstructions'] = "Place your ID document in front of the camera and place it in the marked box. Click the <i>Capture</i> button or press the space bar on your keyboard.";
|
||||
$strings['ImageStudentCameraInstructions'] = "Place your face in front of the camera and place it within the marked circle. Click the <i>Capture</i> button or press the space bar on your keyboard";
|
||||
|
||||
$strings['Snapshots'] = "Snapshots";
|
||||
|
||||
$strings['ExerciseUnmonitored'] = "Exercise unmonitored";
|
||||
$strings['Birthdate'] = "Birthdate";
|
||||
$strings['AdultStudent'] = "Adult student";
|
||||
$strings['MinorStudent'] = "Minor student";
|
||||
32
plugin/exercisemonitoring/lang/spanish.php
Normal file
32
plugin/exercisemonitoring/lang/spanish.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
$strings['plugin_title'] = "Monitoreo de Ejercicios";
|
||||
$strings['plugin_comment'] = "Mecanismo de toma de fotos aleatorias durante un ejercicio";
|
||||
|
||||
$strings['tool_enable'] = "Enable tool";
|
||||
$strings['intructions'] = 'Intrucciones';
|
||||
$strings['age_distinction_enable'] = 'Habilitar distinción de edad';
|
||||
$strings['legal_age'] = 'Mayoría de edad';
|
||||
$strings['extrafield_birtdate'] = 'Campo extra para fecha de nacimiento';
|
||||
$strings['extrafield_birtdate_help'] = 'El nombre del campo con el cual se calculará la edad, por ejemplo <code>birthdate</code>';
|
||||
$strings['instructions_adults'] = 'Intrucciones para estudiantes adultos';
|
||||
$strings['instructions_minors'] = 'Intrucciones para estudiantes menores de edad';
|
||||
$strings['snapshots_lifetime'] = 'Tiempo de vida de las fotos tomadas';
|
||||
$strings['snapshots_lifetime_help'] = 'Cantidad de días que las fotos tomadas pueden permanecer almacenadas en el servidor.<br>El script de limpieza está ubicado en <code>plugin/exercisemonitoring/cron/cleanup.php</code>';
|
||||
|
||||
$strings['ExerciseMonitored'] = "Ejercicio monitoreado";
|
||||
$strings['Retry'] = "Reintentar";
|
||||
$strings['IdDocumentSnapshot'] = "Foto validada del documento de identidad";
|
||||
$strings['StudentSnapshot'] = "Foto validada del estudiante";
|
||||
|
||||
$strings['ImageIdDocumentCameraInstructions'] = "Coloca tu DNI o documento de identidad frente a la cámara y ubícalo en el recuadro marcado. Dale clic al botón <i>Capturar</i> o presiona la barra de espacio de tu teclado.";
|
||||
$strings['ImageStudentCameraInstructions'] = "Coloca tu rostro frente a la cámara y ubícalo dentro del círculo marcado. Dale click al botón <i>Capturar</i> o presiona la barra de espacio de tu teclado";
|
||||
|
||||
$strings['Snapshots'] = "Fotos tomadas";
|
||||
|
||||
$strings['ExerciseUnmonitored'] = "Ejercicio no monitoreado";
|
||||
$strings['Birthdate'] = "Fecha de nacimiento";
|
||||
$strings['AdultStudent'] = "Estudiante adulto";
|
||||
$strings['MinorStudent'] = "Estudiante menor de edad";
|
||||
32
plugin/exercisemonitoring/pages/detail.php
Normal file
32
plugin/exercisemonitoring/pages/detail.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\PluginBundle\ExerciseMonitoring\Controller\DetailController;
|
||||
use Chamilo\PluginBundle\ExerciseMonitoring\Entity\Log;
|
||||
use Symfony\Component\HttpFoundation\Request as HttpRequest;
|
||||
use Symfony\Component\HttpFoundation\Response as HttpResponse;
|
||||
|
||||
require_once __DIR__.'/../../../main/inc/global.inc.php';
|
||||
|
||||
if (!api_is_allowed_to_edit()) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
$em = Database::getManager();
|
||||
$logRepository = $em->getRepository(Log::class);
|
||||
|
||||
$detailController = new DetailController(
|
||||
ExerciseMonitoringPlugin::create(),
|
||||
HttpRequest::createFromGlobals(),
|
||||
$em,
|
||||
$logRepository
|
||||
);
|
||||
|
||||
try {
|
||||
$response = $detailController();
|
||||
} catch (Exception $e) {
|
||||
$response = HttpResponse::create('', HttpResponse::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
$response->send();
|
||||
18
plugin/exercisemonitoring/pages/exercise_submit.ajax.php
Normal file
18
plugin/exercisemonitoring/pages/exercise_submit.ajax.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
/* For license terms, see /license.txt */
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request as HttpRequest;
|
||||
|
||||
require_once __DIR__.'/../../../main/inc/global.inc.php';
|
||||
|
||||
api_protect_course_script();
|
||||
|
||||
$plugin = ExerciseMonitoringPlugin::create();
|
||||
$request = HttpRequest::createFromGlobals();
|
||||
$em = Database::getManager();
|
||||
|
||||
$exerciseSubmitController = new ExerciseSubmitController($plugin, $request, $em);
|
||||
|
||||
$response = $exerciseSubmitController();
|
||||
$response->send();
|
||||
18
plugin/exercisemonitoring/pages/start.ajax.php
Normal file
18
plugin/exercisemonitoring/pages/start.ajax.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
/* For license terms, see /license.txt */
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request as HttpRequest;
|
||||
|
||||
require_once __DIR__.'/../../../main/inc/global.inc.php';
|
||||
|
||||
api_protect_course_script();
|
||||
|
||||
$plugin = ExerciseMonitoringPlugin::create();
|
||||
$request = HttpRequest::createFromGlobals();
|
||||
$em = Database::getManager();
|
||||
|
||||
$startController = new StartController($plugin, $request, $em);
|
||||
|
||||
$response = $startController();
|
||||
$response->send();
|
||||
10
plugin/exercisemonitoring/plugin.php
Normal file
10
plugin/exercisemonitoring/plugin.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
$plugin_info = ExerciseMonitoringPlugin::create()->get_info();
|
||||
|
||||
$plugin_info['templates'] = [
|
||||
'templates/modal.html.twig',
|
||||
'templates/exercise_submit.html.twig',
|
||||
];
|
||||
111
plugin/exercisemonitoring/src/Controller/DetailController.php
Normal file
111
plugin/exercisemonitoring/src/Controller/DetailController.php
Normal file
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
/* For license terms, see /license.txt */
|
||||
|
||||
namespace Chamilo\PluginBundle\ExerciseMonitoring\Controller;
|
||||
|
||||
use Chamilo\CoreBundle\Entity\TrackEExercises;
|
||||
use Chamilo\CourseBundle\Entity\CQuiz;
|
||||
use Chamilo\PluginBundle\ExerciseFocused\Traits\DetailControllerTrait;
|
||||
use Display;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Exception;
|
||||
use Exercise;
|
||||
use ExerciseMonitoringPlugin;
|
||||
use Symfony\Component\HttpFoundation\Request as HttpRequest;
|
||||
use Symfony\Component\HttpFoundation\Response as HttpResponse;
|
||||
|
||||
class DetailController
|
||||
{
|
||||
use DetailControllerTrait;
|
||||
|
||||
/**
|
||||
* @var ExerciseMonitoringPlugin
|
||||
*/
|
||||
private $plugin;
|
||||
|
||||
/**
|
||||
* @var HttpRequest
|
||||
*/
|
||||
private $request;
|
||||
|
||||
/**
|
||||
* @var EntityManager
|
||||
*/
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* @var EntityRepository
|
||||
*/
|
||||
private $logRepository;
|
||||
|
||||
public function __construct(
|
||||
ExerciseMonitoringPlugin $plugin,
|
||||
HttpRequest $request,
|
||||
EntityManager $em,
|
||||
EntityRepository $logRepository
|
||||
) {
|
||||
$this->plugin = $plugin;
|
||||
$this->request = $request;
|
||||
$this->em = $em;
|
||||
$this->logRepository = $logRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __invoke(): HttpResponse
|
||||
{
|
||||
if (!$this->plugin->isEnabled(true)) {
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
$trackExe = $this->em->find(
|
||||
TrackEExercises::class,
|
||||
$this->request->query->getInt('id')
|
||||
);
|
||||
|
||||
if (!$trackExe) {
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
$exercise = $this->em->find(CQuiz::class, $trackExe->getExeExoId());
|
||||
$user = api_get_user_entity($trackExe->getExeUserId());
|
||||
|
||||
$objExercise = new Exercise($trackExe->getCId());
|
||||
$objExercise->read($trackExe->getExeExoId());
|
||||
|
||||
$logs = $this->logRepository->findSnapshots($objExercise, $trackExe);
|
||||
|
||||
$content = $this->generateHeader($objExercise, $user, $trackExe)
|
||||
.'<hr>'
|
||||
.$this->generateSnapshotList($logs, $trackExe->getExeUserId());
|
||||
|
||||
return HttpResponse::create($content);
|
||||
}
|
||||
|
||||
private function generateSnapshotList(array $logs, int $userId): string
|
||||
{
|
||||
$html = '';
|
||||
|
||||
foreach ($logs as $i => $log) {
|
||||
$date = api_get_local_time($log['createdAt'], null, null, true, true, true);
|
||||
|
||||
$html .= '<div class="col-xs-12 col-sm-6 col-md-3" style="clear: '.($i % 4 === 0 ? 'both' : 'none').';">';
|
||||
$html .= '<div class="thumbnail">';
|
||||
$html .= Display::img(
|
||||
ExerciseMonitoringPlugin::generateSnapshotUrl($userId, $log['imageFilename']),
|
||||
$date
|
||||
);
|
||||
$html .= '<div class="caption">';
|
||||
$html .= Display::tag('p', $date, ['class' => 'text-center']);
|
||||
$html .= Display::tag('div', $log['log_level'], ['class' => 'text-center']);
|
||||
$html .= '</div>';
|
||||
$html .= '</div>';
|
||||
$html .= '</div>';
|
||||
}
|
||||
|
||||
return '<div class="row">'.$html.'</div>';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
/* For license terms, see /license.txt */
|
||||
|
||||
use Chamilo\CoreBundle\Entity\TrackEExercises;
|
||||
use Chamilo\CourseBundle\Entity\CQuiz;
|
||||
use Chamilo\CourseBundle\Entity\CQuizQuestion;
|
||||
use Chamilo\PluginBundle\ExerciseMonitoring\Entity\Log;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
use Symfony\Component\HttpFoundation\Request as HttpRequest;
|
||||
use Symfony\Component\HttpFoundation\Response as HttpResponse;
|
||||
|
||||
class ExerciseSubmitController
|
||||
{
|
||||
private $plugin;
|
||||
private $request;
|
||||
private $em;
|
||||
|
||||
public function __construct(ExerciseMonitoringPlugin $plugin, HttpRequest $request, EntityManager $em)
|
||||
{
|
||||
$this->plugin = $plugin;
|
||||
$this->request = $request;
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Doctrine\ORM\OptimisticLockException
|
||||
* @throws \Doctrine\ORM\ORMException
|
||||
* @throws \Doctrine\ORM\TransactionRequiredException
|
||||
*/
|
||||
public function __invoke(): HttpResponse
|
||||
{
|
||||
$userDirName = $this->createDirectory();
|
||||
|
||||
$existingExeId = (int) ChamiloSession::read('exe_id');
|
||||
|
||||
$levelId = $this->request->request->getInt('level_id');
|
||||
$exerciseId = $this->request->request->getInt('exercise_id');
|
||||
|
||||
$exercise = $this->em->find(CQuiz::class, $exerciseId);
|
||||
|
||||
$objExercise = new Exercise();
|
||||
$objExercise->read($exerciseId);
|
||||
|
||||
$trackingExercise = $this->em->find(TrackEExercises::class, $existingExeId);
|
||||
|
||||
$newFilename = '';
|
||||
$level = 0;
|
||||
|
||||
/** @var UploadedFile $imgSubmit */
|
||||
if ($imgSubmit = $this->request->files->get('snapshot')) {
|
||||
$newFilename = uniqid().'_submit.jpg';
|
||||
|
||||
$imgSubmit->move($userDirName, $newFilename);
|
||||
}
|
||||
|
||||
if (ONE_PER_PAGE == $objExercise->selectType()) {
|
||||
$question = $this->em->find(CQuizQuestion::class, $levelId);
|
||||
$level = $question->getIid();
|
||||
}
|
||||
|
||||
$log = new Log();
|
||||
$log
|
||||
->setExercise($exercise)
|
||||
->setExe($trackingExercise)
|
||||
->setLevel($level)
|
||||
->setImageFilename($newFilename)
|
||||
;
|
||||
|
||||
$this->em->persist($log);
|
||||
|
||||
$this->updateOrphanSnapshots($exercise, $trackingExercise);
|
||||
|
||||
$this->em->flush();
|
||||
|
||||
return HttpResponse::create();
|
||||
}
|
||||
|
||||
private function createDirectory(): string
|
||||
{
|
||||
$user = api_get_user_entity(api_get_user_id());
|
||||
|
||||
$pluginDirName = api_get_path(SYS_UPLOAD_PATH).'plugins/exercisemonitoring';
|
||||
$userDirName = $pluginDirName.'/'.$user->getId();
|
||||
|
||||
$fs = new Filesystem();
|
||||
$fs->mkdir(
|
||||
[$pluginDirName, $userDirName],
|
||||
api_get_permissions_for_new_directories()
|
||||
);
|
||||
|
||||
return $userDirName;
|
||||
}
|
||||
|
||||
private function updateOrphanSnapshots(CQuiz $exercise, TrackEExercises $trackingExe)
|
||||
{
|
||||
$repo = $this->em->getRepository(Log::class);
|
||||
|
||||
$fileNamesToUpdate = ChamiloSession::read($this->plugin->get_name().'_orphan_snapshots', []);
|
||||
|
||||
if (empty($fileNamesToUpdate)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($fileNamesToUpdate as $filename) {
|
||||
$log = $repo->findOneBy(['imageFilename' => $filename, 'exercise' => $exercise, 'exe' => null]);
|
||||
|
||||
if (!$log) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$log->setExe($trackingExe);
|
||||
}
|
||||
|
||||
ChamiloSession::erase($this->plugin->get_name().'_orphan_snapshots');
|
||||
}
|
||||
}
|
||||
93
plugin/exercisemonitoring/src/Controller/StartController.php
Normal file
93
plugin/exercisemonitoring/src/Controller/StartController.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
/* For license terms, see /license.txt */
|
||||
|
||||
use Chamilo\CourseBundle\Entity\CQuiz;
|
||||
use Chamilo\PluginBundle\ExerciseMonitoring\Entity\Log;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
use Symfony\Component\HttpFoundation\Request as HttpRequest;
|
||||
use Symfony\Component\HttpFoundation\Response as HttpResponse;
|
||||
|
||||
class StartController
|
||||
{
|
||||
private $plugin;
|
||||
private $request;
|
||||
private $em;
|
||||
|
||||
public function __construct(ExerciseMonitoringPlugin $plugin, HttpRequest $request, EntityManager $em)
|
||||
{
|
||||
$this->plugin = $plugin;
|
||||
$this->request = $request;
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
public function __invoke(): HttpResponse
|
||||
{
|
||||
$userDirName = $this->createDirectory();
|
||||
|
||||
/** @var UploadedFile $imgIddoc */
|
||||
$imgIddoc = $this->request->files->get('iddoc');
|
||||
/** @var UploadedFile $imgLearner */
|
||||
$imgLearner = $this->request->files->get('learner');
|
||||
|
||||
$exercise = $this->em->find(CQuiz::class, $this->request->request->getInt('exercise_id'));
|
||||
|
||||
$fileNamesToUpdate = [];
|
||||
|
||||
if ($imgIddoc) {
|
||||
$newFilename = uniqid().'_iddoc.jpg';
|
||||
$fileNamesToUpdate[] = $newFilename;
|
||||
|
||||
$imgIddoc->move($userDirName, $newFilename);
|
||||
|
||||
$log = new Log();
|
||||
$log
|
||||
->setExercise($exercise)
|
||||
->setLevel(-1)
|
||||
->setImageFilename($newFilename)
|
||||
;
|
||||
|
||||
$this->em->persist($log);
|
||||
}
|
||||
|
||||
if ($imgLearner) {
|
||||
$newFilename = uniqid().'_learner.jpg';
|
||||
$fileNamesToUpdate[] = $newFilename;
|
||||
|
||||
$imgLearner->move($userDirName, $newFilename);
|
||||
|
||||
$log = new Log();
|
||||
$log
|
||||
->setExercise($exercise)
|
||||
->setLevel(-1)
|
||||
->setImageFilename($newFilename)
|
||||
;
|
||||
|
||||
$this->em->persist($log);
|
||||
}
|
||||
|
||||
$this->em->flush();
|
||||
|
||||
ChamiloSession::write($this->plugin->get_name().'_orphan_snapshots', $fileNamesToUpdate);
|
||||
|
||||
return HttpResponse::create();
|
||||
}
|
||||
|
||||
private function createDirectory(): string
|
||||
{
|
||||
$user = api_get_user_entity(api_get_user_id());
|
||||
|
||||
$pluginDirName = api_get_path(SYS_UPLOAD_PATH).'plugins/exercisemonitoring';
|
||||
$userDirName = $pluginDirName.'/'.$user->getId();
|
||||
|
||||
$fs = new Filesystem();
|
||||
$fs->mkdir(
|
||||
[$pluginDirName, $userDirName],
|
||||
api_get_permissions_for_new_directories()
|
||||
);
|
||||
|
||||
return $userDirName;
|
||||
}
|
||||
}
|
||||
133
plugin/exercisemonitoring/src/Entity/Log.php
Normal file
133
plugin/exercisemonitoring/src/Entity/Log.php
Normal file
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
namespace Chamilo\PluginBundle\ExerciseMonitoring\Entity;
|
||||
|
||||
use Chamilo\CoreBundle\Entity\TrackEExercises;
|
||||
use Chamilo\CoreBundle\Traits\TimestampableTypedEntity;
|
||||
use Chamilo\CourseBundle\Entity\CQuiz;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity(repositoryClass="Chamilo\PluginBundle\ExerciseMonitoring\Repository\LogRepository")
|
||||
* @ORM\Table(name="plugin_exercisemonitoring_log")
|
||||
*/
|
||||
class Log
|
||||
{
|
||||
use TimestampableTypedEntity;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*
|
||||
* @ORM\Column(name="id", type="integer")
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @var CQuiz
|
||||
*
|
||||
* @ORM\ManyToOne(targetEntity="Chamilo\CourseBundle\Entity\CQuiz")
|
||||
* @ORM\JoinColumn(name="exercise_id", referencedColumnName="iid")
|
||||
*/
|
||||
protected $exercise;
|
||||
|
||||
/**
|
||||
* @var TrackEExercises
|
||||
*
|
||||
* @ORM\ManyToOne(targetEntity="Chamilo\CoreBundle\Entity\TrackEExercises")
|
||||
* @ORM\JoinColumn(name="exe_id", referencedColumnName="exe_id")
|
||||
*/
|
||||
private $exe;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*
|
||||
* @ORM\Column(name="level", type="integer")
|
||||
*/
|
||||
private $level;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(name="image_filename", type="string")
|
||||
*/
|
||||
private $imageFilename;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*
|
||||
* @ORM\Column(name="removed", type="boolean", nullable=false, options={"default": false})
|
||||
*/
|
||||
private $removed;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->removed = false;
|
||||
}
|
||||
|
||||
public function getId(): int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getExercise(): CQuiz
|
||||
{
|
||||
return $this->exercise;
|
||||
}
|
||||
|
||||
public function setExercise(CQuiz $exercise): Log
|
||||
{
|
||||
$this->exercise = $exercise;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getExe(): ?TrackEExercises
|
||||
{
|
||||
return $this->exe;
|
||||
}
|
||||
|
||||
public function setExe(?TrackEExercises $exe): Log
|
||||
{
|
||||
$this->exe = $exe;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLevel(): int
|
||||
{
|
||||
return $this->level;
|
||||
}
|
||||
|
||||
public function setLevel(int $level): Log
|
||||
{
|
||||
$this->level = $level;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getImageFilename(): string
|
||||
{
|
||||
return $this->imageFilename;
|
||||
}
|
||||
|
||||
public function setImageFilename(string $imageFilename): Log
|
||||
{
|
||||
$this->imageFilename = $imageFilename;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isRemoved(): bool
|
||||
{
|
||||
return $this->removed;
|
||||
}
|
||||
|
||||
public function setRemoved(bool $removed): void
|
||||
{
|
||||
$this->removed = $removed;
|
||||
}
|
||||
}
|
||||
185
plugin/exercisemonitoring/src/ExerciseMonitoringPlugin.php
Normal file
185
plugin/exercisemonitoring/src/ExerciseMonitoringPlugin.php
Normal file
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\PluginBundle\ExerciseMonitoring\Entity\Log;
|
||||
use Doctrine\ORM\Tools\SchemaTool;
|
||||
use Doctrine\ORM\Tools\ToolsException;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
|
||||
class ExerciseMonitoringPlugin extends Plugin
|
||||
{
|
||||
public const SETTING_TOOL_ENABLE = 'tool_enable';
|
||||
public const SETTING_INSTRUCTIONS = 'intructions';
|
||||
public const SETTING_INSTRUCTION_AGE_DISTINCTION_ENABLE = 'age_distinction_enable';
|
||||
public const SETTING_INSTRUCTION_LEGAL_AGE = 'legal_age';
|
||||
public const SETTING_EXTRAFIELD_BIRTHDATE = 'extrafield_birtdate';
|
||||
public const SETTING_INSTRUCTIONS_ADULTS = 'instructions_adults';
|
||||
public const SETTING_INSTRUCTIONS_MINORS = 'instructions_minors';
|
||||
public const SETTING_SNAPSHOTS_LIFETIME = 'snapshots_lifetime';
|
||||
|
||||
public const FIELD_SELECTED = 'exercisemonitoring_selected';
|
||||
|
||||
private const TABLE_LOG = 'plugin_exercisemonitoring_log';
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
$version = '0.0.1';
|
||||
|
||||
$settings = [
|
||||
self::SETTING_TOOL_ENABLE => 'boolean',
|
||||
self::SETTING_INSTRUCTIONS => 'wysiwyg',
|
||||
self::SETTING_INSTRUCTION_AGE_DISTINCTION_ENABLE => 'boolean',
|
||||
self::SETTING_INSTRUCTION_LEGAL_AGE => 'text',
|
||||
self::SETTING_EXTRAFIELD_BIRTHDATE => 'text',
|
||||
self::SETTING_INSTRUCTIONS_ADULTS => 'wysiwyg',
|
||||
self::SETTING_INSTRUCTIONS_MINORS => 'wysiwyg',
|
||||
self::SETTING_SNAPSHOTS_LIFETIME => 'text',
|
||||
];
|
||||
|
||||
parent::__construct(
|
||||
$version,
|
||||
"Angel Fernando Quiroz Campos <angel.quiroz@beeznest.com>",
|
||||
$settings
|
||||
);
|
||||
}
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
static $result = null;
|
||||
|
||||
return $result ?: $result = new self();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ToolsException
|
||||
*/
|
||||
public function install()
|
||||
{
|
||||
$em = Database::getManager();
|
||||
|
||||
if ($em->getConnection()->getSchemaManager()->tablesExist([self::TABLE_LOG])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$schemaTool = new SchemaTool($em);
|
||||
$schemaTool->createSchema(
|
||||
[
|
||||
$em->getClassMetadata(Log::class),
|
||||
]
|
||||
);
|
||||
|
||||
$pluginDirName = api_get_path(SYS_UPLOAD_PATH).'plugins/exercisemonitoring';
|
||||
|
||||
$fs = new Filesystem();
|
||||
$fs->mkdir(
|
||||
$pluginDirName,
|
||||
api_get_permissions_for_new_directories()
|
||||
);
|
||||
|
||||
$objField = new ExtraField('exercise');
|
||||
$objField->save([
|
||||
'variable' => self::FIELD_SELECTED,
|
||||
'field_type' => ExtraField::FIELD_TYPE_CHECKBOX,
|
||||
'display_text' => $this->get_title(),
|
||||
'visible_to_self' => true,
|
||||
'changeable' => true,
|
||||
'filter' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
public function uninstall()
|
||||
{
|
||||
$em = Database::getManager();
|
||||
|
||||
if (!$em->getConnection()->getSchemaManager()->tablesExist([self::TABLE_LOG])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$schemaTool = new SchemaTool($em);
|
||||
$schemaTool->dropSchema(
|
||||
[
|
||||
$em->getClassMetadata(Log::class),
|
||||
]
|
||||
);
|
||||
|
||||
$objField = new ExtraField('exercise');
|
||||
$extraFieldInfo = $objField->get_handler_field_info_by_field_variable(self::FIELD_SELECTED);
|
||||
|
||||
if ($extraFieldInfo) {
|
||||
$objField->delete($extraFieldInfo['id']);
|
||||
}
|
||||
}
|
||||
|
||||
public function getAdminUrl(): string
|
||||
{
|
||||
$name = $this->get_name();
|
||||
$webPath = api_get_path(WEB_PLUGIN_PATH).$name;
|
||||
|
||||
return "$webPath/admin.php";
|
||||
}
|
||||
|
||||
public function generateDetailLink(int $exeId, int $userId): string
|
||||
{
|
||||
$title = $this->get_lang('ExerciseMonitored');
|
||||
$webcamIcon = Display::return_icon('webcam.png', $title);
|
||||
$webcamNaIcon = Display::return_icon('webcam_na.png', $this->get_lang('ExerciseUnmonitored'));
|
||||
|
||||
$monitoringDetailUrl = api_get_path(WEB_PLUGIN_PATH).'exercisemonitoring/pages/detail.php?'.api_get_cidreq()
|
||||
.'&'.http_build_query(['id' => $exeId]);
|
||||
|
||||
$url = Display::url(
|
||||
$webcamIcon,
|
||||
$monitoringDetailUrl,
|
||||
[
|
||||
'class' => 'ajax',
|
||||
'data-title' => $title,
|
||||
'data-size' => 'lg',
|
||||
]
|
||||
);
|
||||
|
||||
$showLink = true;
|
||||
|
||||
if ('true' === $this->get(self::SETTING_INSTRUCTION_AGE_DISTINCTION_ENABLE) && !$this->isAdult($userId)) {
|
||||
$showLink = false;
|
||||
}
|
||||
|
||||
return $showLink ? $url : $webcamNaIcon;
|
||||
}
|
||||
|
||||
public static function generateSnapshotUrl(
|
||||
int $userId,
|
||||
string $imageFileName,
|
||||
string $path = WEB_UPLOAD_PATH
|
||||
): string {
|
||||
$pluginDirName = api_get_path($path).'plugins/exercisemonitoring';
|
||||
|
||||
return $pluginDirName.'/'.$userId.'/'.$imageFileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function isAdult(int $userId = 0): bool
|
||||
{
|
||||
$userId = $userId ?: api_get_user_id();
|
||||
$fieldVariable = $this->get(self::SETTING_EXTRAFIELD_BIRTHDATE);
|
||||
$legalAge = (int) $this->get(self::SETTING_INSTRUCTION_LEGAL_AGE);
|
||||
|
||||
$value = UserManager::get_extra_user_data_by_field($userId, $fieldVariable);
|
||||
|
||||
if (empty($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($value[$fieldVariable])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$birthdate = new DateTime($value[$fieldVariable]);
|
||||
$now = new DateTime();
|
||||
$diff = $birthdate->diff($now);
|
||||
|
||||
return !$diff->invert && $diff->y >= $legalAge;
|
||||
}
|
||||
}
|
||||
47
plugin/exercisemonitoring/src/Repository/LogRepository.php
Normal file
47
plugin/exercisemonitoring/src/Repository/LogRepository.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
namespace Chamilo\PluginBundle\ExerciseMonitoring\Repository;
|
||||
|
||||
use Chamilo\CoreBundle\Entity\TrackEExercises;
|
||||
use Chamilo\CourseBundle\Entity\CQuizQuestion;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\Query\Expr\Join;
|
||||
use Exercise;
|
||||
|
||||
class LogRepository extends EntityRepository
|
||||
{
|
||||
public function findByLevelAndExe(int $level, TrackEExercises $exe): array
|
||||
{
|
||||
return $this->findBy(
|
||||
[
|
||||
'level' => $level,
|
||||
'exe' => $exe,
|
||||
],
|
||||
['createdAt' => 'ASC']
|
||||
);
|
||||
}
|
||||
|
||||
public function findSnapshots(Exercise $objExercise, TrackEExercises $trackExe)
|
||||
{
|
||||
$qb = $this->createQueryBuilder('l');
|
||||
|
||||
$qb->select(['l.imageFilename', 'l.createdAt']);
|
||||
|
||||
if (ONE_PER_PAGE == $objExercise->selectType()) {
|
||||
$qb
|
||||
->addSelect(['qq.question AS log_level'])
|
||||
->leftJoin(CQuizQuestion::class, 'qq', Join::WITH, 'l.level = qq.iid');
|
||||
}
|
||||
|
||||
$query = $qb
|
||||
->andWhere(
|
||||
$qb->expr()->eq('l.exe', $trackExe->getExeId())
|
||||
)
|
||||
->addOrderBy('l.createdAt')
|
||||
->getQuery();
|
||||
|
||||
return $query->getResult();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
{% if exercisemonitoring.show_submit_region and exercisemonitoring.enabled and exercisemonitoring.enable_snapshots %}
|
||||
{% set ALL_ON_ONE_PAGE = exercisemonitoring.exercise_type == 1 %}
|
||||
{% set ONE_PER_PAGE = exercisemonitoring.exercise_type == 2 %}
|
||||
|
||||
<div id="monitoring-camera"></div>
|
||||
|
||||
<script src="{{ _p.web }}web/assets/webcamjs/webcam.js"></script>
|
||||
<script>
|
||||
$(function () {
|
||||
var $btnSaveNow = $('button[name="save_now"]');
|
||||
var $btnEndTest = $('button[name="validate_all"]');
|
||||
|
||||
Webcam.set({
|
||||
height: 480,
|
||||
width: 640,
|
||||
});
|
||||
Webcam.attach('#monitoring-camera');
|
||||
Webcam.on('live', function () {
|
||||
{% if ALL_ON_ONE_PAGE %}
|
||||
snapAndSendData(0);
|
||||
{% elseif ONE_PER_PAGE %}
|
||||
snapByQuestion();
|
||||
{% endif %}
|
||||
});
|
||||
|
||||
{% if ALL_ON_ONE_PAGE %}
|
||||
$btnEndTest.on('click', function () {
|
||||
snapAndSendData(0);
|
||||
});
|
||||
{% elseif ONE_PER_PAGE %}
|
||||
$btnSaveNow.on('click', function () {
|
||||
snapByQuestion();
|
||||
});
|
||||
{% endif %}
|
||||
|
||||
function snapAndSendData(levelId) {
|
||||
Webcam.snap(function (dataUri) {
|
||||
sendData(levelId, dataUri);
|
||||
});
|
||||
}
|
||||
|
||||
function snapByQuestion() {
|
||||
var questionId = $btnSaveNow.data('question') || 0;
|
||||
|
||||
snapAndSendData(questionId);
|
||||
}
|
||||
|
||||
function sendData(questionId, imageUri) {
|
||||
var rawImgIdDoc = imageUri.replace(/^data:image\/\w+;base64,/, '');
|
||||
var blobImgIdDoc = new Blob( [ Webcam.base64DecToArr(rawImgIdDoc) ], {type: 'image/jpeg'} );
|
||||
|
||||
var formData = new FormData();
|
||||
formData.append('exercise_id', '{{ exercisemonitoring.exercise_id }}');
|
||||
formData.append('level_id', questionId);
|
||||
formData.append('snapshot', blobImgIdDoc, 'snapshot.jpg');
|
||||
|
||||
return $.ajax({
|
||||
url: '{{ _p.web_plugin }}exercisemonitoring/pages/exercise_submit.ajax.php',
|
||||
type: 'POST',
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
#plugin_pre_footer {
|
||||
position: relative;
|
||||
}
|
||||
#monitoring-camera {
|
||||
bottom: 15px;
|
||||
right: 15px;
|
||||
max-width: 108px;
|
||||
max-height: 81px;
|
||||
position: fixed;
|
||||
z-index: 1015;
|
||||
}
|
||||
#monitoring-camera video {
|
||||
max-width: 108px;
|
||||
max-height: 81px;
|
||||
}
|
||||
</style>
|
||||
{% endif %}
|
||||
314
plugin/exercisemonitoring/templates/modal.html.twig
Normal file
314
plugin/exercisemonitoring/templates/modal.html.twig
Normal file
@@ -0,0 +1,314 @@
|
||||
{% if exercisemonitoring.show_overview_region and exercisemonitoring.enabled %}
|
||||
{% if exercisemonitoring.enable_snapshots %}
|
||||
<div id="em-modal-start" class="modal fade in" tabindex="-1" role="dialog" data-backdrop="static" data-keyboard="false">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{ 'ExerciseMonitored'|get_plugin_lang('ExerciseMonitoringPlugin') }}</h4>
|
||||
</div>
|
||||
|
||||
<div class="modal-body" id="em-terms-body">
|
||||
{{ exercisemonitoring.instructions }}
|
||||
</div>
|
||||
|
||||
<div class="modal-body text-center" id="em-camera-body" style="display: none;">
|
||||
<p id="txt-iddoc-img-instructions" class="lead" style="display: block;">
|
||||
{{ 'ImageIdDocumentCameraInstructions'|get_plugin_lang('ExerciseMonitoringPlugin') }}
|
||||
</p>
|
||||
<p id="txt-learner-img-instructions" class="lead" style="display: none;">
|
||||
{{ 'ImageStudentCameraInstructions'|get_plugin_lang('ExerciseMonitoringPlugin') }}
|
||||
</p>
|
||||
<div style="position: relative">
|
||||
<div id="monitoring-camera" class="embed-responsive embed-responsive-4by3"></div>
|
||||
<img id="img-iddoc-placeholder" style="display: block;" src="{{ _p.web_plugin }}exercisemonitoring/assets/images/idcard.png">
|
||||
<img id="img-learner-placeholder" style="display: none" src="{{ _p.web_plugin }}exercisemonitoring/assets/images/user.png">
|
||||
</div>
|
||||
<br>
|
||||
<button class="btn btn-default" type="button" id="btn-snap" disabled>
|
||||
<span class="fa fa-camera" aria-hidden="true"></span>
|
||||
{{ 'Snapshot'|get_lang }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body text-center" id="em-iddoc-body" style="display: none;">
|
||||
<p class="lead">{{ 'IdDocumentSnapshot'|get_plugin_lang('ExerciseMonitoringPlugin') }}</p>
|
||||
<div id="img-iddoc"></div>
|
||||
</div>
|
||||
|
||||
<div class="modal-body text-center" id="em-student-body" style="display: none;">
|
||||
<p class="lead">{{ 'StudentSnapshot'|get_plugin_lang('ExerciseMonitoringPlugin') }}</p>
|
||||
<div id="img-learner"></div>
|
||||
</div>
|
||||
|
||||
<div id="em-terms-footer" class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" id="btn-accept">
|
||||
<span class="fa fa-check" aria-hidden="true"></span> {{ 'Accept'|get_lang }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="em-camera-footer" class="modal-footer" style="display: none;">
|
||||
<button class="btn btn-default" type="button" id="btn-retry" disabled>
|
||||
<span class="fa fa-refresh" aria-hidden="true"></span>
|
||||
{{ 'Retry'|get_plugin_lang('ExerciseMonitoringPlugin') }}
|
||||
</button>
|
||||
<button class="btn btn-primary" type="button" id="btn-next" disabled>
|
||||
<span class="fa fa-forward" aria-hidden="true"></span> {{ 'Next'|get_lang }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="{{ _p.web }}web/assets/webcamjs/webcam.js"></script>
|
||||
<script>
|
||||
$(function () {
|
||||
var $btnStartExercise = $('.exercise_overview_options a');
|
||||
var $bodyTerms = $('#em-terms-body');
|
||||
var $bodyCamera = $('#em-camera-body');
|
||||
var $bodyIdDoc = $('#em-iddoc-body');
|
||||
var $bodyStudent = $('#em-student-body');
|
||||
|
||||
var $footTerms = $('#em-terms-footer');
|
||||
var $footCamera = $('#em-camera-footer');
|
||||
|
||||
var $btnSnap = $('#btn-snap');
|
||||
var $btnRetry = $('#btn-retry');
|
||||
var $btnNext = $('#btn-next');
|
||||
|
||||
var $imgIdDoc = $('#img-iddoc');
|
||||
var $imgLearner = $('#img-learner');
|
||||
|
||||
var $txtIdDocInstructions = $('#txt-iddoc-img-instructions');
|
||||
var $txtLearnerInstructions = $('#txt-learner-img-instructions');
|
||||
var $imgIdDocPlaceholder = $('#img-iddoc-placeholder');
|
||||
var $imgLearnerPlaceholder = $('#img-learner-placeholder');
|
||||
|
||||
var hasIdDoc = false;
|
||||
var hasLearner = false;
|
||||
|
||||
var imgIdDoc = null;
|
||||
var imgLearner = null;
|
||||
|
||||
if ($btnStartExercise.length > 0) {
|
||||
$("#em-modal-start").modal("show");
|
||||
}
|
||||
|
||||
$btnStartExercise.addClass('disabled').attr('aria-disabled', 'true');
|
||||
|
||||
$("#btn-accept").on('click', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
$bodyTerms.hide();
|
||||
$footTerms.hide();
|
||||
|
||||
$bodyCamera.show();
|
||||
$footCamera.show();
|
||||
|
||||
Webcam.set({
|
||||
height: 480,
|
||||
width: 640,
|
||||
});
|
||||
Webcam.attach('#monitoring-camera');
|
||||
Webcam.on('live', function () {
|
||||
$txtIdDocInstructions.show();
|
||||
$imgIdDocPlaceholder.show();
|
||||
$txtLearnerInstructions.hide();
|
||||
$imgLearnerPlaceholder.hide();
|
||||
|
||||
$btnSnap.prop({disabled: false}).focus();
|
||||
$('#monitoring-camera video').addClass('embed-responsive-item');
|
||||
});
|
||||
});
|
||||
|
||||
$btnSnap.on('click', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
$btnSnap.prop({disabled: true});
|
||||
$btnRetry.prop({disabled: true});
|
||||
$btnNext.prop({disabled: true});
|
||||
|
||||
snap()
|
||||
.done(function () {
|
||||
$btnRetry.prop({disabled: false});
|
||||
$btnNext.prop({disabled: false});
|
||||
});
|
||||
});
|
||||
|
||||
$btnRetry.on('click', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
$btnSnap.prop({disabled: false}).focus();
|
||||
$btnRetry.prop({disabled: true});
|
||||
$btnNext.prop({disabled: true});
|
||||
|
||||
if (hasIdDoc && !hasLearner) {
|
||||
$bodyCamera.show();
|
||||
$bodyIdDoc.hide();
|
||||
|
||||
hasIdDoc = false;
|
||||
hasLearner = false;
|
||||
} else if (hasIdDoc && hasLearner) {
|
||||
$bodyCamera.show();
|
||||
$bodyStudent.hide();
|
||||
|
||||
hasIdDoc = true;
|
||||
hasLearner = false;
|
||||
}
|
||||
});
|
||||
|
||||
$btnNext.on('click', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
$btnRetry.prop({disabled: true});
|
||||
|
||||
if (hasIdDoc && !hasLearner) {
|
||||
$bodyIdDoc.hide();
|
||||
$bodyCamera.show();
|
||||
|
||||
$txtIdDocInstructions.hide();
|
||||
$imgIdDocPlaceholder.hide();
|
||||
$txtLearnerInstructions.show();
|
||||
$imgLearnerPlaceholder.show();
|
||||
|
||||
$btnSnap.prop({disabled: false}).focus();
|
||||
} else if (hasIdDoc && hasLearner) {
|
||||
$btnNext.prop({disabled: true});
|
||||
$btnSnap.prop({disabled: true});
|
||||
|
||||
Webcam.reset();
|
||||
|
||||
sendData().done(function () {
|
||||
$btnStartExercise.removeClass('disabled').removeAttr('aria-disabled');
|
||||
|
||||
window.location = $btnStartExercise.prop('href');
|
||||
|
||||
$("#em-modal-start").modal('hide');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$(window).on('keyup', function (e) {
|
||||
if (32 === event.which && !$btnSnap.prop('disabled')) {
|
||||
e.preventDefault();
|
||||
|
||||
$btnSnap.trigger('click');
|
||||
}
|
||||
});
|
||||
|
||||
function snap() {
|
||||
var deferred = $.Deferred();
|
||||
|
||||
Webcam.snap(function (dataUri) {
|
||||
var $imgSnapshot = $('<img>')
|
||||
.prop({src: dataUri, id: 'img-snapshot'})
|
||||
.addClass('img-responsive');
|
||||
|
||||
if (!hasIdDoc && !hasLearner) {
|
||||
$imgIdDoc.html($imgSnapshot);
|
||||
|
||||
$bodyCamera.hide();
|
||||
$bodyIdDoc.show();
|
||||
|
||||
hasIdDoc = true;
|
||||
hasLearner = false;
|
||||
|
||||
imgIdDoc = dataUri;
|
||||
} else if (hasIdDoc && !hasLearner) {
|
||||
$imgLearner.html($imgSnapshot);
|
||||
|
||||
$bodyCamera.hide();
|
||||
$bodyStudent.show();
|
||||
|
||||
hasIdDoc = true;
|
||||
hasLearner = true;
|
||||
|
||||
imgLearner = dataUri;
|
||||
}
|
||||
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
return deferred.promise();
|
||||
}
|
||||
|
||||
function sendData() {
|
||||
var rawImgIdDoc = imgIdDoc.replace(/^data:image\/\w+;base64,/, '');
|
||||
var blobImgIdDoc = new Blob( [ Webcam.base64DecToArr(rawImgIdDoc) ], {type: 'image/jpeg'} );
|
||||
|
||||
var rawImgLearner = imgLearner.replace(/^data:image\/\w+;base64,/, '');
|
||||
var blobImgLearner = new Blob( [ Webcam.base64DecToArr(rawImgLearner) ], {type: 'image/jpeg'} );
|
||||
|
||||
var formData = new FormData();
|
||||
formData.append('iddoc', blobImgIdDoc, 'iddoc.jpg');
|
||||
formData.append('learner', blobImgLearner, 'learner.jpg');
|
||||
formData.append('exercise_id', '{{ exercisemonitoring.exercise_id }}');
|
||||
|
||||
return $.ajax({
|
||||
url: '{{ _p.web_plugin }}exercisemonitoring/pages/start.ajax.php',
|
||||
type: 'POST',
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
#monitoring-camera {
|
||||
height: auto !important;
|
||||
max-width: 100% !important;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#em-camera-body img#img-iddoc-placeholder,
|
||||
#em-camera-body img#img-learner-placeholder {
|
||||
height: auto;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#monitoring-camera video {
|
||||
height: auto !important;
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
{% else %}
|
||||
<div id="em-modal-start" class="modal fade in" tabindex="1" role="dialog" data-backdrop="static" data-keyboard="false" data-show="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{ 'ExerciseMonitored'|get_plugin_lang('ExerciseMonitoringPlugin') }}</h4>
|
||||
</div>
|
||||
|
||||
<div class="modal-body" id="em-terms-body">
|
||||
{{ exercisemonitoring.instructions }}
|
||||
</div>
|
||||
|
||||
<div id="em-terms-footer" class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" data-dismiss="modal">
|
||||
<span class="fa fa-check" aria-hidden="true"></span> {{ 'Accept'|get_lang }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$(function () {
|
||||
var $modal = $("#em-modal-start");
|
||||
var $btnStartExercise = $('.exercise_overview_options a');
|
||||
|
||||
if ($btnStartExercise.length > 0) {
|
||||
$modal.modal("show");
|
||||
}
|
||||
|
||||
$modal.on('hidden.bs.modal', function (e) {
|
||||
$btnStartExercise.removeClass('disabled').removeAttr('aria-disabled');
|
||||
|
||||
window.location = $btnStartExercise.prop('href');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
5
plugin/exercisemonitoring/uninstall.php
Normal file
5
plugin/exercisemonitoring/uninstall.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
ExerciseMonitoringPlugin::create()->uninstall();
|
||||
Reference in New Issue
Block a user