parent
9ff9f0fa6c
commit
dbd33061c1
File diff suppressed because one or more lines are too long
@ -0,0 +1,278 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
use Chamilo\CoreBundle\Entity\TrackEAttempt; |
||||
|
||||
/** |
||||
* Class QuestionOptionsEvaluationPlugin. |
||||
*/ |
||||
class QuestionOptionsEvaluationPlugin extends Plugin |
||||
{ |
||||
const SETTING_ENABLE = 'enable'; |
||||
const SETTING_MAX_SCORE = 'exercise_max_score'; |
||||
|
||||
const EXTRAFIELD_FORMULA = 'quiz_evaluation_formula'; |
||||
|
||||
/** |
||||
* QuestionValuationPlugin constructor. |
||||
*/ |
||||
protected function __construct() |
||||
{ |
||||
$version = '1.0'; |
||||
$author = 'Angel Fernando Quiroz Campos'; |
||||
|
||||
parent::__construct( |
||||
$version, |
||||
$author, |
||||
[ |
||||
self::SETTING_ENABLE => 'boolean', |
||||
self::SETTING_MAX_SCORE => 'text', |
||||
] |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* @return QuestionOptionsEvaluationPlugin|null |
||||
*/ |
||||
public static function create() |
||||
{ |
||||
static $result = null; |
||||
|
||||
return $result ? $result : $result = new self(); |
||||
} |
||||
|
||||
/** |
||||
* @param int $exerciseId |
||||
* @param int $iconSize |
||||
* |
||||
* @return string |
||||
*/ |
||||
public static function filterModify($exerciseId, $iconSize = ICON_SIZE_SMALL) |
||||
{ |
||||
$directory = basename(__DIR__); |
||||
$title = get_plugin_lang('plugin_title', self::class); |
||||
$enabled = api_get_plugin_setting('questionoptionsevaluation', 'enable'); |
||||
|
||||
if ('true' !== $enabled) { |
||||
return ''; |
||||
} |
||||
|
||||
return Display::url( |
||||
Display::return_icon('options_evaluation.png', $title, [], $iconSize), |
||||
api_get_path(WEB_PATH)."plugin/$directory/evaluation.php?exercise=$exerciseId", |
||||
[ |
||||
'class' => 'ajax', |
||||
'data-size' => 'md', |
||||
'data-title' => get_plugin_lang('plugin_title', self::class), |
||||
] |
||||
); |
||||
} |
||||
|
||||
public function install() |
||||
{ |
||||
$this->createExtraField(); |
||||
} |
||||
|
||||
public function uninstall() |
||||
{ |
||||
$this->removeExtraField(); |
||||
} |
||||
|
||||
/** |
||||
* @return Plugin |
||||
*/ |
||||
public function performActionsAfterConfigure() |
||||
{ |
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* @param int $formula |
||||
* @param Exercise $exercise |
||||
*/ |
||||
public function saveFormulaForExercise($formula, Exercise $exercise) |
||||
{ |
||||
$this->recalculateQuestionScore($formula, $exercise); |
||||
|
||||
$extraFieldValue = new ExtraFieldValue('quiz'); |
||||
$extraFieldValue->save( |
||||
[ |
||||
'item_id' => $exercise->iId, |
||||
'variable' => self::EXTRAFIELD_FORMULA, |
||||
'value' => $formula, |
||||
] |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* @param int $exerciseId |
||||
* |
||||
* @return int |
||||
*/ |
||||
public function getFormulaForExercise($exerciseId) |
||||
{ |
||||
$extraFieldValue = new ExtraFieldValue('quiz'); |
||||
$value = $extraFieldValue->get_values_by_handler_and_field_variable( |
||||
$exerciseId, |
||||
self::EXTRAFIELD_FORMULA |
||||
); |
||||
|
||||
if (empty($value)) { |
||||
return 0; |
||||
} |
||||
|
||||
return (int) $value['value']; |
||||
} |
||||
|
||||
/** |
||||
* @return int |
||||
*/ |
||||
public function getMaxScore() |
||||
{ |
||||
$max = $this->get(self::SETTING_MAX_SCORE); |
||||
|
||||
if (!empty($max)) { |
||||
return (int) $max; |
||||
} |
||||
|
||||
return 10; |
||||
} |
||||
|
||||
/** |
||||
* @param int $trackId |
||||
* @param int $formula |
||||
* |
||||
* @throws \Doctrine\ORM\ORMException |
||||
* @throws \Doctrine\ORM\OptimisticLockException |
||||
* @throws \Doctrine\ORM\TransactionRequiredException |
||||
* |
||||
* @return float|int |
||||
*/ |
||||
public function getResultWithFormula($trackId, $formula) |
||||
{ |
||||
$em = Database::getManager(); |
||||
|
||||
$eTrack = $em->find('ChamiloCoreBundle:TrackEExercises', $trackId); |
||||
|
||||
$qTracks = $em |
||||
->createQuery( |
||||
'SELECT a FROM ChamiloCoreBundle:TrackEAttempt a |
||||
WHERE a.exeId = :id AND a.userId = :user AND a.cId = :course AND a.sessionId = :session' |
||||
) |
||||
->setParameters( |
||||
[ |
||||
'id' => $eTrack->getExeId(), |
||||
'course' => $eTrack->getCId(), |
||||
'session' => $eTrack->getSessionId(), |
||||
'user' => $eTrack->getExeUserId(), |
||||
] |
||||
) |
||||
->getResult(); |
||||
|
||||
$counts = ['correct' => 0, 'incorrect' => 0]; |
||||
|
||||
/** @var TrackEAttempt $qTrack */ |
||||
foreach ($qTracks as $qTrack) { |
||||
if ($qTrack->getMarks() > 0) { |
||||
$counts['correct']++; |
||||
} elseif ($qTrack->getMarks() < 0) { |
||||
$counts['incorrect']++; |
||||
} |
||||
} |
||||
|
||||
switch ($formula) { |
||||
case 1: |
||||
$result = $counts['correct'] - $counts['incorrect']; |
||||
break; |
||||
case 2: |
||||
$result = $counts['correct'] - $counts['incorrect'] / 2; |
||||
break; |
||||
case 3: |
||||
$result = $counts['correct'] - $counts['incorrect'] / 3; |
||||
break; |
||||
} |
||||
|
||||
$score = ($result / count($qTracks)) * $this->getMaxScore(); |
||||
|
||||
return $score >= 0 ? $score : 0; |
||||
} |
||||
|
||||
/** |
||||
* @param int $formula |
||||
* @param Exercise $exercise |
||||
*/ |
||||
private function recalculateQuestionScore($formula, Exercise $exercise) |
||||
{ |
||||
$tblQuestion = Database::get_course_table(TABLE_QUIZ_QUESTION); |
||||
$tblAnswer = Database::get_course_table(TABLE_QUIZ_ANSWER); |
||||
|
||||
foreach ($exercise->questionList as $questionId) { |
||||
$question = Question::read($questionId, $exercise->course, false); |
||||
if (!in_array($question->selectType(), [UNIQUE_ANSWER, MULTIPLE_ANSWER])) { |
||||
continue; |
||||
} |
||||
|
||||
$questionAnswers = new Answer($questionId, $exercise->course_id, $exercise); |
||||
$counts = array_count_values($questionAnswers->correct); |
||||
|
||||
$questionPonderation = 0; |
||||
|
||||
foreach ($questionAnswers->correct as $i => $isCorrect) { |
||||
if (!isset($questionAnswers->iid[$i])) { |
||||
continue; |
||||
} |
||||
|
||||
$iid = $questionAnswers->iid[$i]; |
||||
|
||||
if ($question->selectType() == MULTIPLE_ANSWER || 0 === $formula) { |
||||
$ponderation = 1 == $isCorrect ? 1 / $counts[1] : -1 / $counts[0]; |
||||
} else { |
||||
$ponderation = 1 == $isCorrect ? 1 : -1 / $formula; |
||||
} |
||||
|
||||
if ($ponderation > 0) { |
||||
$questionPonderation += $ponderation; |
||||
} |
||||
|
||||
//error_log("question: $questionId -- i: $i -- w: $ponderation"); |
||||
Database::query("UPDATE $tblAnswer SET ponderation = $ponderation WHERE iid = $iid"); |
||||
} |
||||
|
||||
Database::query("UPDATE $tblQuestion SET ponderation = $questionPonderation WHERE iid = {$question->iid}"); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Creates an extrafield. |
||||
*/ |
||||
private function createExtraField() |
||||
{ |
||||
$extraField = new ExtraField('quiz'); |
||||
|
||||
if (false === $extraField->get_handler_field_info_by_field_variable(self::EXTRAFIELD_FORMULA)) { |
||||
$extraField |
||||
->save( |
||||
[ |
||||
'variable' => self::EXTRAFIELD_FORMULA, |
||||
'field_type' => ExtraField::FIELD_TYPE_TEXT, |
||||
'display_text' => $this->get_lang('EvaluationFormula'), |
||||
'visible_to_self' => false, |
||||
'changeable' => false, |
||||
] |
||||
); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Removes the extrafield . |
||||
*/ |
||||
private function removeExtraField() |
||||
{ |
||||
$extraField = new ExtraField('quiz'); |
||||
$value = $extraField->get_handler_field_info_by_field_variable(self::EXTRAFIELD_FORMULA); |
||||
|
||||
if (false !== $value) { |
||||
$extraField->delete($value['id']); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,20 @@ |
||||
# Questions Options Evaluation |
||||
|
||||
Allow recalulate the options score in questions: |
||||
|
||||
* Successes - Failures |
||||
* Successes - Failures / 2 |
||||
* Successes - Failures / 3 |
||||
* Recalculate question scores |
||||
|
||||
**Setup instructions** |
||||
|
||||
- Install plugin |
||||
- Set enabled in configuration |
||||
- Edit `configuration.php` file |
||||
```php |
||||
$_configuration['exercise_additional_teacher_modify_actions'] = [ |
||||
// ... |
||||
'questionoptionsevaluation' => ['QuestionOptionsEvaluationPlugin', 'filterModify'] |
||||
]; |
||||
``` |
||||
@ -0,0 +1,74 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
require_once __DIR__.'/../../main/inc/global.inc.php'; |
||||
|
||||
api_protect_teacher_script(); |
||||
api_protect_course_script(); |
||||
|
||||
$exerciseId = isset($_REQUEST['exercise']) ? (int) $_REQUEST['exercise'] : 0; |
||||
|
||||
if (empty($exerciseId)) { |
||||
echo Display::return_message(get_lang('NotAllowed'), 'error'); |
||||
|
||||
exit; |
||||
} |
||||
|
||||
$exercise = new Exercise(); |
||||
|
||||
if (!$exercise->read($exerciseId, false)) { |
||||
echo Display::return_message(get_lang('ExerciseNotFound'), 'error'); |
||||
|
||||
exit; |
||||
} |
||||
|
||||
$plugin = QuestionOptionsEvaluationPlugin::create(); |
||||
|
||||
if ($plugin->get('enable') !== 'true') { |
||||
echo Display::return_message(get_lang('NotAllowed'), 'error'); |
||||
|
||||
exit; |
||||
} |
||||
|
||||
$formEvaluation = new FormValidator('evaluation'); |
||||
$formEvaluation |
||||
->addRadio( |
||||
'formula', |
||||
$plugin->get_lang('EvaluationFormula'), |
||||
[ |
||||
-1 => $plugin->get_lang('NoFormula'), |
||||
0 => $plugin->get_lang('RecalculateQuestionScores'), |
||||
1 => $plugin->get_lang('Formula1'), |
||||
2 => $plugin->get_lang('Formula2'), |
||||
3 => $plugin->get_lang('Formula3'), |
||||
] |
||||
) |
||||
->setColumnsSize([4, 7, 1]); |
||||
$formEvaluation->addButtonSave(get_lang('Save'))->setColumnsSize([4, 7, 1]); |
||||
$formEvaluation->addHidden('exercise', $exerciseId); |
||||
|
||||
if ($formEvaluation->validate()) { |
||||
$exercise->read($exerciseId, true); |
||||
$values = $formEvaluation->exportValues(); |
||||
$formula = isset($values['formula']) ? (int) $values['formula'] : 0; |
||||
$plugin->saveFormulaForExercise($formula, $exercise); |
||||
Display::addFlash( |
||||
Display::return_message( |
||||
sprintf($plugin->get_lang('FormulaSavedForExerciseX'), $exercise->selectTitle(true)), |
||||
'success' |
||||
) |
||||
); |
||||
|
||||
header( |
||||
'Location: '.api_get_path(WEB_CODE_PATH).'exercise/exercise.php?'.api_get_cidreq()."&exerciseId=$exerciseId" |
||||
); |
||||
exit; |
||||
} |
||||
|
||||
$formEvaluation->setDefaults(['formula' => $plugin->getFormulaForExercise($exercise->iId)]); |
||||
|
||||
echo Display::return_message( |
||||
$plugin->get_lang('QuizQuestionsScoreRulesTitleConfirm'), |
||||
'warning' |
||||
); |
||||
$formEvaluation->display(); |
||||
@ -0,0 +1,4 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
QuestionOptionsEvaluationPlugin::create()->install(); |
||||
@ -0,0 +1,19 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
$strings['plugin_title'] = 'Question options evaluation'; |
||||
$strings['plugin_comment'] = 'Allow recalulate the options score in questions'; |
||||
|
||||
$strings['enable'] = 'Enable'; |
||||
$strings['exercise_max_score'] = 'Max score in exercises'; |
||||
$strings['exercise_max_score_help'] = 'Default is 10.'; |
||||
|
||||
$strings['QuizQuestionsScoreRulesTitleConfirm'] = 'Changing the evaluation formula generates changes for each question in the exercise and prevents undoing this change below. Are you sure you want to proceed?'; |
||||
$strings['EvaluationFormula'] = 'Evaluation formula'; |
||||
$strings['NoFormula'] = 'No formula'; |
||||
$strings['Formula1'] = 'Successes - Failures'; |
||||
$strings['Formula2'] = 'Successes - Failures / 2'; |
||||
$strings['Formula3'] = 'Successes - Failures / 3'; |
||||
$strings['QuestionsEvaluated'] = 'Questions evaluated'; |
||||
$strings['RecalculateQuestionScores'] = 'Recalculate question scores'; |
||||
$strings['FormulaSavedForExerciseX'] = 'Formula saved for exercise "%s".'; |
||||
@ -0,0 +1,19 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
$strings['plugin_title'] = 'Evaluación para opciones de pregunta'; |
||||
$strings['plugin_comment'] = 'Permite recalcular los puntajes de opciones en preguntas'; |
||||
|
||||
$strings['enable'] = 'Habilitar'; |
||||
$strings['exercise_max_score'] = 'Puntuación máxima por ejercicio'; |
||||
$strings['exercise_max_score_help'] = 'Por defecto es 10.'; |
||||
|
||||
$strings['QuizQuestionsScoreRulesTitleConfirm'] = 'Cambiar la fórmula de evaluación genera cambios para cada pregunta del ejercicio e impide deshacer este cambio a continuación. ¿Está seguro que desea proceder?'; |
||||
$strings['EvaluationFormula'] = 'Fórmula de evaluación'; |
||||
$strings['NoFormula'] = 'Sin formula'; |
||||
$strings['Formula1'] = 'Aciertos - Fallos'; |
||||
$strings['Formula2'] = 'Aciertos - Fallos / 2'; |
||||
$strings['Formula3'] = 'Aciertos - Fallos / 3'; |
||||
$strings['QuestionsEvaluated'] = 'Preguntas evaluadas'; |
||||
$strings['RecalculateQuestionScores'] = 'Recalcular puntuaciones de preguntas'; |
||||
$strings['FormulaSavedForExerciseX'] = 'Formula guardada para el ejercicio "%s".'; |
||||
@ -0,0 +1,4 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
$plugin_info = QuestionOptionsEvaluationPlugin::create()->get_info(); |
||||
@ -0,0 +1,4 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
QuestionOptionsEvaluationPlugin::create()->uninstall(); |
||||
@ -0,0 +1,42 @@ |
||||
# Survey Export CSV |
||||
|
||||
Exports survey results to a CSV file with a very specific format. |
||||
|
||||
This plugin will add a new action button in the surveys list, allowing the |
||||
teacher to export the survey in a CSV format meant at exchanging with external |
||||
analysis tools. |
||||
|
||||
The CSV format looks this way: |
||||
|
||||
``` |
||||
DATID;P01;P02;P03;P04;P05;P06;P07;P08;DATOBS |
||||
1;"1";"2";"26";"10";"2";"2";"2";"4";"2" |
||||
2;"1";"2";"32";"10";"6";"4";"4";"5";"2" |
||||
3;"2";"3";"27";"8";"5";"5";"2";"5";"1" |
||||
4;"1";"3";"33";"11";"1";"4";"1";"6";"1" |
||||
``` |
||||
|
||||
Where: |
||||
- DATID represents a sequential ID for the participants (not related to |
||||
their internal user ID) |
||||
- P01,P02,... represent the sequential ID of each question inside the survey |
||||
- DATOBS represents the free answer of the user to an open remarks form at |
||||
the end of the survey |
||||
|
||||
**Setup instructions** |
||||
|
||||
- Install plugin |
||||
- Set enabled in configuration |
||||
- Edit `configuration.php` file |
||||
```php |
||||
$_configuration['survey_additional_teacher_modify_actions'] = [ |
||||
// ... |
||||
'SurveyExportCSVPlugin' => ['SurveyExportCsvPlugin', 'filterModify'], |
||||
]; |
||||
``` |
||||
If you have large surveys with large numbers of users answering them, you |
||||
might want to ensure your c_survey_answer table is properly indexed. If not, |
||||
use the following SQL statement to modify that: |
||||
```sql |
||||
alter table c_survey_answer add index idx_c_survey_answerucsq (user, c_id, survey_id, question_id); |
||||
``` |
||||
@ -0,0 +1,95 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
/** |
||||
* Class SurveyExportCsvPlugin. |
||||
*/ |
||||
class SurveyExportCsvPlugin extends Plugin |
||||
{ |
||||
/** |
||||
* SurveyExportCsvPlugin constructor. |
||||
*/ |
||||
protected function __construct() |
||||
{ |
||||
$settings = [ |
||||
'enabled' => 'boolean', |
||||
'export_incomplete' => 'boolean', |
||||
]; |
||||
|
||||
parent::__construct('0.1', 'Angel Fernando Quiroz Campos', $settings); |
||||
} |
||||
|
||||
/** |
||||
* @return SurveyExportCsvPlugin|null |
||||
*/ |
||||
public static function create() |
||||
{ |
||||
static $result = null; |
||||
|
||||
return $result ? $result : $result = new self(); |
||||
} |
||||
|
||||
/** |
||||
* Installation process. |
||||
*/ |
||||
public function install() |
||||
{ |
||||
} |
||||
|
||||
/** |
||||
* Uninstallation process. |
||||
*/ |
||||
public function uninstall() |
||||
{ |
||||
} |
||||
|
||||
/** |
||||
* @param array $params |
||||
* |
||||
* @return string |
||||
*/ |
||||
public static function filterModify($params) |
||||
{ |
||||
$enabled = api_get_plugin_setting('surveyexportcsv', 'enabled'); |
||||
|
||||
if ($enabled !== 'true') { |
||||
return ''; |
||||
} |
||||
|
||||
$surveyId = isset($params['survey_id']) ? (int) $params['survey_id'] : 0; |
||||
$iconSize = isset($params['icon_size']) ? $params['icon_size'] : ICON_SIZE_SMALL; |
||||
|
||||
if (empty($surveyId)) { |
||||
return ''; |
||||
} |
||||
|
||||
return Display::url( |
||||
Display::return_icon('export_csv.png', get_lang('ExportAsCSV'), [], $iconSize), |
||||
api_get_path(WEB_PLUGIN_PATH).'surveyexportcsv/export.php?survey='.$surveyId.'&'.api_get_cidreq() |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Create tools for all courses. |
||||
*/ |
||||
private function createLinkToCourseTools() |
||||
{ |
||||
$result = Database::getManager() |
||||
->createQuery('SELECT c.id FROM ChamiloCoreBundle:Course c') |
||||
->getResult(); |
||||
|
||||
foreach ($result as $item) { |
||||
$this->createLinkToCourseTool($this->get_name().':teacher', $item['id'], 'survey.png'); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Remove all course tools created by plugin. |
||||
*/ |
||||
private function removeLinkToCourseTools() |
||||
{ |
||||
Database::getManager() |
||||
->createQuery('DELETE FROM ChamiloCourseBundle:CTool t WHERE t.link LIKE :link AND t.category = :category') |
||||
->execute(['link' => 'surveyexportcsv/start.php%', 'category' => 'plugin']); |
||||
} |
||||
} |
||||
@ -0,0 +1,215 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
use Chamilo\CourseBundle\Entity\CSurveyAnswer; |
||||
use Chamilo\CourseBundle\Entity\CSurveyQuestionOption; |
||||
|
||||
require_once __DIR__.'/../../main/inc/global.inc.php'; |
||||
|
||||
api_protect_course_script(true); |
||||
api_protect_teacher_script(); |
||||
|
||||
$surveyId = isset($_GET['survey']) ? (int) $_GET['survey'] : 0; |
||||
$surveyData = SurveyManager::get_survey($surveyId); |
||||
$courseId = api_get_course_int_id(); |
||||
|
||||
if (empty($surveyData)) { |
||||
api_not_allowed(true); |
||||
} |
||||
|
||||
$plugin = SurveyExportCsvPlugin::create(); |
||||
$allowExportIncomplete = 'true' === $plugin->get('export_incomplete'); |
||||
|
||||
if ($plugin->get('enabled') !== 'true') { |
||||
api_not_allowed(true); |
||||
} |
||||
|
||||
$questionsData = SurveyManager::get_questions($surveyId, $courseId); |
||||
// Sort questions by their "sort" field |
||||
$questionsData = array_filter( |
||||
$questionsData, |
||||
function ($questionData) { |
||||
return in_array($questionData['type'], ['yesno', 'multiplechoice', 'open']); |
||||
} |
||||
); |
||||
$numberOfQuestions = count($questionsData); |
||||
|
||||
usort( |
||||
$questionsData, |
||||
function ($qL, $qR) { |
||||
if ($qL['sort'] == $qR['sort']) { |
||||
return 0; |
||||
} |
||||
|
||||
return $qL['sort'] < $qR['sort'] ? -1 : 1; |
||||
} |
||||
); |
||||
|
||||
$content = []; |
||||
$content[] = firstRow($questionsData); |
||||
|
||||
$surveyAnswers = getSurveyAnswers($courseId, $surveyId); |
||||
|
||||
// Process answers |
||||
$i = 1; |
||||
foreach ($surveyAnswers as $answer) { |
||||
$row = otherRow($questionsData, $answer['user'], $courseId); |
||||
|
||||
if (!$allowExportIncomplete && count($row) < $numberOfQuestions) { |
||||
continue; |
||||
} |
||||
|
||||
array_unshift($row, $i); |
||||
|
||||
$content[] = $row; |
||||
$i++; |
||||
} |
||||
|
||||
// Generate file |
||||
$fileName = md5($surveyId.time()); |
||||
|
||||
Export::arrayToCsv($content, $fileName, false, "'"); |
||||
|
||||
/** |
||||
* Generate the first row for file. |
||||
* |
||||
* @param $questions |
||||
* |
||||
* @return array |
||||
*/ |
||||
function firstRow($questions) |
||||
{ |
||||
array_pop($questions); |
||||
$positions = array_keys($questions); |
||||
|
||||
$row = ['DATID']; |
||||
|
||||
foreach ($positions as $position) { |
||||
$row[] = sprintf("P%02d", $position + 1); |
||||
} |
||||
|
||||
$row[] = 'DATOBS'; |
||||
|
||||
return $row; |
||||
} |
||||
|
||||
/** |
||||
* Get unique answer for surveys by users. |
||||
* |
||||
* @param int $courseId |
||||
* @param int $surveyId |
||||
* |
||||
* @return array |
||||
*/ |
||||
function getSurveyAnswers($courseId, $surveyId) |
||||
{ |
||||
$surveyAnswers = Database::getManager() |
||||
->createQuery( |
||||
'SELECT sa.user, MIN(sa.iid) AS id FROM ChamiloCourseBundle:CSurveyAnswer sa |
||||
WHERE sa.cId = :course AND sa.surveyId = :survey |
||||
GROUP BY sa.user ORDER BY id ASC' |
||||
) |
||||
->setParameters(['course' => $courseId, 'survey' => $surveyId]) |
||||
->getResult(); |
||||
|
||||
return $surveyAnswers; |
||||
} |
||||
|
||||
/** |
||||
* @param string $user |
||||
* @param int $courseId |
||||
* @param int $surveyId |
||||
* @param int $questionId |
||||
* |
||||
* @return array |
||||
*/ |
||||
function getQuestionOptions($user, $courseId, $surveyId, $questionId) |
||||
{ |
||||
$options = Database::getManager() |
||||
->createQuery( |
||||
'SELECT sqo FROM ChamiloCourseBundle:CSurveyQuestionOption sqo |
||||
INNER JOIN ChamiloCourseBundle:CSurveyAnswer sa |
||||
WITH |
||||
sqo.cId = sa.cId |
||||
AND sqo.questionId = sa.questionId |
||||
AND sqo.surveyId = sa.surveyId |
||||
AND sqo.iid = sa.optionId |
||||
WHERE sa.user = :user AND sa.cId = :course AND sa.surveyId = :survey AND sa.questionId = :question' |
||||
) |
||||
->setParameters( |
||||
[ |
||||
'user' => $user, |
||||
'course' => $courseId, |
||||
'survey' => $surveyId, |
||||
'question' => $questionId, |
||||
] |
||||
) |
||||
->getResult(); |
||||
|
||||
return $options; |
||||
} |
||||
|
||||
/** |
||||
* @param int $questionId |
||||
* @param int $surveyId |
||||
* @param int $courseId |
||||
* @param string $user |
||||
* |
||||
* @throws \Doctrine\ORM\NonUniqueResultException |
||||
* |
||||
* @return CSurveyAnswer|null |
||||
*/ |
||||
function getOpenAnswer($questionId, $surveyId, $courseId, $user) |
||||
{ |
||||
$answer = Database::getManager() |
||||
->createQuery( |
||||
'SELECT sa FROM ChamiloCourseBundle:CSurveyAnswer sa |
||||
WHERE sa.cId = :course AND sa.surveyId = :survey AND sa.questionId = :question AND sa.user = :user' |
||||
) |
||||
->setParameters(['course' => $courseId, 'survey' => $surveyId, 'question' => $questionId, 'user' => $user]) |
||||
->getOneOrNullResult(); |
||||
|
||||
return $answer; |
||||
} |
||||
|
||||
/** |
||||
* Generate the content rows for file. |
||||
* |
||||
* @param array $questions |
||||
* @param string $user |
||||
* @param int $courseId |
||||
* |
||||
* @throws \Doctrine\ORM\NonUniqueResultException |
||||
* |
||||
* @return array |
||||
*/ |
||||
function otherRow($questions, $user, $courseId) |
||||
{ |
||||
$row = []; |
||||
|
||||
foreach ($questions as $question) { |
||||
if ('open' === $question['type']) { |
||||
$answer = getOpenAnswer($question['question_id'], $question['survey_id'], $courseId, $user); |
||||
|
||||
if ($answer) { |
||||
$row[] = Security::remove_XSS($answer->getOptionId()); |
||||
} |
||||
} else { |
||||
$options = getQuestionOptions( |
||||
$user, |
||||
$courseId, |
||||
$question['survey_id'], |
||||
$question['question_id'] |
||||
); |
||||
/** @var CSurveyQuestionOption|null $option */ |
||||
$option = end($options); |
||||
|
||||
if ($option) { |
||||
$value = $option->getSort(); |
||||
$row[] = '"'.$value.'"'; |
||||
} |
||||
} |
||||
} |
||||
|
||||
return $row; |
||||
} |
||||
@ -0,0 +1,4 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
SurveyExportCsvPlugin::create()->install(); |
||||
@ -0,0 +1,9 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
$strings['plugin_title'] = "Survey Export CSV"; |
||||
$strings['plugin_comment'] = "Export surveys results to CSV file"; |
||||
|
||||
$strings['enabled'] = 'Enabled'; |
||||
$strings['export_incomplete'] = 'Export incomplete'; |
||||
$strings['export_incomplete_help'] = 'Allow export incomplete surveys. Disabled by default.'; |
||||
@ -0,0 +1,9 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
$strings['plugin_title'] = "Survey Export CSV"; |
||||
$strings['plugin_comment'] = "Exportar resultados de encuestas en archivo CSV."; |
||||
|
||||
$strings['enabled'] = 'Habilitado'; |
||||
$strings['export_incomplete'] = 'Exportar incompletos'; |
||||
$strings['export_incomplete_help'] = 'Permitir exportar encuestas incompletas. Deshabilitado por defecto.'; |
||||
@ -0,0 +1,4 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
$plugin_info = SurveyExportCsvPlugin::create()->get_info(); |
||||
@ -0,0 +1,50 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
require_once __DIR__.'/../../main/inc/global.inc.php'; |
||||
|
||||
api_protect_course_script(true); |
||||
api_protect_teacher_script(); |
||||
|
||||
$plugin = SurveyExportCsvPlugin::create(); |
||||
|
||||
$courseCode = api_get_course_id(); |
||||
|
||||
// Create a sortable table with survey-data |
||||
$table = new SortableTable( |
||||
'surveys', |
||||
['SurveyUtil', 'get_number_of_surveys'], |
||||
['SurveyUtil', 'get_survey_data'], |
||||
2 |
||||
); |
||||
$table->set_additional_parameters(['cidReq' => $courseCode]); |
||||
$table->set_header(0, '', false); |
||||
$table->setHideColumn(0); |
||||
$table->set_header(1, get_lang('SurveyName')); |
||||
$table->set_header(2, get_lang('SurveyCode'), true, ['class' => 'text-center'], ['class' => 'text-center']); |
||||
$table->set_header(3, get_lang('NumberOfQuestions'), true, ['class' => 'text-right'], ['class' => 'text-right']); |
||||
$table->set_header(4, get_lang('Author')); |
||||
$table->set_header(5, get_lang('AvailableFrom'), true, ['class' => 'text-center'], ['class' => 'text-center']); |
||||
$table->set_header(6, get_lang('AvailableUntil'), true, ['class' => 'text-center'], ['class' => 'text-center']); |
||||
$table->set_header(7, get_lang('Invite'), true, ['class' => 'text-right'], ['class' => 'text-right']); |
||||
$table->set_header(8, get_lang('Anonymous'), true, ['class' => 'text-center'], ['class' => 'text-center']); |
||||
$table->set_column_filter(8, ['SurveyUtil', 'anonymous_filter']); |
||||
|
||||
if (api_get_configuration_value('allow_mandatory_survey')) { |
||||
$table->set_header(9, get_lang('IsMandatory'), true, ['class' => 'text-center'], ['class' => 'text-center']); |
||||
$table->set_header(10, get_lang('Export'), false, ['class' => 'text-center'], ['class' => 'text-center']); |
||||
$table->set_column_filter(10, ['SurveyExportCsvPlugin', 'filterModify']); |
||||
} else { |
||||
$table->set_header(9, get_lang('Export'), false, ['class' => 'text-center'], ['class' => 'text-center']); |
||||
$table->set_column_filter(9, ['SurveyExportCsvPlugin', 'filterModify']); |
||||
} |
||||
|
||||
$pageTitle = $plugin->get_title(); |
||||
|
||||
$template = new Template($pageTitle); |
||||
|
||||
$content = $table->return_table(); |
||||
|
||||
$template->assign('header', $pageTitle); |
||||
$template->assign('content', $content); |
||||
$template->display_one_col_template(); |
||||
@ -0,0 +1,4 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
SurveyExportCsvPlugin::create()->uninstall(); |
||||
@ -0,0 +1,10 @@ |
||||
Speech authentication with Whispeak |
||||
=================================== |
||||
|
||||
Instructions: |
||||
------------- |
||||
|
||||
1. Install plugin in Chamilo. |
||||
2. Set the plugin configuration with the token and API url. And enable the plugin. |
||||
3. Set the `login_bottom` region to the plugin. |
||||
4. Add `$_configuration['whispeak_auth_enabled'] = true;` to `configuration.php` file. |
||||
@ -0,0 +1,293 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
use Chamilo\CoreBundle\Entity\ExtraField; |
||||
use Chamilo\CoreBundle\Entity\ExtraFieldValues; |
||||
use Chamilo\UserBundle\Entity\User; |
||||
|
||||
/** |
||||
* Class WhispeakAuthPlugin. |
||||
*/ |
||||
class WhispeakAuthPlugin extends Plugin |
||||
{ |
||||
const SETTING_ENABLE = 'enable'; |
||||
const SETTING_API_URL = 'api_url'; |
||||
const SETTING_TOKEN = 'token'; |
||||
const SETTING_INSTRUCTION = 'instruction'; |
||||
|
||||
const EXTRAFIELD_AUTH_UID = 'whispeak_auth_uid'; |
||||
|
||||
/** |
||||
* StudentFollowUpPlugin constructor. |
||||
*/ |
||||
protected function __construct() |
||||
{ |
||||
parent::__construct( |
||||
'0.1', |
||||
'Angel Fernando Quiroz', |
||||
[ |
||||
self::SETTING_ENABLE => 'boolean', |
||||
self::SETTING_API_URL => 'text', |
||||
self::SETTING_TOKEN => 'text', |
||||
self::SETTING_INSTRUCTION => 'html', |
||||
] |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* @return WhispeakAuthPlugin |
||||
*/ |
||||
public static function create() |
||||
{ |
||||
static $result = null; |
||||
|
||||
return $result ? $result : $result = new self(); |
||||
} |
||||
|
||||
public function install() |
||||
{ |
||||
UserManager::create_extra_field( |
||||
self::EXTRAFIELD_AUTH_UID, |
||||
\ExtraField::FIELD_TYPE_TEXT, |
||||
$this->get_lang('Whispeak uid'), |
||||
'' |
||||
); |
||||
} |
||||
|
||||
public function uninstall() |
||||
{ |
||||
$extraField = self::getAuthUidExtraField(); |
||||
|
||||
if (empty($extraField)) { |
||||
return; |
||||
} |
||||
|
||||
$em = Database::getManager(); |
||||
|
||||
$em->createQuery('DELETE FROM ChamiloCoreBundle:ExtraFieldValues efv WHERE efv.field = :field') |
||||
->execute(['field' => $extraField]); |
||||
|
||||
$em->remove($extraField); |
||||
$em->flush(); |
||||
} |
||||
|
||||
/** |
||||
* @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; |
||||
} |
||||
|
||||
/** |
||||
* @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; |
||||
} |
||||
|
||||
/** |
||||
* @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 User $user |
||||
* @param string $filePath |
||||
* |
||||
* @return array |
||||
*/ |
||||
public function requestEnrollment(User $user, $filePath) |
||||
{ |
||||
$metadata = [ |
||||
'motherTongue' => $user->getLanguage(), |
||||
'spokenTongue' => $user->getLanguage(), |
||||
'audioType' => 'pcm', |
||||
]; |
||||
|
||||
return $this->sendRequest( |
||||
'enrollment', |
||||
$metadata, |
||||
$user, |
||||
$filePath |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* @param User $user |
||||
* @param string $uid |
||||
* |
||||
* @throws \Doctrine\ORM\OptimisticLockException |
||||
*/ |
||||
public function saveEnrollment(User $user, $uid) |
||||
{ |
||||
$em = Database::getManager(); |
||||
$value = self::getAuthUidValue($user->getId()); |
||||
|
||||
if (empty($value)) { |
||||
$ef = self::getAuthUidExtraField(); |
||||
$now = new DateTime('now', new DateTimeZone('UTC')); |
||||
|
||||
$value = new ExtraFieldValues(); |
||||
$value |
||||
->setField($ef) |
||||
->setItemId($user->getId()) |
||||
->setUpdatedAt($now); |
||||
} |
||||
|
||||
$value->setValue($uid); |
||||
|
||||
$em->persist($value); |
||||
$em->flush(); |
||||
} |
||||
|
||||
public function requestAuthentify(User $user, $filePath) |
||||
{ |
||||
$value = self::getAuthUidValue($user->getId()); |
||||
|
||||
if (empty($value)) { |
||||
return null; |
||||
} |
||||
|
||||
$metadata = [ |
||||
'uid' => $value->getValue(), |
||||
'audioType' => 'pcm', |
||||
]; |
||||
|
||||
return $this->sendRequest( |
||||
'authentify', |
||||
$metadata, |
||||
$user, |
||||
$filePath |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* @return string |
||||
*/ |
||||
public function getAuthentifySampleText() |
||||
{ |
||||
$phrases = []; |
||||
|
||||
for ($i = 1; $i <= 6; $i++) { |
||||
$phrases[] = $this->get_lang("AuthentifySampleText$i"); |
||||
} |
||||
|
||||
$rand = array_rand($phrases, 1); |
||||
|
||||
return $phrases[$rand]; |
||||
} |
||||
|
||||
/** |
||||
* @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); |
||||
} |
||||
|
||||
/** |
||||
* @return string |
||||
*/ |
||||
private function getApiUrl() |
||||
{ |
||||
$url = $this->get(self::SETTING_API_URL); |
||||
|
||||
return trim($url, " \t\n\r \v/"); |
||||
} |
||||
|
||||
/** |
||||
* @param string $endPoint |
||||
* @param array $metadata |
||||
* @param User $user |
||||
* @param string $filePath |
||||
* |
||||
* @return array |
||||
*/ |
||||
private function sendRequest($endPoint, array $metadata, User $user, $filePath) |
||||
{ |
||||
$moderator = $user->getCreatorId() ?: $user->getId(); |
||||
$apiUrl = $this->getApiUrl()."/$endPoint"; |
||||
$headers = [ |
||||
//"Content-Type: application/x-www-form-urlencoded", |
||||
"Authorization: Bearer ".$this->get(self::SETTING_TOKEN), |
||||
]; |
||||
$post = [ |
||||
'metadata' => json_encode($metadata), |
||||
'moderator' => "moderator_$moderator", |
||||
'client' => base64_encode($user->getUserId()), |
||||
'voice' => new CURLFile($filePath), |
||||
]; |
||||
|
||||
$ch = curl_init(); |
||||
curl_setopt($ch, CURLOPT_URL, $apiUrl); |
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); |
||||
curl_setopt($ch, CURLOPT_POST, true); |
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $post); |
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); |
||||
$result = curl_exec($ch); |
||||
curl_close($ch); |
||||
|
||||
$result = json_decode($result, true); |
||||
|
||||
if (!empty($result['error'])) { |
||||
return null; |
||||
} |
||||
|
||||
return json_decode($result, true); |
||||
} |
||||
} |
||||
@ -0,0 +1,136 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
use Chamilo\UserBundle\Entity\User; |
||||
use FFMpeg\FFMpeg; |
||||
use FFMpeg\Format\Audio\Wav; |
||||
|
||||
$cidReset = true; |
||||
|
||||
require_once __DIR__.'/../../../main/inc/global.inc.php'; |
||||
|
||||
$action = isset($_POST['action']) ? $_POST['action'] : 'enrollment'; |
||||
$isEnrollment = 'enrollment' === $action; |
||||
$isAuthentify = 'authentify' === $action; |
||||
|
||||
$isAllowed = true; |
||||
|
||||
if ($isEnrollment) { |
||||
api_block_anonymous_users(false); |
||||
|
||||
$isAllowed = !empty($_FILES['audio']); |
||||
} elseif ($isAuthentify) { |
||||
$isAllowed = !empty($_POST['username']) && !empty($_FILES['audio']); |
||||
} |
||||
|
||||
if (!$isAllowed) { |
||||
echo Display::return_message(get_lang('NotAllowed'), 'error'); |
||||
|
||||
exit; |
||||
} |
||||
|
||||
$plugin = WhispeakAuthPlugin::create(); |
||||
|
||||
$plugin->protectTool(false); |
||||
|
||||
if ($isAuthentify) { |
||||
$em = Database::getManager(); |
||||
/** @var User|null $user */ |
||||
$user = $em->getRepository('ChamiloUserBundle:User')->findOneBy(['username' => $_POST['username']]); |
||||
} else { |
||||
/** @var User $user */ |
||||
$user = api_get_user_entity(api_get_user_id()); |
||||
} |
||||
|
||||
if (empty($user)) { |
||||
echo Display::return_message(get_lang('NoUser'), 'error'); |
||||
|
||||
exit; |
||||
} |
||||
|
||||
$path = api_upload_file('whispeakauth', $_FILES['audio'], $user->getId()); |
||||
|
||||
if (false === $path) { |
||||
echo Display::return_message(get_lang('UploadError'), 'error'); |
||||
|
||||
exit; |
||||
} |
||||
|
||||
$newFullPath = $originFullPath = api_get_path(SYS_UPLOAD_PATH).'whispeakauth'.$path['path_to_save']; |
||||
$fileType = mime_content_type($originFullPath); |
||||
|
||||
if ('wav' !== substr($fileType, -3)) { |
||||
$directory = dirname($originFullPath); |
||||
$newFullPath = $directory.'/audio.wav'; |
||||
|
||||
try { |
||||
$ffmpeg = FFMpeg::create(); |
||||
|
||||
$audio = $ffmpeg->open($originFullPath); |
||||
$audio->save(new Wav(), $newFullPath); |
||||
} catch (Exception $exception) { |
||||
echo Display::return_message($exception->getMessage(), 'error'); |
||||
|
||||
exit; |
||||
} |
||||
} |
||||
|
||||
if ($isEnrollment) { |
||||
$result = $plugin->requestEnrollment($user, $newFullPath); |
||||
|
||||
if (empty($result)) { |
||||
echo Display::return_message($plugin->get_lang('EnrollmentFailed')); |
||||
|
||||
exit; |
||||
} |
||||
|
||||
$reliability = (int) $result['reliability']; |
||||
|
||||
if ($reliability <= 0) { |
||||
echo Display::return_message($plugin->get_lang('EnrollmentSignature0'), 'error'); |
||||
|
||||
exit; |
||||
} |
||||
|
||||
$plugin->saveEnrollment($user, $result['uid']); |
||||
|
||||
$message = '<strong>'.$plugin->get_lang('EnrollmentSuccess').'</strong>'; |
||||
$message .= PHP_EOL; |
||||
$message .= $plugin->get_lang("EnrollmentSignature$reliability"); |
||||
|
||||
echo Display::return_message($message, 'success', false); |
||||
|
||||
exit; |
||||
} |
||||
|
||||
if ($isAuthentify) { |
||||
$result = $plugin->requestAuthentify($user, $newFullPath); |
||||
|
||||
if (empty($result)) { |
||||
echo Display::return_message($plugin->get_lang('AuthentifyFailed'), 'error'); |
||||
|
||||
exit; |
||||
} |
||||
|
||||
$success = (bool) $result['audio'][0]['result']; |
||||
|
||||
if (!$success) { |
||||
echo Display::return_message($plugin->get_lang('TryAgain'), 'warning'); |
||||
|
||||
exit; |
||||
} |
||||
|
||||
$loggedUser = [ |
||||
'user_id' => $user->getId(), |
||||
'status' => $user->getStatus(), |
||||
'uidReset' => true, |
||||
]; |
||||
|
||||
ChamiloSession::write('_user', $loggedUser); |
||||
Login::init_user($user->getId(), true); |
||||
|
||||
echo Display::return_message($plugin->get_lang('AuthentifySuccess'), 'success'); |
||||
echo '<script>window.location.href = "'.api_get_path(WEB_PATH).'";</script>'; |
||||
|
||||
exit; |
||||
} |
||||
@ -0,0 +1,139 @@ |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
window.RecordAudio = (function () { |
||||
function useRecordRTC(rtcInfo) { |
||||
$(rtcInfo.blockId).show(); |
||||
|
||||
var mediaConstraints = {audio: true}, |
||||
localStream = null, |
||||
recordRTC = null, |
||||
btnStart = $(rtcInfo.btnStartId), |
||||
btnStop = $(rtcInfo.btnStopId), |
||||
btnSave = $(rtcInfo.btnSaveId), |
||||
tagAudio = $(rtcInfo.plyrPreviewId); |
||||
|
||||
function saveAudio() { |
||||
var recordedBlob = recordRTC.getBlob(); |
||||
|
||||
if (!recordedBlob) { |
||||
return; |
||||
} |
||||
|
||||
var btnSaveText = btnSave.html(); |
||||
var fileExtension = recordedBlob.type.split('/')[1]; |
||||
|
||||
var formData = new FormData(); |
||||
formData.append('audio', recordedBlob, 'audio.' + fileExtension); |
||||
|
||||
for (var prop in rtcInfo.data) { |
||||
if (!rtcInfo.data.hasOwnProperty(prop)) { |
||||
continue; |
||||
} |
||||
|
||||
formData.append(prop, rtcInfo.data[prop]); |
||||
} |
||||
|
||||
$.ajax({ |
||||
url: _p.web_plugin + 'whispeakauth/ajax/record_audio.php', |
||||
data: formData, |
||||
processData: false, |
||||
contentType: false, |
||||
type: 'POST', |
||||
beforeSend: function () { |
||||
btnStart.prop('disabled', true); |
||||
btnStop.prop('disabled', true); |
||||
btnSave.prop('disabled', true).text(btnSave.data('loadingtext')); |
||||
} |
||||
}).done(function (response) { |
||||
$('#messages-deck').append(response); |
||||
}).always(function () { |
||||
btnSave.prop('disabled', true).html(btnSaveText).parent().addClass('hidden'); |
||||
btnStop.prop('disabled', true).parent().addClass('hidden'); |
||||
btnStart.prop('disabled', false).parent().removeClass('hidden'); |
||||
}); |
||||
} |
||||
|
||||
btnStart.on('click', function () { |
||||
tagAudio.prop('src', ''); |
||||
|
||||
function successCallback(stream) { |
||||
localStream = stream; |
||||
|
||||
recordRTC = RecordRTC(stream, { |
||||
recorderType: StereoAudioRecorder, |
||||
numberOfAudioChannels: 1, |
||||
type: 'audio' |
||||
}); |
||||
recordRTC.startRecording(); |
||||
|
||||
btnSave.prop('disabled', true).parent().addClass('hidden'); |
||||
btnStop.prop('disabled', false).parent().removeClass('hidden'); |
||||
btnStart.prop('disabled', true).parent().addClass('hidden'); |
||||
tagAudio.removeClass('show').parents('#audio-wrapper').addClass('hidden'); |
||||
} |
||||
|
||||
function errorCallback(error) { |
||||
alert(error.message); |
||||
} |
||||
|
||||
if (navigator.mediaDevices.getUserMedia) { |
||||
navigator.mediaDevices.getUserMedia(mediaConstraints) |
||||
.then(successCallback) |
||||
.catch(errorCallback); |
||||
|
||||
return; |
||||
} |
||||
|
||||
navigator.getUserMedia = navigator.getUserMedia || navigator.mozGetUserMedia || navigator.webkitGetUserMedia; |
||||
|
||||
if (navigator.getUserMedia) { |
||||
navigator.getUserMedia(mediaConstraints, successCallback, errorCallback); |
||||
} |
||||
}); |
||||
|
||||
btnStop.on('click', function () { |
||||
if (!recordRTC) { |
||||
return; |
||||
} |
||||
|
||||
recordRTC.stopRecording(function (audioURL) { |
||||
btnStart.prop('disabled', false).parent().removeClass('hidden'); |
||||
btnStop.prop('disabled', true).parent().addClass('hidden'); |
||||
btnSave.prop('disabled', false).parent().removeClass('hidden'); |
||||
|
||||
tagAudio |
||||
.prop('src', audioURL) |
||||
.parents('#audio-wrapper') |
||||
.removeClass('hidden') |
||||
.addClass('show'); |
||||
|
||||
localStream.getTracks()[0].stop(); |
||||
}); |
||||
}); |
||||
|
||||
btnSave.on('click', function () { |
||||
if (!recordRTC) { |
||||
return; |
||||
} |
||||
|
||||
saveAudio(); |
||||
}); |
||||
} |
||||
|
||||
return { |
||||
init: function (rtcInfo) { |
||||
$(rtcInfo.blockId).hide(); |
||||
|
||||
var userMediaEnabled = (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) || |
||||
!!navigator.webkitGetUserMedia || |
||||
!!navigator.mozGetUserMedia || |
||||
!!navigator.getUserMedia; |
||||
|
||||
if (!userMediaEnabled) { |
||||
return; |
||||
} |
||||
|
||||
useRecordRTC(rtcInfo); |
||||
} |
||||
} |
||||
})(); |
||||
@ -0,0 +1,26 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
$cidReset = true; |
||||
|
||||
require_once __DIR__.'/../../main/inc/global.inc.php'; |
||||
|
||||
$plugin = WhispeakAuthPlugin::create(); |
||||
|
||||
$plugin->protectTool(); |
||||
|
||||
$form = new FormValidator('enter_username', 'post', '#'); |
||||
$form->addText('username', get_lang('Username')); |
||||
|
||||
$htmlHeadXtra[] = api_get_js('rtc/RecordRTC.js'); |
||||
$htmlHeadXtra[] = api_get_js_simple(api_get_path(WEB_PLUGIN_PATH).'whispeakauth/assets/js/RecordAudio.js'); |
||||
|
||||
$template = new Template(); |
||||
$template->assign('form', $form->returnForm()); |
||||
$template->assign('sample_text', $plugin->getAuthentifySampleText()); |
||||
|
||||
$content = $template->fetch('whispeakauth/view/authentify_recorder.html.twig'); |
||||
|
||||
$template->assign('header', $plugin->get_title()); |
||||
$template->assign('content', $content); |
||||
$template->display_one_col_template(); |
||||
@ -0,0 +1,28 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
$cidReset = true; |
||||
|
||||
require_once __DIR__.'/../../main/inc/global.inc.php'; |
||||
|
||||
api_block_anonymous_users(true); |
||||
|
||||
$userId = api_get_user_id(); |
||||
$plugin = WhispeakAuthPlugin::create(); |
||||
|
||||
$plugin->protectTool(); |
||||
|
||||
$sampleText = $plugin->get_lang('EnrollmentSampleText'); |
||||
|
||||
$htmlHeadXtra[] = api_get_js('rtc/RecordRTC.js'); |
||||
$htmlHeadXtra[] = api_get_js_simple(api_get_path(WEB_PLUGIN_PATH).'whispeakauth/assets/js/RecordAudio.js'); |
||||
|
||||
$template = new Template(); |
||||
$template->assign('is_authenticated', WhispeakAuthPlugin::checkUserIsEnrolled($userId)); |
||||
$template->assign('sample_text', $sampleText); |
||||
|
||||
$content = $template->fetch('whispeakauth/view/record_audio.html.twig'); |
||||
|
||||
$template->assign('header', $plugin->get_title()); |
||||
$template->assign('content', $content); |
||||
$template->display_one_col_template(); |
||||
@ -0,0 +1,14 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
$plugin = WhispeakAuthPlugin::create(); |
||||
|
||||
if ($plugin->toolIsEnabled()) { |
||||
echo Display::toolbarButton( |
||||
$plugin->get_lang('SpeechAuthentication'), |
||||
api_get_path(WEB_PLUGIN_PATH).'whispeakauth/authentify.php', |
||||
'sign-in', |
||||
'info', |
||||
['class' => 'btn-block'] |
||||
); |
||||
} |
||||
@ -0,0 +1,4 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
WhispeakAuthPlugin::create()->install(); |
||||
@ -0,0 +1,33 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
$strings['plugin_title'] = 'Speech authentication with Whispeak'; |
||||
$strings['plugin_comment'] = 'Allow speech authentication in Chamilo.'; |
||||
|
||||
$strings['enable'] = 'Enable'; |
||||
$strings['api_url'] = 'API URL'; |
||||
$strings['api_url_help'] = 'http://api.whispeak.io:8080/v1/'; |
||||
$strings['token'] = 'API key'; |
||||
$strings['instruction'] = '<p>Add <code>$_configuration[\'whispeak_auth_enabled\'] = true;</code>'. |
||||
'in the <code>configuration.php</code> file</p>'; |
||||
|
||||
$strings['EnrollmentSampleText'] = 'The famous Mona Lisa painting was painted by Leonardo Da Vinci.'; |
||||
$strings['AuthentifySampleText1'] = 'Dropping Like Flies.'; |
||||
$strings['AuthentifySampleText2'] = 'Keep Your Eyes Peeled.'; |
||||
$strings['AuthentifySampleText3'] = 'The fox screams at midnight.'; |
||||
$strings['AuthentifySampleText4'] = 'Go Out On a Limb.'; |
||||
$strings['AuthentifySampleText5'] = 'Under the Water.'; |
||||
$strings['AuthentifySampleText6'] = 'Barking Up The Wrong Tree.'; |
||||
|
||||
$strings['RepeatThisPhrase'] = 'Repeat this phrase three times after allowing audio recording:'; |
||||
$strings['EnrollmentSignature0'] = 'Unsustainable signature requires a new enrollment.'; |
||||
$strings['EnrollmentSignature1'] = 'Passable signature, advice to make a new enrollment.'; |
||||
$strings['EnrollmentSignature2'] = 'Correct signature.'; |
||||
$strings['EnrollmentSignature3'] = 'Good signature.'; |
||||
$strings['SpeechAuthAlreadyEnrolled'] = 'Speech authentication already enrolled previously.'; |
||||
$strings['SpeechAuthentication'] = 'Speech authentication'; |
||||
$strings['EnrollmentFailed'] = 'Enrollment failed.'; |
||||
$strings['EnrollmentSuccess'] = 'Enrollment success.'; |
||||
$strings['AuthentifyFailed'] = 'Login failed.'; |
||||
$strings['AuthentifySuccess'] = 'Authentication success!'; |
||||
$strings['TryAgain'] = 'Try again'; |
||||
@ -0,0 +1,18 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
$strings['plugin_title'] = 'Authentification vocale avec Whispeak'; |
||||
|
||||
$strings['EnrollmentSampleText'] = 'Le fameux chef-d\'oeuvre Mona Lisa a été peint par Léonardo da Vinci.'; |
||||
|
||||
$strings['RepeatThisPhrase'] = 'Autorisez l\'enregistrement audio puis répétez cette phrase trois fois:'; |
||||
$strings['EnrollmentSignature0'] = 'Signature non viable, nécessite un nouvel enrôlement'; |
||||
$strings['EnrollmentSignature1'] = 'Signature passable, conseil de faire un nouvel enrôlement.'; |
||||
$strings['EnrollmentSignature2'] = 'Signature correcte.'; |
||||
$strings['EnrollmentSignature3'] = 'Signature bonne.'; |
||||
$strings['SpeechAuthentication'] = 'Authentification de voix'; |
||||
$strings['EnrollmentFailed'] = 'Échec à l\'inscription.'; |
||||
$strings['EnrollmentSuccess'] = 'Inscription réussie.'; |
||||
$strings['AuthentifyFailed'] = 'Échec de l\'authentification.'; |
||||
$strings['AuthentifySuccess'] = 'Authentification réussie!'; |
||||
$strings['TryAgain'] = 'Essayez encore'; |
||||
@ -0,0 +1,33 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
$strings['plugin_title'] = 'Authenticación de voz con Whispeak'; |
||||
$strings['plugin_comment'] = 'Permitir autenticación de voz en Chamilo.'; |
||||
|
||||
$strings['enable'] = 'Habilitar'; |
||||
$strings['api_url'] = 'URL del API'; |
||||
$strings['api_url_help'] = 'http://api.whispeak.io:8080/v1/'; |
||||
$strings['token'] = 'Llave del API'; |
||||
$strings['instruction'] = '<p>Agrega <code>$_configuration[\'whispeak_auth_enabled\'] = true;</code>'. |
||||
'al archivo <code>configuration.php</code></p>'; |
||||
|
||||
$strings['EnrollmentSampleText'] = 'El famoso cuadro de Mona Lisa fue pintado por Leonardo Da Vinci.'; |
||||
$strings['AuthentifySampleText1'] = 'Cayendo como moscas.'; |
||||
$strings['AuthentifySampleText2'] = 'Mantén tus ojos abiertos.'; |
||||
$strings['AuthentifySampleText3'] = 'El zorro grita a medianoche.'; |
||||
$strings['AuthentifySampleText4'] = 'Ir por las ramas.'; |
||||
$strings['AuthentifySampleText5'] = 'Debajo del agua.'; |
||||
$strings['AuthentifySampleText6'] = 'Ladrando al árbol equivocado.'; |
||||
|
||||
$strings['RepeatThisPhrase'] = 'Repita esta frase tres veces después de permitir la grabación de audio:'; |
||||
$strings['EnrollmentSignature0'] = 'Firma insostenible, requiere una nueva inscripción.'; |
||||
$strings['EnrollmentSignature1'] = 'Firma aceptable, pero se aconseja hacer una nueva inscripción.'; |
||||
$strings['EnrollmentSignature2'] = 'Firma correcta.'; |
||||
$strings['EnrollmentSignature3'] = 'Buena firma.'; |
||||
$strings['SpeechAuthAlreadyEnrolled'] = 'Autenticación de voz registrada anteriormente.'; |
||||
$strings['SpeechAuthentication'] = 'Atenticación con voz'; |
||||
$strings['EnrollmentFailed'] = 'Inscripción fallida.'; |
||||
$strings['EnrollmentSuccess'] = 'Inscripción correcta.'; |
||||
$strings['AuthentifyFailed'] = 'Inicio de sesión fallido.'; |
||||
$strings['AuthentifySuccess'] = '¡Autenticación correcta!'; |
||||
$strings['TryAgain'] = 'Intente de nuevo.'; |
||||
@ -0,0 +1,4 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
$plugin_info = WhispeakAuthPlugin::create()->get_info(); |
||||
@ -0,0 +1,4 @@ |
||||
<?php |
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
WhispeakAuthPlugin::create()->uninstall(); |
||||
@ -0,0 +1,38 @@ |
||||
{% extends 'whispeakauth/view/record_audio.html.twig' %} |
||||
|
||||
{% block intro %} |
||||
<form class="form-horizontal" action="#" method="post"> |
||||
<div class="form-group "> |
||||
<label for="enter_username_username" class="col-sm-4 control-label"> |
||||
{{ 'Username'|get_lang }} |
||||
</label> |
||||
<div class="col-sm-8"> |
||||
<input class="form-control" name="username" type="text" id="username"> |
||||
</div> |
||||
</div> |
||||
</form> |
||||
|
||||
<hr> |
||||
|
||||
{{ parent() }} |
||||
{% endblock %} |
||||
|
||||
{% block config_data %} |
||||
$('#username').on('change', function () { |
||||
$('#record-audio-recordrtc, #btn-start-record, #btn-stop-record, #btn-save-record').off('click', ''); |
||||
|
||||
RecordAudio.init( |
||||
{ |
||||
blockId: '#record-audio-recordrtc', |
||||
btnStartId: '#btn-start-record', |
||||
btnStopId: '#btn-stop-record', |
||||
btnSaveId: '#btn-save-record', |
||||
plyrPreviewId: '#record-preview', |
||||
data: { |
||||
action: 'authentify', |
||||
username: $('#username').val() |
||||
} |
||||
} |
||||
); |
||||
}); |
||||
{% endblock %} |
||||
@ -0,0 +1,67 @@ |
||||
<div id="record-audio-recordrtc" class="row"> |
||||
<div class="col-sm-6"> |
||||
{% block intro %} |
||||
<p class="text-center">{{ 'RepeatThisPhrase'|get_plugin_lang('WhispeakAuthPlugin') }}</p> |
||||
<div class="well well-sm"> |
||||
<div class="row"> |
||||
<div class="col-sm-3 text-center"> |
||||
<span class="fa fa-microphone fa-5x fa-fw" aria-hidden="true"></span> |
||||
<span class="sr-only">{{ 'RecordAudio'|get_lang }}</span> |
||||
</div> |
||||
<div class="col-sm-9 text-center"> |
||||
<p class="lead">{{ sample_text }}</p> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
{% endblock %} |
||||
|
||||
<ul class="list-inline text-center"> |
||||
<li> |
||||
<button class="btn btn-primary" type="button" id="btn-start-record"> |
||||
<span class="fa fa-circle fa-fw" aria-hidden="true"></span> {{ 'StartRecordingAudio'|get_lang }} |
||||
</button> |
||||
</li> |
||||
<li class="hidden"> |
||||
<button class="btn btn-danger" type="button" id="btn-stop-record" disabled> |
||||
<span class="fa fa-square fa-fw" aria-hidden="true"></span> {{ 'StopRecordingAudio'|get_lang }} |
||||
</button> |
||||
</li> |
||||
<li class="hidden"> |
||||
<button class="btn btn-success" type="button" id="btn-save-record" |
||||
data-loadingtext="{{ 'Uploading'|get_lang }}" disabled> |
||||
<span class="fa fa-send fa-fw" aria-hidden="true"></span> {{ 'SaveRecordedAudio'|get_lang }} |
||||
</button> |
||||
</li> |
||||
</ul> |
||||
<p class="hidden" id="audio-wrapper"> |
||||
<audio class="center-block" controls id="record-preview"></audio> |
||||
</p> |
||||
</div> |
||||
<div class="col-sm-5 col-sm-offset-1" id="messages-deck"> |
||||
{% if is_authenticated %} |
||||
<div class="alert alert-info"> |
||||
<span class="fa fa-info-circle" aria-hidden="true"></span> |
||||
<strong>{{ 'SpeechAuthAlreadyEnrolled'|get_plugin_lang('WhispeakAuthPlugin') }}</strong> |
||||
</div> |
||||
{% endif %} |
||||
</div> |
||||
</div> |
||||
|
||||
<script> |
||||
$(function () { |
||||
{% block config_data %} |
||||
RecordAudio.init( |
||||
{ |
||||
blockId: '#record-audio-recordrtc', |
||||
btnStartId: '#btn-start-record', |
||||
btnStopId: '#btn-stop-record', |
||||
btnSaveId: '#btn-save-record', |
||||
plyrPreviewId: '#record-preview', |
||||
data: { |
||||
action: 'enrollment' |
||||
} |
||||
} |
||||
); |
||||
{% endblock %} |
||||
}); |
||||
</script> |
||||
Loading…
Reference in new issue