'boolean', self::SETTING_API_URL => 'text', self::SETTING_TOKEN => 'text', self::SETTING_MAX_ATTEMPTS => 'text', self::SETTING_2FA => 'boolean', ] ); } /** * Get the admin URL for the plugin if Plugin::isAdminPlugin is true. * * @return string */ public function getAdminUrl() { $webPath = api_get_path(WEB_PLUGIN_PATH).$this->get_name(); return "$webPath/admin.php"; } /** * @return WhispeakAuthPlugin */ public static function create() { static $result = null; return $result ? $result : $result = new self(); } /** * @throws \Doctrine\ORM\Tools\ToolsException */ public function install() { $this->installExtraFields(); $this->installEntities(); $this->installHook(); } public function uninstall() { $this->uninstallHook(); $this->uninstallExtraFields(); $this->uninstallEntities(); } /** * @return ExtraField */ public static function getAuthUidExtraField() { $em = Database::getManager(); $efRepo = $em->getRepository('ChamiloCoreBundle:ExtraField'); /** @var ExtraField $extraField */ $extraField = $efRepo->findOneBy( [ 'variable' => self::EXTRAFIELD_AUTH_UID, 'extraFieldType' => ExtraField::USER_FIELD_TYPE, ] ); return $extraField; } /** * @return ExtraField */ public static function getLpItemExtraField() { $efRepo = Database::getManager()->getRepository('ChamiloCoreBundle:ExtraField'); /** @var ExtraField $extraField */ $extraField = $efRepo->findOneBy( [ 'variable' => self::EXTRAFIELD_LP_ITEM, 'extraFieldType' => ExtraField::LP_ITEM_FIELD_TYPE, ] ); return $extraField; } /** * @return ExtraField */ public static function getQuizQuestionExtraField() { $efRepo = Database::getManager()->getRepository('ChamiloCoreBundle:ExtraField'); /** @var ExtraField $extraField */ $extraField = $efRepo->findOneBy( [ 'variable' => self::EXTRAFIELD_QUIZ_QUESTION, 'extraFieldType' => ExtraField::QUESTION_FIELD_TYPE, ] ); return $extraField; } /** * @param int $userId * * @return ExtraFieldValues */ public static function getAuthUidValue($userId) { $extraField = self::getAuthUidExtraField(); $em = Database::getManager(); $efvRepo = $em->getRepository('ChamiloCoreBundle:ExtraFieldValues'); /** @var ExtraFieldValues $value */ $value = $efvRepo->findOneBy(['field' => $extraField, 'itemId' => $userId]); return $value; } /** * Get the whispeak_lp_item value for a LP item ID. * * @param int $lpItemId * * @return array|false */ public static function getLpItemValue($lpItemId) { $efv = new ExtraFieldValue('lp_item'); $value = $efv->get_values_by_handler_and_field_variable($lpItemId, self::EXTRAFIELD_LP_ITEM); return $value; } /** * @param int $lpItemId * * @return bool */ public static function isLpItemMarked($lpItemId) { if (!self::create()->isEnabled()) { return false; } $value = self::getLpItemValue($lpItemId); return !empty($value) && !empty($value['value']); } /** * Get the whispeak_quiz_question value for a quiz question ID. * * @param int $questionId * * @return array|false */ public static function getQuizQuestionValue($questionId) { $efv = new ExtraFieldValue('question'); $value = $efv->get_values_by_handler_and_field_variable($questionId, self::EXTRAFIELD_QUIZ_QUESTION); return $value; } /** * @param int $questionId * * @return bool */ public static function isQuizQuestionMarked($questionId) { if (!self::create()->isEnabled()) { return false; } $value = self::getQuizQuestionValue($questionId); return !empty($value) && !empty($value['value']); } /** * @param int $questionId * * @return bool */ public static function questionRequireAuthentify($questionId) { $isMarked = self::isQuizQuestionMarked($questionId); if (!$isMarked) { return false; } $questionInfo = ChamiloSession::read(self::SESSION_QUIZ_QUESTION, []); if (empty($questionInfo)) { return true; } if ((int) $questionId !== $questionInfo['question']) { return true; } if (false === $questionInfo['passed']) { return true; } return false; } /** * @param int $userId * * @return bool */ public static function checkUserIsEnrolled($userId) { $value = self::getAuthUidValue($userId); if (empty($value)) { return false; } return !empty($value->getValue()); } /** * @return string */ public static function getEnrollmentUrl() { return api_get_path(WEB_PLUGIN_PATH).'whispeakauth/enrollment.php'; } /** * @param string $uid * * @throws \Doctrine\ORM\OptimisticLockException */ public function saveEnrollment(User $user, $uid) { $em = Database::getManager(); $extraFieldValue = self::getAuthUidValue($user->getId()); if (empty($extraFieldValue)) { $extraField = self::getAuthUidExtraField(); $now = new DateTime('now', new DateTimeZone('UTC')); $extraFieldValue = new ExtraFieldValues(); $extraFieldValue ->setField($extraField) ->setItemId($user->getId()) ->setUpdatedAt($now); } $extraFieldValue->setValue($uid); $em->persist($extraFieldValue); $em->flush(); } /** * @return bool */ public function toolIsEnabled() { return 'true' === $this->get(self::SETTING_ENABLE); } /** * Access not allowed when tool is not enabled. * * @param bool $printHeaders Optional. Print headers. */ public function protectTool($printHeaders = true) { if ($this->toolIsEnabled()) { return; } api_not_allowed($printHeaders); } /** * Get the max_attemtps option. * * @return int */ public function getMaxAttempts() { return (int) $this->get(self::SETTING_MAX_ATTEMPTS); } /** * Install hook when saving the plugin configuration. * * @return WhispeakAuthPlugin */ public function performActionsAfterConfigure() { $observer = WhispeakConditionalLoginHook::create(); if ('true' === $this->get(self::SETTING_2FA)) { HookConditionalLogin::create()->attach($observer); } else { HookConditionalLogin::create()->detach($observer); } return $this; } /** * This method will call the Hook management insertHook to add Hook observer from this plugin. */ public function installHook() { $observer = WhispeakMyStudentsLpTrackingHook::create(); HookMyStudentsLpTracking::create()->attach($observer); $observer = WhispeakMyStudentsQuizTrackingHook::create(); HookMyStudentsQuizTracking::create()->attach($observer); } /** * This method will call the Hook management deleteHook to disable Hook observer from this plugin. */ public function uninstallHook() { $observer = WhispeakConditionalLoginHook::create(); HookConditionalLogin::create()->detach($observer); $observer = WhispeakMyStudentsLpTrackingHook::create(); HookMyStudentsLpTracking::create()->detach($observer); } /** * @param int $userId * * @throws \Doctrine\ORM\OptimisticLockException * * @return bool */ public static function deleteEnrollment($userId) { $extraFieldValue = self::getAuthUidValue($userId); if (empty($extraFieldValue)) { return false; } $em = Database::getManager(); $em->remove($extraFieldValue); $em->flush(); return true; } /** * Check if the WhispeakAuth plugin is installed and enabled. * * @param bool $checkEnabled Check if, additionnally to being installed, the plugin is enabled */ public function isEnabled(bool $checkEnabled = false): bool { return parent::isEnabled() && 'true' === api_get_plugin_setting('whispeakauth', self::SETTING_ENABLE); } /** * @param int $lpItemId * * @return bool */ public static function isAllowedToSaveLpItem($lpItemId) { if (!self::isLpItemMarked($lpItemId)) { return true; } $markedItem = ChamiloSession::read(self::SESSION_LP_ITEM, []); if (empty($markedItem)) { return true; } if ((int) $lpItemId !== (int) $markedItem['lp_item']) { return true; } return false; } /** * Display a error message. * * @param string|null $error Optional. The message text */ public static function displayNotAllowedMessage($error = null) { $error = empty($error) ? get_lang('NotAllowed') : $error; echo Display::return_message($error, 'error', false); exit; } /** * @param int $questionId * * @throws Exception * * @return string */ public static function quizQuestionAuthentify($questionId, Exercise $exercise) { ChamiloSession::write( self::SESSION_QUIZ_QUESTION, [ 'quiz' => (int) $exercise->iid, 'question' => (int) $questionId, 'url_params' => $_SERVER['QUERY_STRING'], 'passed' => false, ] ); $template = new Template('', false, false, false, true, false, false); $template->assign('question', $questionId); $template->assign('exercise', $exercise->iid); $content = $template->fetch('whispeakauth/view/quiz_question.html.twig'); echo $content; } /** * @param int $status * @param int $userId * @param int $lpItemId * @param int $lpId * * @throws \Doctrine\ORM\ORMException * @throws \Doctrine\ORM\OptimisticLockException * @throws \Doctrine\ORM\TransactionRequiredException * * @return LogEventLp|null */ public function addAttemptInLearningPath($status, $userId, $lpItemId, $lpId) { $em = Database::getManager(); $user = api_get_user_entity($userId); $lpItem = $em->find('ChamiloCourseBundle:CLpItem', $lpItemId); $lp = $em->find('ChamiloCourseBundle:CLp', $lpId); if (empty($lp) || empty($lpItem)) { return null; } $logEvent = new LogEventLp(); $logEvent ->setLpItem($lpItem) ->setLp($lp) ->setUser($user) ->setDatetime( api_get_utc_datetime(null, false, true) ) ->setActionStatus($status); $em->persist($logEvent); $em->flush(); return $logEvent; } /** * @param int $status * @param int $userId * @param int $questionId * @param int $quizId * * @throws \Doctrine\ORM\ORMException * @throws \Doctrine\ORM\OptimisticLockException * @throws \Doctrine\ORM\TransactionRequiredException * * @return LogEventQuiz|null */ public function addAttemptInQuiz($status, $userId, $questionId, $quizId) { $em = Database::getManager(); $user = api_get_user_entity($userId); $question = $em->find('ChamiloCourseBundle:CQuizQuestion', $questionId); $quiz = $em->find('ChamiloCourseBundle:CQuiz', $quizId); if (empty($quiz) || empty($question)) { return null; } $logEvent = new LogEventQuiz(); $logEvent ->setQuestion($question) ->setQuiz($quiz) ->setUser($user) ->setDatetime( api_get_utc_datetime(null, false, true) ) ->setActionStatus($status); $em->persist($logEvent); $em->flush(); return $logEvent; } /** * @param int $status * @param int $userId * * @throws \Doctrine\ORM\ORMException * @throws \Doctrine\ORM\OptimisticLockException * @throws \Doctrine\ORM\TransactionRequiredException * * @return LogEvent|null */ public function addAuthenticationAttempt($status, $userId) { $em = Database::getManager(); $user = api_get_user_entity($userId); $logEvent = new LogEvent(); $logEvent ->setUser($user) ->setDatetime( api_get_utc_datetime(null, false, true) ) ->setActionStatus($status); $em->persist($logEvent); $em->flush(); return $logEvent; } /** * @param int $lpId * @param int $userId * * @throws \Doctrine\ORM\Query\QueryException * * @return string */ public static function countAllAttemptsInLearningPath($lpId, $userId) { $query = Database::getManager() ->createQuery( 'SELECT COUNT(log) AS c FROM ChamiloPluginBundle:WhispeakAuth\LogEventLp log WHERE log.lp = :lp AND log.user = :user' ) ->setParameters(['lp' => $lpId, 'user' => $userId]); $totalCount = (int) $query->getSingleScalarResult(); return $totalCount; } /** * @param int $lpId * @param int $userId * * @throws \Doctrine\ORM\Query\QueryException * * @return string */ public static function countSuccessAttemptsInLearningPath($lpId, $userId) { $query = Database::getManager() ->createQuery( 'SELECT COUNT(log) AS c FROM ChamiloPluginBundle:WhispeakAuth\LogEventLp log WHERE log.lp = :lp AND log.user = :user AND log.actionStatus = :status' ) ->setParameters(['lp' => $lpId, 'user' => $userId, 'status' => LogEvent::STATUS_SUCCESS]); $totalCount = (int) $query->getSingleScalarResult(); return $totalCount; } /** * @param int $quizId * @param int $userId * * @throws \Doctrine\ORM\Query\QueryException * * @return string */ public static function countAllAttemptsInQuiz($quizId, $userId) { $query = Database::getManager() ->createQuery( 'SELECT COUNT(log) AS c FROM ChamiloPluginBundle:WhispeakAuth\LogEventQuiz log WHERE log.quiz = :quiz AND log.user = :user' ) ->setParameters(['quiz' => $quizId, 'user' => $userId]); $totalCount = (int) $query->getSingleScalarResult(); return $totalCount; } /** * @param int $quizId * @param int $userId * * @throws \Doctrine\ORM\Query\QueryException * * @return string */ public static function countSuccessAttemptsInQuiz($quizId, $userId) { $query = Database::getManager() ->createQuery( 'SELECT COUNT(log) AS c FROM ChamiloPluginBundle:WhispeakAuth\LogEventQuiz log WHERE log.quiz = :quiz AND log.user = :user AND log.actionStatus = :status' ) ->setParameters(['quiz' => $quizId, 'user' => $userId, 'status' => LogEvent::STATUS_SUCCESS]); $totalCount = (int) $query->getSingleScalarResult(); return $totalCount; } /** * @return string */ public function getApiUrl() { $url = $this->get(self::SETTING_API_URL); return trim($url, " \t\n\r \v/").'/'; } /** * Install extra fields for user, learning path and quiz question. */ private function installExtraFields() { UserManager::create_extra_field( self::EXTRAFIELD_AUTH_UID, \ExtraField::FIELD_TYPE_TEXT, $this->get_lang('Whispeak uid'), '' ); LpItem::createExtraField( self::EXTRAFIELD_LP_ITEM, \ExtraField::FIELD_TYPE_CHECKBOX, $this->get_lang('MarkForSpeechAuthentication'), '0', true, true ); $extraField = new \ExtraField('question'); $params = [ 'variable' => self::EXTRAFIELD_QUIZ_QUESTION, 'field_type' => \ExtraField::FIELD_TYPE_CHECKBOX, 'display_text' => $this->get_lang('MarkForSpeechAuthentication'), 'default_value' => '0', 'changeable' => true, 'visible_to_self' => true, 'visible_to_others' => false, ]; $extraField->save($params); } /** * Install the Doctrine's entities. * * @throws \Doctrine\ORM\Tools\ToolsException */ private function installEntities() { $em = Database::getManager(); if ($em->getConnection()->getSchemaManager()->tablesExist(['whispeak_log_event'])) { return; } $schemaTool = new SchemaTool($em); $schemaTool->createSchema( [ $em->getClassMetadata(LogEvent::class), $em->getClassMetadata(LogEventLp::class), $em->getClassMetadata(LogEventQuiz::class), ] ); } /** * Uninstall extra fields for user, learning path and quiz question. */ private function uninstallExtraFields() { $em = Database::getManager(); $authIdExtrafield = self::getAuthUidExtraField(); if (!empty($authIdExtrafield)) { $em ->createQuery('DELETE FROM ChamiloCoreBundle:ExtraFieldValues efv WHERE efv.field = :field') ->execute(['field' => $authIdExtrafield]); $em->remove($authIdExtrafield); $em->flush(); } $lpItemExtrafield = self::getLpItemExtraField(); if (!empty($lpItemExtrafield)) { $em ->createQuery('DELETE FROM ChamiloCoreBundle:ExtraFieldValues efv WHERE efv.field = :field') ->execute(['field' => $lpItemExtrafield]); $em->remove($lpItemExtrafield); $em->flush(); } $quizQuestionExtrafield = self::getQuizQuestionExtraField(); if (!empty($quizQuestionExtrafield)) { $em ->createQuery('DELETE FROM ChamiloCoreBundle:ExtraFieldValues efv WHERE efv.field = :field') ->execute(['field' => $quizQuestionExtrafield]); $em->remove($quizQuestionExtrafield); $em->flush(); } } /** * Uninstall the Doctrine's entities. */ private function uninstallEntities() { $em = Database::getManager(); if (!$em->getConnection()->getSchemaManager()->tablesExist(['whispeak_log_event'])) { return; } $schemaTool = new SchemaTool($em); $schemaTool->dropSchema( [ $em->getClassMetadata(LogEvent::class), $em->getClassMetadata(LogEventLp::class), $em->getClassMetadata(LogEventQuiz::class), ] ); } }