upgrade
This commit is contained in:
32
plugin/exercisefocused/admin.php
Normal file
32
plugin/exercisefocused/admin.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\PluginBundle\ExerciseFocused\Controller\AdminController;
|
||||
use Chamilo\PluginBundle\ExerciseFocused\Entity\Log;
|
||||
use Symfony\Component\HttpFoundation\Request as HttpRequest;
|
||||
use Symfony\Component\HttpFoundation\Response as HttpResponse;
|
||||
|
||||
$cidReset = true;
|
||||
|
||||
require_once __DIR__.'/../../main/inc/global.inc.php';
|
||||
|
||||
api_protect_admin_script();
|
||||
|
||||
$em = Database::getManager();
|
||||
$logRepository = $em->getRepository(Log::class);
|
||||
|
||||
$reportingController = new AdminController(
|
||||
ExerciseFocusedPlugin::create(),
|
||||
HttpRequest::createFromGlobals(),
|
||||
$em,
|
||||
$logRepository
|
||||
);
|
||||
|
||||
try {
|
||||
$response = $reportingController();
|
||||
} catch (Exception $e) {
|
||||
$response = HttpResponse::create('', HttpResponse::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
$response->send();
|
||||
48
plugin/exercisefocused/index.php
Normal file
48
plugin/exercisefocused/index.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\CoreBundle\Entity\TrackEExercises;
|
||||
use Chamilo\PluginBundle\ExerciseFocused\Entity\Log;
|
||||
|
||||
$plugin = ExerciseFocusedPlugin::create();
|
||||
|
||||
$exerciseId = (int) ($_GET['exerciseId'] ?? 0);
|
||||
|
||||
$renderRegion = $plugin->isEnableForExercise($exerciseId);
|
||||
|
||||
if ($renderRegion) {
|
||||
$_template['show_region'] = true;
|
||||
|
||||
$em = Database::getManager();
|
||||
|
||||
$existingExeId = (int) ChamiloSession::read('exe_id');
|
||||
$trackingExercise = null;
|
||||
|
||||
if ($existingExeId) {
|
||||
$trackingExercise = $em->find(TrackEExercises::class, $existingExeId);
|
||||
}
|
||||
|
||||
$_template['sec_token'] = Security::get_token('exercisefocused');
|
||||
|
||||
if ('true' === $plugin->get(ExerciseFocusedPlugin::SETTING_ENABLE_OUTFOCUSED_LIMIT)) {
|
||||
$logRepository = $em->getRepository(Log::class);
|
||||
|
||||
if ($trackingExercise) {
|
||||
$countOutfocused = $logRepository->countByActionInExe($trackingExercise, Log::TYPE_OUTFOCUSED);
|
||||
} else {
|
||||
$countOutfocused = 0;
|
||||
}
|
||||
|
||||
$_template['count_outfocused'] = $countOutfocused;
|
||||
$_template['remaining_outfocused'] = (int) $plugin->get(ExerciseFocusedPlugin::SETTING_OUTFOCUSED_LIMIT) - $countOutfocused;
|
||||
}
|
||||
|
||||
if ($trackingExercise) {
|
||||
$exercise = new Exercise($trackingExercise->getCId());
|
||||
|
||||
if ($exercise->read($trackingExercise->getExeExoId())) {
|
||||
$_template['exercise_type'] = (int) $exercise->selectType();
|
||||
}
|
||||
}
|
||||
}
|
||||
5
plugin/exercisefocused/install.php
Normal file
5
plugin/exercisefocused/install.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
ExerciseFocusedPlugin::create()->install();
|
||||
39
plugin/exercisefocused/lang/english.php
Normal file
39
plugin/exercisefocused/lang/english.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
$strings['plugin_title'] = "Exercise Focused";
|
||||
$strings['plugin_comment'] = "Show a message to return to the exercise when the user exits the Chamilo window/tab.";
|
||||
|
||||
$strings['tool_enable'] = "Enable tool";
|
||||
$strings['enable_time_limit'] = 'Enable time limit';
|
||||
$strings['time_limit'] = "Limit time";
|
||||
$strings['time_limit_help'] = "Limit time (in seconds) to return to the exercise. After this time the exercise will be closed.";
|
||||
$strings['enable_outfocused_limit'] = "Enable maximum of outfocused";
|
||||
$strings['outfocused_limit'] = "Maximum number of outfocused allowed";
|
||||
$strings['outfocused_limit_help'] = "Number of outfocused allowed. After this limit the exercise will be closed.";
|
||||
$strings['session_field_filters'] = "Session field as filter";
|
||||
$strings['session_field_filters_help'] = "Extra field names separeted by a comma.";
|
||||
$strings['percentage_sampling'] = "Percentage of sampling attempts";
|
||||
$strings['percentage_sampling_help'] = "A percentage of attempts will be selected for random review";
|
||||
|
||||
$strings['ReportByAttempts'] = "Exercise focused: Report by attempts";
|
||||
$strings['YouHaveLeftTheExercise'] = "Careful! We detect that you have left the exam window.<br><br>You must return and complete it.";
|
||||
$strings['YouHaveXTimeToReturn'] = "You have <span class=\"h3 text-danger\" id=\"time-limit-target\">%s</span> seconds to return";
|
||||
$strings['YouAreAllowedXOutfocused'] = "You are allowed <span class=\"h3 text-danger\" id=\"outfocused-limit-target\">%d</span> outfocused";
|
||||
$strings['OutfocusedLimitExceeded'] = "You have exceeded the allowed limit of outfocused";
|
||||
$strings['SelectExercise'] = "Select exercise";
|
||||
$strings['UnselectExercise'] = "Unselect exercise";
|
||||
$strings['Returns'] = "Returns";
|
||||
$strings['MaxOutfocusedReached'] = "Max outfocused reached";
|
||||
$strings['TimeLimitReached'] = "Time limit reached";
|
||||
$strings['Outfocused'] = "Outfocused";
|
||||
$strings['Return'] = "Return";
|
||||
$strings['Motive'] = "Motive";
|
||||
$strings['AlertBeforeLeaving'] = "Please stay within the exam";
|
||||
$strings['RandomSampling'] = "Random sampling";
|
||||
$strings['WindowTitleOutfocused'] = '🚨 Stay within the exam!';
|
||||
$strings['LevelReached'] = 'Level reached';
|
||||
$strings['ExerciseStartDateAndTime'] = "Exercise start date and time";
|
||||
$strings['ExerciseEndDateAndTime'] = "Exercise end date and time";
|
||||
$strings['MotiveExerciseFinished'] = "Successfully completed the exam";
|
||||
39
plugin/exercisefocused/lang/spanish.php
Normal file
39
plugin/exercisefocused/lang/spanish.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
$strings['plugin_title'] = "Enfoque en el Ejercicio";
|
||||
$strings['plugin_comment'] = "Mostrar un mensaje para regresar al ejercicio cuando el usuario sale de la ventana/pestaña de Chamilo.";
|
||||
|
||||
$strings['tool_enable'] = "Habilitar herramienta";
|
||||
$strings['enable_time_limit'] = 'Habilitar límite de tiempo';
|
||||
$strings['time_limit'] = "Límite de tiempo";
|
||||
$strings['time_limit_help'] = "Límite el tiempo (en segundos) para regresar al ejercicio. Pasado este tiempo, el ejercicio se cerrará.";
|
||||
$strings['enable_outfocused_limit'] = "Habilitar el máximo de desenfoque";
|
||||
$strings['outfocused_limit'] = "Número máximo de desenfoques permitidos";
|
||||
$strings['outfocused_limit_help'] = "Número de desenfoques permitidos. Después de este límite, el ejercicio se cerrará.";
|
||||
$strings['session_field_filters'] = "Campo de sesión como filtro";
|
||||
$strings['session_field_filters_help'] = "Nombres de campos adicionales separados por comas.";
|
||||
$strings['percentage_sampling'] = "Porcentaje de intentos de muestreo";
|
||||
$strings['percentage_sampling_help'] = "Se seleccionará un porcentaje de intentos para una revisión aleatoria";
|
||||
|
||||
$strings['ReportByAttempts'] = "Enfoque en el Ejercicio: Informe por intentos";
|
||||
$strings['YouHaveLeftTheExercise'] = "¡Cuidado! Detectamos que has abandonado la ventana del examen.<br><br>Debes retornar y culminarlo.";
|
||||
$strings['YouHaveXTimeToReturn'] = "Tienes <span class=\"h3 text-danger\" id=\"time-limit-target\">%s</span> segundos para regresar";
|
||||
$strings['YouAreAllowedXOutfocused'] = "Se te permite <span class=\"h3 text-danger\" id=\"outfocused-limit-target\">%d</span> desenfoques";
|
||||
$strings['OutfocusedLimitExceeded'] = "Has excedido el límite permitido de desenfoques";
|
||||
$strings['SelectExercise'] = "Seleccionar ejercicio";
|
||||
$strings['UnselectExercise'] = "Deseleccionar ejercicio";
|
||||
$strings['Returns'] = "Regresos";
|
||||
$strings['MaxOutfocusedReached'] = "Se ha alcanzado el máximo de desenfoques";
|
||||
$strings['TimeLimitReached'] = "Se ha alcanzado el límite de tiempo";
|
||||
$strings['Outfocused'] = "Desenfoques";
|
||||
$strings['Return'] = "Regresos";
|
||||
$strings['Motive'] = "Motivo";
|
||||
$strings['AlertBeforeLeaving'] = "Por favor, mantente dentro del examen.";
|
||||
$strings['RandomSampling'] = "Muestreo Aleatorio";
|
||||
$strings['WindowTitleOutfocused'] = '🚨 Retorna y culmina tu examen';
|
||||
$strings['LevelReached'] = 'Nivel alcanzado';
|
||||
$strings['ExerciseStartDateAndTime'] = "Fecha y hora de inicio del ejercicio";
|
||||
$strings['ExerciseEndDateAndTime'] = "Fecha y hora de finalización del ejercicio";
|
||||
$strings['MotiveExerciseFinished'] = "Culminó exitosamente el examen";
|
||||
32
plugin/exercisefocused/pages/detail.php
Normal file
32
plugin/exercisefocused/pages/detail.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\PluginBundle\ExerciseFocused\Controller\DetailController;
|
||||
use Chamilo\PluginBundle\ExerciseFocused\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(
|
||||
ExerciseFocusedPlugin::create(),
|
||||
HttpRequest::createFromGlobals(),
|
||||
$em,
|
||||
$logRepository
|
||||
);
|
||||
|
||||
try {
|
||||
$response = $detailController();
|
||||
} catch (Exception $e) {
|
||||
$response = HttpResponse::create('', HttpResponse::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
$response->send();
|
||||
298
plugin/exercisefocused/pages/export.php
Normal file
298
plugin/exercisefocused/pages/export.php
Normal file
@@ -0,0 +1,298 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\CoreBundle\Entity\TrackEAttempt;
|
||||
use Chamilo\CoreBundle\Entity\TrackEExercises;
|
||||
use Chamilo\CourseBundle\Entity\CQuiz;
|
||||
use Chamilo\PluginBundle\ExerciseFocused\Entity\Log as FocusedLog;
|
||||
use Chamilo\PluginBundle\ExerciseMonitoring\Entity\Log as MonitoringLog;
|
||||
use Chamilo\UserBundle\Entity\User;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Query\Expr\Join;
|
||||
use Symfony\Component\HttpFoundation\Request as HttpRequest;
|
||||
|
||||
require_once __DIR__.'/../../../main/inc/global.inc.php';
|
||||
|
||||
api_protect_course_script(true);
|
||||
|
||||
if (!api_is_allowed_to_edit()) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
$plugin = ExerciseFocusedPlugin::create();
|
||||
$monitoringPlugin = ExerciseMonitoringPlugin::create();
|
||||
$monitoringPluginIsEnabled = $monitoringPlugin->isEnabled(true);
|
||||
$request = HttpRequest::createFromGlobals();
|
||||
$em = Database::getManager();
|
||||
$focusedLogRepository = $em->getRepository(FocusedLog::class);
|
||||
$attempsRepository = $em->getRepository(TrackEAttempt::class);
|
||||
|
||||
if (!$plugin->isEnabled(true)) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
$params = $request->query->all();
|
||||
|
||||
$results = findResults($params, $em, $plugin);
|
||||
|
||||
$data = [];
|
||||
|
||||
/** @var array<string, mixed> $result */
|
||||
foreach ($results as $result) {
|
||||
/** @var TrackEExercises $trackExe */
|
||||
$trackExe = $result['exe'];
|
||||
$user = api_get_user_entity($trackExe->getExeUserId());
|
||||
|
||||
$outfocusedLimitCount = $focusedLogRepository->countByActionInExe($trackExe, FocusedLog::TYPE_OUTFOCUSED_LIMIT);
|
||||
$timeLimitCount = $focusedLogRepository->countByActionInExe($trackExe, FocusedLog::TYPE_TIME_LIMIT);
|
||||
|
||||
$exercise = new Exercise($trackExe->getCId());
|
||||
$exercise->read($trackExe->getExeExoId());
|
||||
|
||||
$quizType = (int) $exercise->selectType();
|
||||
|
||||
$data[] = [
|
||||
get_lang('LoginName'),
|
||||
$user->getUsername(),
|
||||
];
|
||||
$data[] = [
|
||||
get_lang('Student'),
|
||||
$user->getFirstname(),
|
||||
$user->getLastname(),
|
||||
];
|
||||
|
||||
if ($monitoringPluginIsEnabled
|
||||
&& 'true' === $monitoringPlugin->get(ExerciseMonitoringPlugin::SETTING_INSTRUCTION_AGE_DISTINCTION_ENABLE)
|
||||
) {
|
||||
$fieldVariable = $monitoringPlugin->get(ExerciseMonitoringPlugin::SETTING_EXTRAFIELD_BIRTHDATE);
|
||||
$birthdateValue = UserManager::get_extra_user_data_by_field($user->getId(), $fieldVariable);
|
||||
|
||||
$data[] = [
|
||||
$monitoringPlugin->get_lang('Birthdate'),
|
||||
$birthdateValue ? $birthdateValue[$fieldVariable] : '----',
|
||||
$monitoringPlugin->isAdult($user->getId())
|
||||
? $monitoringPlugin->get_lang('AdultStudent')
|
||||
: $monitoringPlugin->get_lang('MinorStudent'),
|
||||
];
|
||||
}
|
||||
|
||||
if ($trackExe->getSessionId()) {
|
||||
$data[] = [
|
||||
get_lang('SessionName'),
|
||||
api_get_session_entity($trackExe->getSessionId())->getName(),
|
||||
];
|
||||
}
|
||||
|
||||
$data[] = [
|
||||
get_lang('CourseTitle'),
|
||||
api_get_course_entity($trackExe->getCId())->getTitle(),
|
||||
];
|
||||
$data[] = [
|
||||
get_lang('ExerciseName'),
|
||||
$exercise->getUnformattedTitle(),
|
||||
];
|
||||
$data[] = [
|
||||
$plugin->get_lang('ExerciseStartDateAndTime'),
|
||||
api_get_local_time($result['exe']->getStartDate(), null, null, true, true, true),
|
||||
];
|
||||
$data[] = [
|
||||
$plugin->get_lang('ExerciseEndDateAndTime'),
|
||||
api_get_local_time($result['exe']->getExeDate(), null, null, true, true, true),
|
||||
];
|
||||
$data[] = [
|
||||
get_lang('IP'),
|
||||
$result['exe']->getUserIp(),
|
||||
];
|
||||
$data[] = [
|
||||
$plugin->get_lang('Motive'),
|
||||
$plugin->calculateMotive($outfocusedLimitCount, $timeLimitCount),
|
||||
];
|
||||
$data[] = [];
|
||||
|
||||
$data[] = [
|
||||
$plugin->get_lang('LevelReached'),
|
||||
get_lang('DateExo'),
|
||||
get_lang('Score'),
|
||||
$plugin->get_lang('Outfocused'),
|
||||
$plugin->get_lang('Returns'),
|
||||
$monitoringPluginIsEnabled ? $monitoringPlugin->get_lang('Snapshots') : '',
|
||||
];
|
||||
|
||||
if (ONE_PER_PAGE === $quizType) {
|
||||
$questionList = explode(',', $trackExe->getDataTracking());
|
||||
|
||||
foreach ($questionList as $idx => $questionId) {
|
||||
$attempt = $attempsRepository->findOneBy(
|
||||
['exeId' => $trackExe->getExeId(), 'questionId' => $questionId],
|
||||
['tms' => 'DESC']
|
||||
);
|
||||
|
||||
if (!$attempt) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$result = $exercise->manage_answer(
|
||||
$trackExe->getExeId(),
|
||||
$questionId,
|
||||
null,
|
||||
'exercise_result',
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
$exercise->selectPropagateNeg()
|
||||
);
|
||||
|
||||
$row = [
|
||||
get_lang('QuestionNumber').' '.($idx + 1),
|
||||
api_get_local_time($attempt->getTms()),
|
||||
$result['score'].' / '.$result['weight'],
|
||||
$focusedLogRepository->countByActionAndLevel($trackExe, FocusedLog::TYPE_OUTFOCUSED, $questionId),
|
||||
$focusedLogRepository->countByActionAndLevel($trackExe, FocusedLog::TYPE_RETURN, $questionId),
|
||||
getSnapshotListForLevel($questionId, $trackExe),
|
||||
];
|
||||
|
||||
$data[] = $row;
|
||||
}
|
||||
} elseif (ALL_ON_ONE_PAGE === $quizType) {
|
||||
}
|
||||
|
||||
$data[] = [];
|
||||
$data[] = [];
|
||||
$data[] = [];
|
||||
}
|
||||
|
||||
Export::arrayToXls($data);
|
||||
|
||||
function getSessionIdFromFormValues(array $formValues, array $fieldVariableList): array
|
||||
{
|
||||
$fieldItemIdList = [];
|
||||
$objFieldValue = new ExtraFieldValue('session');
|
||||
|
||||
foreach ($fieldVariableList as $fieldVariable) {
|
||||
if (!isset($formValues["extra_$fieldVariable"])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$itemValues = $objFieldValue->get_item_id_from_field_variable_and_field_value(
|
||||
$fieldVariable,
|
||||
$formValues["extra_$fieldVariable"],
|
||||
false,
|
||||
false,
|
||||
true
|
||||
);
|
||||
|
||||
foreach ($itemValues as $itemValue) {
|
||||
$fieldItemIdList[] = (int) $itemValue['item_id'];
|
||||
}
|
||||
}
|
||||
|
||||
return array_unique($fieldItemIdList);
|
||||
}
|
||||
|
||||
function findResults(array $formValues, EntityManagerInterface $em, ExerciseFocusedPlugin $plugin)
|
||||
{
|
||||
$cId = api_get_course_int_id();
|
||||
$sId = api_get_session_id();
|
||||
|
||||
$qb = $em->createQueryBuilder();
|
||||
$qb
|
||||
->select('te AS exe, q.title, te.startDate , u.firstname, u.lastname, u.username')
|
||||
->from(TrackEExercises::class, 'te')
|
||||
->innerJoin(CQuiz::class, 'q', Join::WITH, 'te.exeExoId = q.iid')
|
||||
->innerJoin(User::class, 'u', Join::WITH, 'te.exeUserId = u.id');
|
||||
|
||||
$params = [];
|
||||
|
||||
if ($cId) {
|
||||
$qb->andWhere($qb->expr()->eq('te.cId', ':cId'));
|
||||
|
||||
$params['cId'] = $cId;
|
||||
|
||||
$sessionItemIdList = $sId ? [$sId] : [];
|
||||
} else {
|
||||
$sessionItemIdList = getSessionIdFromFormValues(
|
||||
$formValues,
|
||||
$plugin->getSessionFieldList()
|
||||
);
|
||||
}
|
||||
|
||||
if ($sessionItemIdList) {
|
||||
$qb->andWhere($qb->expr()->in('te.sessionId', ':sessionItemIdList'));
|
||||
|
||||
$params['sessionItemIdList'] = $sessionItemIdList;
|
||||
}
|
||||
|
||||
if (!empty($formValues['username'])) {
|
||||
$qb->andWhere($qb->expr()->eq('u.username', ':username'));
|
||||
|
||||
$params['username'] = $formValues['username'];
|
||||
}
|
||||
|
||||
if (!empty($formValues['firstname'])) {
|
||||
$qb->andWhere($qb->expr()->eq('u.firstname', ':firstname'));
|
||||
|
||||
$params['firstname'] = $formValues['firstname'];
|
||||
}
|
||||
|
||||
if (!empty($formValues['lastname'])) {
|
||||
$qb->andWhere($qb->expr()->eq('u.lastname', ':lastname'));
|
||||
|
||||
$params['lastname'] = $formValues['lastname'];
|
||||
}
|
||||
|
||||
if (!empty($formValues['start_date'])) {
|
||||
$qb->andWhere(
|
||||
$qb->expr()->andX(
|
||||
$qb->expr()->gte('te.startDate', ':start_date'),
|
||||
$qb->expr()->lte('te.exeDate', ':end_date')
|
||||
)
|
||||
);
|
||||
|
||||
$params['start_date'] = api_get_utc_datetime($formValues['start_date'].' 00:00:00', false, true);
|
||||
$params['end_date'] = api_get_utc_datetime($formValues['start_date'].' 23:59:59', false, true);
|
||||
}
|
||||
|
||||
if (empty($params)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ($cId && !empty($formValues['id'])) {
|
||||
$qb->andWhere($qb->expr()->eq('q.iid', ':q_id'));
|
||||
|
||||
$params['q_id'] = $formValues['id'];
|
||||
}
|
||||
|
||||
$qb->setParameters($params);
|
||||
|
||||
$query = $qb->getQuery();
|
||||
|
||||
return $query->getResult();
|
||||
}
|
||||
|
||||
function getSnapshotListForLevel(int $level, TrackEExercises $trackExe): string
|
||||
{
|
||||
$monitoringPluginIsEnabled = ExerciseMonitoringPlugin::create()->isEnabled(true);
|
||||
|
||||
if (!$monitoringPluginIsEnabled) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$user = api_get_user_entity($trackExe->getExeUserId());
|
||||
$monitoringLogRepository = Database::getManager()->getRepository(MonitoringLog::class);
|
||||
|
||||
$monitoringLogsByQuestion = $monitoringLogRepository->findByLevelAndExe($level, $trackExe);
|
||||
$snapshotList = [];
|
||||
|
||||
/** @var MonitoringLog $logByQuestion */
|
||||
foreach ($monitoringLogsByQuestion as $logByQuestion) {
|
||||
$snapshotUrl = ExerciseMonitoringPlugin::generateSnapshotUrl(
|
||||
$user->getId(),
|
||||
$logByQuestion->getImageFilename()
|
||||
);
|
||||
$snapshotList[] = api_get_local_time($logByQuestion->getCreatedAt()).' '.$snapshotUrl;
|
||||
}
|
||||
|
||||
return implode(PHP_EOL, $snapshotList);
|
||||
}
|
||||
30
plugin/exercisefocused/pages/log.php
Normal file
30
plugin/exercisefocused/pages/log.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\PluginBundle\ExerciseFocused\Controller\LogController;
|
||||
use Chamilo\PluginBundle\ExerciseFocused\Entity\Log;
|
||||
use Symfony\Component\HttpFoundation\Request as HttpRequest;
|
||||
use Symfony\Component\HttpFoundation\Response as HttpResponse;
|
||||
|
||||
require_once __DIR__.'/../../../main/inc/global.inc.php';
|
||||
|
||||
api_protect_course_script(true);
|
||||
|
||||
$em = Database::getManager();
|
||||
$logRepository = $em->getRepository(Log::class);
|
||||
|
||||
$logController = new LogController(
|
||||
ExerciseFocusedPlugin::create(),
|
||||
HttpRequest::createFromGlobals(),
|
||||
$em,
|
||||
$logRepository
|
||||
);
|
||||
|
||||
try {
|
||||
$response = $logController();
|
||||
} catch (Exception $e) {
|
||||
$response = HttpResponse::create('', HttpResponse::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
$response->send();
|
||||
34
plugin/exercisefocused/pages/reporting.php
Normal file
34
plugin/exercisefocused/pages/reporting.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\PluginBundle\ExerciseFocused\Controller\ReportingController;
|
||||
use Chamilo\PluginBundle\ExerciseFocused\Entity\Log;
|
||||
use Symfony\Component\HttpFoundation\Request as HttpRequest;
|
||||
use Symfony\Component\HttpFoundation\Response as HttpResponse;
|
||||
|
||||
require_once __DIR__.'/../../../main/inc/global.inc.php';
|
||||
|
||||
api_protect_course_script(true);
|
||||
|
||||
if (!api_is_allowed_to_edit()) {
|
||||
api_not_allowed(true);
|
||||
}
|
||||
|
||||
$em = Database::getManager();
|
||||
$logRepository = $em->getRepository(Log::class);
|
||||
|
||||
$startController = new ReportingController(
|
||||
ExerciseFocusedPlugin::create(),
|
||||
HttpRequest::createFromGlobals(),
|
||||
$em,
|
||||
$logRepository
|
||||
);
|
||||
|
||||
//try {
|
||||
$response = $startController();
|
||||
//} catch (Exception $e) {
|
||||
//$response = HttpResponse::create('', HttpResponse::HTTP_FORBIDDEN);
|
||||
//}
|
||||
|
||||
$response->send();
|
||||
10
plugin/exercisefocused/plugin.php
Normal file
10
plugin/exercisefocused/plugin.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
$plugin_info = ExerciseFocusedPlugin::create()->get_info();
|
||||
|
||||
$plugin_info['templates'] = [
|
||||
'templates/script.html.twig',
|
||||
'templates/block.html.twig',
|
||||
];
|
||||
52
plugin/exercisefocused/src/Controller/AdminController.php
Normal file
52
plugin/exercisefocused/src/Controller/AdminController.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
namespace Chamilo\PluginBundle\ExerciseFocused\Controller;
|
||||
|
||||
use Chamilo\PluginBundle\ExerciseFocused\Traits\ReportingFilterTrait;
|
||||
use Display;
|
||||
use Symfony\Component\HttpFoundation\Response as HttpResponse;
|
||||
|
||||
class AdminController extends BaseController
|
||||
{
|
||||
use ReportingFilterTrait;
|
||||
|
||||
public function __invoke(): HttpResponse
|
||||
{
|
||||
parent::__invoke();
|
||||
|
||||
$form = $this->createForm();
|
||||
|
||||
$results = [];
|
||||
|
||||
if ($form->validate()) {
|
||||
$results = $this->findResults(
|
||||
$form->exportValues()
|
||||
);
|
||||
}
|
||||
|
||||
$table = $this->createTable($results);
|
||||
|
||||
$content = $form->returnForm()
|
||||
.Display::page_subheader2($this->plugin->get_lang('ReportByAttempts'))
|
||||
.$table->toHtml();
|
||||
|
||||
$this->setBreadcrumb();
|
||||
|
||||
return $this->renderView(
|
||||
$this->plugin->get_title(),
|
||||
$content
|
||||
);
|
||||
}
|
||||
|
||||
private function setBreadcrumb()
|
||||
{
|
||||
$codePath = api_get_path(WEB_CODE_PATH);
|
||||
|
||||
$GLOBALS['interbreadcrumb'][] = [
|
||||
'url' => $codePath.'admin/index.php',
|
||||
'name' => get_lang('Administration'),
|
||||
];
|
||||
}
|
||||
}
|
||||
86
plugin/exercisefocused/src/Controller/BaseController.php
Normal file
86
plugin/exercisefocused/src/Controller/BaseController.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace Chamilo\PluginBundle\ExerciseFocused\Controller;
|
||||
|
||||
use Chamilo\PluginBundle\ExerciseFocused\Repository\LogRepository;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Exception;
|
||||
use ExerciseFocusedPlugin;
|
||||
use Symfony\Component\HttpFoundation\Request as HttpRequest;
|
||||
use Symfony\Component\HttpFoundation\Response as HttpResponse;
|
||||
use Template;
|
||||
|
||||
abstract class BaseController
|
||||
{
|
||||
/**
|
||||
* @var ExerciseFocusedPlugin
|
||||
*/
|
||||
protected $plugin;
|
||||
|
||||
/**
|
||||
* @var HttpRequest
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* @var EntityManager
|
||||
*/
|
||||
protected $em;
|
||||
|
||||
/**
|
||||
* @var LogRepository
|
||||
*/
|
||||
protected $logRepository;
|
||||
|
||||
/**
|
||||
* @var Template
|
||||
*/
|
||||
protected $template;
|
||||
|
||||
public function __construct(
|
||||
ExerciseFocusedPlugin $plugin,
|
||||
HttpRequest $request,
|
||||
EntityManager $em,
|
||||
LogRepository $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();
|
||||
}
|
||||
|
||||
return HttpResponse::create();
|
||||
}
|
||||
|
||||
protected function renderView(
|
||||
string $title,
|
||||
string $content,
|
||||
?string $header = null,
|
||||
array $actions = []
|
||||
): HttpResponse {
|
||||
if (!$header) {
|
||||
$header = $title;
|
||||
}
|
||||
|
||||
$this->template = new Template($title);
|
||||
$this->template->assign('header', $header);
|
||||
$this->template->assign('actions', implode(PHP_EOL, $actions));
|
||||
$this->template->assign('content', $content);
|
||||
|
||||
ob_start();
|
||||
$this->template->display_one_col_template();
|
||||
$html = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
return HttpResponse::create($html);
|
||||
}
|
||||
}
|
||||
98
plugin/exercisefocused/src/Controller/DetailController.php
Normal file
98
plugin/exercisefocused/src/Controller/DetailController.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
namespace Chamilo\PluginBundle\ExerciseFocused\Controller;
|
||||
|
||||
use Chamilo\CoreBundle\Entity\TrackEExercises;
|
||||
use Chamilo\CourseBundle\Entity\CQuizQuestion;
|
||||
use Chamilo\PluginBundle\ExerciseFocused\Entity\Log;
|
||||
use Chamilo\PluginBundle\ExerciseFocused\Traits\DetailControllerTrait;
|
||||
use Doctrine\ORM\OptimisticLockException;
|
||||
use Doctrine\ORM\ORMException;
|
||||
use Doctrine\ORM\TransactionRequiredException;
|
||||
use Exception;
|
||||
use Exercise;
|
||||
use HTML_Table;
|
||||
use Symfony\Component\HttpFoundation\Response as HttpResponse;
|
||||
|
||||
class DetailController extends BaseController
|
||||
{
|
||||
use DetailControllerTrait;
|
||||
|
||||
/**
|
||||
* @throws OptimisticLockException
|
||||
* @throws TransactionRequiredException
|
||||
* @throws ORMException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __invoke(): HttpResponse
|
||||
{
|
||||
parent::__invoke();
|
||||
|
||||
$exeId = $this->request->query->getInt('id');
|
||||
$exe = $this->em->find(TrackEExercises::class, $exeId);
|
||||
|
||||
if (!$exe) {
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
$user = api_get_user_entity($exe->getExeUserId());
|
||||
|
||||
$objExercise = new Exercise($exe->getCId());
|
||||
$objExercise->read($exe->getExeExoId());
|
||||
|
||||
$logs = $this->logRepository->findBy(['exe' => $exe], ['updatedAt' => 'ASC']);
|
||||
$table = $this->getTable($objExercise, $logs);
|
||||
|
||||
$content = $this->generateHeader($objExercise, $user, $exe)
|
||||
.'<hr>'
|
||||
.$table->toHtml();
|
||||
|
||||
return HttpResponse::create($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, Log> $logs
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function getTable(Exercise $objExercise, array $logs): HTML_Table
|
||||
{
|
||||
$table = new HTML_Table(['class' => 'table table-hover table-striped data_table']);
|
||||
$table->setHeaderContents(0, 0, get_lang('Action'));
|
||||
$table->setHeaderContents(0, 1, get_lang('DateTime'));
|
||||
$table->setHeaderContents(0, 2, $this->plugin->get_lang('LevelReached'));
|
||||
|
||||
$row = 1;
|
||||
|
||||
foreach ($logs as $log) {
|
||||
$strLevel = '';
|
||||
|
||||
if (ONE_PER_PAGE == $objExercise->selectType()) {
|
||||
try {
|
||||
$question = $this->em->find(CQuizQuestion::class, $log->getLevel());
|
||||
|
||||
$strLevel = $question->getQuestion();
|
||||
} catch (Exception $exception) {
|
||||
}
|
||||
}
|
||||
|
||||
$table->setCellContents(
|
||||
$row,
|
||||
0,
|
||||
$this->plugin->getActionTitle($log->getAction())
|
||||
);
|
||||
$table->setCellContents(
|
||||
$row,
|
||||
1,
|
||||
api_get_local_time($log->getCreatedAt(), null, null, true, true, true)
|
||||
);
|
||||
$table->setCellContents($row, 2, $strLevel);
|
||||
|
||||
$row++;
|
||||
}
|
||||
|
||||
return $table;
|
||||
}
|
||||
}
|
||||
97
plugin/exercisefocused/src/Controller/LogController.php
Normal file
97
plugin/exercisefocused/src/Controller/LogController.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
namespace Chamilo\PluginBundle\ExerciseFocused\Controller;
|
||||
|
||||
use Chamilo\CoreBundle\Entity\TrackEExercises;
|
||||
use Chamilo\CourseBundle\Entity\CQuizQuestion;
|
||||
use Chamilo\PluginBundle\ExerciseFocused\Entity\Log;
|
||||
use ChamiloSession;
|
||||
use Exception;
|
||||
use Exercise;
|
||||
use ExerciseFocusedPlugin;
|
||||
use Security;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class LogController extends BaseController
|
||||
{
|
||||
public const VALID_ACTIONS = [
|
||||
Log::TYPE_OUTFOCUSED,
|
||||
Log::TYPE_RETURN,
|
||||
Log::TYPE_OUTFOCUSED_LIMIT,
|
||||
Log::TYPE_TIME_LIMIT,
|
||||
];
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __invoke(): Response
|
||||
{
|
||||
parent::__invoke();
|
||||
|
||||
$tokenIsValid = Security::check_token('get', null, 'exercisefocused');
|
||||
|
||||
if (!$tokenIsValid) {
|
||||
throw new Exception('token invalid');
|
||||
}
|
||||
|
||||
$action = $this->request->query->get('action');
|
||||
$levelId = $this->request->query->getInt('level_id');
|
||||
|
||||
$exeId = (int) ChamiloSession::read('exe_id');
|
||||
|
||||
if (!in_array($action, self::VALID_ACTIONS)) {
|
||||
throw new Exception('action invalid');
|
||||
}
|
||||
|
||||
$trackingExercise = $this->em->find(TrackEExercises::class, $exeId);
|
||||
|
||||
if (!$trackingExercise) {
|
||||
throw new Exception('no exercise attempt');
|
||||
}
|
||||
|
||||
$objExercise = new Exercise($trackingExercise->getCId());
|
||||
$objExercise->read($trackingExercise->getExeExoId());
|
||||
|
||||
$level = 0;
|
||||
|
||||
if (ONE_PER_PAGE == $objExercise->selectType()) {
|
||||
$question = $this->em->find(CQuizQuestion::class, $levelId);
|
||||
|
||||
if (!$question) {
|
||||
throw new Exception('Invalid level');
|
||||
}
|
||||
|
||||
$level = $question->getIid();
|
||||
}
|
||||
|
||||
$log = new Log();
|
||||
$log
|
||||
->setAction($action)
|
||||
->setExe($trackingExercise)
|
||||
->setLevel($level);
|
||||
|
||||
$this->em->persist($log);
|
||||
$this->em->flush();
|
||||
|
||||
$remainingOutfocused = -1;
|
||||
|
||||
if ('true' === $this->plugin->get(ExerciseFocusedPlugin::SETTING_ENABLE_OUTFOCUSED_LIMIT)) {
|
||||
$countOutfocused = $this->logRepository->countByActionInExe($trackingExercise, Log::TYPE_OUTFOCUSED);
|
||||
|
||||
$remainingOutfocused = (int) $this->plugin->get(ExerciseFocusedPlugin::SETTING_OUTFOCUSED_LIMIT) - $countOutfocused;
|
||||
}
|
||||
|
||||
$exercise = new Exercise(api_get_course_int_id());
|
||||
$exercise->read($trackingExercise->getExeExoId());
|
||||
|
||||
$json = [
|
||||
'sec_token' => Security::get_token('exercisefocused'),
|
||||
'remainingOutfocused' => $remainingOutfocused,
|
||||
];
|
||||
|
||||
return JsonResponse::create($json);
|
||||
}
|
||||
}
|
||||
138
plugin/exercisefocused/src/Controller/ReportingController.php
Normal file
138
plugin/exercisefocused/src/Controller/ReportingController.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
namespace Chamilo\PluginBundle\ExerciseFocused\Controller;
|
||||
|
||||
use Chamilo\CoreBundle\Entity\TrackEExercises;
|
||||
use Chamilo\CourseBundle\Entity\CQuiz;
|
||||
use Chamilo\PluginBundle\ExerciseFocused\Traits\ReportingFilterTrait;
|
||||
use Display;
|
||||
use Exception;
|
||||
use Symfony\Component\HttpFoundation\Response as HttpResponse;
|
||||
|
||||
class ReportingController extends BaseController
|
||||
{
|
||||
use ReportingFilterTrait;
|
||||
|
||||
public function __invoke(): HttpResponse
|
||||
{
|
||||
parent::__invoke();
|
||||
|
||||
$exercise = $this->em->find(
|
||||
CQuiz::class,
|
||||
$this->request->query->getInt('id')
|
||||
);
|
||||
|
||||
if (!$exercise) {
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
$courseCode = api_get_course_id();
|
||||
$sessionId = api_get_session_id();
|
||||
|
||||
$tab1 = $this->generateTabResume($exercise);
|
||||
|
||||
$tab2 = $this->generateTabSearch($exercise, $courseCode, $sessionId);
|
||||
|
||||
$tab3 = $this->generateTabSampling($exercise);
|
||||
|
||||
$content = Display::tabs(
|
||||
[
|
||||
$this->plugin->get_lang('ReportByAttempts'),
|
||||
get_lang('Search'),
|
||||
$this->plugin->get_lang('RandomSampling'),
|
||||
],
|
||||
[$tab1, $tab2, $tab3],
|
||||
'exercise-focused-tabs',
|
||||
[],
|
||||
[],
|
||||
isset($_GET['submit']) ? 2 : 1
|
||||
);
|
||||
|
||||
$this->setBreadcrumb($exercise->getId());
|
||||
|
||||
return $this->renderView(
|
||||
$this->plugin->get_lang('ReportByAttempts'),
|
||||
$content,
|
||||
$exercise->getTitle()
|
||||
);
|
||||
}
|
||||
|
||||
private function generateTabResume(CQuiz $exercise): string
|
||||
{
|
||||
$results = $this->findResultsInCourse($exercise->getId());
|
||||
|
||||
return $this->createTable($results)->toHtml();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function generateTabSearch(CQuiz $exercise, string $courseCode, int $sessionId): string
|
||||
{
|
||||
$form = $this->createForm();
|
||||
$form->updateAttributes(['action' => api_get_self().'?'.api_get_cidreq().'&id='.$exercise->getId()]);
|
||||
$form->addHidden('cidReq', $courseCode);
|
||||
$form->addHidden('id_session', $sessionId);
|
||||
$form->addHidden('gidReq', 0);
|
||||
$form->addHidden('gradebook', 0);
|
||||
$form->addHidden('origin', api_get_origin());
|
||||
$form->addHidden('id', $exercise->getId());
|
||||
|
||||
$tableHtml = '';
|
||||
$actions = '';
|
||||
|
||||
if ($form->validate()) {
|
||||
$formValues = $form->exportValues();
|
||||
|
||||
$actionLeft = Display::url(
|
||||
Display::return_icon('export_excel.png', get_lang('ExportExcel'), [], ICON_SIZE_MEDIUM),
|
||||
api_get_path(WEB_PLUGIN_PATH).'exercisefocused/pages/export.php?'.http_build_query($formValues)
|
||||
);
|
||||
$actionRight = Display::toolbarButton(
|
||||
get_lang('Clean'),
|
||||
api_get_path(WEB_PLUGIN_PATH)
|
||||
.'exercisefocused/pages/reporting.php?'
|
||||
.api_get_cidreq().'&'.http_build_query(['id' => $exercise->getId(), 'submit' => '']),
|
||||
'search'
|
||||
);
|
||||
|
||||
$actions = Display::toolbarAction(
|
||||
'em-actions',
|
||||
[$actionLeft, $actionRight]
|
||||
);
|
||||
|
||||
$results = $this->findResults($formValues);
|
||||
|
||||
$tableHtml = $this->createTable($results)->toHtml();
|
||||
}
|
||||
|
||||
return $form->returnForm().$actions.$tableHtml;
|
||||
}
|
||||
|
||||
private function generateTabSampling(CQuiz $exercise): string
|
||||
{
|
||||
$results = $this->findRandomResults($exercise->getId());
|
||||
|
||||
return $this->createTable($results)->toHtml();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, TrackEExercises>
|
||||
*/
|
||||
private function setBreadcrumb($exerciseId): void
|
||||
{
|
||||
$codePath = api_get_path('WEB_CODE_PATH');
|
||||
$cidReq = api_get_cidreq();
|
||||
|
||||
$GLOBALS['interbreadcrumb'][] = [
|
||||
'url' => $codePath."exercise/exercise.php?$cidReq",
|
||||
'name' => get_lang('Exercises'),
|
||||
];
|
||||
$GLOBALS['interbreadcrumb'][] = [
|
||||
'url' => $codePath."exercise/exercise_report.php?$cidReq&".http_build_query(['exerciseId' => $exerciseId]),
|
||||
'name' => get_lang('StudentScore'),
|
||||
];
|
||||
}
|
||||
}
|
||||
99
plugin/exercisefocused/src/Entity/Log.php
Normal file
99
plugin/exercisefocused/src/Entity/Log.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
namespace Chamilo\PluginBundle\ExerciseFocused\Entity;
|
||||
|
||||
use Chamilo\CoreBundle\Entity\TrackEExercises;
|
||||
use Chamilo\CoreBundle\Traits\TimestampableTypedEntity;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* Class EmbedRegistry.
|
||||
*
|
||||
* @package Chamilo\PluginBundle\Entity\EmbedRegistry
|
||||
*
|
||||
* @ORM\Entity(repositoryClass="Chamilo\PluginBundle\ExerciseFocused\Repository\LogRepository")
|
||||
* @ORM\Table(name="plugin_exercisefocused_log")
|
||||
*/
|
||||
class Log
|
||||
{
|
||||
use TimestampableTypedEntity;
|
||||
|
||||
public const TYPE_RETURN = 'return';
|
||||
public const TYPE_OUTFOCUSED = 'outfocused';
|
||||
public const TYPE_OUTFOCUSED_LIMIT = 'outfocused_limit';
|
||||
public const TYPE_TIME_LIMIT = 'time_limit';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*
|
||||
* @ORM\Column(name="id", type="integer")
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @var TrackEExercises
|
||||
*
|
||||
* @ORM\ManyToOne(targetEntity="Chamilo\CoreBundle\Entity\TrackEExercises")
|
||||
* @ORM\JoinColumn(name="exe_id", referencedColumnName="exe_id", onDelete="SET NULL")
|
||||
*/
|
||||
private $exe;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*
|
||||
* @ORM\Column(name="level", type="integer")
|
||||
*/
|
||||
private $level;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(name="action", type="string", nullable=false)
|
||||
*/
|
||||
private $action;
|
||||
|
||||
public function getId(): int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
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): self
|
||||
{
|
||||
$this->level = $level;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAction(): string
|
||||
{
|
||||
return $this->action;
|
||||
}
|
||||
|
||||
public function setAction(string $action): Log
|
||||
{
|
||||
$this->action = $action;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
213
plugin/exercisefocused/src/ExerciseFocusedPlugin.php
Normal file
213
plugin/exercisefocused/src/ExerciseFocusedPlugin.php
Normal file
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\CourseBundle\Entity\CTool;
|
||||
use Chamilo\PluginBundle\ExerciseFocused\Entity\Log;
|
||||
use Doctrine\ORM\Tools\SchemaTool;
|
||||
use Doctrine\ORM\Tools\ToolsException;
|
||||
|
||||
class ExerciseFocusedPlugin extends Plugin
|
||||
{
|
||||
public const SETTING_TOOL_ENABLE = 'tool_enable';
|
||||
public const SETTING_ENABLE_TIME_LIMIT = 'enable_time_limit';
|
||||
public const SETTING_TIME_LIMIT = 'time_limit';
|
||||
public const SETTING_ENABLE_OUTFOCUSED_LIMIT = 'enable_outfocused_limit';
|
||||
public const SETTING_OUTFOCUSED_LIMIT = 'outfocused_limit';
|
||||
public const SETTING_SESSION_FIELD_FILTERS = 'session_field_filters';
|
||||
public const SETTING_PERCENTAGE_SAMPLING = 'percentage_sampling';
|
||||
|
||||
public const FIELD_SELECTED = 'exercisefocused_selected';
|
||||
|
||||
private const TABLE_LOG = 'plugin_exercisefocused_log';
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
$settings = [
|
||||
self::SETTING_TOOL_ENABLE => 'boolean',
|
||||
self::SETTING_ENABLE_TIME_LIMIT => 'boolean',
|
||||
self::SETTING_TIME_LIMIT => 'text',
|
||||
self::SETTING_ENABLE_OUTFOCUSED_LIMIT => 'boolean',
|
||||
self::SETTING_OUTFOCUSED_LIMIT => 'text',
|
||||
self::SETTING_SESSION_FIELD_FILTERS => 'text',
|
||||
self::SETTING_PERCENTAGE_SAMPLING => 'text',
|
||||
];
|
||||
|
||||
parent::__construct(
|
||||
"0.0.1",
|
||||
"Angel Fernando Quiroz Campos <angel.quiroz@beeznest.com>",
|
||||
$settings
|
||||
);
|
||||
}
|
||||
|
||||
public static function create(): ?ExerciseFocusedPlugin
|
||||
{
|
||||
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),
|
||||
]
|
||||
);
|
||||
|
||||
$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 getActionTitle($action): string
|
||||
{
|
||||
switch ($action) {
|
||||
case Log::TYPE_OUTFOCUSED:
|
||||
return $this->get_lang('Outfocused');
|
||||
case Log::TYPE_RETURN:
|
||||
return $this->get_lang('Return');
|
||||
case Log::TYPE_OUTFOCUSED_LIMIT:
|
||||
return $this->get_lang('MaxOutfocusedReached');
|
||||
case Log::TYPE_TIME_LIMIT:
|
||||
return $this->get_lang('TimeLimitReached');
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getLinkReporting(int $exerciseId): string
|
||||
{
|
||||
if (!$this->isEnabled(true)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$values = (new ExtraFieldValue('exercise'))
|
||||
->get_values_by_handler_and_field_variable($exerciseId, self::FIELD_SELECTED);
|
||||
|
||||
if (!$values || !$values['value']) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$icon = Display::return_icon(
|
||||
'window_list_slide.png',
|
||||
$this->get_lang('ReportByAttempts'),
|
||||
[],
|
||||
ICON_SIZE_MEDIUM
|
||||
);
|
||||
|
||||
$url = api_get_path(WEB_PLUGIN_PATH)
|
||||
.'exercisefocused/pages/reporting.php?'
|
||||
.api_get_cidreq().'&'.http_build_query(['id' => $exerciseId]);
|
||||
|
||||
return Display::url($icon, $url);
|
||||
}
|
||||
|
||||
public function getSessionFieldList(): array
|
||||
{
|
||||
$settingField = $this->get(self::SETTING_SESSION_FIELD_FILTERS);
|
||||
|
||||
$fields = explode(',', $settingField);
|
||||
|
||||
return array_map('trim', $fields);
|
||||
}
|
||||
|
||||
public function isEnableForExercise(int $exerciseId): bool
|
||||
{
|
||||
$renderRegion = $this->isEnabled(true)
|
||||
&& strpos($_SERVER['SCRIPT_NAME'], '/main/exercise/exercise_submit.php') !== false;
|
||||
|
||||
if (!$renderRegion) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$objFieldValue = new ExtraFieldValue('exercise');
|
||||
$values = $objFieldValue->get_values_by_handler_and_field_variable(
|
||||
$exerciseId,
|
||||
self::FIELD_SELECTED
|
||||
);
|
||||
|
||||
return $values && (bool) $values['value'];
|
||||
}
|
||||
|
||||
public function calculateMotive(int $outfocusedLimitCount, int $timeLimitCount)
|
||||
{
|
||||
$motive = $this->get_lang('MotiveExerciseFinished');
|
||||
|
||||
if ($outfocusedLimitCount > 0) {
|
||||
$motive = $this->get_lang('MaxOutfocusedReached');
|
||||
}
|
||||
|
||||
if ($timeLimitCount > 0) {
|
||||
$motive = $this->get_lang('TimeLimitReached');
|
||||
}
|
||||
|
||||
return $motive;
|
||||
}
|
||||
|
||||
protected function createLinkToCourseTool($name, $courseId, $iconName = null, $link = null, $sessionId = 0, $category = 'plugin'): ?CTool
|
||||
{
|
||||
$tool = parent::createLinkToCourseTool($name, $courseId, $iconName, $link, $sessionId, $category);
|
||||
|
||||
if (!$tool) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$tool->setName(
|
||||
$tool->getName().':teacher'
|
||||
);
|
||||
|
||||
$em = Database::getManager();
|
||||
$em->persist($tool);
|
||||
$em->flush();
|
||||
|
||||
return $tool;
|
||||
}
|
||||
}
|
||||
28
plugin/exercisefocused/src/Repository/LogRepository.php
Normal file
28
plugin/exercisefocused/src/Repository/LogRepository.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
namespace Chamilo\PluginBundle\ExerciseFocused\Repository;
|
||||
|
||||
use Chamilo\CoreBundle\Entity\TrackEExercises;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
|
||||
class LogRepository extends EntityRepository
|
||||
{
|
||||
public function countByActionInExe(TrackEExercises $exe, string $action): int
|
||||
{
|
||||
return $this->count([
|
||||
'exe' => $exe,
|
||||
'action' => $action,
|
||||
]);
|
||||
}
|
||||
|
||||
public function countByActionAndLevel(TrackEExercises $exe, string $action, int $level): int
|
||||
{
|
||||
return $this->count([
|
||||
'exe' => $exe,
|
||||
'action' => $action,
|
||||
'level' => $level,
|
||||
]);
|
||||
}
|
||||
}
|
||||
28
plugin/exercisefocused/src/Traits/DetailControllerTrait.php
Normal file
28
plugin/exercisefocused/src/Traits/DetailControllerTrait.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
/* For license terms, see /license.txt */
|
||||
|
||||
namespace Chamilo\PluginBundle\ExerciseFocused\Traits;
|
||||
|
||||
use Chamilo\CoreBundle\Entity\TrackEExercises;
|
||||
use Chamilo\UserBundle\Entity\User;
|
||||
use Display;
|
||||
use Exercise;
|
||||
|
||||
trait DetailControllerTrait
|
||||
{
|
||||
private function generateHeader(Exercise $objExercise, User $student, TrackEExercises $trackExe): string
|
||||
{
|
||||
$startDate = api_get_local_time($trackExe->getStartDate(), null, null, true, true, true);
|
||||
$endDate = api_get_local_time($trackExe->getExeDate(), null, null, true, true, true);
|
||||
|
||||
return Display::page_subheader2($objExercise->selectTitle())
|
||||
.Display::tag('p', $student->getCompleteNameWithUsername(), ['class' => 'lead'])
|
||||
.Display::tag(
|
||||
'p',
|
||||
sprintf(get_lang('QuizRemindStartDate'), $startDate)
|
||||
.sprintf(get_lang('QuizRemindEndDate'), $endDate)
|
||||
.sprintf(get_lang('QuizRemindDuration'), api_format_time($trackExe->getExeDuration()))
|
||||
);
|
||||
}
|
||||
}
|
||||
393
plugin/exercisefocused/src/Traits/ReportingFilterTrait.php
Normal file
393
plugin/exercisefocused/src/Traits/ReportingFilterTrait.php
Normal file
@@ -0,0 +1,393 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
namespace Chamilo\PluginBundle\ExerciseFocused\Traits;
|
||||
|
||||
use Chamilo\CoreBundle\Entity\TrackEExercises;
|
||||
use Chamilo\CourseBundle\Entity\CQuiz;
|
||||
use Chamilo\PluginBundle\ExerciseFocused\Entity\Log;
|
||||
use Chamilo\UserBundle\Entity\User;
|
||||
use Database;
|
||||
use Display;
|
||||
use Doctrine\ORM\Query\Expr\Join;
|
||||
use Exception;
|
||||
use ExerciseFocusedPlugin;
|
||||
use ExerciseMonitoringPlugin;
|
||||
use ExtraField;
|
||||
use ExtraFieldValue;
|
||||
use FormValidator;
|
||||
use HTML_Table;
|
||||
|
||||
trait ReportingFilterTrait
|
||||
{
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function createForm(): FormValidator
|
||||
{
|
||||
$extraFieldNameList = $this->plugin->getSessionFieldList();
|
||||
$cId = api_get_course_int_id();
|
||||
$sessionId = api_get_session_id();
|
||||
|
||||
$form = new FormValidator('exercisefocused', 'get');
|
||||
$form->addText('username', get_lang('LoginName'), false);
|
||||
$form->addText('firstname', get_lang('FirstName'), false);
|
||||
$form->addText('lastname', get_lang('LastName'), false);
|
||||
|
||||
if ($extraFieldNameList && ($sessionId || !$cId)) {
|
||||
(new ExtraField('session'))
|
||||
->addElements(
|
||||
$form,
|
||||
$sessionId,
|
||||
[],
|
||||
false,
|
||||
false,
|
||||
$extraFieldNameList
|
||||
);
|
||||
|
||||
$extraNames = [];
|
||||
|
||||
foreach ($extraFieldNameList as $key => $value) {
|
||||
$extraNames[$key] = "extra_$value";
|
||||
}
|
||||
|
||||
if ($sessionId) {
|
||||
$form->freeze($extraNames);
|
||||
}
|
||||
}
|
||||
|
||||
$form->addDatePicker('start_date', get_lang('StartDate'));
|
||||
$form->addButtonSearch(get_lang('Search'));
|
||||
//$form->protect();
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function findResults(array $formValues = []): array
|
||||
{
|
||||
$cId = api_get_course_int_id();
|
||||
$sId = api_get_session_id();
|
||||
|
||||
$qb = $this->em->createQueryBuilder();
|
||||
$qb
|
||||
->select('te AS exe, q.title, te.startDate, u.id AS user_id, u.firstname, u.lastname, u.username, te.sessionId, te.cId')
|
||||
->from(TrackEExercises::class, 'te')
|
||||
->innerJoin(CQuiz::class, 'q', Join::WITH, 'te.exeExoId = q.iid')
|
||||
->innerJoin(User::class, 'u', Join::WITH, 'te.exeUserId = u.id');
|
||||
|
||||
$params = [];
|
||||
|
||||
if ($cId) {
|
||||
$qb->andWhere($qb->expr()->eq('te.cId', ':cId'));
|
||||
|
||||
$params['cId'] = $cId;
|
||||
|
||||
$sessionItemIdList = $sId ? [$sId] : [];
|
||||
} else {
|
||||
$sessionItemIdList = $this->getSessionIdFromFormValues(
|
||||
$formValues,
|
||||
$this->plugin->getSessionFieldList()
|
||||
);
|
||||
}
|
||||
|
||||
if ($sessionItemIdList) {
|
||||
$qb->andWhere($qb->expr()->in('te.sessionId', ':sessionItemIdList'));
|
||||
|
||||
$params['sessionItemIdList'] = $sessionItemIdList;
|
||||
}
|
||||
|
||||
if (!empty($formValues['username'])) {
|
||||
$qb->andWhere($qb->expr()->eq('u.username', ':username'));
|
||||
|
||||
$params['username'] = $formValues['username'];
|
||||
}
|
||||
|
||||
if (!empty($formValues['firstname'])) {
|
||||
$qb->andWhere($qb->expr()->like('u.firstname', ':firstname'));
|
||||
|
||||
$params['firstname'] = $formValues['firstname'].'%';
|
||||
}
|
||||
|
||||
if (!empty($formValues['lastname'])) {
|
||||
$qb->andWhere($qb->expr()->like('u.lastname', ':lastname'));
|
||||
|
||||
$params['lastname'] = $formValues['lastname'].'%';
|
||||
}
|
||||
|
||||
if (!empty($formValues['start_date'])) {
|
||||
$qb->andWhere(
|
||||
$qb->expr()->andX(
|
||||
$qb->expr()->gte('te.startDate', ':start_date'),
|
||||
$qb->expr()->lte('te.exeDate', ':end_date')
|
||||
)
|
||||
);
|
||||
|
||||
$params['start_date'] = api_get_utc_datetime($formValues['start_date'].' 00:00:00', false, true);
|
||||
$params['end_date'] = api_get_utc_datetime($formValues['start_date'].' 23:59:59', false, true);
|
||||
}
|
||||
|
||||
if (empty($params)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ($cId && !empty($formValues['id'])) {
|
||||
$qb->andWhere($qb->expr()->eq('q.iid', ':q_id'));
|
||||
|
||||
$params['q_id'] = $formValues['id'];
|
||||
}
|
||||
|
||||
$qb->setParameters($params);
|
||||
|
||||
return $this->formatResults(
|
||||
$qb->getQuery()->getResult()
|
||||
);
|
||||
}
|
||||
|
||||
protected function formatResults(array $queryResults): array
|
||||
{
|
||||
$results = [];
|
||||
|
||||
foreach ($queryResults as $value) {
|
||||
$outfocusedCount = $this->logRepository->countByActionInExe($value['exe'], Log::TYPE_OUTFOCUSED);
|
||||
$returnCount = $this->logRepository->countByActionInExe($value['exe'], Log::TYPE_RETURN);
|
||||
$outfocusedLimitCount = $this->logRepository->countByActionInExe($value['exe'], Log::TYPE_OUTFOCUSED_LIMIT);
|
||||
$timeLimitCount = $this->logRepository->countByActionInExe($value['exe'], Log::TYPE_TIME_LIMIT);
|
||||
|
||||
$class = 'success';
|
||||
$motive = $this->plugin->get_lang('MotiveExerciseFinished');
|
||||
|
||||
if ($outfocusedCount > 0 || $returnCount > 0) {
|
||||
$class = 'warning';
|
||||
}
|
||||
|
||||
if ($outfocusedLimitCount > 0 || $timeLimitCount > 0) {
|
||||
$class = 'danger';
|
||||
|
||||
if ($outfocusedLimitCount > 0) {
|
||||
$motive = $this->plugin->get_lang('MaxOutfocusedReached');
|
||||
}
|
||||
|
||||
if ($timeLimitCount > 0) {
|
||||
$motive = $this->plugin->get_lang('TimeLimitReached');
|
||||
}
|
||||
}
|
||||
|
||||
$session = api_get_session_entity($value['sessionId']);
|
||||
$course = api_get_course_entity($value['cId']);
|
||||
|
||||
$results[] = [
|
||||
'id' => $value['exe']->getExeId(),
|
||||
'quiz_title' => $value['title'],
|
||||
'user_id' => $value['user_id'],
|
||||
'username' => $value['username'],
|
||||
'firstname' => $value['firstname'],
|
||||
'lastname' => $value['lastname'],
|
||||
'start_date' => $value['exe']->getStartDate(),
|
||||
'end_date' => $value['exe']->getExeDate(),
|
||||
'count_outfocused' => $outfocusedCount,
|
||||
'count_return' => $returnCount,
|
||||
'motive' => Display::span($motive, ['class' => "text-$class"]),
|
||||
'class' => $class,
|
||||
'session_name' => $session ? $session->getName() : null,
|
||||
'course_title' => $course->getTitle(),
|
||||
];
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
protected function createTable(array $resultData): HTML_Table
|
||||
{
|
||||
$courseId = api_get_course_int_id();
|
||||
|
||||
$pluginMonitoring = ExerciseMonitoringPlugin::create();
|
||||
$isPluginMonitoringEnabled = $pluginMonitoring->isEnabled(true);
|
||||
|
||||
$detailIcon = Display::return_icon('forum_listview.png', get_lang('Detail'));
|
||||
|
||||
$urlDetail = api_get_path(WEB_PLUGIN_PATH).'exercisefocused/pages/detail.php?'.api_get_cidreq().'&';
|
||||
|
||||
$tableHeaders = [];
|
||||
$tableHeaders[] = get_lang('LoginName');
|
||||
$tableHeaders[] = get_lang('FirstName');
|
||||
$tableHeaders[] = get_lang('LastName');
|
||||
|
||||
if (!$courseId) {
|
||||
$tableHeaders[] = get_lang('SessionName');
|
||||
$tableHeaders[] = get_lang('CourseTitle');
|
||||
$tableHeaders[] = get_lang('ExerciseName');
|
||||
}
|
||||
|
||||
$tableHeaders[] = $this->plugin->get_lang('ExerciseStartDateAndTime');
|
||||
$tableHeaders[] = $this->plugin->get_lang('ExerciseEndDateAndTime');
|
||||
$tableHeaders[] = $this->plugin->get_lang('Outfocused');
|
||||
$tableHeaders[] = $this->plugin->get_lang('Returns');
|
||||
$tableHeaders[] = $this->plugin->get_lang('Motive');
|
||||
$tableHeaders[] = get_lang('Actions');
|
||||
|
||||
$tableData = [];
|
||||
|
||||
foreach ($resultData as $result) {
|
||||
$actionLinks = Display::url(
|
||||
$detailIcon,
|
||||
$urlDetail.http_build_query(['id' => $result['id']]),
|
||||
[
|
||||
'class' => 'ajax',
|
||||
'data-title' => get_lang('Detail'),
|
||||
]
|
||||
);
|
||||
|
||||
if ($isPluginMonitoringEnabled) {
|
||||
$actionLinks .= $pluginMonitoring->generateDetailLink(
|
||||
(int) $result['id'],
|
||||
$result['user_id']
|
||||
);
|
||||
}
|
||||
|
||||
$row = [];
|
||||
|
||||
$row[] = $result['username'];
|
||||
$row[] = $result['firstname'];
|
||||
$row[] = $result['lastname'];
|
||||
|
||||
if (!$courseId) {
|
||||
$row[] = $result['session_name'];
|
||||
$row[] = $result['course_title'];
|
||||
$row[] = $result['quiz_title'];
|
||||
}
|
||||
|
||||
$row[] = api_get_local_time($result['start_date'], null, null, true, true, true);
|
||||
$row[] = api_get_local_time($result['end_date'], null, null, true, true, true);
|
||||
$row[] = $result['count_outfocused'];
|
||||
$row[] = $result['count_return'];
|
||||
$row[] = $result['motive'];
|
||||
$row[] = $actionLinks;
|
||||
|
||||
$tableData[] = $row;
|
||||
}
|
||||
|
||||
$table = new HTML_Table(['class' => 'table table-hover table-striped data_table']);
|
||||
$table->setHeaders($tableHeaders);
|
||||
$table->setData($tableData);
|
||||
$table->setColAttributes($courseId ? 3 : 6, ['class' => 'text-center']);
|
||||
$table->setColAttributes($courseId ? 4 : 7, ['class' => 'text-center']);
|
||||
$table->setColAttributes($courseId ? 5 : 8, ['class' => 'text-right']);
|
||||
$table->setColAttributes($courseId ? 6 : 9, ['class' => 'text-right']);
|
||||
$table->setColAttributes($courseId ? 7 : 10, ['class' => 'text-center']);
|
||||
$table->setColAttributes($courseId ? 8 : 11, ['class' => 'text-right']);
|
||||
|
||||
foreach ($resultData as $idx => $result) {
|
||||
$table->setRowAttributes($idx + 1, ['class' => $result['class']], true);
|
||||
}
|
||||
|
||||
return $table;
|
||||
}
|
||||
|
||||
protected function findResultsInCourse(int $exerciseId, bool $randomResults = false): array
|
||||
{
|
||||
$exeIdList = $this->getAttemptsIdForExercise($exerciseId);
|
||||
|
||||
if ($randomResults) {
|
||||
$exeIdList = $this->pickRandomAttempts($exeIdList) ?: $exeIdList;
|
||||
}
|
||||
|
||||
if (empty($exeIdList)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$qb = $this->em->createQueryBuilder();
|
||||
$qb
|
||||
->select('te AS exe, q.title, te.startDate, u.id AS user_id, u.firstname, u.lastname, u.username, te.sessionId, te.cId')
|
||||
->from(TrackEExercises::class, 'te')
|
||||
->innerJoin(CQuiz::class, 'q', Join::WITH, 'te.exeExoId = q.iid')
|
||||
->innerJoin(User::class, 'u', Join::WITH, 'te.exeUserId = u.id')
|
||||
->andWhere(
|
||||
$qb->expr()->in('te.exeId', $exeIdList)
|
||||
)
|
||||
->addOrderBy('te.startDate');
|
||||
|
||||
return $this->formatResults(
|
||||
$qb->getQuery()->getResult()
|
||||
);
|
||||
}
|
||||
|
||||
protected function findRandomResults(int $exerciseId): array
|
||||
{
|
||||
return $this->findResultsInCourse($exerciseId, true);
|
||||
}
|
||||
|
||||
private function getSessionIdFromFormValues(array $formValues, array $fieldVariableList): array
|
||||
{
|
||||
$fieldItemIdList = [];
|
||||
$objFieldValue = new ExtraFieldValue('session');
|
||||
|
||||
foreach ($fieldVariableList as $fieldVariable) {
|
||||
if (!isset($formValues["extra_$fieldVariable"])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$itemValues = $objFieldValue->get_item_id_from_field_variable_and_field_value(
|
||||
$fieldVariable,
|
||||
$formValues["extra_$fieldVariable"],
|
||||
false,
|
||||
false,
|
||||
true
|
||||
);
|
||||
|
||||
foreach ($itemValues as $itemValue) {
|
||||
$fieldItemIdList[] = (int) $itemValue['item_id'];
|
||||
}
|
||||
}
|
||||
|
||||
return array_unique($fieldItemIdList);
|
||||
}
|
||||
|
||||
private function getAttemptsIdForExercise(int $exerciseId): array
|
||||
{
|
||||
$cId = api_get_course_int_id();
|
||||
$sId = api_get_session_id();
|
||||
|
||||
$tblTrackExe = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
|
||||
|
||||
$sessionCondition = api_get_session_condition($sId);
|
||||
|
||||
$result = Database::query(
|
||||
"SELECT exe_id FROM $tblTrackExe
|
||||
WHERE c_id = $cId
|
||||
AND exe_exo_id = $exerciseId
|
||||
$sessionCondition
|
||||
ORDER BY exe_id"
|
||||
);
|
||||
|
||||
return array_column(
|
||||
Database::store_result($result),
|
||||
'exe_id'
|
||||
);
|
||||
}
|
||||
|
||||
private function pickRandomAttempts(array $attemptIdList): array
|
||||
{
|
||||
$settingPercentage = (int) $this->plugin->get(ExerciseFocusedPlugin::SETTING_PERCENTAGE_SAMPLING);
|
||||
|
||||
if (!$settingPercentage) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$percentage = count($attemptIdList) * ($settingPercentage / 100);
|
||||
$round = round($percentage) ?: 1;
|
||||
|
||||
$random = (array) array_rand($attemptIdList, $round);
|
||||
|
||||
$selection = [];
|
||||
|
||||
foreach ($random as $rand) {
|
||||
$selection[] = $attemptIdList[$rand];
|
||||
}
|
||||
|
||||
return $selection;
|
||||
}
|
||||
}
|
||||
105
plugin/exercisefocused/templates/block.html.twig
Normal file
105
plugin/exercisefocused/templates/block.html.twig
Normal file
@@ -0,0 +1,105 @@
|
||||
{% if exercisefocused.show_region %}
|
||||
{% set enable_time_limit = 'true' == exercisefocused.plugin_info.obj.get('enable_time_limit') %}
|
||||
{% set time_limit = exercisefocused.plugin_info.obj.get('time_limit') %}
|
||||
{% set enable_outfocused_limit = 'true' == exercisefocused.plugin_info.obj.get('enable_outfocused_limit') %}
|
||||
{% set outfocused_limit = exercisefocused.plugin_info.obj.get('outfocused_limit') %}
|
||||
|
||||
<div id="exercisefocused-block" style="display: none;">
|
||||
<div class="exercisefocused-block__container">
|
||||
<div class="exercisefocused-block__message card">
|
||||
<p class="h3 text-danger">{{ 'YouHaveLeftTheExercise'|get_plugin_lang('ExerciseFocusedPlugin') }}</p>
|
||||
|
||||
{% if enable_time_limit %}
|
||||
<div id="time-limit-block">
|
||||
<p class="h4">
|
||||
{{ 'YouHaveXTimeToReturn'|get_plugin_lang('ExerciseFocusedPlugin')|format(time_limit) }}
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if enable_outfocused_limit %}
|
||||
<div id="outfocused-limit-block">
|
||||
<p class="h4">{{ 'YouAreAllowedXOutfocused'|get_plugin_lang('ExerciseFocusedPlugin')|format(outfocused_limit) }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<style>
|
||||
#exercisefocused-block {
|
||||
background-color: #CCCC;
|
||||
border: 1px solid #DDD;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
min-height: 100%;
|
||||
min-width: 100%;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
user-select: none;
|
||||
-ms-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
top: 0;
|
||||
z-index: 1010;
|
||||
}
|
||||
.exercisefocused-block__container {
|
||||
left: 50%;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
-webkit-transform: translate(-50%, -50%);
|
||||
}
|
||||
.exercisefocused-block__message {
|
||||
margin-bottom: 0;
|
||||
padding: 20px;
|
||||
}
|
||||
.exercisefocused-block__message p,
|
||||
.exercisefocused-block__message span {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.exercisefocused-backdrop {
|
||||
background-color: rgba(0, 0, 0, 0.02);
|
||||
cursor: not-allowed;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
user-select: none;
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
}
|
||||
.exercisefocused-backdrop::before {
|
||||
background-color: #FFF;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 2px 2px rgba(204, 197, 185, 0.5);
|
||||
content: attr(data-alert);
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
margin: 50px auto;
|
||||
opacity: 0;
|
||||
padding: 10px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
width: 340px;
|
||||
transition: opacity 0s ease-in-out;
|
||||
}
|
||||
.exercisefocused-backdrop:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.exercisefocused-backdrop.inmediate::before {
|
||||
transition-delay: 3.5s !important;
|
||||
}
|
||||
.exercisefocused-backdrop.out::before {
|
||||
transition-delay: 0s !important;
|
||||
}
|
||||
|
||||
form#exercise_form {
|
||||
background-color: #FFF;
|
||||
position: relative;
|
||||
z-index: 1005;
|
||||
}
|
||||
</style>
|
||||
{% endif %}
|
||||
163
plugin/exercisefocused/templates/script.html.twig
Normal file
163
plugin/exercisefocused/templates/script.html.twig
Normal file
@@ -0,0 +1,163 @@
|
||||
{% if exercisefocused.show_region %}
|
||||
{% set enable_time_limit = 'true' == exercisefocused.plugin_info.obj.get('enable_time_limit') %}
|
||||
{% set time_limit = exercisefocused.plugin_info.obj.get('time_limit') %}
|
||||
{% set enable_outfocused_limit = 'true' == exercisefocused.plugin_info.obj.get('enable_outfocused_limit') %}
|
||||
{% set outfocused_limit = exercisefocused.plugin_info.obj.get('outfocused_limit') %}
|
||||
|
||||
{% set ALL_ON_ONE_PAGE = exercisefocused.exercise_type == 1 %}
|
||||
{% set ONE_PER_PAGE = exercisefocused.exercise_type == 2 %}
|
||||
|
||||
<script>
|
||||
$(function () {
|
||||
var $exerciseFocused = $("#exercisefocused-block").appendTo('body');
|
||||
var $timeLimitBlock = $exerciseFocused.find('#time-limit-block');
|
||||
var $timeLimitTarget = $exerciseFocused.find('#time-limit-target');
|
||||
var $outfocusedLimitBlock = $exerciseFocused.find('#outfocused-limit-block');
|
||||
var $outfocusedLimitTarget = $exerciseFocused.find('#outfocused-limit-target');
|
||||
|
||||
var $backdrop = $('<div>')
|
||||
.addClass('exercisefocused-backdrop text-danger')
|
||||
.attr('data-alert', '{{ 'AlertBeforeLeaving'|get_plugin_lang('ExerciseFocusedPlugin') }}')
|
||||
.hover(
|
||||
function () {$backdrop.removeClass('out').addClass('inmediate'); },
|
||||
function () { $backdrop.addClass('out').removeClass('inmediate'); }
|
||||
);
|
||||
|
||||
var $btnSaveNow = $('button[name="save_now"]');
|
||||
|
||||
$backdrop.appendTo('body');
|
||||
|
||||
var secToken = "{{ exercisefocused.sec_token }}";
|
||||
var initDocumentTitle = document.title;
|
||||
var countdownInterval;
|
||||
var remainingTime;
|
||||
var enableTimeLimit = {{ enable_time_limit ? 'true' : 'false' }};
|
||||
var enableOutfocusedLimit = {{ enable_outfocused_limit ? 'true' : 'false' }};
|
||||
|
||||
{% if enable_outfocused_limit %}
|
||||
var remainingOutfocused = {{ exercisefocused.remaining_outfocused }};
|
||||
{% endif %}
|
||||
|
||||
function finishExam() {
|
||||
$(window).off("blur", onBlur)
|
||||
$(window).off("focus", onFocus)
|
||||
|
||||
{% if ALL_ON_ONE_PAGE %}
|
||||
save_now_all('validate');
|
||||
{% elseif ONE_PER_PAGE %}
|
||||
window.quizTimeEnding = true;
|
||||
$('[name="save_now"]').trigger('click');
|
||||
{% endif %}
|
||||
}
|
||||
|
||||
{% if enable_time_limit %}
|
||||
function updateCountdown() {
|
||||
var seconds = remainingTime;
|
||||
var strSeconds = `${seconds.toString().padStart(2, '0')}`;
|
||||
|
||||
$timeLimitTarget.text(strSeconds);
|
||||
document.title = $timeLimitTarget.parent().text();
|
||||
|
||||
remainingTime--;
|
||||
|
||||
if (remainingTime < 0) {
|
||||
clearInterval(countdownInterval);
|
||||
|
||||
sendAction('time_limit', function () {
|
||||
finishExam()
|
||||
});
|
||||
}
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
function sendAction(action, callback) {
|
||||
{% if ALL_ON_ONE_PAGE %}
|
||||
var levelId = 0;
|
||||
{% elseif ONE_PER_PAGE %}
|
||||
var levelId = $btnSaveNow.data('question') || -1;
|
||||
{% endif %}
|
||||
|
||||
$.ajax({
|
||||
url: "{{ _p.web_plugin }}exercisefocused/pages/log.php",
|
||||
data: {
|
||||
action: action,
|
||||
exercisefocused_sec_token: secToken,
|
||||
level_id: levelId
|
||||
},
|
||||
success: function (response) {
|
||||
if (!response) {
|
||||
return;
|
||||
}
|
||||
|
||||
secToken = response.sec_token;
|
||||
|
||||
if (callback) {
|
||||
callback(response)
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function onBlur() {
|
||||
$exerciseFocused.show();
|
||||
|
||||
if (enableOutfocusedLimit) {
|
||||
if (remainingOutfocused <= 0) {
|
||||
$outfocusedLimitBlock.find('p').text("{{ 'OutfocusedLimitExceeded'|get_plugin_lang('ExerciseFocusedPlugin')|escape('js') }}");
|
||||
$timeLimitBlock.hide();
|
||||
|
||||
sendAction('outfocused_limit', function () {
|
||||
finishExam()
|
||||
});
|
||||
|
||||
return;
|
||||
} else {
|
||||
$outfocusedLimitTarget.text(remainingOutfocused);
|
||||
}
|
||||
|
||||
remainingOutfocused--;
|
||||
}
|
||||
|
||||
sendAction('outfocused');
|
||||
|
||||
{% if enable_time_limit %}
|
||||
remainingTime = {{ time_limit }};
|
||||
|
||||
updateCountdown();
|
||||
|
||||
countdownInterval = window.setInterval(updateCountdown, 1000);
|
||||
{% else %}
|
||||
document.title = "{{ 'WindowTitleOutfocused'|get_plugin_lang('ExerciseFocusedPlugin')|escape('js') }}";
|
||||
{% endif %}
|
||||
}
|
||||
|
||||
function onFocus() {
|
||||
sendAction('return');
|
||||
|
||||
document.title = initDocumentTitle;
|
||||
|
||||
window.setTimeout(
|
||||
function () {
|
||||
$exerciseFocused.hide();
|
||||
},
|
||||
3500
|
||||
);
|
||||
|
||||
{% if enable_time_limit %}
|
||||
clearInterval(countdownInterval);
|
||||
{% endif %}
|
||||
}
|
||||
|
||||
$(window).on("blur", onBlur)
|
||||
$(window).on("focus", onFocus)
|
||||
|
||||
$('body').on('click', 'a, button', function (e) {
|
||||
var $el = $(e.target);
|
||||
|
||||
if (0 === $el.parents('form#exercise_form').length) {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
})
|
||||
</script>
|
||||
{% endif %}
|
||||
5
plugin/exercisefocused/uninstall.php
Normal file
5
plugin/exercisefocused/uninstall.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
ExerciseFocusedPlugin::create()->uninstall();
|
||||
Reference in New Issue
Block a user