Merge branch 'master' into ui

pull/2715/head
Alex Aragón 7 years ago
commit 9f57df788f
  1. BIN
      main/img/lp_readout_text.png
  2. 1
      main/inc/lib/api.lib.php
  3. 37
      main/inc/lib/javascript/readout_text/css/start.css
  4. 79
      main/inc/lib/javascript/readout_text/js/start.js
  5. 13
      main/inc/lib/userportal.lib.php
  6. 30
      main/lang/english/trad4all.inc.php
  7. 33
      main/lang/spanish/trad4all.inc.php
  8. 534
      main/lp/learnpath.class.php
  9. 24
      main/lp/lp_add_item.php
  10. 21
      main/lp/lp_controller.php
  11. 23
      main/lp/lp_nav.php
  12. 75
      main/lp/readout_text.php
  13. 34
      plugin/ims_lti/Entity/ImsLtiTool.php
  14. 135
      plugin/ims_lti/ImsLtiPlugin.php
  15. 47
      plugin/ims_lti/README.md
  16. 24
      plugin/ims_lti/add.php
  17. 13
      plugin/ims_lti/create.php
  18. 13
      plugin/ims_lti/edit.php
  19. 32
      plugin/ims_lti/form.php
  20. 60
      plugin/ims_lti/item_return.php
  21. 5
      plugin/ims_lti/lang/english.php
  22. 5
      plugin/ims_lti/lang/french.php
  23. 5
      plugin/ims_lti/lang/spanish.php
  24. 16
      plugin/ims_lti/view/add.tpl
  25. 2
      plugin/ims_lti/view/start.tpl
  26. 7
      plugin/whispeakauth/README.md
  27. 289
      plugin/whispeakauth/WhispeakAuthPlugin.php
  28. 136
      plugin/whispeakauth/ajax/record_audio.php
  29. 139
      plugin/whispeakauth/assets/js/RecordAudio.js
  30. 26
      plugin/whispeakauth/authentify.php
  31. 28
      plugin/whispeakauth/enrollment.php
  32. 14
      plugin/whispeakauth/index.php
  33. 4
      plugin/whispeakauth/install.php
  34. 33
      plugin/whispeakauth/lang/english.php
  35. 18
      plugin/whispeakauth/lang/french.php
  36. 33
      plugin/whispeakauth/lang/spanish.php
  37. 4
      plugin/whispeakauth/plugin.php
  38. 4
      plugin/whispeakauth/uninstall.php
  39. 38
      plugin/whispeakauth/view/authentify_recorder.html.twig
  40. 67
      plugin/whispeakauth/view/record_audio.html.twig

Binary file not shown.

After

Width:  |  Height:  |  Size: 565 B

@ -107,6 +107,7 @@ define('SURVEY_VISIBLE_PUBLIC', 2);
/* When you add a new tool you must add it into function api_get_tools_lists() too */
define('TOOL_DOCUMENT', 'document');
define('TOOL_LP_FINAL_ITEM', 'final_item');
define('TOOL_READOUT_TEXT', 'readout_text');
define('TOOL_THUMBNAIL', 'thumbnail');
define('TOOL_HOTPOTATOES', 'hotpotatoes');
define('TOOL_CALENDAR_EVENT', 'calendar_event');

@ -0,0 +1,37 @@
body {
font-size: 25px;
line-height: 1.25em !important;
text-align: justify;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.text-highlight {
-webkit-transition: color .3s linear, text-shadow .3s linear;
-khtml-transition: color .3s linear, text-shadow .3s linear;
-moz-transition: color .3s linear, text-shadow .3s linear;
-ms-transition: color .3s linear, text-shadow .3s linear;
transition: color .3s linear, text-shadow .3s linear;
}
.text-highlight.active {
color: red;
-webkit-text-shadow: 0px 0px 1px rgba(255, 0, 0, 1);
-khtml-text-shadow: 0px 0px 1px rgba(255, 0, 0, 1);
-moz-text-shadow: 0px 0px 1px rgba(255, 0, 0, 1);
-ms-text-shadow: 0px 0px 1px rgba(255, 0, 0, 1);
text-shadow: 0px 0px 1px rgba(255, 0, 0, 1);
-webkit-transition: color .3s linear, text-shadow .3s linear;
-khtml-transition: color .3s linear, text-shadow .3s linear;
-moz-transition: color .3s linear, text-shadow .3s linear;
-ms-transition: color .3s linear, text-shadow .3s linear;
transition: color .3s linear, text-shadow .3s linear;
}
br {
margin-bottom: 1em;
}

@ -0,0 +1,79 @@
/* For licensing terms, see /license.txt */
$(function () {
var parent$ = window.parent.$,
$player = parent$('audio#lp_audio_media_player_html5');
if ($player.length === 0) {
$player = parent$('audio#lp_audio_media_player');
}
var player = $player.get(0),
def = $.Deferred();
if (!$player.length) {
processText(wordsCount);
return;
}
player.preload = 'auto';
function processText(turns) {
var tagEnd = '</span> ',
tagStart = tagEnd + '<span class="text-highlight">',
wordsPerSecond = Math.ceil(wordsCount / turns);
var indexes = Object.keys(words);
var output = '';
for (var i = 0; i < turns; i++) {
var block = indexes.slice(i * wordsPerSecond, i * wordsPerSecond + wordsPerSecond),
index = block[0];
if (!index) {
continue;
}
output += tagStart + words[index];
for (var j = 1; j < block.length; j++) {
index = block[j];
output += ' ' + words[index];
}
}
output += tagEnd;
output = output.slice(tagEnd.length);
$('.page-blank').html(output);
def.resolve(output);
return def.promise();
}
player.ontimeupdate = function () {
var block = Math.ceil(this.currentTime);
$('.text-highlight')
.removeClass('active')
.filter(function (index) {
return index + 1 == block;
})
.addClass('active');
};
player.onloadedmetadata = function () {
var turns = Math.ceil(this.duration);
processText(turns)
.then(function (output) {
var to = window.setTimeout(function () {
player.play();
window.clearTimeout(to);
}, 1500);
});
}
});

@ -888,6 +888,19 @@ class IndexManager
];
}
if (true === api_get_configuration_value('whispeak_auth_enabled')) {
//if (!WhispeakAuthPlugin::checkUserIsEnrolled($userId)) {
$itemTitle = WhispeakAuthPlugin::create()->get_title();
$items[] = [
'class' => 'whispeak-enrollment',
'icon' => Display::return_icon('addworkuser.png', $itemTitle),
'link' => WhispeakAuthPlugin::getEnrollmentUrl(),
'title' => $itemTitle,
];
//}
}
return $items;
}

@ -8277,4 +8277,34 @@ $FilterSessions = "Filter sessions";
$ScoreModel = "Score model";
$SyncDatabaseWithSchema = "Synchronize the database with the schema";
$SkillRelCourses = "Courses-Skills associations";
$UserRequestWaitingForAction = "A user is waiting for an action about his/her personal data request";
$TheUserXIsWaitingForAnActionGoHereX = "The user %s is waiting for an action about it's personal data request. \n\n To manage personal data requests you can follow this link : %s";
$RequestType = "Request type";
$DeleteAccount = "Delete account";
$RequestDate = "Request date";
$RemoveTerms = "Remove legal agreement";
$InformationRightToBeForgottenLinkX = "You can find more information about the user's right to be forgotten through the following page: %s";
$ExplanationDeleteLegal = "Please tell us why you want to withdraw the rights you previously gave us, to let us make it in the smoothest way possible.";
$ExplanationDeleteAccount = "Explain in this box why you want your account deleted";
$WhyYouWantToDeleteYourLegalAgreement = "You can ask below for your legal agreement to be deleted or your account to be deleted.</br>In the case of the legal agreement, once deleted you will have to accept it again on your next login to be able to access the platform and recover your access, because we cannot reasonably at the same time give you a personal environment and not treat your personal data.</br>In the case of an account deletion, your account will be deleted along with all of your course subscriptions and all the information related to your account. Please select the corresponding option with care. In both cases, one of our administrators will review your request before it is effective, to avoid any misunderstanding and definitive loss of your data.";
$PersonalDataPrivacy = "Personal data protection";
$RequestForAccountDeletion = "Request for account removal";
$TheUserXAskedForAccountDeletionWithJustificationXGoHereX = "User %s asked for the deletion of his/her account, explaining that \"%s\". You can process the request here: %s";
$TheUserXAskedLegalConsentWithdrawalWithJustificationXGoHereX = "User %s asked for the removal of his/her consent to our legal terms, explaining that \"%s\". You can process the request here: %s";
$RequestForLegalConsentWithdrawal = "Request for consent withdrawal on legal terms";
$Terms_and_conditionFields = "Terms and condition fields";
$ContentNotAccessibleRequestFromDataPrivacyOfficer = "This content is not accessible to you directly because of course-related access rules. If you require access to that data, please contact the Data Privacy Officer as defined in our privacy terms.";
$LearningCalendar = "Learning calendar";
$ControlPointAdded = "Control point added";
$NumberDaysAccumulatedInCalendar = "Number of days accumulated in calendar";
$DifferenceOfDaysAndCalendar = "Difference between days and calendar";
$SkillUserList = "Skills and users list";
$CourseId = "Course ID";
$CareDetailView = "Student care detail view";
$MoreDataAvailableInTheDatabaseButTrunkedForEfficiencyReasons = "More data available in the database but trunked for efficiency reasons.";
$SendAnnouncementCopyToMyself = "Send a copy by email to myself.";
$CourseHoursDuration = "Course duration (h)";
$AnnouncementWillBeSentTo = "Announcement will be sent to";
$FrmReadOutTextIntro = "You need attach a audio file according to the text, clicking on the %s icon.";
$CreateReadOutText = "Create read-out text";
?>

@ -8299,4 +8299,37 @@ $FilterSessions = "Filtrar sesiones";
$ScoreModel = "Modelo de evaluación";
$SyncDatabaseWithSchema = "Sincronizar la base de datos con el esquema";
$SkillRelCourses = "Asociaciones cursos-competencias";
$UserRequestWaitingForAction = "Un(a) usuario/a está esperando su intervención para un pedido de datos personales";
$TheUserXIsWaitingForAnActionGoHereX = "El usuario/a %s está esperando una intervención suya sobre un pedido de datos personales.\n\nPara gestionar los pedidos de datos personales, puede seguir este enlace: %s";
$RequestType = "Tipo de solicitud";
$DeleteAccount = "Eliminar cuenta";
$RequestDate = "Fecha de solicitud";
$RemoveTerms = "Anular acuerdo de condiciones de uso";
$InformationRightToBeForgottenLinkX = "Puede encontrar más información sobre el derecho al olvido en la página siguiente: %s";
$ExplanationDeleteLegal = "Por favor indíquenos porqué desea eliminar su acuerda a nuestras condiciones de uso, para asegurar que lo podamos hacer de la mejor manera posible.";
$ExplanationDeleteAccount = "Por favor indíquenos porqué desea que eliminemos su cuenta, para asegurar que lo podamos hacer de la mejor manera.";
$WhyYouWantToDeleteYourLegalAgreement = "Puede solicitar a bajo la eliminación de su acuerdo a nuestras condiciones de uso o la eliminación de su cuenta.<br />
En el caso de la eliminación de su acuerdo, tendrá que volver a aceptar nuestras condiciones en su próxima conexión, pues no nos es posible proveerle una experiencia personalizada sin al mismo tiempo gestionar algunos de sus datos personales.<br />
En el caso de la eliminación completa de su cuenta, ésta será eliminada junta con todas sus suscripciones a cursos y toda la información relacionada con su cuenta.<br />
Por favor seleccione la opción correspondiente con mucho cuidado. En ambos casos, su solicitud será revisada por uno de nuestros administradores, con el fin de evitar cualquier malentendimiento y/o pérdida definitiva de sus datos.";
$PersonalDataPrivacy = "Protección de datos personales";
$RequestForAccountDeletion = "Pedido de eliminación de cuenta";
$TheUserXAskedForAccountDeletionWithJustificationXGoHereX = "El usuario %s ha solicitado la eliminación de su cuenta, explicando que \"%s\". Puede gestionar esta solicitud aquí: %s";
$TheUserXAskedLegalConsentWithdrawalWithJustificationXGoHereX = "El usuario %s ha solicitado la eliminación de su acuerdo con nuestras condiciones de uso, explicando que \"%s\". Puede gestionar esta solicitud aquí: %s";
$RequestForLegalConsentWithdrawal = "Pedido de retractación de condiciones de uso";
$Terms_and_conditionFields = "Champs de conditions d'utilisation";
$ContentNotAccessibleRequestFromDataPrivacyOfficer = "Este contenido no le es accesible directamente por reglas de accesos definidas por curso. Si necesita acceder a esta información, por favor contacte con nuestro oficial de protección de datos, tal como definido en nuestros términos de protección de datos personales.";
$LearningCalendar = "Calendario de aprendizaje";
$ControlPointAdded = "Punto de control añadido";
$NumberDaysAccumulatedInCalendar = "Número de días acumulados en el calendario";
$DifferenceOfDaysAndCalendar = "Diferencia entre días y calendario";
$SkillUserList = "Lista de competencias y usuarios";
$CourseId = "ID de curso";
$CareDetailView = "Vista detallada de vida estudiantil";
$MoreDataAvailableInTheDatabaseButTrunkedForEfficiencyReasons = "Más datos disponibles en nuestra base de datos pero truncados por razones de eficiencia.";
$SendAnnouncementCopyToMyself = "Enviarme una copia por e-mail.";
$CourseHoursDuration = "Duración del curso (h)";
$AnnouncementWillBeSentTo = "El anuncio será enviado a";
$FrmReadOutTextIntro = "Necesita adjuntar un archivo de audio de acuerdo al texto, haciendo click en el ícono %s.";
$CreateReadOutText = "Crear texto leído";
?>

@ -6,6 +6,7 @@ use Chamilo\CoreBundle\Entity\Repository\ItemPropertyRepository;
use Chamilo\CourseBundle\Component\CourseCopy\CourseArchiver;
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
use Chamilo\CourseBundle\Entity\CDocument;
use Chamilo\CourseBundle\Entity\CItemProperty;
use Chamilo\CourseBundle\Entity\CLp;
use Chamilo\CourseBundle\Entity\CLpCategory;
@ -2300,22 +2301,30 @@ class learnpath
if (!empty($row['audio'])) {
$list = $_SESSION['oLP']->get_toc();
$type_quiz = false;
foreach ($list as $toc) {
if ($toc['id'] == $_SESSION['oLP']->current && $toc['type'] == 'quiz') {
$type_quiz = true;
}
}
switch ($row['item_type']) {
case 'quiz':
$type_quiz = false;
if ($type_quiz) {
if ($_SESSION['oLP']->prevent_reinit == 1) {
$autostart_audio = $row['status'] === 'completed' ? 'false' : 'true';
} else {
$autostart_audio = $autostart;
}
} else {
$autostart_audio = 'true';
foreach ($list as $toc) {
if ($toc['id'] == $_SESSION['oLP']->current) {
$type_quiz = true;
}
}
if ($type_quiz) {
if ($_SESSION['oLP']->prevent_reinit == 1) {
$autostart_audio = $row['status'] === 'completed' ? 'false' : 'true';
} else {
$autostart_audio = $autostart;
}
}
break;
case TOOL_READOUT_TEXT:;
$autostart_audio = 'false';
break;
default:
$autostart_audio = 'true';
}
$courseInfo = api_get_course_info();
@ -6253,7 +6262,7 @@ class learnpath
$title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
// Link for the documents
if ($arrLP[$i]['item_type'] == 'document') {
if ($arrLP[$i]['item_type'] == 'document' || $arrLP[$i]['item_type'] == TOOL_READOUT_TEXT) {
$url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
$title_cut = Display::url(
$title_cut,
@ -6466,6 +6475,7 @@ class learnpath
switch ($arrLP[$i]['item_type']) {
case TOOL_DOCUMENT:
case TOOL_LP_FINAL_ITEM:
case TOOL_READOUT_TEXT:
$urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
$previewIcon = Display::url(
$previewImage,
@ -7336,6 +7346,7 @@ class learnpath
$return .= $this->getSavedFinalItem();
break;
case TOOL_DOCUMENT:
case TOOL_READOUT_TEXT:
$tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
$sql_doc = "SELECT path FROM ".$tbl_doc."
WHERE c_id = ".$course_id." AND iid = ".intval($row['path']);
@ -7439,6 +7450,27 @@ class learnpath
$row_step
);
break;
case TOOL_READOUT_TEXT:
$tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
$sql = "SELECT lp.*, doc.path as dir
FROM $tbl_lp_item as lp
LEFT JOIN $tbl_doc as doc
ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
WHERE
doc.c_id = $course_id AND
lp.iid = ".$item_id;
$res_step = Database::query($sql);
$row_step = Database::fetch_array($res_step, 'ASSOC');
$return .= $this->display_manipulate(
$item_id,
$row['item_type']
);
$return .= $this->displayFrmReadOutText(
'edit',
$item_id,
$row_step
);
break;
case TOOL_LINK:
$link_id = (string) $row['path'];
if (ctype_digit($link_id)) {
@ -9104,6 +9136,471 @@ class learnpath
return $form->returnForm();
}
/**
* Returns the form to update or create a read-out text.
*
* @param string $action "add" or "edit"
* @param int $id ID of the lp_item (if already exists)
* @param mixed $extra_info Integer if document ID, string if info ('new')
*
* @throws Exception
* @throws HTML_QuickForm_Error
*
* @return string HTML form
*/
public function displayFrmReadOutText($action = 'add', $id = 0, $extra_info = 'new')
{
$course_id = api_get_course_int_id();
$_course = api_get_course_info();
$tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
$tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
$no_display_edit_textarea = false;
$item_description = '';
//If action==edit document
//We don't display the document form if it's not an editable document (html or txt file)
if ($action == 'edit') {
if (is_array($extra_info)) {
$path_parts = pathinfo($extra_info['dir']);
if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") {
$no_display_edit_textarea = true;
}
}
}
$no_display_add = false;
if ($id != 0 && is_array($extra_info)) {
$item_title = stripslashes($extra_info['title']);
$item_description = stripslashes($extra_info['description']);
$item_terms = stripslashes($extra_info['terms']);
if (empty($item_title)) {
$path_parts = pathinfo($extra_info['path']);
$item_title = stripslashes($path_parts['filename']);
}
} elseif (is_numeric($extra_info)) {
$sql = "SELECT path, title FROM $tbl_doc WHERE c_id = ".$course_id." AND iid = ".intval($extra_info);
$result = Database::query($sql);
$row = Database::fetch_array($result);
$item_title = $row['title'];
$item_title = str_replace('_', ' ', $item_title);
if (empty($item_title)) {
$path_parts = pathinfo($row['path']);
$item_title = stripslashes($path_parts['filename']);
}
} else {
$item_title = '';
$item_description = '';
}
if ($id != 0 && is_array($extra_info)) {
$parent = $extra_info['parent_item_id'];
} else {
$parent = 0;
}
$sql = "SELECT * FROM $tbl_lp_item WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
$result = Database::query($sql);
$arrLP = [];
while ($row = Database::fetch_array($result)) {
$arrLP[] = [
'id' => $row['iid'],
'item_type' => $row['item_type'],
'title' => $row['title'],
'path' => $row['path'],
'description' => $row['description'],
'parent_item_id' => $row['parent_item_id'],
'previous_item_id' => $row['previous_item_id'],
'next_item_id' => $row['next_item_id'],
'display_order' => $row['display_order'],
'max_score' => $row['max_score'],
'min_score' => $row['min_score'],
'mastery_score' => $row['mastery_score'],
'prerequisite' => $row['prerequisite'],
];
}
$this->tree_array($arrLP);
$arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
unset($this->arrMenu);
if ($action == 'add') {
$formHeader = get_lang('CreateTheDocument');
} else {
$formHeader = get_lang('EditTheCurrentDocument');
}
if ('edit' === $action) {
$urlAudioIcon = Display::url(
Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY),
api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'&'
.http_build_query(['view' => 'build', 'id' => $id, 'action' => 'add_audio'])
);
} else {
$urlAudioIcon = Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY);
}
$form = new FormValidator(
'frm_add_reading',
'POST',
$this->getCurrentBuildingModeURL(),
'',
['enctype' => 'multipart/form-data']
);
$form->addHeader($formHeader);
$form->addHtml(
Display::return_message(
sprintf(get_lang('FrmReadOutTextIntro'), $urlAudioIcon),
'normal',
false
)
);
$defaults['title'] = !empty($item_title) ? Security::remove_XSS($item_title) : '';
$defaults['description'] = $item_description;
$data = $this->generate_lp_folder($_course);
if ($action != 'edit') {
$folders = DocumentManager::get_all_document_folders($_course, 0, true);
DocumentManager::build_directory_selector(
$folders,
'',
[],
true,
$form,
'directory_parent_id'
);
}
if (isset($data['id'])) {
$defaults['directory_parent_id'] = $data['id'];
}
$form->addElement(
'text',
'title',
get_lang('Title')
);
$form->applyFilter('title', 'trim');
$form->applyFilter('title', 'html_filter');
$arrHide[0]['value'] = $this->name;
$arrHide[0]['padding'] = 20;
for ($i = 0; $i < count($arrLP); $i++) {
if ($action != 'add') {
if ($arrLP[$i]['item_type'] == 'dir' &&
!in_array($arrLP[$i]['id'], $arrHide) &&
!in_array($arrLP[$i]['parent_item_id'], $arrHide)
) {
$arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
$arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
}
} else {
if ($arrLP[$i]['item_type'] == 'dir') {
$arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
$arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
}
}
}
$parent_select = $form->addSelect(
'parent',
get_lang('Parent'),
[],
['onchange' => "javascript: load_cbo(this.value, 'frm_add_reading_previous');"]
);
$my_count = 0;
foreach ($arrHide as $key => $value) {
if ($my_count != 0) {
// The LP name is also the first section and is not in the same charset like the other sections.
$value['value'] = Security::remove_XSS($value['value']);
$parent_select->addOption(
$value['value'],
$key,
'style="padding-left:'.$value['padding'].'px;"'
);
} else {
$value['value'] = Security::remove_XSS($value['value']);
$parent_select->addOption(
$value['value'],
$key,
'style="padding-left:'.$value['padding'].'px;"'
);
}
$my_count++;
}
if (!empty($id)) {
$parent_select->setSelected($parent);
} else {
$parent_item_id = Session::read('parent_item_id', 0);
$parent_select->setSelected($parent_item_id);
}
if (is_array($arrLP)) {
reset($arrLP);
}
$arrHide = [];
$s_selected_position = null;
// POSITION
$lastPosition = null;
for ($i = 0; $i < count($arrLP); $i++) {
if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
$arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
) {
if ((isset($extra_info['previous_item_id']) &&
$extra_info['previous_item_id'] == $arrLP[$i]['id']) || $action == 'add'
) {
$s_selected_position = $arrLP[$i]['id'];
}
$arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
}
$lastPosition = $arrLP[$i]['id'];
}
if (empty($s_selected_position)) {
$s_selected_position = $lastPosition;
}
$position = $form->addSelect(
'previous',
get_lang('Position'),
[]
);
$position->addOption(get_lang('FirstPosition'), 0);
foreach ($arrHide as $key => $value) {
$padding = isset($value['padding']) ? $value['padding'] : 20;
$position->addOption(
$value['value'],
$key,
'style="padding-left:'.$padding.'px;"'
);
}
$position->setSelected($s_selected_position);
if (is_array($arrLP)) {
reset($arrLP);
}
$arrHide = [];
for ($i = 0; $i < count($arrLP); $i++) {
if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
$arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
) {
$arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
}
}
if (!$no_display_add) {
$item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
$edit = isset($_GET['edit']) ? $_GET['edit'] : null;
if ($extra_info == 'new' || $item_type == TOOL_READOUT_TEXT || $edit == 'true') {
if (!$no_display_edit_textarea) {
$content = '';
if (isset($_POST['content'])) {
$content = stripslashes($_POST['content']);
} elseif (is_array($extra_info)) {
$content = $this->display_document($extra_info['path'], false, false);
} elseif (is_numeric($extra_info)) {
$content = $this->display_document($extra_info, false, false);
}
// A new document, it is in the root of the repository.
if (is_array($extra_info) && $extra_info != 'new') {
} else {
$this->generate_lp_folder($_course);
}
if ($_GET['action'] == 'add_item') {
$text = get_lang('LPCreateDocument');
} else {
$text = get_lang('SaveDocument');
}
$form->addTextarea('content_lp', get_lang('Content'), ['rows' => 20]);
$form
->defaultRenderer()
->setElementTemplate($form->getDefaultElementTemplate(), 'content_lp');
$form->addButtonSave($text, 'submit_button');
$defaults['content_lp'] = $content;
}
} elseif (is_numeric($extra_info)) {
$form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
$return = $this->display_document($extra_info, true, true, true);
$form->addElement('html', $return);
}
}
if (is_numeric($extra_info)) {
$form->addElement('hidden', 'path', $extra_info);
} elseif (is_array($extra_info)) {
$form->addElement('hidden', 'path', $extra_info['path']);
}
$form->addElement('hidden', 'type', TOOL_READOUT_TEXT);
$form->addElement('hidden', 'post_time', time());
$form->setDefaults($defaults);
return $form->returnForm();
}
/**
* @param array $courseInfo
* @param string $content
* @param string $title
* @param int $parentId
*
* @return int
* @throws \Doctrine\ORM\ORMException
* @throws \Doctrine\ORM\OptimisticLockException
* @throws \Doctrine\ORM\TransactionRequiredException
*/
public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0) {
$creatorId = api_get_user_id();
$sessionId = api_get_session_id();
// Generates folder
$result = $this->generate_lp_folder($courseInfo);
$dir = $result['dir'];
if (empty($parentId) || $parentId == '/') {
$postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
$dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
if ($parentId === '/') {
$dir = '/';
}
// Please, do not modify this dirname formatting.
if (strstr($dir, '..')) {
$dir = '/';
}
if (!empty($dir[0]) && $dir[0] == '.') {
$dir = substr($dir, 1);
}
if (!empty($dir[0]) && $dir[0] != '/') {
$dir = '/'.$dir;
}
if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
$dir .= '/';
}
} else {
$parentInfo = DocumentManager::get_document_data_by_id(
$parentId,
$courseInfo['code']
);
if (!empty($parentInfo)) {
$dir = $parentInfo['path'].'/';
}
}
$filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
if (!is_dir($filepath)) {
$dir = '/';
$filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
}
$originalTitle = !empty($title) ? $title : $_POST['title'];
if (!empty($title)) {
$title = api_replace_dangerous_char(stripslashes($title));
} else {
$title = api_replace_dangerous_char(stripslashes($_POST['title']));
}
$title = disable_dangerous_file($title);
$filename = $title;
$content = !empty($content) ? $content : $_POST['content_lp'];
$tmpFileName = $filename;
$i = 0;
while (file_exists($filepath.$tmpFileName.'.html')) {
$tmpFileName = $filename.'_'.++$i;
}
$filename = $tmpFileName.'.html';
$content = stripslashes($content);
if (file_exists($filepath.$filename)) {
return 0;
}
$putContent = file_put_contents($filepath.$filename, $content);
if ($putContent === false) {
return 0;
}
$fileSize = filesize($filepath.$filename);
$saveFilePath = $dir.$filename;
$documentId = add_document(
$courseInfo,
$saveFilePath,
'file',
$fileSize,
$tmpFileName,
'',
0, //readonly
true,
null,
$sessionId,
$creatorId
);
if (!$documentId) {
return 0;
}
api_item_property_update(
$courseInfo,
TOOL_DOCUMENT,
$documentId,
'DocumentAdded',
$creatorId,
null,
null,
null,
null,
$sessionId
);
$newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
$newTitle = $originalTitle;
if ($newComment || $newTitle) {
$em = Database::getManager();
/** @var CDocument $doc */
$doc = $em->find('ChamiloCourseBundle:CDocument', $documentId);
if ($newComment) {
$doc->setComment($newComment);
}
if ($newTitle) {
$doc->setTitle($newTitle);
}
$em->persist($doc);
$em->flush();
}
return $documentId;
}
/**
* Return HTML form to add/edit a link item.
*
@ -10020,6 +10517,7 @@ class learnpath
$headers = [
get_lang('Files'),
get_lang('CreateTheDocument'),
get_lang('CreateReadOutText'),
get_lang('Upload'),
];
@ -10091,9 +10589,10 @@ class learnpath
$url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
$form->addMultipleUpload($url);
$new = $this->display_document_form('add', 0);
$frmReadOutText = $this->displayFrmReadOutText('add');
$tabs = Display::tabs(
$headers,
[$documentTree, $new, $form->returnForm()],
[$documentTree, $new, $frmReadOutText, $form->returnForm()],
'subtab'
);
@ -12890,6 +13389,9 @@ EOD;
return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
.$myrow['forum_id'].'&lp=true&'.$extraParams;
case TOOL_READOUT_TEXT:
return api_get_path(WEB_CODE_PATH).'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'
.$extraParams;
case TOOL_DOCUMENT:
$document = $em
->getRepository('ChamiloCourseBundle:CDocument')

@ -47,6 +47,30 @@ if ($learnPath->get_lp_session_id() != api_get_session_id()) {
}
$htmlHeadXtra[] = '<script>'.$learnPath->get_js_dropdown_array()."
function load_cbo(id, previousId) {
if (!id) {
return false;
}
previousId = previousId || 'previous';
var cbo = document.getElementById(previousId);
for (var i = cbo.length - 1; i > 0; i--) {
cbo.options[i] = null;
}
var k=0;
for (var i = 1; i <= child_name[id].length; i++){
var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
option.style.paddingLeft = '40px';
cbo.options[i] = option;
k = i;
}
cbo.options[k].selected = true;
$('#' + previousId).selectpicker('refresh');
}
$(function() {
if ($('#previous')) {
if('parent is'+$('#idParent').val()) {

@ -451,6 +451,27 @@ switch ($action) {
$description,
$prerequisites
);
} elseif ($_POST['type'] == TOOL_READOUT_TEXT) {
if (isset($_POST['path']) && $_GET['edit'] != 'true') {
$document_id = $_POST['path'];
} else {
$document_id = $_SESSION['oLP']->createReadOutText(
$_course,
$_POST['content_lp'],
$_POST['title'],
$directoryParentId
);
}
$new_item_id = $_SESSION['oLP']->add_item(
$parent,
$previous,
TOOL_READOUT_TEXT,
$document_id,
$post_title,
$description,
$prerequisites
);
} else {
// For all other item types than documents,
// load the item using the item type and path rather than its ID.

@ -50,17 +50,16 @@ if ($myLP) {
$progress_bar = $myLP->getProgressBar();
$navigation_bar = $myLP->get_navigation_bar();
$mediaplayer = $myLP->get_mediaplayer($lpItemId, $autostart);
if ($mediaplayer) {
echo $mediaplayer;
?>
<script>
$(function() {
jQuery('video:not(.skip), audio:not(.skip)').mediaelementplayer();
});
</script>
<?php
}
}
session_write_close();
?>
<script>
$(document).ready(function() {
jQuery('video:not(.skip), audio:not(.skip)').mediaelementplayer({
success: function(player, node) {
}
});
});
</script>
<span>
<?php echo !empty($mediaplayer) ? $mediaplayer : '&nbsp;'; ?>
</span>

@ -0,0 +1,75 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Print a read-out text inside a session
*
* @package chamilo.learnpath
*/
use Chamilo\CourseBundle\Entity\CDocument;
$_in_course = true;
require_once __DIR__.'/../inc/global.inc.php';
$current_course_tool = TOOL_LEARNPATH;
api_protect_course_script(true);
$id = isset($_GET['id']) ? intval($_GET['id']) : 0;
$lpId = isset($_GET['lp_id']) ? intval($_GET['lp_id']) : 0;
$courseInfo = api_get_course_info();
$courseCode = $courseInfo['code'];
$courseId = $courseInfo['real_id'];
$userId = api_get_user_id();
$sessionId = api_get_session_id();
$em = Database::getManager();
$documentRepo = $em->getRepository('ChamiloCourseBundle:CDocument');
// This page can only be shown from inside a learning path
if (!$id && !$lpId) {
api_not_allowed(true);
exit;
}
/** @var CDocument $document */
$document = $documentRepo->findOneBy(['cId' => $courseId, 'iid' => $id]);
if (empty($document)) {
// Try with normal id
/** @var CDocument $document */
$document = $documentRepo->findOneBy(['cId' => $courseId, 'id' => $id]);
if (empty($document)) {
Display::return_message(get_lang('FileNotFound'), 'error');
exit;
}
}
$documentPathInfo = pathinfo($document->getPath());
$jplayer_supported_files = ['mp4', 'ogv', 'flv', 'm4v'];
$extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
$coursePath = api_get_path(SYS_COURSE_PATH).$courseInfo['directory'];
$documentPath = '/document'.$document->getPath();
$documentText = file_get_contents($coursePath.$documentPath);
$documentText = api_remove_tags_with_space($documentText);
$wordsInfo = preg_split('/ |\n/', $documentText, -1, PREG_SPLIT_OFFSET_CAPTURE);
$words = [];
foreach ($wordsInfo as $wordInfo) {
$words[$wordInfo[1]] = nl2br($wordInfo[0]);
}
$htmlHeadXtra[] = '<script>
var words = '.json_encode($words, JSON_OBJECT_AS_ARRAY).',
wordsCount = '.count($words).'
</script>';
$htmlHeadXtra[] = api_get_js('readout_text/js/start.js');
$htmlHeadXtra[] = api_get_css(api_get_path(WEB_LIBRARY_JS_PATH).'readout_text/css/start.css');
$template = new Template(strip_tags($document->getTitle()));
$template->display_blank_template();

@ -63,6 +63,20 @@ class ImsLtiTool
* @ORM\Column(name="is_global", type="boolean")
*/
private $isGlobal = false;
/**
* @var bool
*
* @ORM\Column(name="active_deep_linking", type="boolean", nullable=false, options={"default": false})
*/
private $activeDeepLinking = false;
public function __construct()
{
$this->description = null;
$this->customParams = null;
$this->isGlobal = false;
$this->activeDeepLinking = false;
}
/**
* @return int
@ -218,4 +232,24 @@ class ImsLtiTool
'value' => $pairs[1]
];
}
/**
* Set activeDeepLinking.
*
* @param bool $activeDeepLinking
*/
public function setActiveDeepLinking($activeDeepLinking)
{
$this->activeDeepLinking = $activeDeepLinking;
}
/**
* Get activeDeepLinking.
*
* @return bool
*/
public function isActiveDeepLinking()
{
return $this->activeDeepLinking;
}
}

@ -29,7 +29,7 @@ class ImsLtiPlugin extends Plugin
*/
protected function __construct()
{
$version = '1.0 (beta)';
$version = '1.1 (beta)';
$author = 'Angel Fernando Quiroz Campos';
parent::__construct($version, $author, ['enabled' => 'boolean']);
@ -175,7 +175,7 @@ class ImsLtiPlugin extends Plugin
private function setCourseSettings()
{
$button = Display::toolbarButton(
$this->get_lang('AddExternalTool'),
$this->get_lang('ConfigureExternalTool'),
api_get_path(WEB_PLUGIN_PATH).'ims_lti/add.php?'.api_get_cidreq(),
'cog',
'primary'
@ -190,37 +190,69 @@ class ImsLtiPlugin extends Plugin
}
/**
* Add the course tool
* @param Course $course
* @param ImsLtiTool $tool
* @param Course $course
* @param ImsLtiTool $ltiTool
*
* @return CTool
*/
public function findCourseToolByLink(Course $course, ImsLtiTool $ltiTool)
{
$em = Database::getManager();
$toolRepo = $em->getRepository('ChamiloCourseBundle:CTool');
/** @var CTool $cTool */
$cTool = $toolRepo->findOneBy(
[
'cId' => $course,
'link' => self::generateToolLink($ltiTool)
]
);
return $cTool;
}
/**
* @param CTool $courseTool
* @param ImsLtiTool $ltiTool
*
* @throws \Doctrine\ORM\OptimisticLockException
*/
public function addCourseTool(Course $course, ImsLtiTool $tool)
public function updateCourseTool(CTool $courseTool, ImsLtiTool $ltiTool)
{
$em = Database::getManager();
$cTool = new CTool();
$cTool
->setCourse($course)
->setName($tool->getName())
->setLink($this->get_name().'/start.php?'.http_build_query(['id' => $tool->getId()]))
->setImage($this->get_name().'.png')
->setVisibility(1)
->setAdmin(0)
->setAddress('squaregray.gif')
->setAddedTool('NO')
->setTarget('_self')
->setCategory('plugin')
->setSessionId(0);
$em->persist($cTool);
$em->flush();
$cTool->setId($cTool->getIid());
$courseTool->setName($ltiTool->getName());
$em->persist($cTool);
$em->persist($courseTool);
$em->flush();
}
/**
* @param ImsLtiTool $tool
*
* @return string
*/
private static function generateToolLink(ImsLtiTool $tool)
{
return 'ims_lti/start.php?id='.$tool->getId();
}
/**
* Add the course tool
*
* @param Course $course
* @param ImsLtiTool $tool
*/
public function addCourseTool(Course $course, ImsLtiTool $tool)
{
$this->createLinkToCourseTool(
$tool->getName(),
$course->getId(),
null,
self::generateToolLink($tool)
);
}
/**
* @return string
*/
@ -256,11 +288,11 @@ class ImsLtiPlugin extends Plugin
public static function getUserRoles(User $user)
{
if ($user->getStatus() === INVITEE) {
return 'Learner/GuestLearner';
return 'Learner/GuestLearner,Learner';
}
if (!api_is_allowed_to_edit(false, true)) {
return 'Learner/Learner';
return 'Learner,Learner/Learner';
}
$roles = ['Instructor'];
@ -310,4 +342,55 @@ class ImsLtiPlugin extends Plugin
return implode(',', $scope);
}
/**
* @param array $contentItem
* @param ImsLtiTool $ltiTool
* @param Course $course
*
* @throws \Doctrine\ORM\OptimisticLockException
*/
public function saveItemAsLtiLink(array $contentItem, ImsLtiTool $ltiTool, Course $course)
{
$em = Database::getManager();
$ltiToolRepo = $em->getRepository('ChamiloPluginBundle:ImsLti\ImsLtiTool');
$url = empty($contentItem['url']) ? $ltiTool->getLaunchUrl() : $contentItem['url'];
/** @var ImsLtiTool $newLtiTool */
$newLtiTool = $ltiToolRepo->findOneBy(['launchUrl' => $url, 'isGlobal' => false]);
if (empty($newLtiTool)) {
$newLtiTool = new ImsLtiTool();
$newLtiTool
->setLaunchUrl($url)
->setConsumerKey(
$ltiTool->getConsumerKey()
)
->setSharedSecret(
$ltiTool->getSharedSecret()
);
}
$newLtiTool
->setName(
!empty($contentItem['title']) ? $contentItem['title'] : $ltiTool->getName()
)
->setDescription(
!empty($contentItem['text']) ? $contentItem['text'] : null
);
$em->persist($newLtiTool);
$em->flush();
$courseTool = $this->findCourseToolByLink($course, $newLtiTool);
if ($courseTool) {
$this->updateCourseTool($courseTool, $newLtiTool);
return;
}
$this->addCourseTool($course, $newLtiTool);
}
}

@ -1,24 +1,45 @@
IMS/LTI plugin
===
Version 1.0 (beta)
Version 1.1 (beta)
Installation
------------
1. Install the plugin from Plugin page
2. Enable the plugin from Plugin Settings page
3. Assign to the Administrator region
This plugin is meant to be later integrated into Chamilo (in a major version
release).
IMS/LTI defines the possibility to integrate tools or content into Chamilo.
This plugin allows the integration of a new tool into courses, without (for now)
obtaining any data back from those tools.
It will gradually be developed to support IMS/LTI content items.
As platform admin you can register external tools available for all courses.
You need set the tools settings in the IMS/LTI administration page.
Then the registered tools should be add in each course individually.
As teacher you can register external tools available only for the current course.
You need follow the link in the IMS/LTI block located in the Course Settings tool.
Then select a previously tool registered or register a new external tool.
As teacher you can register external tools available only for the current
course. You need follow the link in the IMS/LTI block located in the Course
Settings tool. Then select a previously tool registered or register a new
external tool.
This plugin is meant to be later integrated into Chamilo (in a major version release).
# Changelog
IMS/LTI defines the possibility to integrate tools or content into Chamilo.
This plugin allows the integration of a new tool into courses, without (for now) obtaining any data back from those tools.
It will gradually be developed to support IMS/LTI content items.
**v1.1**
* Support for Deep-Linking added.
# Installation
1. Install the plugin from Plugin page
2. Enable the plugin from Plugin Settings page
3. Assign to the Administrator region
# Upgrading
**To v1.1**
Run this changes on database:
```sql
ALTER TABLE plugin_ims_lti_tool
ADD active_deep_linking TINYINT(1) DEFAULT '0' NOT NULL,
CHANGE id id INT AUTO_INCREMENT NOT NULL,
CHANGE launch_url launch_url VARCHAR(255) NOT NULL;
```

@ -30,24 +30,36 @@ if ($baseTool && !$baseTool->isGlobal()) {
}
$form = new FormValidator('ims_lti_add_tool');
$form->addHeader($plugin->get_lang('ToolSettings'));
if ($baseTool) {
$form->addHtml('<p class="lead">'.Security::remove_XSS($baseTool->getDescription()).'</p>');
}
$form->addText('name', get_lang('Title'));
$form->addTextarea('description', get_lang('Description'), ['rows' => 10]);
if (!$baseTool) {
$form->addElement('url', 'url', $plugin->get_lang('LaunchUrl'));
$form->addText('consumer_key', $plugin->get_lang('ConsumerKey'), true);
$form->addText('shared_secret', $plugin->get_lang('SharedSecret'), true);
$form->addTextarea('custom_params', $plugin->get_lang('CustomParams'));
$form->addRule('url', get_lang('Required'), 'required');
} else {
}
$form->addButtonAdvancedSettings('lti_adv');
$form->addHtml('<div id="lti_adv_options" style="display:none;">');
$form->addTextarea('description', get_lang('Description'), ['rows' => 3]);
if (!$baseTool) {
$form->addTextarea('custom_params', [$plugin->get_lang('CustomParams'), $plugin->get_lang('CustomParamsHelp')]);
$form->addCheckBox('deep_linking', $plugin->get_lang('SupportDeepLinking'), get_lang('Yes'));
}
if ($baseTool) {
$form->addHidden('type', $baseTool->getId());
}
$form->addHtml('</div>');
$form->addButtonCreate($plugin->get_lang('AddExternalTool'));
if ($form->validate()) {
@ -93,5 +105,11 @@ $template->assign('form', $form->returnForm());
$content = $template->fetch('ims_lti/view/add.tpl');
$actions = Display::url(
Display::return_icon('add.png', $plugin->get_lang('AddExternalTool'), [], ICON_SIZE_MEDIUM),
api_get_self().'?'.api_get_cidreq()
);
$template->assign('actions', Display::toolbarAction('lti_toolbar', [$actions]));
$template->assign('content', $content);
$template->display_one_col_template();

@ -17,8 +17,12 @@ $form->addText('name', get_lang('Name'));
$form->addText('base_url', $plugin->get_lang('LaunchUrl'));
$form->addText('consumer_key', $plugin->get_lang('ConsumerKey'));
$form->addText('shared_secret', $plugin->get_lang('SharedSecret'));
$form->addTextarea('description', get_lang('Description'), ['rows' => 10]);
$form->addTextarea('custom_params', $plugin->get_lang('CustomParams'));
$form->addButtonAdvancedSettings('lti_adv');
$form->addHtml('<div id="lti_adv_options" style="display:none;">');
$form->addTextarea('description', get_lang('Description'), ['rows' => 3]);
$form->addTextarea('custom_params', [$plugin->get_lang('CustomParams'), $plugin->get_lang('CustomParamsHelp')]);
$form->addCheckBox('deep_linking', $plugin->get_lang('SupportDeepLinking'), get_lang('Yes'));
$form->addHtml('</div>');
$form->addButtonCreate($plugin->get_lang('AddExternalTool'));
if ($form->validate()) {
@ -32,7 +36,10 @@ if ($form->validate()) {
->setConsumerKey($formValues['consumer_key'])
->setSharedSecret($formValues['shared_secret'])
->setCustomParams($formValues['custom_params'])
->setIsGlobal(true);
->setIsGlobal(true)
->setActiveDeepLinking(
isset($formValues['deep_linking'])
);
$em->persist($externalTool);
$em->flush();

@ -31,11 +31,15 @@ if (!$tool) {
$form = new FormValidator('ims_lti_edit_tool');
$form->addText('name', get_lang('Name'));
$form->addTextarea('description', get_lang('Description'), ['rows' => 10]);
$form->addText('url', $plugin->get_lang('LaunchUrl'));
$form->addText('consumer_key', $plugin->get_lang('ConsumerKey'));
$form->addText('shared_secret', $plugin->get_lang('SharedSecret'));
$form->addTextarea('custom_params', $plugin->get_lang('CustomParams'));
$form->addButtonAdvancedSettings('lti_adv');
$form->addHtml('<div id="lti_adv_options" style="display:none;">');
$form->addTextarea('description', get_lang('Description'), ['rows' => 3]);
$form->addTextarea('custom_params', [$plugin->get_lang('CustomParams'), $plugin->get_lang('CustomParamsHelp')]);
$form->addCheckBox('deep_linking', $plugin->get_lang('SupportDeepLinking'), get_lang('Yes'));
$form->addHtml('</div>');
$form->addButtonSave(get_lang('Save'));
$form->addHidden('id', $tool->getId());
$form->setDefaults([
@ -44,7 +48,8 @@ $form->setDefaults([
'url' => $tool->getLaunchUrl(),
'consumer_key' => $tool->getConsumerKey(),
'shared_secret' => $tool->getSharedSecret(),
'custom_params' => $tool->getCustomParams()
'custom_params' => $tool->getCustomParams(),
'deep_linking' => $tool->isActiveDeepLinking(),
]);
if ($form->validate()) {
@ -62,7 +67,7 @@ if ($form->validate()) {
$em->flush();
Display::addFlash(
Display::return_message($plugin->get_lang('ToolEdited'), 'success')
Display::return_message($plugin->get_lang('ToolUpdated'), 'success')
);
header('Location: '.api_get_path(WEB_PLUGIN_PATH).'ims_lti/admin.php');

@ -9,7 +9,8 @@ use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
require_once __DIR__.'/../../main/inc/global.inc.php';
require './OAuthSimple.php';
api_protect_course_script();
api_protect_course_script(false);
api_block_anonymous_users(false);
$em = Database::getManager();
@ -33,11 +34,27 @@ $siteName = api_get_setting('siteName');
$toolUserId = ImsLtiPlugin::generateToolUserId($user);
$params = [];
$params['lti_message_type'] = 'basic-lti-launch-request';
$params['lti_version'] = 'LTI-1p0';
$params['resource_link_id'] = $tool->getId();
$params['resource_link_title'] = $tool->getName();
$params['resource_link_description'] = $tool->getDescription();
if ($tool->isActiveDeepLinking()) {
$params['lti_message_type'] = 'ContentItemSelectionRequest';
$params['content_item_return_url'] = api_get_path(WEB_PLUGIN_PATH).'ims_lti/item_return.php';
$params['accept_media_types'] = '*/*';
$params['accept_presentation_document_targets'] = 'iframe';
//$params['accept_unsigned'];
//$params['accept_multiple'];
//$params['accept_copy_advice'];
//$params['auto_create']';
$params['title'] = $tool->getName();
$params['text'] = $tool->getDescription();
$params['data'] = 'tool:'.$tool->getId();
} else {
$params['lti_message_type'] = 'basic-lti-launch-request';
$params['resource_link_id'] = $tool->getId();
$params['resource_link_title'] = $tool->getName();
$params['resource_link_description'] = $tool->getDescription();
}
$params['user_id'] = ImsLtiPlugin::generateToolUserId($user->getId());
$params['user_image'] = UserManager::getUserPicture($user->getId());
$params['roles'] = ImsLtiPlugin::getUserRoles($user);
@ -90,10 +107,13 @@ $result = $oauth->sign(array(
echo '<input type="hidden" name="'.$key.'" value="'.$values.'" />';
}
?>
<input type="submit" value="Press to continue to external tool"/>
<button type="submit">
<?php echo $imsLtiPlugin->get_lang('PressToContinue') ?>
</button>
</form>
<script language="javascript">
document.querySelector('form [type="submit"]').style.display = "none";
document.ltiLaunchForm.submit();
</script>
</body>

@ -0,0 +1,60 @@
<?php
/* For license terms, see /license.txt */
use Chamilo\CoreBundle\Entity\Course;
use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
require_once __DIR__.'/../../main/inc/global.inc.php';
api_protect_course_script(false);
api_block_anonymous_users(false);
if (empty($_POST['content_items']) || empty($_POST['data'])) {
api_not_allowed(false);
}
$toolId = str_replace('tool:', '', $_POST['data']);
$plugin = ImsLtiPlugin::create();
$em = Database::getManager();
/** @var Course $course */
$course = $em->find('ChamiloCoreBundle:Course', api_get_course_int_id());
/** @var ImsLtiTool|null $ltiTool */
$ltiTool = $em->find('ChamiloPluginBundle:ImsLti\ImsLtiTool', $toolId);
if (!$ltiTool) {
api_not_allowed(false);
}
$contentItems = json_decode($_POST['content_items'], true);
$contentItems = $contentItems['@graph'];
foreach ($contentItems as $contentItem) {
if ('LtiLinkItem' === $contentItem['@type']) {
if ('application/vnd.ims.lti.v1.ltilink' === $contentItem['mediaType']) {
$plugin->saveItemAsLtiLink($contentItem, $ltiTool, $course);
Display::addFlash(
Display::return_message($plugin->get_lang('ToolAdded'), 'success')
);
}
}
}
$currentUrl = api_get_path(WEB_PLUGIN_PATH).'ims_lti/start.php?id='.$ltiTool->getId();
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script>
window.parent.location.href = '<?php echo $currentUrl ?>';
</script>
</body>
</html>

@ -13,6 +13,7 @@ $strings['LaunchUrl'] = 'Launch URL';
$strings['ConsumerKey'] = 'Consumer key';
$strings['SharedSecret'] = 'Shared secret';
$strings['CustomParams'] = 'Custom params';
$strings['CustomParamsHelp'] = 'Custom params required by the Tool Provider. Format: <code>name=value</code>';
$strings['ToolName'] = 'Tool name';
$strings['ToolNotAdded'] = 'Tool not added';
$strings['AvailableTools'] = 'Available tools';
@ -22,3 +23,7 @@ $strings['IsGlobal'] = 'Is global';
$strings['EditExternalTool'] = 'Edit external tool';
$strings['ToolDeleted'] = 'Tool deleted';
$strings['ToolAdded'] = 'Tool added';
$strings['ToolUpdated'] = 'Tool updated';
$strings['PressToContinue'] = 'Press to continue to external tool';
$strings['ConfigureExternalTool'] = 'Configure external tools';
$strings['SupportDeepLinking'] = 'Support Deep-Linking';

@ -13,6 +13,7 @@ $strings['LaunchUrl'] = 'URL de démarrage (Launch URL)';
$strings['ConsumerKey'] = 'Clef consommateur (Consumer key)';
$strings['SharedSecret'] = 'Secret partagé (Shared secret)';
$strings['CustomParams'] = 'Paramètres personnalisés';
$strings['CustomParamsHelp'] = 'Paramètres personnalisés requis par le fournisseur d\'outils. Format: <code>nom=valeur</code>.';
$strings['ToolName'] = 'Nom de l\'outil';
$strings['ToolNotAdded'] = 'Outil non ajouté';
$strings['AvailableTools'] = 'Outils disponibles';
@ -22,3 +23,7 @@ $strings['IsGlobal'] = 'Est global';
$strings['EditExternalTool'] = 'Éditer outil externe';
$strings['ToolDeleted'] = 'Outil supprimé';
$strings['ToolAdded'] = 'Outil ajouté';
$strings['ToolAdded'] = 'outil mis à jour';
$strings['PressToContinue'] = 'Appuyez sur pour continuer à l\'outil externe';
$strings['ConfigureExternalTool'] = 'Configure external tools';
$strings['SupportDeepLinking'] = 'Support Deep-Linking';

@ -13,6 +13,7 @@ $strings['LaunchUrl'] = 'URL de arranque (launch URL)';
$strings['ConsumerKey'] = 'Clave consumidor (consumer key)';
$strings['SharedSecret'] = 'Secreto compartido (shared secret)';
$strings['CustomParams'] = 'Parámetros personalizados';
$strings['CustomParamsHelp'] = 'Parámetros personalizados requeridos por el Proveedor de la Herramienta. Formato: <code>name=value</code>.';
$strings['ToolName'] = 'Nombre de la herramienta';
$strings['ToolNotAdded'] = 'Herramienta no añadida';
$strings['AvailableTools'] = 'Herramientas disponibles';
@ -22,3 +23,7 @@ $strings['IsGlobal'] = 'Es global';
$strings['EditExternalTool'] = 'Editar herramienta externa';
$strings['ToolDeleted'] = 'Herramienta eliminada';
$strings['ToolAdded'] = 'Herramienta agregada';
$strings['ToolUpdated'] = 'Herramienta actualizada';
$strings['PressToContinue'] = 'Presione para continuar con la herramienta externa';
$strings['ConfigureExternalTool'] = 'Configure external tools';
$strings['SupportDeepLinking'] = 'Support Deep-Linking';

@ -1,18 +1,26 @@
<div class="row">
{% if tools|length %}
<div class="col-sm-3">
<h2 class="page-header">{{ 'AvailableTools'|get_plugin_lang('ImsLtiPlugin') }}</h2>
<h2>{{ 'AvailableTools'|get_plugin_lang('ImsLtiPlugin') }}</h2>
<ul class="nav nav-pills nav-stacked">
{% for tool in tools %}
<li class="{{ type == tool.id ? 'active' : '' }}">
<a href="{{ _p.web_self }}?type={{ tool.id }}&{{ _p.web_cid_query }}">{{ tool.name }}</a>
{% if tool.isActiveDeepLinking %}
<a href="{{ _p.web_plugin }}ims_lti/start.php?id={{ tool.id }}&{{ _p.web_cid_query }}">{{ tool.name }}</a>
{% else %}
<a href="{{ _p.web_self }}?type={{ tool.id }}&{{ _p.web_cid_query }}">{{ tool.name }}</a>
{% endif %}
</li>
{% endfor %}
</ul>
</div>
{% endif %}
<div class="col-sm-9 {{ tools|length ? '' : 'col-sm-offset-3' }}">
<h2 class="page-header">{{ 'ToolSettings'|get_plugin_lang('ImsLtiPlugin') }}</h2>
<div class="{{ tools|length ? 'col-sm-9' : 'col-sm-12' }}">
{% if tools|length == 0 %}
<h2>{{ 'ToolSettings'|get_plugin_lang('ImsLtiPlugin') }}</h2>
{% endif %}
{{ form }}
</div>
</div>

@ -1,6 +1,6 @@
{% if tool.description %}
<p class="lead">{{ tool.description }}</p>
{% endif %}
<div class="embed-responsive embed-responsive-16by9">
<div class="embed-responsive embed-responsive-4by3">
<iframe src="{{ launch_url }}" class="plugin-ims-lti-iframe"></iframe>
</div>

@ -0,0 +1,7 @@
# 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,289 @@
<?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->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;
}
$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>
$(document).on('ready', 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…
Cancel
Save