Internal: Partial merge from 1.11.x

pull/3544/head
Julio Montoya 4 years ago
parent f3d1bbb230
commit 63d4f72eb3
  1. 2
      config/packages/doctrine_migrations.yaml
  2. 2
      config/packages/framework.yaml
  3. 5
      public/main/admin/access_url_add_courses_to_url.php
  4. 16
      public/main/admin/course_edit.php
  5. 100
      public/main/admin/questions.php
  6. 8
      public/main/admin/statistics/index.php
  7. 12
      public/main/admin/user_add.php
  8. 27
      public/main/admin/user_anonymize_import.php
  9. 11
      public/main/admin/user_edit.php
  10. 15
      public/main/admin/user_information.php
  11. 31
      public/main/admin/user_list.php
  12. 10
      public/main/admin/user_list_consent.php
  13. 12
      public/main/admin/user_move_stats.php
  14. 349
      public/main/admin/user_update_import.php
  15. 7
      public/main/admin/usergroup_user_import.php
  16. 9
      public/main/admin/usergroups.php
  17. 80
      public/main/course_info/delete_course.php
  18. 18
      public/main/course_info/legal.php
  19. 31
      public/main/coursecopy/copy_course.php
  20. 2
      public/main/coursecopy/copy_course_session.php
  21. 19
      public/main/create_course/add_course.php
  22. 4
      public/main/exercise/ReadingComprehension.php
  23. 459
      public/main/exercise/TestCategory.php
  24. 6
      public/main/exercise/UniqueAnswerImage.php
  25. 7
      public/main/exercise/admin.php
  26. 2
      public/main/exercise/answer.class.php
  27. 2
      public/main/exercise/category.php
  28. 59
      public/main/exercise/export/aiken/aiken_import.inc.php
  29. 2
      public/main/exercise/export/exercise_import.inc.php

@ -3,4 +3,4 @@ doctrine_migrations:
'Chamilo\CoreBundle\Migrations\Schema\V200': '%kernel.project_dir%/src/CoreBundle/Migrations/Schema/V200'
all_or_nothing: true
check_database_platform: true
check_database_platform: true

@ -16,4 +16,4 @@ framework:
#fragments: true
php_errors:
log: true
serializer: {enable_annotations: true}
serializer: {enable_annotations: true}

@ -21,6 +21,7 @@ if (!api_get_multiple_access_url()) {
$first_letter_course = '';
$courses = [];
$url_list = [];
$users = [];
$tbl_access_url = Database::get_main_table(TABLE_MAIN_ACCESS_URL);
$tbl_course = Database::get_main_table(TABLE_MAIN_COURSE);
@ -46,6 +47,10 @@ if (isset($_POST['form_sent']) && $_POST['form_sent']) {
$url_list = is_array($_POST['url_list']) ? $_POST['url_list'] : [];
$first_letter_course = $_POST['first_letter_course'];
foreach ($users as $key => $value) {
$users[$key] = intval($value);
}
if (1 == $form_sent) {
if (0 == count($courses) || 0 == count($url_list)) {
echo Display::return_message(get_lang('At least one course and one URL'), 'error');

@ -271,6 +271,14 @@ $extra = $extra_field->addElements(
true
);
if (api_get_configuration_value('multiple_access_url_show_shared_course_marker')) {
$urls = UrlManager::get_access_url_from_course($courseId);
$urlToString = '';
foreach ($urls as $url) {
$urlToString .= $url['url'].'<br />';
}
$form->addLabel('URLs', $urlToString);
}
$htmlHeadXtra[] = '
<script>
$(function() {
@ -430,10 +438,8 @@ if ($form->validate()) {
Display::addFlash(Display::return_message(get_lang('Item updated').': '.$message, 'info', false));
if ($visual_code_is_used) {
Display::addFlash(Display::return_message($warn));
header('Location: course_list.php');
} else {
header('Location: course_list.php');
}
header('Location: course_list.php');
exit;
}
@ -442,6 +448,10 @@ Display::display_header($tool_name);
echo '<div class="actions">';
echo Display::url(Display::return_icon('back.png', get_lang('Back')), api_get_path(WEB_CODE_PATH).'admin/course_list.php');
echo Display::url(Display::return_icon('course_home.png', get_lang('Course homepage')), $courseInfo['course_public_url'], ['target' => '_blank']);
echo Display::url(
Display::return_icon('info2.png', get_lang('Information')),
api_get_path(WEB_CODE_PATH)."admin/course_information.php?code=$courseCode"
);
echo '</div>';
echo "<script>

@ -33,6 +33,8 @@ $pagination = '';
$formSent = isset($_REQUEST['form_sent']) ? (int) $_REQUEST['form_sent'] : 0;
$length = 20;
$questionCount = 0;
$start = 0;
$end = 0;
if ($formSent) {
$id = isset($_REQUEST['id']) ? (int) $_REQUEST['id'] : '';
@ -96,9 +98,25 @@ if ($formSent) {
if ($pagination) {
$urlExercise = api_get_path(WEB_CODE_PATH).'exercise/admin.php?';
$exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/exercise.php?';
$warningText = addslashes(api_htmlentities(get_lang('ConfirmYourChoice')));
/** @var CQuizQuestion $question */
foreach ($pagination as $question) {
for ($i = 0; $i < $length; $i++) {
$index = $i;
if (!empty($page)) {
$index = ($page - 1) * $length + $i;
}
if (0 === $i) {
$start = $index;
}
if (!isset($pagination[$index])) {
continue;
}
if ($i < $length) {
$end = $index;
}
$question = &$pagination[$index];
$courseId = $question->getCId();
$courseInfo = api_get_course_info_by_id($courseId);
$courseCode = $courseInfo['code'];
@ -122,49 +140,38 @@ if ($formSent) {
);
$question->questionData = ob_get_contents();
$deleteUrl = $url.'&'.http_build_query([
'courseId' => $question->getCId(),
'questionId' => $question->getId(),
'action' => 'delete',
]);
$exerciseData = '';
$exerciseId = 0;
if (!empty($questionObject->exerciseList)) {
// Question exists in a valid exercise
$exerciseData .= get_lang('Tests').'<br />';
foreach ($questionObject->exerciseList as $exerciseId) {
$exercise = new Exercise();
$exercise = new Exercise($question->getCId());
$exercise->course_id = $question->getCId();
$exercise->read($exerciseId);
$exerciseData .= $exercise->title.'&nbsp;';
$exerciseData .= Display::url(
Display::return_icon('edit.png', get_lang('Edit')),
$urlExercise.http_build_query([
$urlExercise.http_build_query(
[
'cidReq' => $courseCode,
'id_session' => $exercise->sessionId,
'exerciseId' => $exerciseId,
'type' => $question->getType(),
'editQuestion' => $question->getIid(),
])
);
'editQuestion' => $question->getId(),
]
),
['target' => '_blank']
).'<br />';
}
$question->questionData .= '<br />'.$exerciseData;
} else {
// Question exists but it's orphan or it belongs to a deleted exercise
$question->questionData .= Display::url(
Display::return_icon('edit.png', get_lang('Edit')),
$urlExercise.http_build_query([
'cidReq' => $courseCode,
'id_session' => $exercise->sessionId,
'exerciseId' => $exerciseId,
'type' => $question->getType(),
'editQuestion' => $question->getIid(),
])
);
$question->questionData .= Display::url(
Display::return_icon('delete.png', get_lang('Delete')),
$url.'&'.http_build_query([
'courseId' => $question->getCId(),
'questionId' => $question->getIid(),
'action' => 'delete',
])
).'<br />';
// This means the question is added in a deleted exercise
if ($questionObject->getCountExercise() > 0) {
@ -184,7 +191,28 @@ if ($formSent) {
// This question is orphan :(
$question->questionData .= '&nbsp;'.get_lang('Orphan question');
}
$question->questionData .= Display::url(
Display::return_icon('edit.png', get_lang('Edit')),
$urlExercise.http_build_query(
[
'cidReq' => $courseCode,
'id_session' => 0, //$exercise->sessionId,
'exerciseId' => $exerciseId,
'type' => $question->getType(),
'editQuestion' => $question->getId(),
]
),
['target' => '_blank']
);
}
$question->questionData .= '<div class="pull-right">'.Display::url(
get_lang('Delete'),
$deleteUrl,
[
'class' => 'btn btn-danger',
'onclick' => 'javascript: if(!confirm(\''.$warningText.'\')) return false',
]
).'</div>';
ob_end_clean();
}
}
@ -199,15 +227,17 @@ switch ($action) {
$courseId = isset($_REQUEST['courseId']) ? $_REQUEST['courseId'] : '';
$courseInfo = api_get_course_info_by_id($courseId);
$objQuestionTmp = Question::read($questionId, $courseInfo);
if (!empty($objQuestionTmp)) {
$result = $objQuestionTmp->delete();
if ($result) {
Display::addFlash(
Display::return_message(
get_lang('Deleted').' #'.$questionId.' - "'.$objQuestionTmp->question.'"'
)
);
if (!empty($courseInfo)) {
$objQuestionTmp = Question::read($questionId, $courseInfo);
if (!empty($objQuestionTmp)) {
$result = $objQuestionTmp->delete();
if ($result) {
Display::addFlash(
Display::return_message(
get_lang('Deleted').' #'.$questionId.' - "'.$objQuestionTmp->question.'"'
)
);
}
}
}
@ -220,6 +250,8 @@ $tpl = new Template(get_lang('Questions'));
$tpl->assign('form', $formContent);
$tpl->assign('pagination', $pagination);
$tpl->assign('pagination_length', $length);
$tpl->assign('start', $start);
$tpl->assign('end', $end);
$tpl->assign('question_count', $questionCount);
$layout = $tpl->get_template('admin/questions.tpl');

@ -23,6 +23,7 @@ in_array(
)
) {
$htmlHeadXtra[] = api_get_js('chartjs/Chart.min.js');
$htmlHeadXtra[] = api_get_asset('chartjs-plugin-labels/build/chartjs-plugin-labels.min.js');
// Prepare variables for the JS charts
$url = $reportName = $reportType = $reportOptions = '';
switch ($report) {
@ -594,7 +595,7 @@ switch ($report) {
$form->addHidden('report', 'user_session');
$form->addButtonSearch(get_lang('Search'));
$date = new DateTime();
$date = new DateTime($now);
$startDate = $date->format('Y-m-d').' 00:00:00';
$endDate = $date->format('Y-m-d').' 23:59:59';
$start = $startDate;
@ -674,7 +675,7 @@ switch ($report) {
jQuery("#user_session_grid").jqGrid("navButtonAdd","#user_session_grid_pager", {
caption:"",
onClickButton : function () {
jQuery("#user_session_grid").jqGrid("excelExport",{"url":"<?php echo $url; ?>&export_format=xls"});
jQuery("#user_session_grid").jqGrid("excelExport",{"url":"'.$url.'&export_format=xls"});
}
});
});
@ -853,6 +854,7 @@ switch ($report) {
$item[] = $certificate ? get_lang('Yes') : get_lang('No');
$item[] = $birthDate;
$data[] = $item;
$row++;
}
if (isset($_REQUEST['action_table']) && 'export' === $_REQUEST['action_table']) {
@ -919,7 +921,7 @@ switch ($report) {
);
$scoreDisplay = ScoreDisplay::instance();
$table = new HTML_Table(['class' => 'data_table']);
$table = new HTML_Table(['class' => 'table table-hover table-striped data_table']);
$headers = [
get_lang('Name'),
get_lang('Count'),

@ -250,6 +250,18 @@ $form->addElement(
//drh list (display only if student)
$display = (isset($_POST['status']) && STUDENT == $_POST['status']) || !isset($_POST['status']) ? 'block' : 'none';
//@todo remove the drh list here. This code is unused
$form->addElement('html', '<div id="drh_list" style="display:'.$display.';">');
if (isset($drh_list) && is_array($drh_list)) {
foreach ($drh_list as $drh) {
$drh_select->addOption(
api_get_person_name($drh['firstname'], $drh['lastname']),
$drh['user_id']
);
}
}
$form->addElement('html', '</div>');
if (api_is_platform_admin()) {
// Platform admin
$group = [];

@ -34,6 +34,7 @@ $usernameTextarea = $step2Form->addTextarea(
'readonly' => 1,
]
);
$anonymizedSessions = $step2Form->addCheckBox('anonymize_sessions', null, get_lang('AnonymizeUserSessions'));
$step2Form->addButtonUpdate(get_lang('Anonymize'));
if ($step1Form->validate() && $usernameListFile->isUploadedFile()) {
@ -105,17 +106,39 @@ if ($step1Form->validate() && $usernameListFile->isUploadedFile()) {
Criteria::expr()->in('username', $usernames)
)
);
$anonymizedSessionsValue = $anonymizedSessions->getValue();
if (count($users) === count($usernames)) {
printf('<p>'.get_lang('AnonymizingXUsers')."</p>\n", count($users));
$anonymized = [];
$errors = [];
$tableSession = Database::get_main_table(TABLE_MAIN_SESSION);
foreach ($users as $user) {
$username = $user->getUsername();
$userId = $user->getId();
$name = api_get_person_name($user->getFirstname(), $user->getLastname());
echo "<p>$username ($name, id=$userId):\n";
echo "<h4>$username ($name, id= $userId) </h4>";
try {
if (UserManager::anonymize($userId)) {
if ($anonymizedSessionsValue) {
$sessions = SessionManager::getSessionsFollowedByUser($userId);
if ($sessions) {
echo '<p> '.get_lang('Sessions').' </p>';
foreach ($sessions as $session) {
$sessionId = $session['id'];
$sessionTitle = $session['name'];
$usersCount = SessionManager::get_users_by_session($sessionId, null, true);
echo '<p> '.$sessionTitle.' ('.$sessionId.') - '.get_lang('Students').': '.$usersCount.'</p>';
if (1 === $usersCount) {
$uniqueId = uniqid('anon_session', true);
echo '<p> '.get_lang('Rename').': '.$sessionTitle.' -> '.$uniqueId.'</p>';
$sql = "UPDATE $tableSession SET name = '$uniqueId' WHERE id = $sessionId";
Database::query($sql);
} else {
echo '<p> '.sprintf(get_lang('SessionXSkipped'), $sessionTitle).'</p>';
}
}
}
}
echo get_lang('Done');
$anonymized[] = $username;
} else {
@ -139,7 +162,7 @@ if ($step1Form->validate() && $usernameListFile->isUploadedFile()) {
.'<pre>%s</pre></p>',
count($users),
count($errors),
join("\n", $errors)
implode("\n", $errors)
);
}
} else {

@ -399,18 +399,17 @@ if ($form->validate()) {
$email = $user['email'];
$phone = $user['phone'];
$username = isset($user['username']) ? $user['username'] : $userInfo['username'];
$status = intval($user['status']);
$platform_admin = intval($user['platform_admin']);
$send_mail = intval($user['send_mail']);
$reset_password = intval($user['reset_password']);
$status = (int) $user['status'];
$platform_admin = (int) $user['platform_admin'];
$send_mail = (int) $user['send_mail'];
$reset_password = (int) $user['reset_password'];
$hr_dept_id = isset($user['hr_dept_id']) ? intval($user['hr_dept_id']) : null;
$language = $user['language'];
$address = isset($user['address']) ? $user['address'] : null;
$expiration_date = null;
if (!$user_data['platform_admin'] && '1' == $user['radio_expiration_date']) {
$expiration_date = $user['expiration_date'];
} else {
$expiration_date = null;
}
$active = $user_data['platform_admin'] ? 1 : intval($user['active']);

@ -391,10 +391,23 @@ if (count($sessions) > 0) {
[$session_item['access_start_date'], $session_item['access_end_date']]
);
$certificateLink = Display::url(
Display::return_icon('pdf.png', get_lang('CertificateOfAchievement'), [], ICON_SIZE_SMALL),
api_get_path(WEB_CODE_PATH).'mySpace/session.php?'
.http_build_query(
[
'action' => 'export_to_pdf',
'type' => 'achievement',
'session_to_export' => $id_session,
'student' => $userId,
]
),
['target' => '_blank']
);
$sessionInformation .= Display::page_subheader(
'<a href="'.api_get_path(WEB_CODE_PATH).'session/resume_session.php?id_session='.$id_session.'">'.
$session_item['session_name'].'</a>',
' '.implode(' - ', $dates)
$certificateLink.' '.implode(' - ', $dates)
);
$sessionInformation .= Display::return_sortable_table(

@ -531,7 +531,7 @@ function get_user_data($from, $number_of_items, $column, $direction)
*/
function email_filter($email)
{
return Display::encrypted_mailto_link($email, cut($email, 26));
return Display::encrypted_mailto_link($email, cut($email, 26), 'small clickable_email_link');
}
/**
@ -1071,6 +1071,22 @@ $form->addText('keyword_username', get_lang('Login'), false);
$form->addText('keyword_email', get_lang('e-mail'), false);
$form->addText('keyword_officialcode', get_lang('Code'), false);
$classId = isset($_REQUEST['class_id']) && !empty($_REQUEST['class_id']) ? (int) $_REQUEST['class_id'] : 0;
$options = [];
if ($classId) {
$userGroup = new UserGroup();
$groupInfo = $userGroup->get($classId);
if ($groupInfo) {
$options = [$classId => $groupInfo['name']];
}
}
$form->addSelectAjax(
'class_id',
get_lang('SocialGroup').' / '.get_lang('Class'),
$options,
['url' => api_get_path(WEB_AJAX_PATH).'usergroup.ajax.php?a=get_class_by_keyword']
);
$status_options = [];
$status_options['%'] = get_lang('All');
$status_options[STUDENT] = get_lang('Learner');
@ -1115,7 +1131,11 @@ $table = new SortableTable(
'users',
'get_number_of_users',
'get_user_data',
(api_is_western_name_order() xor api_sort_by_first_name()) ? 3 : 2
(api_is_western_name_order() xor api_sort_by_first_name()) ? 3 : 2,
20,
'ASC',
null,
['style' => 'font-size: 1.4rem;', 'class' => 'table table-hover table-striped table-bordered table-condensed']
);
$table->set_additional_parameters($parameters);
$table->set_header(0, '', false, 'width="18px"');
@ -1177,7 +1197,7 @@ if (0 == $table->get_total_number_of_items()) {
if (!empty($user_list)) {
$extra_search_options = Display::page_subheader(get_lang('Users found in other portals'));
$table = new HTML_Table(['class' => 'data_table']);
$table = new HTML_Table(['class' => 'table table-hover table-striped data_table']);
$column = 0;
$row = 0;
$headers = [get_lang('User'), 'URL', get_lang('Detail')];
@ -1220,11 +1240,6 @@ if (0 == $table->get_total_number_of_items()) {
);
$column++;
}
$table->updateRowAttributes(
$row,
$row % 2 ? 'class="row_even"' : 'class="row_odd"',
true
);
$row++;
}
}

@ -611,11 +611,11 @@ if (api_is_western_name_order()) {
$table->set_header(5, get_lang('Login'));
$table->set_header(6, get_lang('e-mail'));
$table->set_header(7, get_lang('Profile'));
$table->set_header(8, get_lang('active'), true, 'width="15px"');
$table->set_header(9, get_lang('Registration date'), true, 'width="90px"');
$table->set_header(10, get_lang('Request type'), true, 'width="15px"');
$table->set_header(11, get_lang('Request date'), true, 'width="15px"');
$table->set_header(12, get_lang('Action'), false, 'width="220px"');
$table->set_header(8, get_lang('active'));
$table->set_header(9, get_lang('Registration date'));
$table->set_header(10, get_lang('Request type'));
$table->set_header(11, get_lang('Request date'));
$table->set_header(12, get_lang('Action'), false);
$table->set_column_filter(3, 'user_filter');
$table->set_column_filter(4, 'user_filter');

@ -706,7 +706,7 @@ if (isset($_GET['page']) && !empty($_GET['page'])) {
$page = intval($_GET['page']);
}
$default = 20;
$count = UserManager::get_number_of_users();
$count = UserManager::get_number_of_users(null, api_get_current_access_url_id());
$nro_pages = round($count / $default) + 1;
$begin = $default * ($page - 1);
$end = $default * $page;
@ -782,13 +782,16 @@ if (!empty($user_list)) {
$course_list = $course_list_registered;
echo '<div>';
echo '<table class="data_table">';
echo '<div class="table-responsive">';
echo '<table class="table table-hover table-striped data_table">';
echo '<thead>';
echo '<tr>';
echo '<th style="text-align:left;" colspan="'.count($course_list).'">';
echo "<h3>$name #$user_id </h3> ";
echo '</th>';
echo '</tr>';
echo '</thead>';
echo '<tbody>';
if (!empty($course_list)) {
echo '<tr>';
@ -820,7 +823,7 @@ if (!empty($user_list)) {
$unique_id = uniqid();
$combinations[$unique_id] = ['course_code' => $course_code, 'session_id' => $session_id];
echo '<select id="'.$unique_id.'" name="'.$unique_id.'">';
echo '<select id="'.$unique_id.'" name="'.$unique_id.'" class="form-control">';
echo $options;
echo '</select>';
echo '<br />';
@ -835,6 +838,7 @@ if (!empty($user_list)) {
echo get_lang('This user isn\'t subscribed in a course');
echo '</td>';
}
echo '</tbody>';
echo '</table>';
echo '</div>';
}

@ -6,67 +6,52 @@
* This tool allows platform admins to add users by uploading a CSV or XML file.
*/
/**
* Validate the imported data.
*/
use Symfony\Component\DomCrawler\Crawler;
$cidReset = true;
require_once __DIR__.'/../inc/global.inc.php';
// Set this option to true to enforce strict purification for usenames.
// Set this option to true to enforce strict purification for usernames.
$purification_option_for_usernames = false;
/**
* @param array $users
*
* @return array
*/
function validate_data($users)
{
global $defined_auth_sources;
$errors = [];
$usernames = [];
// 1. Check if mandatory fields are set.
$mandatory_fields = ['LastName', 'FirstName'];
if ('true' == api_get_setting('registration', 'email')) {
$mandatory_fields[] = 'Email';
}
$classExistList = [];
$usergroup = new UserGroup();
foreach ($users as $user) {
foreach ($mandatory_fields as $field) {
if (isset($user[$field])) {
if (empty($user[$field])) {
$user['error'] = get_lang($field.'Mandatory');
$errors[] = $user;
}
}
}
// 2. Check username, first, check whether it is empty.
if (isset($user['NewUserName'])) {
if (!UserManager::is_username_empty($user['NewUserName'])) {
// 2.1. Check whether username is too long.
if (UserManager::is_username_too_long($user['NewUserName'])) {
$user['error'] = get_lang('This login is too long');
$errors[] = $user;
$errors[$user['UserName']][] = get_lang('UserNameTooLong');
}
// 2.2. Check whether the username was used twice in import file.
if (isset($usernames[$user['NewUserName']])) {
$user['error'] = get_lang('Login is used twice');
$errors[] = $user;
$errors[$user['UserName']][] = get_lang('UserNameUsedTwice');
}
$usernames[$user['UserName']] = 1;
// 2.3. Check whether username is allready occupied.
if (!UserManager::is_username_available($user['NewUserName']) && $user['NewUserName'] != $user['UserName']) {
$user['error'] = get_lang('This login is not available');
$errors[] = $user;
if (!UserManager::is_username_available($user['NewUserName']) &&
$user['NewUserName'] != $user['UserName']
) {
$errors[$user['UserName']][] = get_lang('UserNameNotAvailable');
}
}
}
// 3. Check status.
if (isset($user['Status']) && !api_status_exists($user['Status'])) {
$user['error'] = get_lang('This status doesn\'t exist');
$errors[] = $user;
$errors[$user['UserName']][] = get_lang('WrongStatus');
}
// 4. Check ClassId
@ -78,8 +63,7 @@ function validate_data($users)
}
$info = $usergroup->get($id);
if (empty($info)) {
$user['error'] = sprintf(get_lang('Class ID does not exist'), $id);
$errors[] = $user;
$errors[$user['UserName']][] = sprintf(get_lang('ClassIdDoesntExists'), $id);
} else {
$classExistList[] = $info['id'];
}
@ -89,8 +73,7 @@ function validate_data($users)
// 5. Check authentication source
if (!empty($user['AuthSource'])) {
if (!in_array($user['AuthSource'], $defined_auth_sources)) {
$user['error'] = get_lang('Authentication source unavailable.');
$errors[] = $user;
$errors[$user['UserName']][] = get_lang('AuthSourceNotAvailable');
}
}
}
@ -98,74 +81,53 @@ function validate_data($users)
return $errors;
}
/**
* Add missing user-information (which isn't required, like password, username etc).
*/
function complete_missing_data($user)
{
global $purification_option_for_usernames;
// 1. Create a username if necessary.
if (UserManager::is_username_empty($user['UserName'])) {
$user['UserName'] = UserManager::create_unique_username($user['FirstName'], $user['LastName']);
} else {
$user['UserName'] = UserManager::purify_username($user['UserName'], $purification_option_for_usernames);
}
// 2. Generate a password if necessary.
if (empty($user['Password'])) {
$user['Password'] = api_generate_password();
}
// 3. Set status if not allready set.
if (empty($user['Status'])) {
$user['Status'] = 'user';
}
// 4. Set authsource if not allready set.
if (empty($user['AuthSource'])) {
$user['AuthSource'] = PLATFORM_AUTH_SOURCE;
}
return $user;
}
/**
* Update users from the imported data.
*
* @param array $users List of users
*
* @return false|null
*
* @uses \global variable $inserted_in_course, which returns the list of courses the user was inserted in
* @param array $users List of users.
* @param bool $resetPassword Optional.
* @param bool $sendEmail Optional.
*/
function updateUsers($users)
function updateUsers(
$users,
$resetPassword = false,
$sendEmail = false)
{
global $insertedIn_course;
// Not all scripts declare the $inserted_in_course array (although they should).
if (!isset($inserted_in_course)) {
$inserted_in_course = [];
}
$usergroup = new UserGroup();
$send_mail = !empty($_POST['sendMail']) ? true : false;
if (is_array($users)) {
foreach ($users as $user) {
$user = complete_missing_data($user);
$user['Status'] = api_status_key($user['Status']);
$userName = $user['UserName'];
$userInfo = api_get_user_info_from_username($userName);
$user_id = $userInfo['user_id'];
if (0 == $user_id) {
return false;
if (isset($user['Status'])) {
$user['Status'] = api_status_key($user['Status']);
}
$userInfo = api_get_user_info_from_username($user['UserName']);
if (empty($userInfo)) {
continue;
}
$user_id = $userInfo['user_id'];
$firstName = isset($user['FirstName']) ? $user['FirstName'] : $userInfo['firstname'];
$lastName = isset($user['LastName']) ? $user['LastName'] : $userInfo['lastname'];
$userName = isset($user['NewUserName']) ? $user['NewUserName'] : $userInfo['username'];
$changePassMethod = 0;
$password = isset($user['Password']) ? $user['Password'] : '';
if (!empty($password)) {
$changePassMethod = 2;
}
$authSource = isset($user['AuthSource']) ? $user['AuthSource'] : '';
if (2 === $changePassMethod && !empty($authSource) && $authSource != $userInfo['auth_source']) {
$changePassMethod = 3;
$password = null;
$authSource = $userInfo['auth_source'];
if ($resetPassword) {
$changePassMethod = 1;
} else {
if (isset($user['Password'])) {
$changePassMethod = 2;
$password = $user['Password'];
}
if (isset($user['AuthSource']) && $user['AuthSource'] != $authSource) {
$authSource = $user['AuthSource'];
$changePassMethod = 3;
}
}
$email = isset($user['Email']) ? $user['Email'] : $userInfo['email'];
$status = isset($user['Status']) ? $user['Status'] : $userInfo['status'];
$officialCode = isset($user['OfficialCode']) ? $user['OfficialCode'] : $userInfo['official_code'];
@ -176,8 +138,14 @@ function updateUsers($users)
$creatorId = $userInfo['creator_id'];
$hrDeptId = $userInfo['hr_dept_id'];
$language = isset($user['Language']) ? $user['Language'] : $userInfo['language'];
$sendEmail = isset($user['SendEmail']) ? $user['SendEmail'] : $userInfo['language'];
$userUpdated = UserManager :: update_user(
//$sendEmail = isset($user['SendEmail']) ? $user['SendEmail'] : $userInfo['language'];
//$sendEmail = false;
// see BT#17893
if ($resetPassword && $sendEmail == false) {
$sendEmail = true;
}
UserManager::update_user(
$user_id,
$firstName,
$lastName,
@ -196,9 +164,10 @@ function updateUsers($users)
null,
$language,
'',
'',
$sendEmail,
$changePassMethod
);
if (!empty($user['Courses']) && !is_array($user['Courses'])) {
$user['Courses'] = [$user['Courses']];
}
@ -206,8 +175,6 @@ function updateUsers($users)
foreach ($user['Courses'] as $course) {
if (CourseManager::course_exists($course)) {
CourseManager::subscribeUser($user_id, $course, $user['Status']);
$course_info = api_get_course_info($course);
$inserted_in_course[$course] = $course_info['title'];
}
}
}
@ -237,6 +204,11 @@ function updateUsers($users)
);
}
}
$userUpdated = api_get_user_info($user_id);
Display::addFlash(
Display::return_message(get_lang('UserUpdated').': '.$userUpdated['complete_name_with_username'])
);
}
}
}
@ -246,16 +218,25 @@ function updateUsers($users)
*
* @param string $file Path to the CSV-file
*
* @throws Exception
*
* @return array All userinformation read from the file
*/
function parse_csv_data($file)
{
$users = Import :: csvToArray($file);
foreach ($users as $index => $user) {
if (isset($user['Courses'])) {
$user['Courses'] = explode('|', trim($user['Courses']));
$data = Import::csv_reader($file);
if (empty($data)) {
throw new Exception(get_lang('NoDataAvailable'));
}
$users = [];
foreach ($data as $row) {
if (isset($row['Courses'])) {
$row['Courses'] = explode('|', trim($row['Courses']));
}
if (!isset($row['UserName'])) {
throw new Exception(get_lang('ThisFieldIsRequired').': UserName');
}
$users[$index] = $user;
$users[] = $row;
}
return $users;
@ -263,14 +244,14 @@ function parse_csv_data($file)
function parse_xml_data($file)
{
$crawler = new \Symfony\Component\DomCrawler\Crawler();
$crawler = new Crawler();
$crawler->addXmlContent(file_get_contents($file));
$crawler = $crawler->filter('Contacts > Contact ');
$array = [];
foreach ($crawler as $domElement) {
$row = [];
foreach ($domElement->childNodes as $node) {
if ('#text' != $node->nodeName) {
if ($node->nodeName != '#text') {
$row[$node->nodeName] = $node->nodeValue;
}
}
@ -286,116 +267,105 @@ $this_section = SECTION_PLATFORM_ADMIN;
api_protect_admin_script(true, null);
$defined_auth_sources[] = PLATFORM_AUTH_SOURCE;
if (isset($extAuthSource) && is_array($extAuthSource)) {
$defined_auth_sources = array_merge($defined_auth_sources, array_keys($extAuthSource));
}
$tool_name = get_lang('Import users list');
$interbreadcrumb[] = ["url" => 'index.php', "name" => get_lang('Administration')];
$tool_name = get_lang('UpdateUserListXMLCSV');
$interbreadcrumb[] = ["url" => 'index.php', "name" => get_lang('PlatformAdmin')];
set_time_limit(0);
$extra_fields = UserManager::get_extra_fields(0, 0, 5, 'ASC', true);
$user_id_error = [];
$error_message = '';
if (isset($_POST['formSent']) && $_POST['formSent'] && 0 !== $_FILES['import_file']['size']) {
$file_type = 'csv';
Security::clear_token();
$tok = Security::get_token();
$allowed_file_mimetype = ['csv', 'xml'];
$error_kind_file = false;
$form = new FormValidator('user_update_import', 'post', api_get_self());
$form->addHeader($tool_name);
$form->addFile('import_file', get_lang('ImportFileLocation'), ['accept' => 'text/csv', 'id' => 'import_file']);
$form->addCheckBox('reset_password', '', get_lang('AutoGeneratePassword'));
$group = [
$form->createElement('radio', 'sendMail', '', get_lang('Yes'), 1),
$form->createElement('radio', 'sendMail', null, get_lang('No'), 0),
];
$form->addGroup($group, '', get_lang('SendMailToUsers'));
$defaults['sendMail'] = 0;
$uploadInfo = pathinfo($_FILES['import_file']['name']);
$ext_import_file = $uploadInfo['extension'];
if ($form->validate()) {
if (Security::check_token('post')) {
Security::clear_token();
$formValues = $form->exportValues();
if (in_array($ext_import_file, $allowed_file_mimetype)) {
if (0 === strcmp($file_type, 'csv') && $ext_import_file == $allowed_file_mimetype[0]) {
$users = parse_csv_data($_FILES['import_file']['tmp_name']);
$errors = validate_data($users);
$error_kind_file = false;
} elseif (0 === strcmp($file_type, 'xml') && $ext_import_file == $allowed_file_mimetype[1]) {
$users = parse_xml_data($_FILES['import_file']['tmp_name']);
$errors = validate_data($users);
$error_kind_file = false;
} else {
$error_kind_file = true;
if (empty($_FILES['import_file']) || empty($_FILES['import_file']['size'])) {
header('Location: '.api_get_self());
exit;
}
} else {
$error_kind_file = true;
}
// List user id with error.
$users_to_insert = $user_id_error = [];
$uploadInfo = pathinfo($_FILES['import_file']['name']);
if ($uploadInfo['extension'] !== 'csv') {
Display::addFlash(
Display::return_message(get_lang('YouMustImportAFileAccordingToSelectedOption'), 'error')
);
if (is_array($errors)) {
foreach ($errors as $my_errors) {
$user_id_error[] = $my_errors['UserName'];
header('Location: '.api_get_self());
exit;
}
}
if (is_array($users)) {
foreach ($users as $my_user) {
if (!in_array($my_user['UserName'], $user_id_error)) {
$users_to_insert[] = $my_user;
try {
$users = parse_csv_data($_FILES['import_file']['tmp_name']);
} catch (Exception $exception) {
Display::addFlash(
Display::return_message($exception->getMessage(), 'error')
);
header('Location: '.api_get_self());
exit;
}
$errors = validate_data($users);
$errorUsers = array_keys($errors);
$usersToUpdate = [];
foreach ($users as $user) {
if (!in_array($user['UserName'], $errorUsers)) {
$usersToUpdate[] = $user;
}
}
}
$inserted_in_course = [];
if (0 === strcmp($file_type, 'csv')) {
updateUsers($users_to_insert);
}
$sendEmail = $_POST['sendMail'] ? true : false;
updateUsers($usersToUpdate, isset($formValues['reset_password']), $sendEmail);
if (count($errors) > 0) {
$see_message_import = get_lang('The users that were not registered on the platform have been imported');
} else {
$see_message_import = get_lang('File imported');
}
if (empty($errors)) {
Display::addFlash(
Display::return_message(get_lang('FileImported'), 'success')
);
} else {
$warningMessage = '';
foreach ($errors as $errorUsername => $errorUserMessages) {
$warningMessage .= "<strong>$errorUsername</strong>";
$warningMessage .= '<ul><li>'.implode('</li><li>', $errorUserMessages).'</li></ul>';
}
$warning_message = '';
if (0 != count($errors)) {
$warning_message = '<ul>';
foreach ($errors as $index => $error_user) {
$warning_message .= '<li><b>'.$error_user['error'].'</b>: ';
$warning_message .=
'<strong>'.$error_user['UserName'].'</strong>&nbsp;('.
api_get_person_name($error_user['FirstName'], $error_user['LastName']).')';
$warning_message .= '</li>';
Display::addFlash(
Display::return_message(get_lang('FileImportedJustUsersThatAreNotRegistered'), 'warning')
);
Display::addFlash(Display::return_message($warningMessage, 'warning', false));
}
$warning_message .= '</ul>';
}
// if the warning message is too long then we display the warning message trough a session
if (!empty($warning_message)) {
Display::addFlash(Display::return_message($warning_message, 'warning', false));
}
if ($error_kind_file) {
Display::addFlash(Display::return_message(get_lang('You must import a file corresponding to the selected format'), 'error', false));
header('Location: '.api_get_self());
exit;
} else {
header('Location: '.api_get_path(WEB_CODE_PATH).'admin/user_list.php?sec_token='.$tok);
Display::addFlash(Display::return_message(get_lang('LinkExpired'), 'warning', false));
header('Location: '.api_get_self());
exit;
}
}
Display::display_header($tool_name);
if (!empty($error_message)) {
echo Display::return_message($error_message, 'error');
}
$form = new FormValidator('user_update_import', 'post', api_get_self());
$form->addElement('header', $tool_name);
$form->addElement('hidden', 'formSent');
$form->addElement('file', 'import_file', get_lang('Import marks in an assessment'));
$group = [];
Display::display_header($tool_name);
$token = Security::get_token();
$form->addButtonImport(get_lang('Import'));
$defaults['formSent'] = 1;
$defaults['sendMail'] = 0;
$defaults['file_type'] = 'csv';
$form->setDefaults($defaults);
$form->addHidden('sec_token', $token);
$form->addButtonImport(get_lang('Import'));
$form->display();
$list = [];
@ -417,15 +387,16 @@ if ($count_fields > 0) {
}
?>
<p><?php echo get_lang('The CSV file must look like this').' ('.get_lang('Fields in <strong>bold</strong> are mandatory.').')'; ?> :</p>
<blockquote>
<p><?php echo get_lang('CSVMustLookLike').' ('.get_lang('MandatoryFields').')'; ?> :</p>
<blockquote>
<pre>
<b>UserName</b>;LastName;FirstName;Email;NewUserName;Password;AuthSource;OfficialCode;PhoneNumber;Status;ExpiryDate;Active;Language;Courses;ClassId;
xxx;xxx;xxx;xxx;xxx;xxx;xxx;xxx;xxx;user/teacher/drh;YYYY-MM-DD 00:00:00;0/1;xxx;<span style="color:red;"><?php if (count($list_reponse) > 0) {
xxx;xxx;xxx;xxx;xxx;xxx;xxx;xxx;xxx;user/teacher/drh;YYYY-MM-DD 00:00:00;0/1;xxx;<span
style="color:red;"><?php if (count($list_reponse) > 0) {
echo implode(';', $list_reponse).';';
} ?></span>xxx1|xxx2|xxx3;1;<br />
} ?></span>xxx1|xxx2|xxx3;1;<br/>
</pre>
</blockquote>
<p><?php
Display :: display_footer();
</blockquote>
<p>
<?php
Display::display_footer();

@ -39,7 +39,7 @@ function validate_data($user_classes)
$user_class['error'] = get_lang('This code does not exist').': '.$user_class['ClassName'];
$errors[] = $user_class;
} else {
$classcodes[$user_class['CourseCode']] = 1;
$classcodes[$user_class['ClassName']] = 1;
}
}
}
@ -122,10 +122,9 @@ function save_data($users_classes, $deleteUsersNotInList = false)
*/
function parse_csv_data($file)
{
$courses = Import::csvToArray($file);
return $courses;
return Import::csvToArray($file);
}
$cidReset = true;
require_once __DIR__.'/../inc/global.inc.php';

@ -33,10 +33,10 @@ $columns = [
//Column config
$column_model = [
['name' => 'name', 'index' => 'name', 'width' => '35', 'align' => 'left'],
['name' => 'users', 'index' => 'users', 'width' => '15', 'align' => 'left'],
['name' => 'courses', 'index' => 'courses', 'width' => '15', 'align' => 'left'],
['name' => 'sessions', 'index' => 'sessions', 'width' => '15', 'align' => 'left'],
['name' => 'group_type', 'index' => 'group_type', 'width' => '15', 'align' => 'center'],
['name' => 'users', 'index' => 'users', 'width' => '15', 'align' => 'left', 'search' => 'false'],
['name' => 'courses', 'index' => 'courses', 'width' => '15', 'align' => 'left', 'search' => 'false'],
['name' => 'sessions', 'index' => 'sessions', 'width' => '15', 'align' => 'left', 'search' => 'false'],
['name' => 'group_type', 'index' => 'group_type', 'width' => '15', 'align' => 'center', 'search' => 'false'],
[
'name' => 'actions',
'index' => 'actions',
@ -44,6 +44,7 @@ $column_model = [
'align' => 'center',
'sortable' => 'false',
'formatter' => 'action_formatter',
'search' => 'false',
],
];

@ -23,33 +23,63 @@ if (!api_is_allowed_to_edit()) {
api_not_allowed(true);
}
$tool_name = get_lang('Completely delete this course');
if (isset($_GET['delete']) && 'yes' === $_GET['delete']) {
CourseManager::delete_course($_course['sysCode']);
// DELETE CONFIRMATION MESSAGE
Session::erase('_cid');
Session::erase('_real_cid');
$message = '<h2>'.get_lang('Course').' : '.$current_course_name.' ('.$current_course_code.') </h2>';
$message .= get_lang('has been deleted');
Display::addFlash(Display::return_message($message, 'warning', false));
$url = api_get_path(WEB_CODE_PATH).'index/user_portal.php';
header('Location: '.$url);
exit;
$tool_name = get_lang('DelCourse');
$type_info_message = 'warning';
if (isset($_GET['delete']) && $_GET['delete'] === 'yes' && $_GET['course_code'] && !empty($_GET['course_code'])) {
if ($current_course_code == $_GET['course_code']) {
CourseManager::delete_course($_course['sysCode']);
// DELETE CONFIRMATION MESSAGE
Session::erase('_cid');
Session::erase('_real_cid');
$message = '<h3>'.get_lang('CourseTitle').' : '.$current_course_name.'</h3>';
$message .= '<h3>'.get_lang('CourseCode').' : '.$current_course_code.'</h3>';
$message .= get_lang('HasDel');
$message .= '<br /><br /><a href="../../index.php">'.get_lang('BackHome').'</a>';
} else {
/* message if code course is incorrect */
$message = '<h3>'.get_lang('CourseTitle').' : '.$current_course_name.'</h3>';
$message .= '<h3>'.get_lang('CourseCode').' : '.$current_course_code.'</h3>';
$message .= '<p>'.get_lang('CourseRegistrationCodeIncorrect').'</p>';
$message .= '<p><a class="btn btn-primary" href="'
.api_get_path(WEB_CODE_PATH)
.'course_info/delete_course.php?'
.api_get_cidreq()
.'">'.get_lang('BackToPreviousPage').'</a>';
$message .= '<br /><br /><a href="../../index.php">'.get_lang('BackHome').'</a>';
$type_info_message = 'error';
}
} else {
$message = '<h3>'.get_lang('Course').' : '.$current_course_name.' ('.$current_course_code.') </h3>';
$message .= '<p>'.get_lang('Deleting this area will permanently delete all the content (documents, links...) it contains and unregister all its members (not remove them from other courses). <p>Do you really want to delete the course?').'</p>';
$message .= '<p><a class="btn btn-primary"
href="'.api_get_path(WEB_CODE_PATH).'course_info/maintenance.php?'.api_get_cidreq().'">'.
get_lang('No').'</a>&nbsp;<a class="btn btn-danger" href="'.api_get_self().'?delete=yes&'.api_get_cidreq().'">'.
get_lang('Yes').'</a></p>';
$message = '<h3>'.get_lang('CourseTitle').' : '.$current_course_name.'</h3>';
$message .= '<h3>'.get_lang('CourseCode').' : '.$current_course_code.'</h3>';
$message .= '<p>'.get_lang('ByDel').'</p>';
$message .= '<p><span class="form_required">*</span>'
.get_lang('CourseCodeConfirmation')
.'&nbsp;<input type="text" name="course_code" id="course_code"></p>';
$message .= '<p>';
$message .= '<button class="btn btn-danger delete-course">'.get_lang('ValidateChanges').'</button>';
$message .= '&nbsp;';
$message .= '<a class="btn btn-primary"href="'
.api_get_path(WEB_CODE_PATH)
.'course_info/maintenance.php?'
.api_get_cidreq().'">'
.get_lang('No')
.'</a>';
$message .= '</p>';
$interbreadcrumb[] = [
'url' => 'maintenance.php',
'name' => get_lang('Backup'),
'name' => get_lang('Maintenance'),
];
$tpl = new Template($tool_name);
$tpl->assign('content', Display::return_message($message, 'warning', false));
$tpl->display_one_col_template();
}
$htmlHeadXtra[] = '<script>
$(function(){
/* Asking by course code to confirm recycling*/
$(".delete-course").on("click",function(){
window.location ="'.api_get_self().'?delete=yes&'.api_get_cidreq().'&course_code=" + $("#course_code").val();
})
})
</script>';
$tpl = new Template($tool_name);
$tpl->assign('content', Display::return_message($message, $type_info_message, false));
$tpl->display_one_col_template();

@ -79,10 +79,16 @@ $url = api_get_course_url($course_code, $session_id);
if ($form->validate()) {
$accept_legal = $form->exportValue('accept_legal');
if (1 == $accept_legal) {
CourseManager::save_user_legal($user_id, $course_code, $session_id);
if (api_check_user_access_to_legal($course_info['visibility'])) {
if (empty($session_id) &&
COURSE_VISIBILITY_REGISTERED == $course_info['visibility'] &&
1 == $course_info['subscribe']
) {
CourseManager::subscribeUser($user_id, $course_info['code'], STUDENT, 0);
}
CourseManager::save_user_legal($user_id, $course_info, $session_id);
if (api_check_user_access_to_legal($course_info)) {
Session::write($variable, true);
}
@ -94,13 +100,13 @@ if ($form->validate()) {
}
$user_pass_open_course = false;
if (api_check_user_access_to_legal($course_info['visibility']) && Session::read($variable)) {
if (api_check_user_access_to_legal($course_info) && Session::read($variable)) {
$user_pass_open_course = true;
}
if (empty($session_id)) {
if (CourseManager::is_user_subscribed_in_course($user_id, $course_code) ||
api_check_user_access_to_legal($course_info['visibility'])
api_check_user_access_to_legal($course_info)
) {
$user_accepted_legal = CourseManager::is_user_accepted_legal(
$user_id,
@ -122,7 +128,7 @@ if (empty($session_id)) {
$userStatus = SessionManager::get_user_status_in_course_session($user_id, $course_info['real_id'], $session_id);
if (isset($userStatus) || api_check_user_access_to_legal($course_info['visibility'])) {
if (isset($userStatus) || api_check_user_access_to_legal($course_info)) {
$user_accepted_legal = CourseManager::is_user_accepted_legal(
$user_id,
$course_code,

@ -76,28 +76,33 @@ if (Security::check_token('post') && (
$hiddenFields['sec_token'] = Security::get_token();
CourseSelectForm::display_form($course, $hiddenFields, true);
} else {
$table_c = Database::get_main_table(TABLE_MAIN_COURSE);
$table_cu = Database::get_main_table(TABLE_MAIN_COURSE_USER);
$user_info = api_get_user_info();
$course_info = api_get_course_info();
$courseList = CourseManager::get_courses_list_by_user_id(
$user_info['user_id'],
$courseList = CourseManager::getCoursesFollowedByUser(
api_get_user_id(),
COURSEMANAGER,
null,
null,
null,
null,
false,
null,
null,
false,
false,
[$course_info['real_id']]
'ORDER BY c.title'
);
if (empty($courseList)) {
echo Display::return_message(get_lang('No destination course available'), 'normal');
} else {
$options = [];
$courses = [];
foreach ($courseList as $courseItem) {
$courseInfo = api_get_course_info_by_id($courseItem['real_id']);
$options[$courseInfo['code']] = $courseInfo['title'].' ('.$courseInfo['code'].')';
if ($courseItem['real_id'] == $course_info['real_id']) {
continue;
}
$courses[$courseItem['code']] = $courseItem['title'].' ('.$courseItem['code'].')';
}
if (empty($courses)) {
echo Display::return_message(get_lang('NoDestinationCoursesAvailable'), 'normal');
} else {
$form = new FormValidator(
'copy_course',
'post',

@ -16,7 +16,7 @@ $cidReset = true;
require_once __DIR__.'/../inc/global.inc.php';
$current_course_tool = TOOL_COURSE_MAINTENANCE;
api_protect_global_admin_script();
api_protect_admin_script();
api_protect_limit_for_session_admin();
api_set_more_memory_and_time_limits();

@ -129,11 +129,19 @@ if ($countCategories >= 100) {
$accessUrlId,
api_get_configuration_value('allow_base_course_category')
);
$categoriesOptions = [null => get_lang('none')];
$categoriesOptions = [null => get_lang('None')];
$categoryToAvoid = '';
if (!api_is_platform_admin()) {
$categoryToAvoid = api_get_configuration_value('course_category_code_to_use_as_model');
}
/** @var CourseCategory $category */
foreach ($categories as $category) {
$categoriesOptions[$category->getCode()] = $category->__toString();
$categoryCode = $category->getCode();
if (!empty($categoryToAvoid) && $categoryToAvoid == $categoryCode) {
continue;
}
$categoriesOptions[$categoryCode] = $category->__toString();
}
$form->addSelect(
@ -434,6 +442,13 @@ if ($form->validate()) {
} else {
if (!$course_validation_feature) {
$message = Display::return_message(get_lang('Once you click on "Create a course", a course is created with a section for Tests, Project based learning, Assessments, Courses, Dropbox, Agenda and much more. Logging in as teacher provides you with editing privileges for this course.'));
// If the donation feature is enabled, show a message with a donate button
if (api_get_configuration_value('course_creation_donate_message_show') == true) {
$button = api_get_configuration_value('course_creation_donate_link');
if (!empty($button)) {
$message .= Display::return_message(get_lang('DonateToTheProject').'<br /><br /><div style="display:block; margin-left:42%;">'.$button.'</div>', 'warning', false);
}
}
}
// Display the form.
$formContent = $form->returnForm();

@ -128,8 +128,8 @@ class ReadingComprehension extends UniqueAnswer
public function createForm(&$form, $exercise)
{
// Categories
$categories = TestCategory::getCategoriesForSelect();
$form->addSelect('questionCategory', get_lang('Category'), $categories);
$tabCat = TestCategory::getCategoriesIdAndName();
$form->addSelect('questionCategory', get_lang('Category'), $tabCat);
// Advanced parameters
$levels = self::get_default_levels();
$form->addSelect('questionLevel', get_lang('Difficulty'), $levels);

@ -30,6 +30,190 @@ class TestCategory
$this->description = '';
}
/**
* return the TestCategory object with id=in_id.
*
* @param int $id
* @param int $courseId
*
* @return TestCategory
*/
public function getCategory($id, $courseId = 0)
{
$table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
$id = (int) $id;
$courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
$sql = "SELECT * FROM $table
WHERE id = $id AND c_id = ".$courseId;
$res = Database::query($sql);
if (Database::num_rows($res)) {
$row = Database::fetch_array($res);
$this->id = $row['id'];
$this->name = $row['title'];
$this->description = $row['description'];
return $this;
}
return false;
}
/**
* Save TestCategory in the database if name doesn't exists.
*
* @param int $courseId
*
* @return bool
*/
public function save($courseId = 0)
{
$courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
$courseInfo = api_get_course_info_by_id($courseId);
if (empty($courseInfo)) {
return false;
}
$table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
// check if name already exists
$sql = "SELECT count(*) AS nb FROM $table
WHERE title = '".Database::escape_string($this->name)."' AND c_id = $courseId";
$result = Database::query($sql);
$row = Database::fetch_array($result);
// lets add in BDD if not the same name
if ($row['nb'] <= 0) {
$repo = Container::getQuestionCategoryRepository();
$course = $courseInfo['entity'];
$category = new CQuizQuestionCategory();
$category
->setTitle($this->name)
->setCourse($course)
->setDescription($this->description)
->setParent($course)
->addCourseLink($course, api_get_session_entity());
$em = $repo->getEntityManager();
$em->persist($category);
$em->flush();
if ($category) {
return $category->getIid();
}
}
return false;
}
/**
* Removes the category from the database
* if there were question in this category, the link between question and category is removed.
*
* @param int $id
*
* @return bool
*/
public function removeCategory($id)
{
$tbl_question_rel_cat = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
$id = (int) $id;
$course_id = api_get_course_int_id();
$category = $this->getCategory($id, $course_id);
if ($category) {
// remove link between question and category
$sql = "DELETE FROM $tbl_question_rel_cat
WHERE category_id = $id AND c_id=".$course_id;
Database::query($sql);
$repo = Container::getQuestionCategoryRepository();
$category = $repo->find($id);
$repo->hardDelete($category);
return true;
}
return false;
}
/**
* Modify category name or description of category with id=in_id.
*
* @param int $courseId
*
* @return bool
*/
public function modifyCategory($courseId = 0)
{
$courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
$courseInfo = api_get_course_info_by_id($courseId);
if (empty($courseInfo)) {
return false;
}
$repo = Container::getQuestionCategoryRepository();
/** @var CQuizQuestionCategory $category */
$category = $repo->find($this->id);
if ($category) {
$category
->setTitle($this->name)
->setDescription($this->description);
$repo->getEntityManager()->persist($category);
$repo->getEntityManager()->flush();
return true;
}
return false;
}
/**
* Gets the number of question of category id=in_id.
*/
public function getCategoryQuestionsNumber()
{
$table = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
$id = (int) $this->id;
$sql = "SELECT count(*) AS nb
FROM $table
WHERE category_id = $id AND c_id=".api_get_course_int_id();
$res = Database::query($sql);
$row = Database::fetch_array($res);
return $row['nb'];
}
/**
* return the number of question of a category id in a test.
*
* @param int $exerciseId
* @param int $categoryId
*
* @return int
*
* @author hubert.borderiou 07-04-2011
*/
public static function getNumberOfQuestionsInCategoryForTest($exerciseId, $categoryId)
{
$nbCatResult = 0;
$quiz = new Exercise();
$quiz->read($exerciseId);
$questionList = $quiz->selectQuestionList();
// the array given by selectQuestionList start at indice 1 and not at indice 0 !!! ? ? ?
for ($i = 1; $i <= count($questionList); $i++) {
if (self::getCategoryForQuestion($questionList[$i]) == $categoryId) {
$nbCatResult++;
}
}
return $nbCatResult;
}
/**
* return the number of question for a test using random by category
* input : test_id, number of random question (min 1).
@ -93,31 +277,6 @@ class TestCategory
return $categories;
}
/**
* return the number of question of a category id in a test.
*
* @param int $exerciseId
* @param int $categoryId
*
* @return int
*
* @author hubert.borderiou 07-04-2011
*/
public static function getNumberOfQuestionsInCategoryForTest($exerciseId, $categoryId)
{
$nbCatResult = 0;
$quiz = new Exercise();
$quiz->read($exerciseId);
$questionList = $quiz->selectQuestionList();
// the array given by selectQuestionList start at indice 1 and not at indice 0 !!! ? ? ?
for ($i = 1; $i <= count($questionList); $i++) {
if (self::getCategoryForQuestion($questionList[$i]) == $categoryId) {
$nbCatResult++;
}
}
return $nbCatResult;
}
/**
* Return the TestCategory id for question with question_id = $questionId
@ -130,6 +289,17 @@ class TestCategory
* @return int
*/
public static function getCategoryForQuestion($questionId, $courseId = 0)
{
$categoryInfo = self::getCategoryInfoForQuestion($questionId, $courseId);
if (!empty($categoryInfo) && isset($categoryInfo['category_id'])) {
return (int) $categoryInfo['category_id'];
}
return 0;
}
public static function getCategoryInfoForQuestion($questionId, $courseId = 0)
{
$courseId = (int) $courseId;
$questionId = (int) $questionId;
@ -143,27 +313,43 @@ class TestCategory
}
$table = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
$sql = "SELECT category_id
$sql = "SELECT *
FROM $table
WHERE question_id = $questionId ";
WHERE question_id = $questionId AND c_id = $courseId";
$res = Database::query($sql);
$result = 0;
if (Database::num_rows($res) > 0) {
$data = Database::fetch_array($res);
$result = (int) $data['category_id'];
return Database::fetch_array($res, 'ASSOC');
}
return $result;
return [];
}
public static function getCategoriesForSelect()
/**
* Return the category name for question with question_id = $questionId
* In this version, a question has only 1 category.
*
* @param $questionId
* @param int $courseId
*
* @return string
*/
public static function getCategoryNameForQuestion($questionId, $courseId = 0)
{
$courseId = api_get_course_int_id();
$categories = self::getCategories($courseId);
$result = ['0' => get_lang('No category selected')];
foreach ($categories as $category) {
$result[$category->getIid()] = $category->getTitle();
if (empty($courseId)) {
$courseId = api_get_course_int_id();
}
$courseId = (int) $courseId;
$categoryId = self::getCategoryForQuestion($questionId, $courseId);
$table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
$sql = "SELECT title
FROM $table
WHERE id = $categoryId AND c_id = $courseId";
$res = Database::query($sql);
$data = Database::fetch_array($res);
$result = '';
if (Database::num_rows($res) > 0) {
$result = $data['title'];
}
return $result;
@ -257,36 +443,6 @@ class TestCategory
return $categories;
}
/**
* return the TestCategory object with id=in_id.
*
* @param int $id
* @param int $courseId
*
* @return TestCategory
*/
public function getCategory($id, $courseId = 0)
{
$table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
$id = (int) $id;
$courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
$sql = "SELECT * FROM $table
WHERE id = $id AND c_id = ".$courseId;
$res = Database::query($sql);
if (Database::num_rows($res)) {
$row = Database::fetch_array($res);
$this->id = $row['id'];
$this->name = $row['title'];
$this->description = $row['description'];
return $this;
}
return false;
}
/**
* Returns an array of question ids for each category
* $categories[1][30] = 10, array with category id = 1 and question_id = 10
@ -532,16 +688,19 @@ class TestCategory
*
* @return string
*/
public static function get_stats_table_by_attempt(
$exerciseId,
$category_list = []
) {
public static function get_stats_table_by_attempt($exercise, $category_list = [])
{
$exerciseId = $exercise->iId;
if (empty($category_list)) {
return null;
}
$category_name_list = self::getListOfCategoriesNameForTest($exerciseId);
$table = new HTML_Table(['class' => 'table table-bordered', 'id' => 'category_results']);
$categoryNameList = self::getListOfCategoriesNameForTest($exerciseId);
$table = new HTML_Table(
[
'class' => 'table table-hover table-striped table-bordered',
'id' => 'category_results',
]
);
$table->setHeaderContents(0, 0, get_lang('Categories'));
$table->setHeaderContents(0, 1, get_lang('Absolute score'));
$table->setHeaderContents(0, 2, get_lang('Relative score'));
@ -558,9 +717,12 @@ class TestCategory
$total = $category_list['total'];
unset($category_list['total']);
}
if (count($category_list) > 1) {
$radar = '';
$countCategories = count($category_list);
if ($countCategories > 1) {
$resultsArray = [];
foreach ($category_list as $category_id => $category_item) {
$table->setCellContents($row, 0, $category_name_list[$category_id]);
$table->setCellContents($row, 0, $categoryNameList[$category_id]);
$table->setCellContents(
$row,
1,
@ -581,11 +743,15 @@ class TestCategory
true
)
);
$resultsArray[] = round($category_item['score'] / $category_item['total'] * 10);
$row++;
}
if ($countCategories > 2 && RESULT_DISABLE_RADAR === (int) $exercise->results_disabled) {
$radar = $exercise->getRadar(array_column($categoryNameList, 'title'), [$resultsArray]);
}
if (!empty($none_category)) {
$table->setCellContents($row, 0, get_lang('none'));
$table->setCellContents($row, 0, get_lang('None'));
$table->setCellContents(
$row,
1,
@ -632,7 +798,7 @@ class TestCategory
);
}
return $table->toHtml();
return $radar.$table->toHtml();
}
return '';
@ -744,81 +910,9 @@ class TestCategory
return false;
}
/**
* Save TestCategory in the database if name doesn't exists.
*
* @param int $courseId
*
* @return bool
*/
public function save($courseId = 0)
{
$courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
$courseInfo = api_get_course_info_by_id($courseId);
if (empty($courseInfo)) {
return false;
}
$table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
// check if name already exists
$sql = "SELECT count(*) AS nb FROM $table
WHERE title = '".Database::escape_string($this->name)."' AND c_id = $courseId";
$result = Database::query($sql);
$row = Database::fetch_array($result);
// lets add in BDD if not the same name
if ($row['nb'] <= 0) {
$repo = Container::getQuestionCategoryRepository();
$course = $courseInfo['entity'];
$category = new CQuizQuestionCategory();
$category
->setTitle($this->name)
->setCourse($course)
->setDescription($this->description)
->setParent($course)
->addCourseLink($course, api_get_session_entity());
$em = $repo->getEntityManager();
$em->persist($category);
$em->flush();
if ($category) {
return $category->getIid();
}
}
return false;
}
/**
* Removes the category from the database
* if there were question in this category, the link between question and category is removed.
*
* @param int $id
*
* @return bool
*/
public function removeCategory($id)
{
$tbl_question_rel_cat = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
$id = (int) $id;
$course_id = api_get_course_int_id();
$category = $this->getCategory($id, $course_id);
if ($category) {
// remove link between question and category
$sql = "DELETE FROM $tbl_question_rel_cat
WHERE category_id = $id AND c_id=".$course_id;
Database::query($sql);
$repo = Container::getQuestionCategoryRepository();
$category = $repo->find($id);
$repo->hardDelete($category);
return true;
}
return false;
}
/**
* @param $primaryKeys
@ -844,37 +938,6 @@ class TestCategory
exit;
}
/**
* Modify category name or description of category with id=in_id.
*
* @param int $courseId
*
* @return bool
*/
public function modifyCategory($courseId = 0)
{
$courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
$courseInfo = api_get_course_info_by_id($courseId);
if (empty($courseInfo)) {
return false;
}
$repo = Container::getQuestionCategoryRepository();
/** @var CQuizQuestionCategory $category */
$category = $repo->find($this->id);
if ($category) {
$category
->setTitle($this->name)
->setDescription($this->description);
$repo->getEntityManager()->persist($category);
$repo->getEntityManager()->flush();
return true;
}
return false;
}
/**
* @param Exercise $exercise
@ -1018,10 +1081,15 @@ class TestCategory
public function returnCategoryForm(Exercise $exercise)
{
$categories = $this->getListOfCategoriesForTest($exercise);
$sortedCategories = [];
foreach ($categories as $catId => $cat) {
$sortedCategories[$cat['title']] = $cat;
}
ksort($sortedCategories);
$saved_categories = $exercise->getCategoriesInExercise();
$return = null;
if (!empty($categories)) {
if (!empty($sortedCategories)) {
$nbQuestionsTotal = $exercise->getNumberQuestionExerciseCategory();
$exercise->setCategoriesGrouping(true);
$real_question_count = count($exercise->getQuestionList());
@ -1035,7 +1103,7 @@ class TestCategory
}
$return .= $warning;
$return .= '<table class="data_table">';
$return .= '<table class="table table-hover table-bordered data_table">';
$return .= '<tr>';
$return .= '<th height="24">'.get_lang('Categories').'</th>';
$return .= '<th width="70" height="24">'.get_lang('N°').'</th></tr>';
@ -1048,9 +1116,9 @@ class TestCategory
'title' => get_lang('General'),
];
$categories[] = $emptyCategory;
$sortedCategories[] = $emptyCategory;
foreach ($categories as $category) {
foreach ($sortedCategories as $category) {
$cat_id = $category['iid'];
$return .= '<tr>';
$return .= '<td>';
@ -1166,21 +1234,6 @@ class TestCategory
return $html;
}
/**
* Gets the number of question of category id=in_id.
*/
public function getCategoryQuestionsNumber()
{
$table = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
$id = (int) $this->id;
$sql = "SELECT count(*) AS nb
FROM $table
WHERE category_id = $id AND c_id=".api_get_course_int_id();
$res = Database::query($sql);
$row = Database::fetch_array($res);
return $row['nb'];
}
/**
* To allowed " in javascript dialog box without bad surprises

@ -87,7 +87,7 @@ class UniqueAnswerImage extends UniqueAnswer
$html .= $includeFile;
$html .= '<script type="text/javascript" charset="utf-8">
$(document).ready(function () {
$(function() {
$(".add_img_link").on("click", function(e){
e.preventDefault();
e.stopPropagation();
@ -434,7 +434,9 @@ class UniqueAnswerImage extends UniqueAnswer
}
$header .= '<th>'.get_lang('Answer').'</th>';
$header .= '<th>'.get_lang('Status').'</th>';
$header .= '<th>'.get_lang('Comment').'</th>';
if (false === $exercise->hideComment) {
$header .= '<th>'.get_lang('Comment').'</th>';
}
$header .= '</tr>';
} else {
$header = parent::return_header($exercise, $counter, $score);

@ -130,7 +130,7 @@ if (!empty($_GET['action']) && 'exportqti2' == $_GET['action'] && !empty($_GET['
}
// Exercise object creation.
if (!is_object($objExercise)) {
if (!($objExercise instanceof Exercise)) {
// creation of a new exercise if wrong or not specified exercise ID
if ($exerciseId) {
$objExercise = new Exercise();
@ -140,12 +140,12 @@ if (!is_object($objExercise)) {
$showPagination = true;
}
$objExercise->read($exerciseId, $parseQuestionList);
Session::write('objExercise', $objExercise);
}
// saves the object into the session
Session::write('objExercise', $objExercise);
}
// Exercise can be edited in their course.
if (empty($objExercise)) {
Session::erase('objExercise');
header('Location: '.api_get_path(WEB_CODE_PATH).'exercise/exercise.php?'.api_get_cidreq());
exit;
}
@ -444,7 +444,6 @@ if (EXERCISE_FEEDBACK_TYPE_EXAM == $objExercise->getFeedbackType()) {
echo Display::return_message(get_lang('This test is configured not to display feedback to learners. Comments will not be seen at the end of the test, but may be useful for you, as teacher, when reviewing the question details.'), 'normal');
}
Session::write('objExercise', $objExercise);
Session::write('objQuestion', $objQuestion);
Session::write('objAnswer', $objAnswer);
Display::display_footer();

@ -46,8 +46,6 @@ class Answer
private $exercise;
/**
* constructor of the class.
*
* @author Olivier Brouckaert
*
* @param int $questionId that answers belong to

@ -147,6 +147,6 @@ switch ($action) {
break;
}
Display::display_header('', get_lang('Test'));
Display::display_header();
echo $content;
Display::display_footer();

@ -97,9 +97,9 @@ function get_and_unzip_uploaded_exercise($baseWorkDir, $uploadPath)
)
) {
return true;
} else {
return false;
}
return false;
}
/**
@ -214,7 +214,9 @@ function aiken_import_exercise($file)
$answer->new_position[$key] = $key;
$answer->new_comment[$key] = '';
// Correct answers ...
if (in_array($key, $question_array['correct_answers'])) {
if (isset($question_array['correct_answers']) &&
in_array($key, $question_array['correct_answers'])
) {
$answer->new_correct[$key] = 1;
if (isset($question_array['feedback'])) {
$answer->new_comment[$key] = $question_array['feedback'];
@ -280,7 +282,7 @@ function aiken_import_exercise($file)
* Parses an Aiken file and builds an array of exercise + questions to be
* imported by the import_exercise() function.
*
* @param array $exercise_info The reference to the array in which to store the questions
* @param array The reference to the array in which to store the questions
* @param string Path to the directory with the file to be parsed (without final /)
* @param string Name of the last directory part for the file (without /)
* @param string Name of the file to be parsed (including extension)
@ -299,17 +301,23 @@ function aiken_parse_file(&$exercise_info, $exercisePath, $file, $questionFile)
if (!is_file($questionFilePath)) {
return 'FileNotFound';
}
$data = file($questionFilePath);
$text = file_get_contents($questionFilePath);
$detect = mb_detect_encoding($text, 'ASCII', true);
if ('ASCII' === $detect) {
$data = explode("\n", $text);
} else {
$text = str_ireplace(["\x0D", "\r\n"], "\n", $text); // Removes ^M char from win files.
$data = explode("\n\n", $text);
}
$question_index = 0;
$answers_array = [];
$new_question = true;
foreach ($data as $line => $info) {
if ($question_index > 0 && true == $new_question && preg_match('/^(\r)?\n/', $info)) {
$info = trim($info);
if (empty($info)) {
// double empty line
continue;
}
$new_question = false;
//make sure it is transformed from iso-8859-1 to utf-8 if in that form
if (!mb_check_encoding($info, 'utf-8') && mb_check_encoding($info, 'iso-8859-1')) {
$info = utf8_encode($info);
@ -325,14 +333,36 @@ function aiken_parse_file(&$exercise_info, $exercisePath, $file, $questionFile)
$exercise_info['question'][$question_index]['correct_answers'][] = $correct_answer_index + 1;
//weight for correct answer
$exercise_info['question'][$question_index]['weighting'][$correct_answer_index] = 1;
$next = $line + 1;
if (false !== strpos($data[$next], 'ANSWER_EXPLANATION:')) {
continue;
}
// Check if next has score, otherwise loop too next question.
if (false === strpos($data[$next], 'SCORE:')) {
$answers_array = [];
$question_index++;
continue;
}
} elseif (preg_match('/^SCORE:\s?(.*)/', $info, $matches)) {
$exercise_info['question'][$question_index]['score'] = (float) $matches[1];
$answers_array = [];
$question_index++;
continue;
} elseif (preg_match('/^DESCRIPTION:\s?(.*)/', $info, $matches)) {
$exercise_info['question'][$question_index]['description'] = $matches[1];
} elseif (preg_match('/^ANSWER_EXPLANATION:\s?(.*)/', $info, $matches)) {
//Comment of correct answer
$correct_answer_index = array_search($matches[1], $answers_array);
$exercise_info['question'][$question_index]['feedback'] = $matches[1];
$next = $line + 1;
// Check if next has score, otherwise loop too next question.
if (false === strpos($data[$next], 'SCORE:')) {
$answers_array = [];
$question_index++;
continue;
}
} elseif (preg_match('/^TEXTO_CORRECTA:\s?(.*)/', $info, $matches)) {
//Comment of correct answer (Spanish e-ducativa format)
$correct_answer_index = array_search($matches[1], $answers_array);
@ -347,7 +377,10 @@ function aiken_parse_file(&$exercise_info, $exercisePath, $file, $questionFile)
} elseif (preg_match('/^ETIQUETAS:\s?([A-Z])\s?/', $info, $matches)) {
//TAGS for chamilo >= 1.10 (Spanish e-ducativa format)
$exercise_info['question'][$question_index]['answer_tags'] = explode(',', $matches[1]);
} elseif (preg_match('/^(\r)?\n/', $info)) {
} elseif (empty($info)) {
/*if (empty($exercise_info['question'][$question_index]['title'])) {
$exercise_info['question'][$question_index]['title'] = $info;
}
//moving to next question (tolerate \r\n or just \n)
if (empty($exercise_info['question'][$question_index]['correct_answers'])) {
error_log('Aiken: Error in question index '.$question_index.': no correct answer defined');
@ -362,16 +395,22 @@ function aiken_parse_file(&$exercise_info, $exercisePath, $file, $questionFile)
$question_index++;
//emptying answers array when moving to next question
$answers_array = [];
$new_question = true;
} else {
if (empty($exercise_info['question'][$question_index]['title'])) {
$exercise_info['question'][$question_index]['title'] = $info;
}
/*$question_index++;
//emptying answers array when moving to next question
$answers_array = [];
$new_question = true;*/
}
}
$total_questions = count($exercise_info['question']);
$total_weight = !empty($_POST['total_weight']) ? (int) ($_POST['total_weight']) : 20;
foreach ($exercise_info['question'] as $key => $question) {
if (!isset($exercise_info['question'][$key]['weighting'])) {
continue;
}
$exercise_info['question'][$key]['weighting'][current(array_keys($exercise_info['question'][$key]['weighting']))] = $total_weight / $total_questions;
}

@ -91,7 +91,7 @@ function import_exercise($file)
return 'UplZipCorrupt';
}
$baseWorkDir .= $uploadPath;
$baseWorkDir = $baseWorkDir.$uploadPath;
// find the different manifests for each question and parse them.
$exerciseHandle = opendir($baseWorkDir);

Loading…
Cancel
Save