Partial merge with 1.11.x see BT#15952

pull/3063/head
Julio Montoya 6 years ago
parent 9ff9f0fa6c
commit dbd33061c1
  1. 4
      .gitignore
  2. 5
      .htaccess
  3. 1
      .php_cs.dist
  4. 2
      .travis.yml
  5. 29
      certificates/index.php
  6. 19
      main/document/showinframes.php
  7. 34
      main/exercise/TestCategory.php
  8. 165
      main/exercise/overview.php
  9. 10
      main/inc/lib/document.lib.php
  10. 2
      main/inc/lib/extra_field.lib.php
  11. 4
      main/inc/lib/javascript/ViewerJS/index.html
  12. 348
      main/inc/lib/myspace.lib.php
  13. 1
      main/inc/lib/pear/HTML/QuickForm/select.php
  14. 1
      main/inc/lib/sessionmanager.lib.php
  15. 119
      main/lang/english/trad4all.inc.php
  16. 118
      main/lang/french/trad4all.inc.php
  17. 18
      main/mySpace/myStudents.php
  18. 2
      main/template/default/exercise/submit.js.tpl
  19. 218
      main/template/default/learnpath/view.tpl
  20. 278
      plugin/questionoptionsevaluation/QuestionOptionsEvaluationPlugin.php
  21. 20
      plugin/questionoptionsevaluation/README.md
  22. 74
      plugin/questionoptionsevaluation/evaluation.php
  23. 4
      plugin/questionoptionsevaluation/install.php
  24. 19
      plugin/questionoptionsevaluation/lang/english.php
  25. 19
      plugin/questionoptionsevaluation/lang/spanish.php
  26. 4
      plugin/questionoptionsevaluation/plugin.php
  27. 4
      plugin/questionoptionsevaluation/uninstall.php
  28. 42
      plugin/surveyexportcsv/README.md
  29. 95
      plugin/surveyexportcsv/SurveyExportCsvPlugin.php
  30. 215
      plugin/surveyexportcsv/export.php
  31. 4
      plugin/surveyexportcsv/install.php
  32. 9
      plugin/surveyexportcsv/lang/english.php
  33. 9
      plugin/surveyexportcsv/lang/spanish.php
  34. 4
      plugin/surveyexportcsv/plugin.php
  35. 50
      plugin/surveyexportcsv/start.php
  36. 4
      plugin/surveyexportcsv/uninstall.php
  37. 10
      plugin/whispeakauth/README.md
  38. 293
      plugin/whispeakauth/WhispeakAuthPlugin.php
  39. 136
      plugin/whispeakauth/ajax/record_audio.php
  40. 139
      plugin/whispeakauth/assets/js/RecordAudio.js
  41. 26
      plugin/whispeakauth/authentify.php
  42. 28
      plugin/whispeakauth/enrollment.php
  43. 14
      plugin/whispeakauth/index.php
  44. 4
      plugin/whispeakauth/install.php
  45. 33
      plugin/whispeakauth/lang/english.php
  46. 18
      plugin/whispeakauth/lang/french.php
  47. 33
      plugin/whispeakauth/lang/spanish.php
  48. 4
      plugin/whispeakauth/plugin.php
  49. 4
      plugin/whispeakauth/uninstall.php
  50. 38
      plugin/whispeakauth/view/authentify_recorder.html.twig
  51. 67
      plugin/whispeakauth/view/record_audio.html.twig
  52. 126
      tests/behat/features/bootstrap/FeatureContext.php
  53. 16
      tests/behat/features/course_user_registration.feature
  54. 7
      tests/behat/features/sessionManagement.feature
  55. 10
      tests/behat/features/toolAnnouncement.feature
  56. 17
      tests/behat/features/toolDocument.feature
  57. 86
      tests/behat/features/toolExercise.feature
  58. 322
      tests/behat/features/toolGroup.feature
  59. 20
      tests/behat/features/toolLp.feature

4
.gitignore vendored

@ -18,6 +18,7 @@ app/logs/*
# Courses
app/courses/*
!app/courses/proxy.php
# Home
app/home/*
@ -49,4 +50,5 @@ plugin/vchamilo/templates/*
# Stuff updated through composer - Remove just before release
vendor
web/assets/*
node_modules
yarn.lock

@ -9,7 +9,7 @@
RewriteEngine on
# Prevent execution of PHP from directories used for different types of uploads
RedirectMatch 403 ^/app/(cache|courses|home|logs|upload|Resources/public/css)/.*\.ph(p[3457]?|t|tml|ar)$
RedirectMatch 403 ^/app/(?!courses/proxy)(cache|courses|home|logs|upload|Resources/public/css)/.*\.ph(p[3457]?|t|tml|ar)$
RedirectMatch 403 ^/main/default_course_document/images/.*\.ph(p[3457]?|t|tml|ar)$
RedirectMatch 403 ^/main/lang/.*\.ph(p[3457]?|t|tml|ar)$
RedirectMatch 403 ^/web/css/.*\.ph(p[3457]?|t|tml|ar)$
@ -75,6 +75,9 @@ RewriteRule ^lti/os$ plugin/ims_lti/outcome_service.php [L]
# http://my.chamilo.net/jdoe to http://my.chamilo.net/user.php?jdoe
RewriteRule ^([^/.]+)/?$ user.php?$1 [L]
# Deny direct access to user my files
RewriteRule ^app/upload/users/([^/]+)/([^/]+)/my_files/(.*)$ main/social/download_my_files.php?user_id=$2&file=$3 [QSA,L]
# Add caching of woff font files to avoid loading 2*15KB each time with Chamilo
# default OpenSans font

@ -58,7 +58,6 @@ $finder = PhpCsFixer\Finder::create()
->exclude('main/inc/lib/ppt2png')
->exclude('main/inc/lib/phpseclib')
->exclude('main/inc/lib/pear')
->exclude('main/inc/lib/phpseclib')
->exclude('main/inc/lib/svg-edit')
->exclude('main/inc/lib/swfobject')
->exclude('main/inc/lib/wami-recorder')

@ -18,11 +18,11 @@ cache:
directories:
- $HOME/.composer/cache/files
php:
- 5.5
- 5.6
- 7.0
- 7.1
- 7.2
- 7.3
env:
global:

@ -1,5 +1,6 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Show specified user certificate.
*
@ -8,9 +9,33 @@
require_once '../main/inc/global.inc.php';
$action = isset($_GET['action']) ? $_GET['action'] : null;
$certificate = new Certificate($_GET['id']);
$userId = isset($_GET['user_id']) ? $_GET['user_id'] : 0;
$certificateId = isset($_GET['id']) ? $_GET['id'] : 0;
$certificate = new Certificate($certificateId, $userId);
$certificateData = $certificate->get($certificateId);
if (empty($certificateData)) {
api_not_allowed(false, Display::return_message(get_lang('NoCertificateAvailable'), 'warning'));
}
$category = Category::findByCertificate($certificateId);
// Check if the certificate should use the course language
if (!empty($category) && !empty($category->get_course_code())) {
$courseInfo = api_get_course_info($category->get_course_code());
$language = $courseInfo['language'];
$languageFilesToLoad = api_get_language_files_to_load($language);
foreach ($languageFilesToLoad as $languageFile) {
include $languageFile;
}
// Overwrite the interface language with the course language
$language_interface = $language;
$language_interface_initial_value = $language_interface;
}
CustomCertificatePlugin::redirectCheck($certificate, $_GET['id']);
CustomCertificatePlugin::redirectCheck($certificate, $certificateId, $userId);
switch ($action) {
case 'export':

@ -174,7 +174,7 @@ if (api_is_course_admin()) {
$frameheight = 165;
}
$frameReady = Display::getFrameReadyBlock('top.mainFrame');
$frameReady = Display::getFrameReadyBlock('#mainFrame');
$web_odf_supported_files = DocumentManager::get_web_odf_extension_list();
// PDF should be displayed with viewerJS
@ -237,19 +237,14 @@ if (!$playerSupported && $execute_iframe) {
</script>';
$htmlHeadXtra[] = '<script type="text/javascript" src="'.api_get_path(WEB_LIBRARY_PATH).'javascript/jquery.frameready.js"></script>';
$htmlHeadXtra[] = '<script>
var updateContentHeight = function() {
my_iframe = document.getElementById("mainFrame");
if (my_iframe) {
//this doesnt seem to work in IE 7,8,9
my_iframe.height = my_iframe.contentWindow.document.body.scrollHeight + 50 + "px";
}
};
// Fixes the content height of the frame
window.onload = function() {
updateContentHeight();
$(function() {
$(\'#mainFrame\').on(\'load\', function () {
this.style.height = (this.contentWindow.document.body.scrollHeight + 50) + \'px\';
});
'.$frameReady.'
}
});
</script>';
}

@ -714,40 +714,6 @@ class TestCategory
return $tabResult;
}
/**
* return total score for test exe_id for all question in the category $in_cat_id for user
* If no question for this category, return "".
*/
public static function getCatScoreForExeidForUserid($in_cat_id, $in_exe_id, $in_user_id)
{
$tbl_track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
$tbl_question_rel_category = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
$in_cat_id = (int) $in_cat_id;
$in_exe_id = (int) $in_exe_id;
$in_user_id = (int) $in_user_id;
$query = "SELECT DISTINCT
marks,
exe_id,
user_id,
ta.question_id,
category_id
FROM $tbl_track_attempt ta
INNER JOIN $tbl_question_rel_category qrc
ON (ta.question_id = qrc.question_id)
WHERE
qrc.category_id = $in_cat_id AND
exe_id = $in_exe_id AND
user_id = $in_user_id";
$res = Database::query($query);
$score = '';
while ($data = Database::fetch_array($res)) {
$score += $data['marks'];
}
return $score;
}
/**
* Return the number max of question in a category
* count the number of questions in all categories, and return the max.

@ -77,38 +77,51 @@ if ($time_control) {
}
if (!in_array($origin, ['learnpath', 'embeddable'])) {
$fluid = false;
Display::display_header();
} else {
$htmlHeadXtra[] = "
<style>
body { background: none;}
</style>
";
$fluid = true;
Display::display_reduced_header();
}
$tpl = new Template('Overview');
$list = [];
$html = '';
$message = '';
$html .= '<div class="exercise-overview">';
$is_allowed_to_edit = api_is_allowed_to_edit(null, true);
$editLink = '';
if ($is_allowed_to_edit) {
if ($objExercise->sessionId == $sessionId) {
$editLink = Display::url(
Display::return_icon('edit.png', get_lang('Edit'), [], ICON_SIZE_SMALL),
api_get_path(WEB_CODE_PATH).'exercise/admin.php?'.api_get_cidreq().'&exerciseId='.$objExercise->id
);
}
$editLink .= Display::url(
Display::return_icon('test_results.png', get_lang('Results'), [], ICON_SIZE_SMALL),
api_get_path(WEB_CODE_PATH).'exercise/exercise_report.php?'.api_get_cidreq().'&exerciseId='.$objExercise->id,
['title' => get_lang('Results')]
);
}
$isAllowedToEdit = api_is_allowed_to_edit(null, true);
$list['id'] = $objExercise->id;
$list['session_id'] = $objExercise->sessionId;
$list['edit'] = $isAllowedToEdit;
$iconExercise = Display::return_icon('test-quiz.png', null, [], ICON_SIZE_MEDIUM);
// Exercise name.
if (api_get_configuration_value('save_titles_as_html')) {
$list['title'] = $objExercise->get_formated_title().PHP_EOL;
$html .= Display::div(
$objExercise->get_formated_title().PHP_EOL.$editLink
);
} else {
$list['title'] = $objExercise->selectTitle().PHP_EOL;
$html .= Display::page_header(
$iconExercise.PHP_EOL.$objExercise->selectTitle().PHP_EOL.$editLink
);
}
//Exercise description
// Exercise description
if (!empty($objExercise->description)) {
$list['description'] = $objExercise->description;
$html .= Display::div($objExercise->description, ['class' => 'exercise_description']);
}
$extra_params = '';
@ -116,7 +129,7 @@ if (isset($_GET['preview'])) {
$extra_params = '&preview=1';
}
$exerciseStatInfo = $objExercise->get_stat_track_exercise_info(
$exercise_stat_info = $objExercise->get_stat_track_exercise_info(
$learnpath_id,
$learnpath_item_id,
0
@ -124,33 +137,38 @@ $exerciseStatInfo = $objExercise->get_stat_track_exercise_info(
//1. Check if this is a new attempt or a previous
$label = get_lang('StartTest');
if ($time_control && !empty($clock_expired_time) || isset($exerciseStatInfo['exe_id'])) {
if ($time_control && !empty($clock_expired_time) || isset($exercise_stat_info['exe_id'])) {
$label = get_lang('ContinueTest');
}
$msgStatus = null;
if (isset($exerciseStatInfo['exe_id'])) {
$msgStatus = get_lang('YouTriedToResolveThisExerciseEarlier');
if (isset($exercise_stat_info['exe_id'])) {
$message = Display::return_message(get_lang('YouTriedToResolveThisExerciseEarlier'));
}
// 2. Exercise button
// Notice we not add there the lp_item_view_id because is not already generated
$list['url'] = api_get_path(WEB_CODE_PATH).'exercise/exercise_submit.php?'.api_get_cidreq().'&exerciseId='.$objExercise->id.'&learnpath_id='.$learnpath_id.'&learnpath_item_id='.$learnpath_item_id.'&learnpath_item_view_id='.$learnpathItemViewId.$extra_params;
$exercise_url = api_get_path(WEB_CODE_PATH).'exercise/exercise_submit.php?'.api_get_cidreq().'&exerciseId='.$objExercise->id.'&learnpath_id='.$learnpath_id.'&learnpath_item_id='.$learnpath_item_id.'&learnpath_item_view_id='.$learnpathItemViewId.$extra_params;
$exercise_url_button = Display::url(
$label,
$exercise_url,
['class' => 'btn btn-success btn-large']
);
//3. Checking visibility of the exercise (overwrites the exercise button)
$list['is_visible'] = $objExercise->is_visible(
$visible_return = $objExercise->is_visible(
$learnpath_id,
$learnpath_item_id,
null,
false
true
);
// Exercise is not visible remove the button
if ($list['is_visible']['value'] == false) {
if ($isAllowedToEdit) {
$message = get_lang('ThisItemIsInvisibleForStudentsButYouHaveAccessAsTeacher');
if ($visible_return['value'] == false) {
if ($is_allowed_to_edit) {
$message = Display::return_message(get_lang('ThisItemIsInvisibleForStudentsButYouHaveAccessAsTeacher'), 'warning');
} else {
$message = $list['is_visible']['message'];
$message = $visible_return['message'];
$exercise_url_button = null;
}
}
@ -166,7 +184,7 @@ $attempts = Event::getExerciseResultsByUser(
$counter = count($attempts);
$my_attempt_array = [];
$tableContent = '';
$table_content = '';
/* Make a special case for IE, which doesn't seem to be able to handle the
* results popup -> send it to the full results page */
@ -193,15 +211,26 @@ if (in_array(
}
}
$certificateBlock = '';
if (!empty($attempts)) {
$i = $counter;
foreach ($attempts as $attempt_result) {
if (empty($certificateBlock)) {
$certificateBlock = ExerciseLib::generateAndShowCertificateBlock(
$attempt_result['exe_result'],
$attempt_result['exe_weighting'],
$objExercise,
$attempt_result['exe_user_id'],
$courseCode,
$sessionId
);
}
$score = ExerciseLib::show_score($attempt_result['exe_result'], $attempt_result['exe_weighting']);
$attempt_url = api_get_path(WEB_CODE_PATH).'exercise/result.php?';
$attempt_url .= api_get_cidreq().'&show_headers=1&';
$attempt_url .= http_build_query([
'id' => $attempt_result['exe_id'],
]);
$attempt_url .= http_build_query(['id' => $attempt_result['exe_id']]);
$attempt_url .= $url_suffix;
$attempt_link = Display::url(
@ -226,7 +255,7 @@ if (!empty($attempts)) {
),
'userIp' => $attempt_result['user_ip'],
];
$attempt_link .= '&nbsp;&nbsp;&nbsp;'.$teacher_revised;
$attempt_link .= PHP_EOL.$teacher_revised;
if (in_array(
$objExercise->results_disabled,
@ -359,24 +388,72 @@ if (!empty($attempts)) {
$row++;
}
}
$tableContent = $table->toHtml();
$table_content = $table->toHtml();
}
if ($objExercise->selectAttempts()) {
$list['attempts'] = $objExercise->selectAttempts();
$attempt_message = get_lang('Attempts').' '.$counter.' / '.$objExercise->selectAttempts();
if ($counter == $objExercise->selectAttempts()) {
$attempt_message = Display::return_message($attempt_message, 'error');
} else {
$attempt_message = Display::return_message($attempt_message, 'info');
}
if ($visible_return['value'] == true) {
$message .= $attempt_message;
}
}
if ($time_control) {
$tpl->assign('time_control', $objExercise->returnTimeLeftDiv());
$html .= $objExercise->returnTimeLeftDiv();
}
$tpl->assign('fluid', $fluid);
$tpl->assign('data', $list);
$tpl->assign('label', $label);
$tpl->assign('message', $message);
$tpl->assign('count', $counter);
$tpl->assign('status', $msgStatus);
$tpl->assign('table_result', $tableContent);
$layout = $tpl->get_template('exercise/overview.tpl');
$tpl->assign('content', $tpl->fetch($layout));
$tpl->display_one_col_template();
$html .= $message;
$disable = api_get_configuration_value('exercises_disable_new_attempts');
if ($disable && empty($exercise_stat_info)) {
$exercise_url_button = Display::return_message(get_lang('NewExerciseAttemptDisabled'));
}
$isLimitReached = ExerciseLib::isQuestionsLimitPerDayReached(
api_get_user_id(),
count($objExercise->get_validated_question_list()),
api_get_course_int_id(),
api_get_session_id()
);
if (!empty($exercise_url_button) && !$isLimitReached) {
$html .= Display::div(
Display::div(
$exercise_url_button,
['class' => 'exercise_overview_options']
),
['class' => 'options']
);
}
if ($isLimitReached) {
$maxQuestionsAnswered = (int) api_get_course_setting('quiz_question_limit_per_day');
$html .= Display::return_message(
sprintf(get_lang('QuizQuestionsLimitPerDayXReached'), $maxQuestionsAnswered),
'warning',
false
);
}
$html .= Display::tag(
'div',
$table_content,
['class' => 'table-responsive']
);
$html .= '</div>';
if ($certificateBlock) {
$html .= PHP_EOL.$certificateBlock;
}
echo $html;
Display::display_footer();

@ -2938,14 +2938,6 @@ class DocumentManager
// When sending zip files
if ($new_path === true && $unzip == 1) {
if ($show_output) {
echo Display::return_message(
get_lang('UplUploadSucceeded').'<br />',
'confirm',
false
);
}
return [
'title' => $files[$fileKey]['name'],
'url' => '#',
@ -5202,7 +5194,7 @@ class DocumentManager
$url = 'show_content.php?'.$courseParams.'&id='.$document_data['id'];
$class = 'ajax';
if ($visibility == false) {
$class = "ajax text-muted";
$class = 'ajax text-muted';
}
return Display::url(

@ -1899,7 +1899,7 @@ class ExtraField extends Model
$deleteId = $field_details['variable'].'_delete';
$form->addHtml("
<script>
$(document).ready(function() {
$(function() {
$('#".$deleteId."').on('click', function() {
$.ajax({
type: 'GET',

File diff suppressed because one or more lines are too long

@ -53,6 +53,10 @@ class MySpace
'url' => api_get_path(WEB_CODE_PATH).'mySpace/exercise_category_report.php',
'content' => get_lang('ExerciseCategoryAllSessionsReport'),
],
[
'url' => api_get_path(WEB_CODE_PATH).'mySpace/survey_report.php',
'content' => get_lang('SurveyReport'),
],
];
return Display::actions($actions, null);
@ -63,8 +67,8 @@ class MySpace
*/
public static function getTopMenu()
{
$menu_items = [];
$menu_items[] = Display::url(
$menuItems = [];
$menuItems[] = Display::url(
Display::return_icon(
'statistics.png',
get_lang('MyStats'),
@ -73,7 +77,7 @@ class MySpace
),
api_get_path(WEB_CODE_PATH)."auth/my_progress.php"
);
$menu_items[] = Display::url(
$menuItems[] = Display::url(
Display::return_icon(
'teacher.png',
get_lang('TeacherInterface'),
@ -82,7 +86,7 @@ class MySpace
),
api_get_path(WEB_CODE_PATH).'mySpace/?view=teacher'
);
$menu_items[] = Display::url(
$menuItems[] = Display::url(
Display::return_icon(
'star_na.png',
get_lang('AdminInterface'),
@ -91,12 +95,12 @@ class MySpace
),
'#'
);
$menu_items[] = Display::url(
$menuItems[] = Display::url(
Display::return_icon('quiz.png', get_lang('ExamTracking'), [], 32),
api_get_path(WEB_CODE_PATH).'tracking/exams.php'
);
$menu = '';
foreach ($menu_items as $item) {
foreach ($menuItems as $item) {
$menu .= $item;
}
$menu .= '<br />';
@ -207,13 +211,15 @@ class MySpace
}
// protect data
$user_id = intval($user_id);
$session_id = intval($session_id);
$user_id = (int) $user_id;
$session_id = (int) $session_id;
$new_course_list = [];
foreach ($course_list as $course_item) {
$courseInfo = api_get_course_info($course_item['code']);
$courseId = $courseInfo['real_id'];
$new_course_list[] = '"'.$courseId.'"';
if ($courseInfo) {
$courseId = $courseInfo['real_id'];
$new_course_list[] = '"'.$courseId.'"';
}
}
$course_list = implode(', ', $new_course_list);
@ -321,80 +327,27 @@ class MySpace
public static function display_tracking_user_overview()
{
self::display_user_overview_export_options();
$t_head = '<table style="width: 100%;border:0;padding:0;border-collapse:collapse;table-layout: fixed">';
$t_head .= '<tr>';
$t_head .= '<th width="155px" style="border-left:0;border-bottom:0"><span>'.get_lang('Course').'</span></th>';
$t_head .= '<th style="padding:0;border-bottom:0"><span>'.cut(get_lang('AvgTimeSpentInTheCourse'), 6, true).'</span></th>';
$t_head .= '<th style="padding:0;border-bottom:0"><span>'.cut(get_lang('AvgStudentsProgress'), 6, true).'</span></th>';
$t_head .= '<th style="padding:0;border-bottom:0"><span>'.cut(get_lang('AvgCourseScore'), 6, true).'</span></th>';
$t_head .= '<th style="padding:0;border-bottom:0"><span>'.cut(get_lang('TotalNumberOfMessages'), 6, true).'</span></th>';
$t_head .= '<th style="padding:0;border-bottom:0"><span>'.cut(get_lang('TotalNumberOfAssignments'), 6, true).'</span></th>';
$t_head .= '<th width="105px" style="border-bottom:0"><span>'.get_lang('TotalExercisesScoreObtained').'</span></th>';
$t_head .= '<th style="padding:0;border-bottom:0"><span>'.cut(get_lang('TotalExercisesAnswered'), 6, true).'</span></th>';
$t_head .= '<th style="padding:0;border-bottom:0;border-right:0;"><span>'.get_lang('LatestLogin').'</span></th>';
$t_head .= '</tr></table>';
$addparams = ['view' => 'admin', 'display' => 'user'];
$params = ['view' => 'admin', 'display' => 'user'];
$table = new SortableTable(
'tracking_user_overview',
['MySpace', 'get_number_of_users_tracking_overview'],
['MySpace', 'get_user_data_tracking_overview'],
0
);
$table->additional_parameters = $addparams;
$table->set_header(
0,
get_lang('OfficialCode'),
true,
['style' => 'font-size:8pt'],
['style' => 'font-size:8pt']
);
if (api_is_western_name_order()) {
$table->set_header(
1,
get_lang('FirstName'),
true,
['style' => 'font-size:8pt'],
['style' => 'font-size:8pt']
);
$table->set_header(
2,
get_lang('LastName'),
true,
['style' => 'font-size:8pt'],
['style' => 'font-size:8pt']
);
} else {
$table->set_header(
1,
get_lang('LastName'),
true,
['style' => 'font-size:8pt'],
['style' => 'font-size:8pt']
);
$table->set_header(
2,
get_lang('FirstName'),
true,
['style' => 'font-size:8pt'],
['style' => 'font-size:8pt']
);
}
$table->set_header(
3,
get_lang('LoginName'),
true,
['style' => 'font-size:8pt'],
['style' => 'font-size:8pt']
);
$table->set_header(
4,
$t_head,
false,
['style' => 'width:90%;border:0;padding:0;font-size:7.5pt;'],
['style' => 'width:90%;padding:0;font-size:7.5pt;']
20,
'ASC',
null, [
'class' => 'table table-transparent',
]
);
$table->set_column_filter(4, ['MySpace', 'course_info_tracking_filter']);
$table->display();
$table->additional_parameters = $params;
$table->set_column_filter(0, ['MySpace', 'returnTrackingUserOverviewFilter']);
$tableContent = $table->return_table();
$tpl = new Template('', false, false, false, false, false, false);
$tpl->assign('table', $tableContent);
$templateName = $tpl->get_template('my_space/user_summary.tpl');
$tpl->display($templateName);
}
/**
@ -415,19 +368,19 @@ class MySpace
$order = [
0 => 'firstname',
1 => 'lastname',
2 => ($sort_by_first_name ? 'firstname' : 'lastname'),
2 => $sort_by_first_name ? 'firstname' : 'lastname',
3 => 'login_date',
4 => ($sort_by_first_name ? 'firstname' : 'lastname'),
5 => ($sort_by_first_name ? 'firstname' : 'lastname'),
4 => $sort_by_first_name ? 'firstname' : 'lastname',
5 => $sort_by_first_name ? 'firstname' : 'lastname',
];
} else {
$order = [
0 => 'lastname',
1 => 'firstname',
2 => ($sort_by_first_name ? 'firstname' : 'lastname'),
2 => $sort_by_first_name ? 'firstname' : 'lastname',
3 => 'login_date',
4 => ($sort_by_first_name ? 'firstname' : 'lastname'),
5 => ($sort_by_first_name ? 'firstname' : 'lastname'),
4 => $sort_by_first_name ? 'firstname' : 'lastname',
5 => $sort_by_first_name ? 'firstname' : 'lastname',
];
}
$table = new SortableTable(
@ -514,7 +467,7 @@ class MySpace
}
}
if (!empty($order[$tracking_column])) {
$sqlCoachs .= " ORDER BY ".$order[$tracking_column]." ".$tracking_direction;
$sqlCoachs .= ' ORDER BY '.$order[$tracking_column].' '.$tracking_direction;
}
$result_coaches = Database::query($sqlCoachs);
@ -664,6 +617,8 @@ class MySpace
/**
* Display a sortable table that contains an overview off all the progress of the user in a session.
*
* @deprecated ?
*
* @author César Perales <cesar.perales@beeznest.com>, Beeznest Team
*/
public static function display_tracking_lp_progress_overview(
@ -784,6 +739,8 @@ class MySpace
*
* @return string HTML array of results formatted for gridJS
*
* @deprecated ?
*
* @author César Perales <cesar.perales@beeznest.com>, Beeznest Team
*/
public static function display_tracking_exercise_progress_overview(
@ -986,31 +943,26 @@ class MySpace
*/
public static function display_tracking_course_overview()
{
$t_head = '<table style="width: 100%;border:0;padding:0;border-collapse:collapse;table-layout: fixed">';
$t_head .= '<tr>';
$t_head .= '<th style="padding:0;border-bottom:0"><span>'.cut(get_lang('AvgTimeSpentInTheCourse'), 6, true).'</span></th>';
$t_head .= '<th style="padding:0;border-bottom:0"><span>'.cut(get_lang('AvgStudentsProgress'), 6, true).'</span></th>';
$t_head .= '<th style="padding:0;border-bottom:0"><span>'.cut(get_lang('AvgCourseScore'), 6, true).'</span></th>';
$t_head .= '<th style="padding:0;border-bottom:0"><span>'.cut(get_lang('TotalNumberOfMessages'), 6, true).'</span></th>';
$t_head .= '<th style="padding:0;border-bottom:0"><span>'.cut(get_lang('TotalNumberOfAssignments'), 6, true).'</span></th>';
$t_head .= '<th width="105px" style="border-bottom:0"><span>'.get_lang('TotalExercisesScoreObtained').'</span></th>';
$t_head .= '<th style="padding:0;border-bottom:0"><span>'.cut(get_lang('TotalExercisesAnswered'), 6, true).'</span></th>';
$t_head .= '<th style="padding:0;border-bottom:0;border-right:0;"><span>'.get_lang('LatestLogin').'</span></th>';
$t_head .= '</tr></table>';
$addparams = ['view' => 'admin', 'display' => 'courseoverview'];
$params = ['view' => 'admin', 'display' => 'courseoverview'];
$table = new SortableTable(
'tracking_session_overview',
['MySpace', 'get_total_number_courses'],
['MySpace', 'get_course_data_tracking_overview'],
1
1,
20,
'ASC',
null, [
'class' => 'table table-transparent',
]
);
$table->additional_parameters = $addparams;
$table->set_header(0, '', false, null, ['style' => 'display: none']);
$table->set_header(1, get_lang('Course'), true, ['style' => 'font-size:8pt'], ['style' => 'font-size:8pt']);
$table->set_header(2, $t_head, false, ['style' => 'width:90%;border:0;padding:0;font-size:7.5pt;'], ['style' => 'width:90%;padding:0;font-size:7.5pt;']);
$table->set_column_filter(2, ['MySpace', 'course_tracking_filter']);
$table->display();
$table->additional_parameters = $params;
$table->set_column_filter(0, ['MySpace', 'course_tracking_filter']);
$tableContent = $table->return_table();
$tpl = new Template('', false, false, false, false, false, false);
$tpl->assign('table', $tableContent);
$templateName = $tpl->get_template('my_space/course_summary.tpl');
$tpl->display($templateName);
}
/**
@ -1054,8 +1006,6 @@ class MySpace
$list[] = [
'0' => $course['code'],
'col0' => $course['code'],
'1' => $course['title'],
'col1' => $course['title'],
];
}
@ -1077,8 +1027,8 @@ class MySpace
$courseInfo = api_get_course_info($course_code);
$courseId = $courseInfo['real_id'];
// the table header
$return = '<table class="data_table" style="width: 100%;border:0;padding:0;border-collapse:collapse;table-layout: fixed">';
$tpl = new Template('', false, false, false, false, false, false);
$data = null;
// database table definition
$tbl_course_rel_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
@ -1146,9 +1096,8 @@ class MySpace
$last_login_date == false
) { // TODO: To be cleaned
$last_login_date = $last_login_date_tmp;
} elseif ($last_login_date_tmp != false &&
$last_login_date != false
) { // TODO: Repeated previous condition. To be cleaned.
} elseif ($last_login_date_tmp != false && $last_login_date != false) {
// TODO: Repeated previous condition. To be cleaned.
// Find the max and assign it to first_login_date
if (strtotime($last_login_date_tmp) > strtotime($last_login_date)) {
$last_login_date = $last_login_date_tmp;
@ -1189,26 +1138,30 @@ class MySpace
} else {
$total_score = '-';
}
$return .= '<tr>';
// time spent in the course
$return .= ' <td style="width:164px;">'.api_time_to_hms($time_spent).'</td>';
// student progress in course
$return .= ' <td>'.$avg_progress.'</td>';
// student score
$return .= ' <td>'.$avg_score.'</td>';
// student messages
$return .= ' <td>'.$nb_messages.'</td>';
// student assignments
$return .= ' <td>'.$nb_assignments.'</td>';
// student exercises results (obtained score, maximum score, number of exercises answered, score percentage)
$return .= '<td width="105px;">'.$total_score.'</td>';
$return .= '<td>'.$total_questions_answered.'</td>';
// last connection
$return .= ' <td>'.$last_login_date.'</td>';
$return .= '</tr>';
$return .= '</table>';
return $return;
$data = [
'course_code' => $course_code,
'id' => $courseId,
'image' => $courseInfo['course_image_large'],
'image_small' => $courseInfo['course_image'],
'title' => $courseInfo['title'],
'url' => $courseInfo['course_public_url'],
'category' => $courseInfo['categoryName'],
'time_spent' => api_time_to_hms($time_spent),
'avg_progress' => $avg_progress,
'avg_score' => $avg_score,
'number_message' => $nb_messages,
'number_assignments' => $nb_assignments,
'total_score' => $total_score,
'questions_answered' => $total_questions_answered,
'last_login' => $last_login_date,
];
$tpl->assign('data', $data);
$layout = $tpl->get_template('my_space/partials/tracking_course_overview.tpl');
$content = $tpl->fetch($layout);
return $content;
}
/**
@ -1415,14 +1368,14 @@ class MySpace
$head .= '<th style="padding:0;border-bottom:0;border-right:0;"><span>'.get_lang('LatestLogin').'</span></th>';
$head .= '</tr></table>';
$addparams = ['view' => 'admin', 'display' => 'sessionoverview'];
$params = ['view' => 'admin', 'display' => 'sessionoverview'];
$table = new SortableTable(
'tracking_session_overview',
['MySpace', 'get_total_number_sessions'],
['MySpace', 'get_session_data_tracking_overview'],
1
);
$table->additional_parameters = $addparams;
$table->additional_parameters = $params;
$table->set_header(0, '', false, null, ['style' => 'display: none']);
$table->set_header(
@ -1644,21 +1597,19 @@ class MySpace
$tbl_user = Database::get_main_table(TABLE_MAIN_USER);
// the values of the sortable table
$from = 0;
if ($_GET['tracking_session_overview_page_nr']) {
$from = $_GET['tracking_session_overview_page_nr'];
} else {
$from = 0;
}
$orderby = 0;
if ($_GET['tracking_session_overview_column']) {
$orderby = $_GET['tracking_session_overview_column'];
} else {
$orderby = 0;
}
$direction = 'ASC';
if ($_GET['tracking_session_overview_direction']) {
$direction = $_GET['tracking_session_overview_direction'];
} else {
$direction = 'ASC';
}
$session_data = self::get_session_data_tracking_overview($from, 1000, $orderby, $direction);
@ -1835,6 +1786,7 @@ class MySpace
*
* @param int $user_id the id of the user
* @param string $course_code the course code
* @param int $session_id
*
* @return array
*
@ -1844,15 +1796,20 @@ class MySpace
*
* @since November 2008
*/
public static function exercises_results($user_id, $course_code, $session_id = false)
public static function exercises_results($user_id, $course_code, $session_id = 0)
{
$user_id = (int) $user_id;
$courseId = api_get_course_int_id($course_code);
$sql = 'SELECT exe_result, exe_weighting
FROM '.Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES)."
$table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
$sql = "SELECT exe_result, exe_weighting
FROM $table
WHERE
c_id = ' . $courseId . ' AND
exe_user_id = '".intval($user_id)."'";
if ($session_id !== false) {
c_id = $courseId AND
exe_user_id = $user_id";
$session_id = (int) $session_id;
if (!empty($session_id)) {
$sql .= " AND session_id = '".$session_id."' ";
}
$result = Database::query($sql);
@ -1865,10 +1822,9 @@ class MySpace
$questions_answered++;
}
$percentage = null;
if ($score_possible != 0) {
$percentage = round(($score_obtained / $score_possible * 100), 2);
} else {
$percentage = null;
}
return [
@ -1926,16 +1882,16 @@ class MySpace
$csv_row = [];
$csv_row[] = get_lang('OfficialCode');
if ($is_western_name_order) {
$csv_row[] = get_lang('FirstName', '');
$csv_row[] = get_lang('LastName', '');
$csv_row[] = get_lang('FirstName');
$csv_row[] = get_lang('LastName');
} else {
$csv_row[] = get_lang('LastName', '');
$csv_row[] = get_lang('FirstName', '');
$csv_row[] = get_lang('LastName');
$csv_row[] = get_lang('FirstName');
}
$csv_row[] = get_lang('LoginName');
$csv_row[] = get_lang('CourseCode');
// the additional user defined fields (only those that were selected to be exported)
// the additional user defined fields (only those that were selected to be exported)
$fields = UserManager::get_extra_fields(0, 50, 5, 'ASC');
$additionalExportFields = Session::read('additional_export_fields');
@ -2163,6 +2119,7 @@ class MySpace
public static function get_user_data_tracking_overview($from, $numberItems, $column, $direction)
{
$isWestern = api_is_western_name_order();
switch ($column) {
case '0':
$column = 'official_code';
@ -2227,9 +2184,9 @@ class MySpace
public static function get_user_overview_export_extra_fields($user_id)
{
// include the user manager
$extra_data = UserManager::get_extra_user_data($user_id, true);
$data = UserManager::get_extra_user_data($user_id, true);
return $extra_data;
return $data;
}
/**
@ -2243,7 +2200,7 @@ class MySpace
*
* @return array with the username, the sufix
*
* @author Julio Montoya Armas
* @author Julio Montoya
*/
public static function make_username($firstname, $lastname, $username, $language = null, $encoding = null)
{
@ -2258,9 +2215,7 @@ class MySpace
}
$desired_username = UserManager::create_username(
$firstname,
$lastname,
$language,
$encoding
$lastname
);
if (UserManager::is_username_available($desired_username.$sufix)) {
break;
@ -2320,10 +2275,10 @@ class MySpace
{
$table_user = Database::get_main_table(TABLE_MAIN_USER);
$tbl_session_rel_course_rel_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
$id_session = intval($id_session);
$id_session = (int) $id_session;
$username = Database::escape_string($username);
foreach ($course_list as $courseId) {
$courseId = intval($courseId);
$courseId = (int) $courseId;
$sql = " SELECT u.user_id FROM $tbl_session_rel_course_rel_user rel
INNER JOIN $table_user u
ON (rel.user_id = u.user_id)
@ -2335,8 +2290,6 @@ class MySpace
$rs = Database::query($sql);
if (Database::num_rows($rs) > 0) {
return Database::result($rs, 0, 0);
} else {
return 0;
}
}
@ -2412,9 +2365,8 @@ class MySpace
public static function get_user_creator($users)
{
$errors = [];
$table_user = Database::get_main_table(TABLE_MAIN_USER);
foreach ($users as $index => $user) {
// database table definition
$table_user = Database::get_main_table(TABLE_MAIN_USER);
$username = Database::escape_string($user['UserName']);
$sql = "SELECT creator_id FROM $table_user WHERE username='$username' ";
@ -2528,24 +2480,20 @@ class MySpace
$users = $new_users;
// Inserting users.
$super_list = [];
foreach ($course_list as $enreg_course) {
$nbr_users = 0;
$new_users = [];
$enreg_course = Database::escape_string($enreg_course);
foreach ($users as $index => $user) {
$userid = intval($user['id']);
$userid = (int) $user['id'];
$sql = "INSERT IGNORE INTO $tbl_session_rel_course_rel_user(session_id, c_id, user_id)
VALUES('$id_session','$enreg_course','$userid')";
$course_session = ['course' => $enreg_course, 'added' => 1];
$result = Database::query($sql);
if (Database::affected_rows($result)) {
$nbr_users++;
}
$new_users[] = $user;
}
$super_list[] = $new_users;
//update the nbr_users field
$sql_select = "SELECT COUNT(user_id) as nbUsers FROM $tbl_session_rel_course_rel_user
@ -2575,7 +2523,6 @@ class MySpace
// Sending emails.
$addedto = '';
if ($sendMail) {
$i = 0;
foreach ($users as $index => $user) {
$emailsubject = '['.api_get_setting('siteName').'] '.get_lang('YourReg').' '.api_get_setting('siteName');
$emailbody = get_lang('Dear').' '.
@ -2615,7 +2562,6 @@ class MySpace
$registered_users .= UserManager::getUserProfileLink($userInfo).' - '.$addedto.'<br />';
}
} else {
$i = 0;
foreach ($users as $index => $user) {
$userInfo = api_get_user_info($user['id']);
if (($user['added_at_platform'] == 1 && $user['added_at_session'] == 1) || $user['added_at_session'] == 1) {
@ -2720,7 +2666,7 @@ class MySpace
if (!empty($studentId)) {
$student = api_get_user_entity($studentId);
if ($student) {
$studentList[$student->getId()] = $student->getCompleteName();
$studentList[$student->getId()] = UserManager::formatUserFullName($student);
}
}
@ -2815,9 +2761,6 @@ class MySpace
['MySpace', 'getUserDataAccessTrackingOverview'],
0
);
//$table->additional_parameters = $form->exportValues();
$table->set_header(0, get_lang('LoginDate'), true);
$table->set_header(1, get_lang('Username'), true);
if (api_is_western_name_order()) {
@ -2876,6 +2819,11 @@ class MySpace
$column,
$orderDirection
) {
$from = (int) $from;
$numberItems = (int) $numberItems;
$column = (int) $column;
$orderDirection = Database::escape_string($orderDirection);
$user = Database::get_main_table(TABLE_MAIN_USER);
$course = Database::get_main_table(TABLE_MAIN_COURSE);
$track_e_login = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
@ -2924,11 +2872,6 @@ class MySpace
$sql .= " AND u.user_id = ".$userId;
}
if (isset($_GET['student_id']) && !empty($_GET['student_id'])) {
$userId = (int) $_GET['student_id'];
$sql .= " AND u.user_id = ".$userId;
}
if (isset($_GET['date']) && !empty($_GET['date'])) {
$dates = DateRangePicker::parseDateRange($_GET['date']);
if (isset($dates['start']) && !empty($dates['start'])) {
@ -3012,10 +2955,16 @@ class MySpace
$user_id = (int) $user_id;
$connections = [];
if (!empty($course_info)) {
$courseId = intval($course_info['real_id']);
$courseId = (int) $course_info['real_id'];
$end_date = add_day_to($end_date);
$start_date = Database::escape_string($start_date);
$end_date = Database::escape_string($end_date);
$sessionCondition = api_get_session_condition($sessionId);
$sql = "SELECT login_course_date, logout_course_date
$sql = "SELECT
login_course_date,
logout_course_date,
TIMESTAMPDIFF(SECOND, login_course_date, logout_course_date) duration
FROM $table
WHERE
user_id = $user_id AND
@ -3030,6 +2979,7 @@ class MySpace
$connections[] = [
'login' => $row['login_course_date'],
'logout' => $row['logout_course_date'],
'duration' => $row['duration'],
];
}
}
@ -3056,11 +3006,15 @@ function get_stats($user_id, $course_info, $sessionId, $start_date = null, $end_
$stringEndDate = '';
if ($start_date != null && $end_date != null) {
$end_date = add_day_to($end_date);
$start_date = Database::escape_string($start_date);
$end_date = Database::escape_string($end_date);
$stringStartDate = "AND login_course_date BETWEEN '$start_date' AND '$end_date'";
$stringEndDate = "AND logout_course_date BETWEEN '$start_date' AND '$end_date'";
}
$user_id = intval($user_id);
$courseId = intval($course_info['real_id']);
$user_id = (int) $user_id;
$courseId = (int) $course_info['real_id'];
$sessionCondition = api_get_session_condition($sessionId);
$sql = "SELECT
SEC_TO_TIME(AVG(time_to_sec(timediff(logout_course_date,login_course_date)))) as avrg,
@ -3092,34 +3046,12 @@ function get_stats($user_id, $course_info, $sessionId, $start_date = null, $end_
function add_day_to($end_date)
{
$foo_date = strtotime($end_date);
$foo_date = strtotime(" +1 day", $foo_date);
$foo_date = date("Y-m-d", $foo_date);
$foo_date = strtotime(' +1 day', $foo_date);
$foo_date = date('Y-m-d', $foo_date);
return $foo_date;
}
/**
* @param array
*
* @author Jorge Frisancho Jibaja
*
* @version OCT-22- 2010
*
* @return array
*/
function convert_to_array($sql_result)
{
$result_to_print = '<table>';
foreach ($sql_result as $key => $data) {
$result_to_print .= '<tr><td>'.date('d-m-Y (H:i:s)', $data['login']).'</td><td>'.
api_time_to_hms($data['logout'] - $data['login']).'</tr></td>'."\n";
}
$result_to_print .= '</table>';
$result_to_print = ["result" => $result_to_print];
return $result_to_print;
}
/**
* Converte an array to a table in html.
*

@ -1,5 +1,4 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* Class to dynamically create an HTML SELECT

@ -4661,7 +4661,6 @@ class SessionManager
*/
public static function countSessionsByEndDate($date = null)
{
$count = 0;
$sessionTable = Database::get_main_table(TABLE_MAIN_SESSION);
$url = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_SESSION);
$date = Database::escape_string($date);

@ -8301,6 +8301,125 @@ $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";
$OutstandingStudents = "Outstanding students";
$PercentileScoresDistribution = "Percentile scores distribution";
$ProgressObtainedFromLPProgressAndTestsAverage = "Note: This progress is obtained through a combination of progress in the learning paths and average scores in the tests";
$CreateNewSurveyDoodle = "Create a new Doodle type survey";
$RemoveMultiplicateQuestions = "Remove multiplicated questions";
$MultiplicateQuestions = "Multiplicate questions";
$QuestionTags = "You can use the tags {{class_name}} and {{student_full_name}} in the question to be able to multiplicate questions.
On the survey's list page in the action field you have a button to multiplicate question that will look for the {{class_name}} tag and duplicate the question for all the class subscribed to the course and rename it with the name of the class.
It will also add a page ending to make a new page for each class.
Then it will look for the {{student_full_name}} tag and duplicate the question for all the student in the class (for each class) and rename it with the student's full name.";
$CreateMeeting = "Create meeting poll";
$QuestionForNextClass = "Question for next class";
$NewExerciseAttemptDisabled = "The portal do not allowed to start new test for the moment, please come back later.";
$PersonalDataOfficer = "Personal data officer";
$MaxNumberSubscribedStudentsReached = "The maximum number of student has already been reached, it is not possible to subscribe more student.";
$UserXAddedToCourseX = "User %s has been registered to course %s";
$LpMinTime = "Minimum time (minutes)";
$LpMinTimeDescription = "Minimum time (in minutes) a student must remain in the learning path to get access to the next one.";
$LpMinTimeWarning = "You didn't spend the minimum time required in the learning path.";
$YouHaveSpentXTime = "You have spent %s";
$TimeSpentInLp = "Time spent in the learning path";
$StudentXFinishedLp = "Student %s has completed his/her learning paths.";
$IHaveFinishedTheLessonsNotifyTheTeacher = "I have finished the lessons, notify the teacher";
$TheStudentXHasFinishedTheCourseYLearningPaths = "The student <b>%s</b> has finished the learning paths from the course <b>%s</b>";
$ThisQuestionExistsInAnotherExercisesWarning = "This question is used in another exercises. If you continue its edition, the changes will affect all exercises that contain this question.";
$TimeSpentTimeRequired = "Time spent / Time required";
$ProgressSpentInLp = "Progress";
$InSessionXYouHadTheFollowingResults = "In session %s, you had the following results";
$TimeSpent = "Time spent";
$NumberOfVisits = "Number of visits";
$FormationUnit = "Formation unit";
$GlobalProgress = "Global progress";
$AttestationOfAttendance = "Attestation of attendance";
$DontShowScoreOnlyWhenUserFinishesAllAttemptsButShowFeedbackEachAttempt = "Do not show the score (only when user finishes all attempts) but show feedback for each attempt.";
$ExerciseRankingMode = "Ranking mode: Do not show results details question by question and show a table with the ranking of all other users.";
$BackToTop = "Back to top";
$PersonalDataTreatmentTitleHelp = "What do we consider personal data?";
$PersonalDataCollectionTitleHelp = "Why do we collect this data?";
$PersonalDataRecordingTitleHelp = "Where do we record the data? (in which system, hosted where, accessible by which company that handles the servers, etc.)";
$PersonalDataOrganizationTitleHelp = "How is the data structured? To what extent does the organization of the data protect your security? ... Do you have established processes that ensure that a hacker will not be able to collect all the personal data on the first attempt? Are they encrypted?";
$PersonalDataStructureTitleHelp = "How is the data structured in this software? (one tab for all the data, it has some categorization / label, ...)";
$PersonalDataConservationTitleHelp = "How long do we save the data? Is there data that expires even though the account is still active? How much time do we keep the data after the last use of the system?";
$PersonalDataAdaptationTitleHelp = "What changes can we make to the data? What changes can be made to the data without affecting the service?";
$PersonalDataExtractionTitleHelp = "What do we extract data for (towards other internal processes) and which data is it?";
$PersonalDataConsultationTitleHelp = "Who can consult the personal data? For what purpose?";
$PersonalDataUsageTitleHelp = "How and for what can we use the personal data?";
$PersonalDataCommunicationTitleHelp = "With whom can we share them? In what opportunities? Through what processes (safe or not)?";
$PersonalDataInterconnectionTitleHelp = "Do we have another system with which Chamilo interacts? What is the interconnection process? What are the data pieces exchanged and in what way?";
$PersonalDataLimitationTitleHelp = "What are the limits that we will always respect when using personal data? How far can we go?";
$PersonalDataDeletionTitleHelp = "After how long do we erase the data? (event, last use, contract validity, etc.) What are the elimination processes?";
$PersonalDataDestructionTitleHelp = "What happens if the data is destroyed as a result of a technical failure? (unauthorized deletion or loss of material, for example)";
$PersonalDataProfilingTitleHelp = "For what purpose do we process personal data? Do we use it to filter the access users have to certain parts of our application? (negative or positive discrimination)";
$SearchUsersByName = "By name";
$GlobalChat = "Chat";
$AskRevision = "Ask for a revision";
$FeatureDisabledBecauseOfUnmaintainedThirdPartyLibraries = "This feature has been disabled because the libraries it depends on are no longer unmaintained.";
$SearchUserByGeolocalization = "By geolocalization";
$GiveRevision = "Give revision";
$ForumPostReported = "Post reported";
$UserXReportedPostXInForumX = "User %s has reported the message %s in the forum %s";
$MyCommunities = "My communities";
$SeeAllCommunities = "See all communities";
$HideForumNotifications = "Hide forum notifications";
$Forum_categoryFields = "Forum Category fields";
$Forum_postFields = "Forum post fields";
$ExerciseShowOnlyGlobalScoreAndCorrectAnswers = "Show only global score (not question score) and show only the correct answers, do not show incorrect answers at all";
$Reported = "Reported";
$FromTimeX = "From %s";
$ToTimeX = "To %s";
$SurveyInviteLink = "Survey invitation link";
$RevisionProposed = "Revision proposed";
$SessionsPlanCalendar = "Sessions plan calendar";
$QuizQuestionsLimitPerDay = "Limit of questions per day";
$QuizQuestionsLimitPerDayComment = "If set to greater than 0, this option will prevent the learner from entering a test that has more than what remains for the daily allowance. For example, is the limit is 50 and the learner has already taken 2 tests of 20 questions, it will not let the learner enter another 20 questions test (20+20+20=60 > 50). However, it will let the learner enter a 10 questions test (20+20+10=50).";
$QuizQuestionsLimitPerDayXReached = "Sorry, you have reached the maximum number of questions (%s) for the day. Please try again tomorrow.";
$VoteLike = "Like";
$VoteDislike = "Dislike";
$GenerateStats = "Generate statistics";
$BasicCourseDocuments = "Basic course documents";
$WeekX = "Week %s";
$QuizWrongAnswerHereIsTheCorrectOne = "Wrong answer. The correct one was:";
$ThisQuizCanBeEmbeddable = "This quiz can be embeddable on videos or mobile content";
$SearchGeolocalization = "Search for this location";
$LegalTermsAgreementStatus = "Terms of use agreement status";
$HideSurveyInvitationLink = "Hide survey invitation link";
$LeaveAMessage = "Leave a message";
$OrphanQuestion = "Orphan question";
$WelcomeToPortalXInCourseSessionX = "Welcome to portal %s Course session: %s";
$WelcomeToPortalXInCourseSessionXCoursePartOfCareerX = "Welcome to portal %s in course %s, part of career %s";
$YourNextModule = "Your next module";
$FirstLesson = "First lesson";
$ImportCourseTeachersAsCourseCoach = "Import course teachers as course coach in the session";
$ResumeImport = "Resume import";
$Candidate = "Candidate";
$GeneralTotal = "General total";
$Domains = "Domains";
$ScormStartAttemptDate = "Date";
$LoginsByDate = "Logins by date";
$AllowHtaccessScormImport = "Allow htaccess in the SCORM import";
$ExerciseAutoEvaluationAndRankingMode = "Auto-evaluation mode and ranking";
$YouAreReceivingACopyBecauseYouAreACourseCoach = "You're receiving a copy because, you're a course coach";
$GenerateReport = "Generate report";
$VersionFromVersionFile = "Version from the version file";
$VersionFromConfigFile = "Version from the config file";
$TheVersionFromTheVersionFileIsUpdatedWithEachVersionIfMainInstallDirectoryIsPresent = "The version from the version.php file is updated with each version but only available if the main/install/ directory is present.";
$TheVersionFromTheConfigurationFileShowsOnTheAdminPageButHasToBeChangedManuallyOnUpgrade = "The version from the main configuration file shows on the main administration page, but has to be changed manually on upgrade.";
$ResultsConfigurationPage = "Results page configuration";
$HideExpectedAnswer = "Hide expected answers column";
$HideTotalScore = "Hide total score";
$HideQuestionScore = "Hide question score";
$SaveAnswers = "Save answers";
$SaveAllAnswers = "Pre-fill with answers from previous attempt";
$UserXReportedPostXInForumX = 'L\'utilisateur %s a signalé le message %s du forum %s';
?>

@ -8233,4 +8233,122 @@ $DifferenceOfDaysAndCalendar = "Différence entre jours et calendrier";
$SkillUserList = "Liste des compétences et utilisateurs";
$CourseId = "ID de cours";
$CareDetailView = "Vue détaillée de vie étudiante";
$MoreDataAvailableInTheDatabaseButTrunkedForEfficiencyReasons = "Plus de données disponibles mais tronquées pour des raisons d'efficacité.";
$SendAnnouncementCopyToMyself = "M'envoyer une copie par e-mail.";
$CourseHoursDuration = "Durée du cours (h)";
$AnnouncementWillBeSentTo = "L'annonce sera envoyée à";
$FrmReadOutTextIntro = "Il est nécessaire d'ajouter un fichier audio correspondant au texte, en cliquant sur l'icône %s.";
$CreateReadOutText = "Créer texte lu";
$OutstandingStudents = "Apprenants distingués";
$PercentileScoresDistribution = "Distribution des scores en pourcents";
$ProgressObtainedFromLPProgressAndTestsAverage = "Note: Le progrès ci-dessus est obtenu par une combinaison du progrès dans les parcours et du score dans les exercices";
$CreateNewSurveyDoodle = "Créer une nouvelle enquête de type Doodle";
$RemoveMultiplicateQuestions = "Supprimer les questions démultipliées";
$MultiplicateQuestions = "Multiplier les questions";
$QuestionTags = "Vous pouvez utiliser les balises {{class_name}} et {{student_full_name}} dans la question afin de multiplier les questions.
Sur la page de liste d'enquêtes dans la colonne d'action, vous avez un bouton pour multiplier les questions qui recherchera la balise {{class_name}} et dupliquera la question pour toutes les classes inscrites au cours et la renommera avec le nom de la classe.
Il ajoutera également un séparateur de page pour créer une nouvelle page pour chaque classe.
Ensuite, il recherchera la balise {{student_full_name}} et dupliquera la question pour tous les étudiants de la classe (pour chaque classe) et la renommera avec le nom complet de l'étudiant.";
$CreateMeeting = "Créer un sondage de réunion";
$QuestionForNextClass = "Questions pour la classe suivante";
$NewExerciseAttemptDisabled = "Le portail ne permet pas le démarrage d'exercice pour le moment, revenez un peu plus tard pour essayer à nouveau";
$PersonalDataOfficer = "Délégué à la protection des données personnelles";
$MaxNumberSubscribedStudentsReached = "Le nombre maximum d'apprenants a déjà été atteint, il n'est pas possible d'en inscrire de nouveau";
$UserXAddedToCourseX = "L'utilisateur %s a été inscrit au cours %s";
$LpMinTime = "Temps minimum (minutes)";
$LpMinTimeDescription = "Temps minimum (en minutes) pendant lequel l'apprenant doit rester dans le parcours d'apprentissage pour pouvoir accéder au suivant.";
$LpMinTimeWarning = "Vous n'avez pas atteint le temps minimum dans le parcours.";
$YouHaveSpentXTime = "Vous avez dépensé %s";
$TimeSpentInLp = "Temps passé dans le parcours";
$StudentXFinishedLp = "L'apprenant %s a terminé ses parcours.";
$IHaveFinishedTheLessonsNotifyTheTeacher = "J'ai terminé les parcours du cours. Prévenir l'enseignant.";
$TheStudentXHasFinishedTheCourseYLearningPaths = "L'apprenant <b>%s</b> a terminé les parcours du cours <b>%s</b>";
$ThisQuestionExistsInAnotherExercisesWarning = "Cette question est utilisée dans d'autres exercices. Si vous l'éditez, les modifications affecteront tous les exercices qui contiennent cette question.";
$TimeSpentTimeRequired = "Temps passé / Temps requis";
$ProgressSpentInLp = "Progrès contenus";
$InSessionXYouHadTheFollowingResults = "Dans la formation %s, vous avez obtenu les résultats suivants:";
$TimeSpent = "Temps de connexion global";
$NumberOfVisits = "Nombre de passages";
$FormationUnit = "Unité de formation";
$GlobalProgress = "Progression Globale";
$AttestationOfAttendance = "Attestation d'assiduité";
$DontShowScoreOnlyWhenUserFinishesAllAttemptsButShowFeedbackEachAttempt = "Ne pas montrer le score (seulement quand l'utilisateur a utilisé toutes les tentatives) mais montrer la rétroalimentation pour chaque tentative.";
$ExerciseRankingMode = "Mode classement: Ne pas montrer les résultats question par question, mais montrer une table de classification par rapport aux autres utilisateurs.";
$BackToTop = "Sommet de la page";
$PersonalDataTreatmentTitleHelp = "Que considérons-nous comme des données personnelles?";
$PersonalDataCollectionTitleHelp = "Pourquoi recueillons-nous ces données?";
$PersonalDataRecordingTitleHelp = "Où enregistrons-nous les données? (dans quel système, hébergé où, accessible par quelle entreprise qui gère les serveurs, etc.)";
$PersonalDataOrganizationTitleHelp = "Comment sont structurées les données? Dans quelle mesure l'organisation des données protège-t-elle votre sécurité? ... Avons-nous mis en place des processus garantissant qu'un pirate informatique ne sera pas en mesure de collecter toutes les données personnelles à la première tentative? Sont-elles cryptées?";
$PersonalDataStructureTitleHelp = "Comment les données sont-elles structurées dans l'application? (un onglet pour toutes les données, il y a une catégorisation / étiquette, ...)";
$PersonalDataConservationTitleHelp = "Combien de temps sauvegardons-nous les données? Y a-t-il des données qui expirent même si le compte est toujours actif? Combien de temps les gardons-nous après la dernière utilisation du système?";
$PersonalDataAdaptationTitleHelp = "Quels changements pouvons-nous apporter aux données? Quelles modifications peuvent être apportées aux données sans affecter le service?";
$PersonalDataExtractionTitleHelp = "Pourquoi y-a-il extraction de données (vers d'autres processus internes) et quelles sont ces données?";
$PersonalDataConsultationTitleHelp = "Qui peut consulter les données personnelles, dans quel but?";
$PersonalDataUsageTitleHelp = "Comment et pourquoi pouvons-nous utiliser les données personnelles?";
$PersonalDataCommunicationTitleHelp = "Avec qui pouvons-nous les partager, dans quelles opportunités, à travers quels processus (sécurisés ou non)?";
$PersonalDataInterconnectionTitleHelp = "Avons-nous un autre système avec lequel Chamilo interagit? Quel est le processus d'interconnexion? Quelles sont les données échangées et de quelle manière?";
$PersonalDataLimitationTitleHelp = "Quelles sont les limites à respecter lors de l'utilisation de données personnelles? Jusqu'où pouvons-nous aller?";
$PersonalDataDeletionTitleHelp = "Après combien de temps effaçons-nous les données? (événement, dernière utilisation, validité du contrat, etc.) Quels sont les processus d'élimination?";
$PersonalDataDestructionTitleHelp = "Que se passe-t-il si les données sont détruites à la suite d'une défaillance technique? (suppression non autorisée ou perte de matériel, par exemple)";
$PersonalDataProfilingTitleHelp = "Dans quel but traitons-nous les données personnelles? Modifions-nous l'accès des utilisateurs à certaines parties de notre application? (discrimination négative ou positive)";
$SearchUsersByName = "Par nom";
$GlobalChat = "Chat";
$AskRevision = "Demander une révision";
$FeatureDisabledBecauseOfUnmaintainedThirdPartyLibraries = "Cette fonctionnalité a été désactivée en raison de l'interruption du support de librairies tierces nécessaires à son fonctionnement.";
$SearchUserByGeolocalization = "Par géolocalisation";
$GiveRevision = "Proposer une révision";
$ForumPostReported = "Signalement d'un message de forum";
$UserXReportedPostXInForumX = "L'utilisateur %s a signalé le message %s du forum %s";
$MyCommunities = "Mes communautés";
$SeeAllCommunities = "Voir toutes mes communautés";
$HideForumNotifications = "Cacher les notifications de forum";
$Forum_categoryFields = "Champs de catégorie de forum";
$Forum_postFields = "Champs de post de forum";
$ExerciseShowOnlyGlobalScoreAndCorrectAnswers = "Afficher uniquement le score global (pas celui de chaque question) et uniquement la réponse correcte (pas les réponses incorrectes)";
$Reported = "Signalé";
$FromTimeX = "De %s";
$ToTimeX = "à %s";
$SurveyInviteLink = "Lien d'invitation à l'enquête";
$RevisionProposed = "Révision proposée";
$SessionsPlanCalendar = "Calendrier de planification des sessions";
$QuizQuestionsLimitPerDay = "Limite de questions par jour";
$QuizQuestionsLimitPerDayComment = "Si ce paramètre est configuré à plus de zéro, il empêchera l'utilisateur d'entrer dans un exercice qui a plus de questions que ce qui lui reste. Par exemple, si la limite est de 50 et que l'utilisateur a déjà passé 2 exercices de 20 questions, le système ne permettra pas à l'utilisateur de commencer un 3ème exercice de 20 questions (20+20+20=60 > 50). Il pourra cependant encore commencer un exercice de 10 questions (20+20+10=50).";
$QuizQuestionsLimitPerDayXReached = "Désolé, vous avez atteint la quantité maximale de questions (%s) par jour. Réessayez demain.";
$VoteLike = "J'aime";
$VoteDislike = "Je n'aime pas";
$GenerateStats = "Générer les statistiques";
$BasicCourseDocuments = "Documents de base du cours";
$WeekX = "Semaine %s";
$QuizWrongAnswerHereIsTheCorrectOne = "Vous vous êtes trompé. La réponse correcte était :";
$ThisQuizCanBeEmbeddable = "Cet exercice peut être inséré dans une vidéo ou du contenu mobile";
$SearchGeolocalization = "Localiser par l'adresse";
$LegalTermsAgreementStatus = "État d'acceptation des conditions d'utilisation";
$HideSurveyInvitationLink = "Cacher le lien d'invitation à l'enquête";
$LeaveAMessage = "Laisser un message";
$OrphanQuestion = "Question orpheline";
$WelcomeToPortalXInCourseSessionX = "Bienvenue sur le portail %s, Cours/session: %s";
$WelcomeToPortalXInCourseSessionXCoursePartOfCareerX = "Bienvenue sur le portail %s dans le cours %s qui fait partie de la carrière %s";
$YourNextModule = "Votre module suivant";
$FirstLesson = "Première leçon";
$ImportCourseTeachersAsCourseCoach = "Importer les professeurs du cours comme tuteur du cours dans la session";
$ResumeImport = "Continuer l'import";
$Candidate = "Candidat";
$GeneralTotal = "Total général";
$Domains = "Domaines";
$ScormStartAttemptDate = "Date";
$LoginsByDate = "Logins par date";
$AllowHtaccessScormImport = "Permettre les fichiers htaccess dans les imports SCORM";
$ExerciseAutoEvaluationAndRankingMode = "Mode auto-évaluation et ranking: Montrer seulement la note globale (pas le détail de chaque question) et montrer la réponse correcte et la réponse sélectionnée par l'apprenant, tout en montrant la table de classification";
$YouAreReceivingACopyBecauseYouAreACourseCoach = "Vous recevez cette copie en tant que tuteur de ce cours";
$GenerateReport = "Générer le rapport";
$VersionFromVersionFile = "Version du fichier de version";
$VersionFromConfigFile = "Version du fichier de configuration";
$TheVersionFromTheVersionFileIsUpdatedWithEachVersionIfMainInstallDirectoryIsPresent = "La version du fichier version.php est mise à jour à chaque nouvelle version installée, mais n'est disponible que si le répertoire main/install/ est présent sur le disque.";
$TheVersionFromTheConfigurationFileShowsOnTheAdminPageButHasToBeChangedManuallyOnUpgrade = "La version du fichier de configuration principal s'affiche sur la page principale d'administration, mais doit être modifiée manuellement à chaque mise à jour.";
$ResultsConfigurationPage = "Configuration de la page de résultats";
$HideExpectedAnswer = "Cacher la colonne des choix attendus";
$HideTotalScore = "Cacher le score total";
$HideQuestionScore = "Cacher le score de chaque question";
$SaveAnswers = "Garder les réponses";
$SaveAllAnswers = "Préremplir avec les réponses de la tentative précédente";
?>

@ -670,6 +670,11 @@ echo '<a href="'.api_get_self().'?'.Security::remove_XSS($_SERVER['QUERY_STRING'
echo '<a href="'.api_get_self().'?'.Security::remove_XSS($_SERVER['QUERY_STRING']).'&export=xls">'
.Display::return_icon('export_excel.png', get_lang('ExportAsXLS'), '', ICON_SIZE_MEDIUM).'</a> ';
echo Display::url(
Display::return_icon('export.png', get_lang('Export'), '', ICON_SIZE_MEDIUM),
api_get_path(WEB_CODE_PATH).'mySpace/access_details_session.php?user_id='.$student_id
);
if (!empty($user_info['email'])) {
$send_mail = '<a href="mailto:'.$user_info['email'].'">'.
Display::return_icon('mail_send.png', get_lang('SendMail'), '', ICON_SIZE_MEDIUM).'</a>';
@ -740,7 +745,18 @@ if (user_is_online($student_id)) {
// get average of score and average of progress by student
$avg_student_progress = $avg_student_score = 0;
if (CourseManager::is_user_subscribed_in_course($user_info['user_id'], $course_code, true)) {
if (empty($sessionId)) {
$isSubscribedToCourse = CourseManager::is_user_subscribed_in_course($user_info['user_id'], $course_code);
} else {
$isSubscribedToCourse = CourseManager::is_user_subscribed_in_course(
$user_info['user_id'],
$course_code,
true,
$sessionId
);
}
if ($isSubscribedToCourse) {
$avg_student_progress = Tracking::get_avg_student_progress(
$user_info['user_id'],
$course_code,

@ -222,7 +222,7 @@ jsPlumb.ready(function () {
}
});
$(document).on('ready', function () {
$(function () {
DraggableAnswer.init(
$(".exercise-draggable-answer"),
$(".droppable")

@ -152,52 +152,77 @@
{# end left zone #}
{% endif %}
<div id="lp_navigation_elem" class="navegation-bar">
{% if show_left_column == 1 %}
<a href="#" title = "{{ 'Expand'|get_lang }}" id="lp-view-expand-toggle" class="icon-toolbar expand" role="button">
{% if lp_mode == 'embedframe' %}
<span class="fa fa-compress" aria-hidden="true"></span>
<span class="sr-only">{{ 'Expand'|get_lang }}</span>
{% else %}
<span class="fa fa-expand" aria-hidden="true"></span>
<span class="sr-only">{{ 'Expand'|get_lang }}</span>
{% endif %}
</a>
{% endif %}
<a id="home-course" title = "{{ 'Home'|get_lang }}" href="{{ button_home_url }}" class="icon-toolbar" target="_self" onclick="javascript: window.parent.API.save_asset();">
<em class="fa fa-home"></em> <span class="hidden-xs hidden-sm"></span>
</a>
{{ navigation_bar }}
</div>
{# right zone #}
{# Right zone #}
<div id="learning_path_right_zone" class="{{ show_left_column == 1 ? 'content-scorm' : 'no-right-col' }}">
<div class="lp-view-zone-container">
<div class="lp-view-tabs">
<div id="navTabsbar" class="nav-tabs-bar">
<ul id="navTabs" class="nav nav-tabs" role="tablist">
<ul id="navTabs" class="nav nav-tabs tabs-right" role="tablist">
<li role="presentation" class="active">
<a href="#lp-view-content" title="{{ 'Lesson'|get_lang }}" aria-controls="lp-view-content" role="tab" data-toggle="tab">
<a href="#lp-view-content" title="{{ 'Lesson'|get_lang }}"
aria-controls="lp-view-content" role="tab" data-toggle="tab">
<span class="fa fa-book fa-2x fa-fw" aria-hidden="true"></span>
<span class="sr-only">{{ 'Lesson'|get_lang }}</span>
</a>
</li>
<li role="presentation">
<a href="#lp-view-forum" title="{{ 'Forum'|get_lang }}" aria-controls="lp-view-forum" role="tab" data-toggle="tab">
<a href="#lp-view-forum" title="{{ 'Forum'|get_lang }}"
aria-controls="lp-view-forum" role="tab" data-toggle="tab">
<span class="fa fa-commenting-o fa-2x fa-fw" aria-hidden="true"></span>
<span class="sr-only">{{ 'Forum'|get_lang }}</span>
</a>
</li>
</ul>
</div>
<div class="tab-content">
<nav id="btn-menu-float" class="circular-menu">
<div class="circle">
{% if show_left_column == 1 %}
<a href="#" title = "{{ 'Expand'|get_lang }}" id="lp-view-expand-toggle"
class="icon-toolbar expand" role="button">
{% if lp_mode == 'embedframe' %}
<span class="fa fa-compress" aria-hidden="true"></span>
<span class="sr-only">{{ 'Expand'|get_lang }}</span>
{% else %}
<span class="fa fa-expand" aria-hidden="true"></span>
<span class="sr-only">{{ 'Expand'|get_lang }}</span>
{% endif %}
</a>
{% endif %}
<a id="home-course"
title = "{{ 'Home'|get_lang }}"
href="{{ button_home_url }}"
class="icon-toolbar" target="_self"
onclick="javascript: window.parent.API.save_asset();">
<em class="fa fa-home"></em> <span class="hidden-xs hidden-sm"></span>
</a>
{{ navigation_bar }}
</div>
<a class="menu-button fa fa-bars icons" href="#"></a>
</nav>
<div id="tab-iframe" class="tab-content">
<div role="tabpanel" class="tab-pane active" id="lp-view-content">
<div id="wrapper-iframe">
{% if lp_mode == 'fullscreen' %}
<iframe id="content_id_blank" name="content_name_blank" src="blank.php" style="width:100%; height:100%" border="0" frameborder="0" allowfullscreen="true" webkitallowfullscreen="true" mozallowfullscreen="true"></iframe>
<iframe
id="content_id_blank"
name="content_name_blank"
src="blank.php"
style="width:100%; height:100%"
border="0"
frameborder="0"
allowfullscreen="true"
webkitallowfullscreen="true" mozallowfullscreen="true"></iframe>
{% else %}
<iframe id="content_id" name="content_name" src="{{ iframe_src }}" style="width:100%; height:100%" border="0" frameborder="0" allowfullscreen="true" webkitallowfullscreen="true" mozallowfullscreen="true"></iframe>
<iframe
id="content_id"
name="content_name"
src="{{ iframe_src }}"
style="width:100%; height:100%"
border="0"
frameborder="0"
allowfullscreen="true"
webkitallowfullscreen="true" mozallowfullscreen="true"></iframe>
{% endif %}
</div>
</div>
@ -211,7 +236,9 @@
</div>
<script>
(function () {
document.querySelector('.menu-button').onclick = function(e) {
e.preventDefault(); document.querySelector('.circle').classList.toggle('open');
}
var LPViewUtils = {
setHeightLPToc: function () {
var scormInfoHeight = $('#scorm-info').outerHeight(true);
@ -225,65 +252,49 @@
if (/iPhone|iPod|iPad/.test(navigator.userAgent)) {
// Fix an issue where you cannot scroll below first screen in
// learning paths on Apple devices
document.getElementById('wrapper-iframe')
.setAttribute(
'style',
'width:100%; overflow:auto; position:auto; -webkit-overflow-scrolling:touch !important;'
);
document.getElementById('wrapper-iframe').setAttribute(
'style',
'width:100%; overflow:auto; position:auto; -webkit-overflow-scrolling:touch !important;'
);
// Fix another issue whereby buttons do not react to click below
// second screen in learning paths on Apple devices
document.getElementById('content_id')
.setAttribute(
'style',
'overflow: auto;'
);
document.getElementById('content_id').setAttribute('style', 'overflow: auto;');
}
{% if lp_mode == 'embedframe' %}
//$('#learning_path_main').addClass('lp-view-collapsed');
$('#lp-view-expand-button, #lp-view-expand-toggle').on('click', function (e) {
e.preventDefault();
$('#learning_path_main').toggleClass('lp-view-collapsed');
$('#lp-view-expand-toggle span.fa').toggleClass('fa-compress');
$('#lp-view-expand-toggle span.fa').toggleClass('fa-expand');
var className = $('#lp-view-expand-toggle span.fa').attr('class');
if (className == 'fa fa-expand') {
$(this).attr('title', '{{ "Expand" | get_lang }}');
} else {
$(this).attr('title', '{{ "Collapse" | get_lang }}');
}
e.preventDefault();
$('#learning_path_main').toggleClass('lp-view-collapsed');
$('#lp-view-expand-toggle span.fa').toggleClass('fa-compress');
$('#lp-view-expand-toggle span.fa').toggleClass('fa-expand');
var className = $('#lp-view-expand-toggle span.fa').attr('class');
if (className == 'fa fa-expand') {
$(this).attr('title', '{{ "Expand" | get_lang }}');
} else {
$(this).attr('title', '{{ "Collapse" | get_lang }}');
}
if($('#navTabsbar').is(':hidden')){
$('#navTabsbar').show();
} else {
$('#navTabsbar').hide();
}
});
if($('#navTabsbar').is(':hidden')) {
$('#navTabsbar').show();
} else {
$('#navTabsbar').hide();
}
});
{% else %}
$('#lp-view-expand-button, #lp-view-expand-toggle').on('click', function (e) {
e.preventDefault();
$('#learning_path_main').toggleClass('lp-view-collapsed');
$('#lp-view-expand-toggle span.fa').toggleClass('fa-expand');
$('#lp-view-expand-toggle span.fa').toggleClass('fa-compress');
var className = $('#lp-view-expand-toggle span.fa').attr('class');
if (className == 'fa fa-expand') {
$(this).attr('title', '{{ "Expand" | get_lang }}');
} else {
$(this).attr('title', '{{ "Collapse" | get_lang }}');
}
$('#lp-view-expand-button, #lp-view-expand-toggle').on('click', function (e) {
e.preventDefault();
$('#learning_path_main').toggleClass('lp-view-collapsed');
$('#lp-view-expand-toggle span.fa').toggleClass('fa-expand');
$('#lp-view-expand-toggle span.fa').toggleClass('fa-compress');
if($('#navTabsbar').is(':hidden')){
$('#navTabsbar').show();
} else {
$('#navTabsbar').hide();
}
});
var className = $('#lp-view-expand-toggle span.fa').attr('class');
if (className == 'fa fa-expand') {
$(this).attr('title', '{{ "Expand" | get_lang }}');
} else {
$(this).attr('title', '{{ "Collapse" | get_lang }}');
}
});
{% endif %}
$('.lp-view-tabs').on('click', '.disabled', function (e) {
@ -303,6 +314,10 @@
LPViewUtils.setHeightLPToc();
$('.image-avatar img').load(function () {
LPViewUtils.setHeightLPToc();
});
$('.scorm_item_normal a, #scorm-previous, #scorm-next').on('click', function () {
$('.lp-view-tabs').animate({opacity: 0}, 500);
});
@ -323,59 +338,55 @@
function(){
// $("<div>I am a div courses</div>").prependTo("body");
},
"top.content_name",
{
load: [
{ type:"script", id:"_fr1", src:"{{ jquery_web_path }}"},
"#content_id",
[
{ type:"script", id:"_fr1", src:"{{ jquery_web_path }}", deps: [
{ type:"script", id:"_fr4", src:"{{ jquery_ui_js_web_path }}"},
{ type:"stylesheet", id:"_fr5", src:"{{ jquery_ui_css_web_path }}"},
{ type:"script", id:"_fr2", src:"{{ _p.web_lib }}javascript/jquery.highlight.js"},
{{ fix_link }}
]
}
]},
{ type:"stylesheet", id:"_fr5", src:"{{ jquery_ui_css_web_path }}"},
]
);
{% elseif show_glossary_in_documents == 'isautomatic' %}
$.frameReady(
function(){
// $("<div>I am a div courses</div>").prependTo("body");
},
"top.content_name",
{
load: [
{ type:"script", id:"_fr1", src:"{{ jquery_web_path }}"},
"#content_id",
[
{ type:"script", id:"_fr1", src:"{{ jquery_web_path }}", deps: [
{ type:"script", id:"_fr4", src:"{{ jquery_ui_js_web_path }}"},
{ type:"stylesheet", id:"_fr5", src:"{{ jquery_ui_css_web_path }}"},
{ type:"script", id:"_fr2", src:"{{ _p.web_lib }}javascript/jquery.highlight.js"},
{{ fix_link }}
]
}
]},
{ type:"stylesheet", id:"_fr5", src:"{{ jquery_ui_css_web_path }}"},
]
);
{% elseif fix_link != '' %}
$.frameReady(
function(){
// $("<div>I am a div courses</div>").prependTo("body");
},
"top.content_name",
{
load: [
{ type:"script", id:"_fr1", src:"{{ jquery_web_path }}"},
"#content_id",
[
{ type:"script", id:"_fr1", src:"{{ jquery_web_path }}", deps: [
{ type:"script", id:"_fr4", src:"{{ jquery_ui_js_web_path }}"},
{ type:"stylesheet", id:"_fr5", src:"{{ jquery_ui_css_web_path }}"},
{{ fix_link }}
]
}
]},
{ type:"stylesheet", id:"_fr5", src:"{{ jquery_ui_css_web_path }}"},
]
);
{% endif %}
})();
{% endif %}
{% if disable_js_in_lp_view == 0 %}
$(function() {
$('iframe#content_id').on('load', function () {
var arr = ['link', 'sco'];
if ($.inArray(olms.lms_item_type, arr) == -1) {
{{ frame_ready }}
}
});
var arr = ['link', 'sco'];
if ($.inArray(olms.lms_item_type, arr) == -1) {
{{ frame_ready }}
}
});
{% endif %}
@ -383,5 +394,4 @@
LPViewUtils.setHeightLPToc();
});
});
})();
</script>

@ -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>

@ -21,7 +21,7 @@ class FeatureContext extends MinkContext
*/
public function iAmAPlatformAdministrator()
{
$this->visit('/index.php?logout=logout');
$this->visit('/index.php?logout=logout&uid=1');
$this->iAmOnHomepage();
$this->fillField('login', 'admin');
$this->fillField('password', 'admin');
@ -95,7 +95,7 @@ class FeatureContext extends MinkContext
public function courseExists($argument)
{
$this->iAmAPlatformAdministrator();
$this->visit('/main/admin/course_list.php?keyword=' . $argument);
$this->visit('/main/admin/course_list.php?keyword='.$argument);
$this->assertPageContainsText($argument);
}
@ -105,7 +105,7 @@ class FeatureContext extends MinkContext
public function courseIsDeleted($argument)
{
$this->iAmAPlatformAdministrator();
$this->visit('/main/admin/course_list.php?keyword=' . $argument);
$this->visit('/main/admin/course_list.php?keyword='.$argument);
$this->clickLink('Delete');
}
@ -142,10 +142,14 @@ class FeatureContext extends MinkContext
{
$this->visit('/index.php?logout=logout');
$this->iAmOnHomepage();
$this->fillFields(new TableNode([
['login', $username],
['password', $username]
]));
$this->fillFields(
new TableNode(
[
['login', $username],
['password', $username],
]
)
);
$this->pressButton('submitAuth');
}
@ -158,16 +162,22 @@ class FeatureContext extends MinkContext
$friendId = $friendId;
$friendUsername = $friendUsername;
$sendInvitationURL = '/main/inc/ajax/message.ajax.php?' . http_build_query([
'a' => 'send_invitation',
'user_id' => $friendId,
'content' => 'Add me'
]);
$acceptInvitationURL = '/main/inc/ajax/social.ajax.php?' . http_build_query([
'a' => 'add_friend',
'friend_id' => $adminId,
'is_my_friend' => 'friend'
]);
$sendInvitationURL = '/main/inc/ajax/message.ajax.php?'.
http_build_query(
[
'a' => 'send_invitation',
'user_id' => $friendId,
'content' => 'Add me',
]
);
$acceptInvitationURL = '/main/inc/ajax/social.ajax.php?'.
http_build_query(
[
'a' => 'add_friend',
'friend_id' => $adminId,
'is_my_friend' => 'friend',
]
);
$this->iAmAPlatformAdministrator();
$this->visit($sendInvitationURL);
@ -182,13 +192,17 @@ class FeatureContext extends MinkContext
public function iHaveAPublicPasswordProtectedCourse($code, $password)
{
$this->visit('/main/admin/course_add.php');
$this->fillFields(new TableNode([
['title', 'Password Protected'],
['visual_code', $code],
['visibility', 3]
]));
$this->fillFields(
new TableNode(
[
['title', 'Password Protected'],
['visual_code', $code],
['visibility', 3],
]
)
);
$this->pressButton('submit');
$this->visit('/main/course_info/infocours.php?cidReq=' . $code);
$this->visit('/main/course_info/infocours.php?cidReq='.$code);
$this->assertPageContainsText('Course registration password');
$this->fillField('course_registration_password', $password);
$this->pressButton('submit_save');
@ -209,7 +223,7 @@ class FeatureContext extends MinkContext
*/
public function iInviteAFriendToASocialGroup($friendId, $groupId)
{
$this->visit('/main/social/group_invitation.php?id=' . $groupId);
$this->visit('/main/social/group_invitation.php?id='.$groupId);
$this->fillField('invitation[]', $friendId);
$this->pressButton('submit');
}
@ -248,7 +262,7 @@ class FeatureContext extends MinkContext
*/
public function iAmOnSocialGroupMembersPageWithId($groupId)
{
$this->visit('/main/social/group_view.php?id=' . $groupId);
$this->visit('/main/social/group_view.php?id='.$groupId);
}
/**
@ -256,11 +270,15 @@ class FeatureContext extends MinkContext
*/
public function iTryDeleteAFriendFromSocialGroup($friendId, $groupId)
{
$this->visit('/main/social/group_members.php?' . http_build_query([
'id' => $groupId,
'u' => $friendId,
'action' => 'delete'
]));
$this->visit(
'/main/social/group_members.php?'.http_build_query(
[
'id' => $groupId,
'u' => $friendId,
'action' => 'delete',
]
)
);
}
/**
@ -384,10 +402,18 @@ class FeatureContext extends MinkContext
*/
public function waitForThePageToBeLoaded()
{
//$this->getSession()->wait(10000, "document.readyState === 'complete'");
$this->getSession()->wait(3000);
}
/**
* @When /^wait very long for the page to be loaded$/
*/
public function waitVeryLongForThePageToBeLoaded()
{
//$this->getSession()->wait(10000, "document.readyState === 'complete'");
$this->getSession()->wait(8000);
}
/**
* @When /^I check the "([^"]*)" radio button$/
*/
@ -473,28 +499,28 @@ class FeatureContext extends MinkContext
return false;
}
/**
* @Given /^I am a student subscribed to session "([^"]*)"$/
*
* @param string$sessionName
* @Then /^I save current URL with name "([^"]*)"$/
*/
public function iAmStudentSubscribedToXSession($sessionName)
public function saveUrlWithName($name)
{
$this->iAmAPlatformAdministrator();
$this->visit('/main/session/session_add.php');
$this->fillField('name', $sessionName);
$this->pressButton('Next step');
$this->selectOption('NoSessionCoursesList[]', 'TEMP (TEMP)');
$this->pressButton('add_course');
$this->pressButton('Next step');
$this->assertPageContainsText('Update successful');
$this->fillField('user_to_add', 'acostea');
$this->waitForThePageToBeLoaded();
$this->clickLink('Costea Andrea (acostea)');
$this->pressButton('Finish session creation');
$this->assertPageContainsText('Session overview');
//$this->assertPageContainsText('Costea Andrea (acostea)');
$this->iAmAStudent();
$url = $this->getSession()->getCurrentUrl();
$this->getSession()->setCookie($name, $url);
}
/**
* @Then /^I visit URL saved with name "([^"]*)"$/
*/
public function visitSavedUrlWithName($name)
{
$url = $this->getSession()->getCookie($name);
echo $url;
if (empty($url)) {
throw new Exception("Url with name: $name not found");
}
$this->visit($url);
}
/**

@ -7,7 +7,7 @@ Feature: Subscribe users to the course
Given I am on "/main/user/subscribe_user.php?keyword=amann&type=5&cidReq=TEMP"
Then I should see "Aimee"
Then I follow "Register"
Then I should see "Aimee Mann (amann) has been registered to your course"
Then I should see "User Aimee Mann (amann) has been registered to course TEMP"
Scenario: Unsubscribe user "amann" the course "TEMP"
Given I am on "/main/user/user.php?cidReq=TEMP"
@ -20,4 +20,16 @@ Feature: Subscribe users to the course
Given I am on "/main/user/subscribe_user.php?keyword=acostea&type=5&cidReq=TEMP"
Then I should see "Andrea"
Then I follow "Register"
Then I should see "Andrea Costea (acostea) has been registered to your course"
Then I should see "User Andrea Costea (acostea) has been registered to course TEMP"
Scenario: Subscribe "fapple" as student to the course "TEMP" (leave it subscribed for further tests)
Given I am on "/main/user/subscribe_user.php?keyword=fapple&type=5&cidReq=TEMP"
Then I should see "Fiona"
Then I follow "Register"
Then I should see "User Fiona Apple Maggart (fapple) has been registered to course TEMP"
Scenario: Subscribe "amann" again as student to the course "TEMP" (leave it subscribed for further tests)
Given I am on "/main/user/subscribe_user.php?keyword=amann&type=5&cidReq=TEMP"
Then I should see "Aimee"
Then I follow "Register"
Then I should see "User Aimee Mann (amann) has been registered to course TEMP"

@ -31,8 +31,9 @@ Feature: Session management tool
Given I am on "/main/session/session_add.php"
When I fill in the following:
| name | Temp Session |
And I press advanced settings
And I fill in select2 input "#coach_username" with id "1" and value "admin"
And I press "advanced_params"
And wait for the page to be loaded
And I fill in ckeditor field "description" with "Description for Temp Session"
And I press "submit"
Then I should see "Add courses to this session (Temp Session)"
@ -50,7 +51,7 @@ Feature: Session management tool
Given I am on "/main/session/session_list.php?keyword=Temp+session"
And wait for the page to be loaded
And I follow "Edit"
When I press "advanced_params"
And I press advanced settings
And I check "Show description"
And I press "submit"
Then I should see "Update successful"
@ -92,4 +93,4 @@ Feature: Session management tool
Given I am on "/main/session/session_category_list.php"
And I follow "Delete"
And I confirm the popup
Then I should see "The selected categories have been deleted"
Then I should see "The selected categories have been deleted"

@ -14,7 +14,10 @@ Feature: Announcement tool
And I select "John Doe" from "users"
And I press "add"
And I fill in ckeditor field "content" with "Announcement description"
And I press "submit"
And I follow "Preview"
And wait for the page to be loaded
Then I should see "Announcement will be sent to"
Then I press "submit"
Then I should see "Announcement has been added"
Scenario: Create an announcement for all users
@ -22,7 +25,10 @@ Feature: Announcement tool
When I fill in the following:
| title | Announcement test |
And I fill in ckeditor field "content" with "Announcement description"
And I press "submit"
And I follow "Preview"
And wait for the page to be loaded
Then I should see "Announcement will be sent to"
Then I press "submit"
Then I should see "Announcement has been added"
Scenario: Delete all announcements

@ -21,7 +21,7 @@ Feature: Document tool
And I press "Create the folder"
Then I should see "Unable to create the folder"
Scenario: Create a HTML document
Scenario: Create a simple document
Given I am on "/main/document/create_document.php?cidReq=TEMP"
Then I should see "Create a rich media page / activity"
Then I fill in the following:
@ -34,6 +34,21 @@ Feature: Document tool
And wait for the page to be loaded
Then I should see "My first document"
Scenario: Create a HTML document
Given I am on "/main/document/create_document.php?cidReq=TEMP"
Then I should see "Create a rich media page / activity"
Then I fill in the following:
| create_document_title | My second document |
And I fill in ckeditor field "content" with "<a href='www.chamilo.org'>Click here</a><span><strong>This is my second document!!!</strong></span>"
And I press "Create a rich media page / activity"
Then I should see "Item added"
And I should see "My second document"
Then I follow "My second document"
And wait for the page to be loaded
Then I should see "My second document"
And I should not see "<strong>"
And I should not see "www.chamilo.org"
Scenario: Upload a document
Given I am on "/main/document/upload.php?cidReq=TEMP"
Then I should see "Upload documents"

@ -232,6 +232,29 @@ Feature: Exercise tool
And I press "submitQuestion"
Then I should see "Item added"
Scenario: Duplicate exercise
Given I am on "/main/exercise/exercise.php?cidReq=TEMP"
And I follow "Copy this exercise as a new one"
And I confirm the popup
Then I should see "Exercise copied"
And I should see "Exercise 1 - Copy"
Scenario: Import exercise to test questions categories
Given I am on "/main/exercise/upload_exercise.php?cidReq=TEMP"
And I should see "Import quiz from Excel"
And I attach the file "/tests/behat/uploadable_files/exercise.xls" to "user_upload_quiz"
When I press "Upload"
And wait for the page to be loaded
Then I should see "Exercise for Behat test"
Scenario: Import exercise from excel
Given I am on "/main/exercise/upload_exercise.php?cidReq=TEMP"
Then I should see "Import quiz from Excel"
Then I attach the file "/main/exercise/quiz_template.xls" to "user_upload_quiz"
And I press "Upload"
And wait for the page to be loaded
Then I should see "Definition of oligarchy"
Scenario: Try exercise "Exercise 1"
Given I am on "/main/exercise/exercise.php?cidReq=TEMP"
And I follow "Exercise 1"
@ -303,28 +326,28 @@ Feature: Exercise tool
And I follow "Grade activity"
Then I should see "Score for the test: 83 / 117"
Scenario: Duplicate exercise
Given I am on "/main/exercise/exercise.php?cidReq=TEMP"
And I follow "Copy this exercise as a new one"
And I confirm the popup
Then I should see "Exercise copied"
And I should see "Exercise 1 - Copy"
Scenario: Import exercise to test questions categories
Given I am on "/main/exercise/upload_exercise.php?cidReq=TEMP"
And I should see "Import quiz from Excel"
And I attach the file "/tests/behat/uploadable_files/exercise.xls" to "user_upload_quiz"
When I press "Upload"
And wait for the page to be loaded
Then I should see "Exercise for Behat test"
Scenario: Create a session "Session Exercise" and add user "acostea"
Given I am on "/main/session/session_add.php"
When I fill in the following:
| name | Session Exercise |
And I fill in select2 input "#coach_username" with id "1" and value "admin"
And I press "submit"
Then wait for the page to be loaded
Then I should see "Add courses to this session (Session Exercise)"
Then I select "TEMP (TEMP)" from "NoSessionCoursesList[]"
And I press "add_course"
And I press "next"
Then I should see "Update successful"
Then I follow "Multiple registration"
Then I select "Costea Andrea (acostea)" from "nosessionUsersList[]"
And I press "add_user"
And I press "next"
Then I should see "Update successful"
Scenario: Try exercise with categorized questions as student
Given I am a student subscribed to session "Session Exercise"
And I am on "/user_portal.php"
And I follow "Session Exercise"
And wait for the page to be loaded
And I follow "tabs2"
And I follow "TEMP"
Given I am a student
And I am on course "TEMP" homepage in session "Session Exercise"
Then I should see "TEMP (Session Exercise)"
And I am on "/main/exercise/exercise.php?cidReq=TEMP"
And I follow "Exercise for Behat test"
And I follow "Start test"
@ -350,8 +373,10 @@ Feature: Exercise tool
And I select "B" from "choice_id_7_2"
And I select "C" from "choice_id_7_3"
And I press "Next question"
And wait for the page to be loaded
And I check "1"
And I press "Next question"
And wait for the page to be loaded
And I press "End test"
Then I should see "Score for the test: 190 / 190"
And I should see the table "#category_results":
@ -361,17 +386,15 @@ Feature: Exercise tool
| none | 80 / 60 | 133.33% |
| Total | 190 / 190 | 100% |
Scenario: Teacher see exercise results by categories
Scenario: Teacher looks at exercise results by categories
Given I am on "/user_portal.php"
And I follow "Session Exercise"
And wait for the page to be loaded
And I follow "tabs2"
And I follow "TEMP"
And I am on course "TEMP" homepage in session "Session Exercise"
Then I should see "TEMP (Session Exercise)"
And I am on "/main/exercise/exercise.php?cidReq=TEMP"
And I follow "Exercise for Behat test"
And I follow "Results and feedback"
Then I should see "Learner score"
And wait for the page to be loaded
And wait very long for the page to be loaded
And I follow "Grade activity"
Then I should see "Score for the test: 190 / 190"
And I should see the table "#category_results":
@ -397,10 +420,9 @@ Feature: Exercise tool
And I follow "Delete"
Then I should see "Category deleted"
Scenario: Import exercise from excel
Given I am on "/main/exercise/upload_exercise.php?cidReq=TEMP"
Then I should see "Import quiz from Excel"
Then I attach the file "/main/exercise/quiz_template.xls" to "user_upload_quiz"
And I press "Upload"
Scenario: Delete session
Given I am on "/main/session/session_list.php?keyword=Session+Exercise"
And wait for the page to be loaded
Then I should see "Definition of oligarchy"
And I follow "Delete"
And I confirm the popup
Then I should see "Deleted"

@ -6,24 +6,41 @@ Feature: Group tool
Given I am a platform administrator
And I am on course "TEMP" homepage
Scenario: Delete default category
Given I am on "/main/group/group.php?cidReq=TEMP&id_session=0"
Then I should see "Default groups"
Then I follow "Delete"
Then I confirm the popup
Then I should see "The category has been deleted"
Scenario: Create a group directory
Given I am on "/main/group/group_category.php?cidReq=TEMP&id_session=0&action=add_category"
When I fill in the following:
| title | Group category 1 |
| title | Group category 1 |
And I press "group_category_submit"
Then I should see "Category created"
Scenario: Create a group
Scenario: Create 4 groups
Given I am on "/main/group/group_creation.php?cidReq=TEMP&id_session=0"
When I fill in the following:
| number_of_groups | 1 |
| number_of_groups | 5 |
And I press "submit"
Then I should see "New groups creation"
When I fill in the following:
| group_0_places | 1 |
| group_1_places | 1 |
| group_2_places | 1 |
| group_3_places | 1 |
| group_4_places | 2 |
And I fill in select bootstrap static by text "#category_0" select "Group category 1"
And I fill in select bootstrap static by text "#category_1" select "Group category 1"
And I fill in select bootstrap static by text "#category_2" select "Group category 1"
And I fill in select bootstrap static by text "#category_3" select "Group category 1"
And I fill in select bootstrap static by text "#category_4" select "Group category 1"
And I press "submit"
Then I should see "group(s) has (have) been added"
Scenario: Create document folder in group
Given I am on "/main/group/group.php?cidReq=TEMP&id_session=0"
And I follow "Group 0001"
@ -88,4 +105,301 @@ Feature: Group tool
Then I follow "Delete"
Then wait for the page to be loaded
Then I should see "Are you sure to delete"
Then I follow "delete_item"
Then I follow "delete_item"
Scenario: Add fapple to the Group 0001
Given I am on "/main/group/group.php?cidReq=TEMP&id_session=0"
And I follow "Group 0001"
Then I should see "Group 0001"
Then I follow "Edit this group"
Then I should see "Group members"
Then wait for the page to be loaded
Then I follow "group_members_tab"
Then I select "Fiona Apple Maggart (fapple)" from "group_members"
Then I press "group_members_rightSelected"
Then I press "Save settings"
And wait for the page to be loaded
Then I should see "Group settings modified"
Then I follow "Group 0001"
Then I should see "Fiona"
Scenario: Add fapple to the Group 0003 not allowed because group category allows 1 user per group
Given I am on "/main/group/group.php?cidReq=TEMP&id_session=0"
And I follow "Group 0003"
Then I should see "Group 0003"
Then I follow "Edit this group"
Then I should see "Group members"
Then wait for the page to be loaded
Then I follow "group_members_tab"
Then I select "Fiona Apple Maggart (fapple)" from "group_members"
Then I press "group_members_rightSelected"
Then I press "Save settings"
And wait for the page to be loaded
Then I should see "Group settings modified"
Then I follow "Group 0003"
Then I should not see "Fiona"
# Group category overwrites all other groups settings.
Scenario: Change Group category to allow multiple inscription of the user
Given I am on "/main/group/group.php?cidReq=TEMP&id_session=0"
And I follow "Edit this category"
Then I should see "Edit group category: Group category 1"
Then I fill in select bootstrap static by text "#groups_per_user" select "10"
Then I press "Edit"
Then I should see "Group settings have been modified"
Scenario: Change Group 0003 settings to make announcements private
Given I am on "/main/group/group.php?cidReq=TEMP&id_session=0"
And I follow "Group 0003"
Then I should see "Group 0003"
Then I follow "Edit this group"
Then I check the "announcements_state" radio button with "2" value
Then I press "Save settings"
Then I should see "Group settings modified"
Scenario: Change Group 0004 settings to make it private
Given I am on "/main/group/group.php?cidReq=TEMP&id_session=0"
And I follow "Group 0004"
Then I should see "Group 0004"
Then I follow "Edit this group"
Then I check the "announcements_state" radio button with "2" value
Then I press "Save settings"
Then I should see "Group settings modified"
Scenario: Change Group 0005 settings to make announcements private between users
Given I am on "/main/group/group.php?cidReq=TEMP&id_session=0"
And I follow "Group 0005"
Then I should see "Group 0005"
Then I follow "Edit this group"
Then I check the "announcements_state" radio button with "3" value
Then I press "Save settings"
Then I should see "Group settings modified"
Scenario: Add fapple and acostea to Group 0005
Given I am on "/main/group/group.php?cidReq=TEMP&id_session=0"
And I follow "Group 0005"
Then I should see "Group 0005"
Then I follow "Edit this group"
Then I should see "Group members"
Then wait for the page to be loaded
Then I follow "group_members_tab"
Then I additionally select "Fiona Apple Maggart (fapple)" from "group_members"
Then I additionally select "Andrea Costea (acostea)" from "group_members"
Then I press "group_members_rightSelected"
Then I press "Save settings"
And wait for the page to be loaded
Then I should see "Group settings modified"
Then I follow "Group 0005"
Then I should see "Fiona"
Then I should see "Andrea"
Scenario: Add fapple to the Group 0003
Given I am on "/main/group/group.php?cidReq=TEMP&id_session=0"
And I follow "Group 0003"
Then I should see "Group 0003"
Then I follow "Edit this group"
Then I should see "Group members"
Then wait for the page to be loaded
Then I follow "group_members_tab"
Then I select "Fiona Apple Maggart (fapple)" from "group_members"
Then I press "group_members_rightSelected"
Then I press "Save settings"
And wait for the page to be loaded
Then I should see "Group settings modified"
Then I follow "Group 0003"
Then I should see "Fiona"
Scenario: Add acostea to the Group 0002
Given I am on "/main/group/group.php?cidReq=TEMP&id_session=0"
And I follow "Group 0002"
Then I should see "Group 0002"
Then I follow "Edit this group"
Then I should see "Group members"
Then wait for the page to be loaded
Then I follow "group_members_tab"
Then I select "Andrea Costea (acostea)" from "group_members"
Then I press "group_members_rightSelected"
Then I press "Save settings"
And wait for the page to be loaded
Then I should see "Group settings modified"
Then I follow "Group 0002"
Then I should see "Andrea"
Scenario: Create an announcement for everybody inside Group 0001
Given I am on "/main/group/group.php?cidReq=TEMP&id_session=0"
And I follow "Group 0001"
Then I should see "Group 0001"
And I follow "Announcements"
Then I should see "Announcements"
Then I follow "Add an announcement"
Then I should see "Add an announcement"
Then wait for the page to be loaded
Then I fill in the following:
| title | Announcement for all users inside Group 0001 |
And I fill in ckeditor field "content" with "Announcement description in Group 0001"
Then I follow "announcement_preview"
And wait for the page to be loaded
Then I should see "Announcement will be sent to"
Then I press "submit"
Then I should see "Announcement has been added"
Scenario: Create an announcement for fapple inside Group 0001
Given I am on "/main/group/group.php?cidReq=TEMP&id_session=0"
And I follow "Group 0001"
Then I should see "Group 0001"
And I follow "Announcements"
Then I should see "Announcements"
Then I follow "Add an announcement"
Then I should see "Add an announcement"
Then wait for the page to be loaded
Then I press "choose_recipients"
Then I select "Fiona Apple" from "users"
Then I press "users_rightSelected"
Then I fill in the following:
| title | Announcement for user fapple inside Group 0001 |
And I fill in ckeditor field "content" with "Announcement description for user fapple inside Group 0001"
Then I follow "announcement_preview"
And wait for the page to be loaded
Then I should see "Announcement will be sent to"
Then I press "submit"
Then I should see "Announcement has been added"
Scenario: Create an announcement for everybody inside Group 0003 (private)
Given I am on "/main/group/group.php?cidReq=TEMP&id_session=0"
And I follow "Group 0003"
Then I should see "Group 0003"
And I follow "Announcements"
Then I should see "Announcements"
Then I follow "Add an announcement"
Then I should see "Add an announcement"
Then wait for the page to be loaded
Then I fill in the following:
| title | Announcement for all users inside Group 0003 |
And I fill in ckeditor field "content" with "Announcement description in Group 0003"
Then I follow "announcement_preview"
And wait for the page to be loaded
Then I should see "Announcement will be sent to"
Then I press "submit"
Then I should see "Announcement has been added"
Scenario: Create an announcement for fapple inside Group 0003
Given I am on "/main/group/group.php?cidReq=TEMP&id_session=0"
And I follow "Group 0003"
Then I should see "Group 0003"
And I follow "Announcements"
Then I should see "Announcements"
Then I follow "Add an announcement"
Then I should see "Add an announcement"
Then wait for the page to be loaded
Then I press "choose_recipients"
Then I select "Fiona Apple" from "users"
Then I press "users_rightSelected"
Then I fill in the following:
| title | Announcement for user fapple inside Group 0003 |
And I fill in ckeditor field "content" with "Announcement description for user fapple inside Group 0003"
Then I follow "announcement_preview"
And wait for the page to be loaded
Then I should see "Announcement will be sent to"
Then I press "submit"
Then I should see "Announcement has been added"
Scenario: Create an announcement as acostea and send only to fapple
Given I am logged as "acostea"
Given I am on "/main/group/group.php?cidReq=TEMP&id_session=0"
And I follow "Group 0005"
Then I should see "Group 0005"
And I follow "Announcements"
Then I should see "Announcements"
Then I follow "Add an announcement"
Then I should see "Add an announcement"
Then wait for the page to be loaded
Then I press "choose_recipients"
Then I select "Fiona Apple Maggart" from "users"
Then I press "users_rightSelected"
Then I fill in the following:
| title | Announcement only for fapple Group 0005 |
And I fill in ckeditor field "content" with "Announcement description only for fapple Group 0005"
Then I follow "announcement_preview"
And wait for the page to be loaded
Then I should see "Announcement will be sent to"
Then I press "submit"
Then I should see "Announcement has been added"
Scenario: Check fapple/acostea access of announcements
Given I am logged as "fapple"
And I am on course "TEMP" homepage
And I am on "/main/group/group.php?cidReq=TEMP&id_session=0"
And I follow "Group 0001"
Then I should see "Group 0001"
Then I follow "Announcements"
And wait for the page to be loaded
Then I should see "Announcement for all users inside Group 0001"
Then I should see "Announcement for user fapple inside Group 0001"
Then I follow "Announcement for user fapple inside Group 0001 Group"
Then I should see "Announcement description for user fapple inside Group 0001"
Then I save current URL with name "announcement_for_user_fapple_group_0001_public"
Then I move backward one page
Then wait for the page to be loaded
Then I should see "Announcement for all users inside Group 0001"
Then I follow "Announcement for all users inside Group 0001"
Then I save current URL with name "announcement_for_all_users_group_0001_public"
Then I should see "Announcement description in Group 0001"
And I am on "/main/group/group.php?cidReq=TEMP&id_session=0"
And I follow "Group 0003"
Then I should see "Group 0003"
Then I follow "Announcements"
And wait for the page to be loaded
Then I should see "Announcement for all users inside Group 0003"
Then I should see "Announcement for user fapple inside Group 0003"
Then I follow "Announcement for user fapple inside Group 0003 Group"
Then I should see "Announcement description for user fapple inside Group 0003"
Then I save current URL with name "announcement_for_user_fapple_group_0003_private"
Then I move backward one page
Then wait for the page to be loaded
Then I should see "Announcement for all users inside Group 0003"
Then I follow "Announcement for all users inside Group 0003"
Then I should see "Announcement description in Group 0003"
Then I save current URL with name "announcement_for_all_users_group_0003_private"
And I am on "/main/group/group.php?cidReq=TEMP&id_session=0"
And I follow "Group 0005"
Then I should see "Group 0005"
Then I follow "Announcements"
And wait for the page to be loaded
Then I should see "Announcement only for fapple Group 0005"
Then I follow "Announcement only for fapple Group 0005"
Then I save current URL with name "announcement_only_for_fapple_private"
## Finish tests with fapple now check access with acostea ##
Given I am logged as "acostea"
And I am on course "TEMP" homepage
Given I am on "/main/group/group.php?cidReq=TEMP&id_session=0"
Then I should see "Group 0001"
And I should see "Group 0002"
And I should see "Group 0003"
And I should see "Group 0004"
Then I visit URL saved with name "announcement_for_user_fapple_group_0001_public"
Then I should see "Sorry, you are not allowed to access this page"
Then I visit URL saved with name "announcement_for_all_users_group_0001_public"
Then I should see "Sorry, you are not allowed to access this page"
Then I visit URL saved with name "announcement_only_for_fapple_private"
Then I should see "Sorry, you are not allowed to access this page"
Given I am logged as "acostea"
And I am on course "TEMP" homepage
Given I am on "/main/group/group.php?cidReq=TEMP&id_session=0"
Then I should see "Group 0001"
And I should see "Group 0002"
And I should see "Group 0003"
And I should see "Group 0004"
And I should see "Group 0005"
Then I visit URL saved with name "announcement_for_user_fapple_group_0001_public"
Then I should see "Sorry, you are not allowed to access this page"
Then I visit URL saved with name "announcement_for_all_users_group_0001_public"
Then I should see "Sorry, you are not allowed to access this page"
Then I visit URL saved with name "announcement_for_user_fapple_group_0003_private"
Then I should see "Sorry, you are not allowed to access this page"
Then I visit URL saved with name "announcement_for_all_users_group_0003_private"
Then I should see "Sorry, you are not allowed to access this page"
Then I visit URL saved with name "announcement_only_for_fapple_private"
Then I should see "Sorry, you are not allowed to access this page"

@ -48,11 +48,8 @@ Feature: LP tool
Then I should see "LP 1"
And I should see "Document 1"
And I should see "Exercise 1"
Scenario: Delete a LP category
Given I am on "/main/lp/lp_controller.php?cidReq=TEMP&action=list"
And I follow "Delete"
Then I should not see "LP category 1"
And I am on "/main/lp/lp_controller.php?cidReq=TEMP&action=list"
Then I should see "LP 1"
# Scenario: Check the PDF export in LP list if hide SCORM PDF link is false
# Given I am on "/main/admin/settings.php?category=Course"
@ -68,8 +65,19 @@ Feature: LP tool
# And I am on "/main/lp/lp_controller.php?cidReq=TEMP&action=list&isStudentView=true"
# Then I should not see an icon with title "Export to PDF"
Scenario: LP exists and LP category exists
And I am on "/main/lp/lp_controller.php?cidReq=TEMP&id_session=0&gidReq=0&gradebook=0&origin="
Then I should see "LP 1"
And I should see "LP category 1"
Scenario: Delete a LP
Given I am on "/main/lp/lp_controller.php?cidReq=TEMP&action=list"
Given I am on "/main/lp/lp_controller.php?cidReq=TEMP"
And I follow "Delete"
And I confirm the popup
Then I should not see "LP 1"
Scenario: Delete a LP category
Given I am on "/main/lp/lp_controller.php?cidReq=TEMP"
Then I should see "LP category 1"
And I follow "Delete"
Then I should see "Deleted"
Loading…
Cancel
Save