Browse Source

Minor - Partial merge from 1.11.x

Julio 6 years ago
parent
commit
196bfc4702
56 changed files with 3399 additions and 870 deletions
  1. 61 0
      main/calendar/planification.php
  2. 68 0
      main/social/download.php
  3. 2 2
      main/social/friends.php
  4. 4 2
      main/social/group_topics.php
  5. 15 5
      main/social/group_view.php
  6. 56 18
      main/social/home.php
  7. 148 0
      main/social/map.php
  8. 1 1
      main/social/myfiles.php
  9. 20 349
      main/social/profile.php
  10. 8 17
      main/social/profile_friends_and_groups.inc.php
  11. 47 42
      main/social/search.php
  12. 1 1
      main/survey/copy_survey.php
  13. 22 18
      main/survey/create_meeting.php
  14. 25 18
      main/survey/create_new_survey.php
  15. 255 0
      main/survey/edit_meeting.php
  16. 15 13
      main/survey/fillsurvey.php
  17. 1 1
      main/survey/link.php
  18. 28 46
      main/survey/meeting.php
  19. 7 2
      main/survey/pending.php
  20. 8 4
      main/survey/preview.php
  21. 16 16
      main/survey/question.php
  22. 3 10
      main/survey/reporting.php
  23. 77 47
      main/survey/survey.lib.php
  24. 7 5
      main/survey/survey.php
  25. 177 68
      main/survey/surveyUtil.class.php
  26. 118 42
      main/survey/survey_invitation.php
  27. 23 13
      main/survey/survey_invite.php
  28. 5 5
      main/survey/survey_list.php
  29. 4 0
      main/survey/survey_question.php
  30. 2 0
      main/ticket/course_user_list.php
  31. 1 1
      main/ticket/new_ticket.php
  32. 1 0
      main/ticket/report.php
  33. 1 1
      main/ticket/ticket_details.php
  34. 1 1
      main/ticket/tickets.php
  35. 1 1
      main/ticket/tutor.php
  36. 1 1
      main/timeline/view.php
  37. 91 94
      main/tracking/courseLog.php
  38. 1 1
      main/tracking/course_log_resources.php
  39. 1 1
      main/tracking/course_log_tools.php
  40. 0 1
      main/tracking/total_time.php
  41. 1 1
      main/user/class.php
  42. 1 1
      main/user/user_import.php
  43. 4 0
      main/webservices/api/v2.php
  44. 136 0
      main/webservices/cm_soap_course.php
  45. 400 0
      main/webservices/cm_webservice_course.php
  46. 0 1
      main/webservices/cm_webservice_forum.php
  47. 1 0
      main/webservices/soap.php
  48. 142 0
      main/webservices/soap_course.php
  49. 307 0
      main/webservices/soap_user.php
  50. 419 0
      main/webservices/webservice_course.php
  51. 649 0
      main/webservices/webservice_user.php
  52. 1 1
      main/wiki/index.php
  53. 0 1
      main/work/add_document.php
  54. 1 1
      main/work/upload.php
  55. 1 1
      main/work/view.php
  56. 13 16
      main/work/work.lib.php

+ 61 - 0
main/calendar/planification.php

@@ -0,0 +1,61 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+use Chamilo\CoreBundle\Component\Utils\ChamiloApi;
+
+$cidReset = true;
+
+require_once __DIR__.'/../inc/global.inc.php';
+
+api_block_anonymous_users();
+
+$current_course_tool = TOOL_CALENDAR_EVENT;
+$this_section = SECTION_MYAGENDA;
+
+$timezone = new DateTimeZone(api_get_timezone());
+$now = new DateTime('now', $timezone);
+$currentYear = (int) $now->format('Y');
+
+$searchYear = isset($_GET['year']) ? (int) $_GET['year'] : $currentYear;
+
+$userInfo = api_get_user_info();
+$userId = $userInfo['id'];
+
+$sessions = [];
+
+if (api_is_drh()) {
+    $count = SessionManager::get_sessions_followed_by_drh($userId, null, null, true);
+} else {
+    $count = UserManager::get_sessions_by_category($userId, false, true, true, true);
+}
+
+$sessionsList = UserManager::getSubscribedSessionsByYear($userInfo, $searchYear);
+
+if ($count > 50) {
+    $message = Display::return_message('TooMuchSessionsInPlanification', 'warning');
+
+    api_not_allowed(true, $message);
+}
+
+$sessions = UserManager::getSessionsCalendarByYear($sessionsList, $searchYear);
+
+$colors = ChamiloApi::getColorPalette(false, true, count($sessions));
+
+$agenda = new Agenda('personal');
+$actions = $agenda->displayActions('list', $userId);
+
+$toolName = get_lang('SessionsPlanCalendar');
+
+$interbreadcrumb[] = [
+    'url' => api_get_path(WEB_CODE_PATH).'calendar/agenda_js.php?type=personal',
+    'name' => get_lang('Agenda'),
+];
+
+$template = new Template($toolName);
+$template->assign('toolbar', $actions);
+$template->assign('student_id', $userId);
+$template->assign('search_year', $searchYear);
+$template->assign('colors', $colors);
+$template->assign('sessions', $sessions);
+$layout = $template->get_template('agenda/planification.tpl');
+$template->display($layout);

+ 68 - 0
main/social/download.php

@@ -0,0 +1,68 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+/**
+ * This file is responsible for  passing requested file attachments from messages
+ * Html files are parsed to fix a few problems with URLs,
+ * but this code will hopefully be replaced soon by an Apache URL
+ * rewrite mechanism.
+ *
+ * @package chamilo.messages
+ */
+session_cache_limiter('public');
+
+require_once __DIR__.'/../inc/global.inc.php';
+
+api_block_anonymous_users();
+
+// IMPORTANT to avoid caching of documents
+header('Expires: Wed, 01 Jan 1990 00:00:00 GMT');
+header('Cache-Control: public');
+header('Pragma: no-cache');
+
+$messageId = isset($_GET['message_id']) ? $_GET['message_id'] : 0;
+$attachmentId = isset($_GET['attachment_id']) ? $_GET['attachment_id'] : 0;
+
+$messageInfo = MessageManager::get_message_by_id($messageId);
+$attachmentInfo = MessageManager::getAttachment($attachmentId);
+
+if (empty($messageInfo) || empty($attachmentInfo)) {
+    api_not_allowed();
+}
+
+// Attachment belongs to the message?
+if ($messageInfo['id'] != $attachmentInfo['message_id']) {
+    api_not_allowed();
+}
+
+// Do not process group items
+if (!empty($messageInfo['group_id'])) {
+    api_not_allowed();
+}
+
+// Only process wall messages
+if (!in_array($messageInfo['msg_status'], [MESSAGE_STATUS_WALL, MESSAGE_STATUS_WALL_POST])) {
+    api_not_allowed();
+}
+
+$dir = UserManager::getUserPathById($messageInfo['user_sender_id'], 'system');
+if (empty($dir)) {
+    api_not_allowed();
+}
+
+$file = $dir.'message_attachments/'.$attachmentInfo['path'];
+$title = api_replace_dangerous_char($attachmentInfo['filename']);
+
+if (Security::check_abs_path($file, $dir.'message_attachments/')) {
+    // launch event
+    Event::event_download($file);
+    $result = DocumentManager::file_send_for_download(
+        $file,
+        false,
+        $title
+    );
+    if ($result === false) {
+        api_not_allowed(true);
+    }
+}
+exit;

+ 2 - 2
main/social/friends.php

@@ -76,9 +76,9 @@ $name_search = isset($_POST['search_name_q']) ? $_POST['search_name_q'] : null;
 $number_friends = 0;
 
 if (isset($name_search) && $name_search != 'undefined') {
-    $friends = SocialManager::get_friends($user_id, null, $name_search);
+    $friends = SocialManager::get_friends($user_id, USER_RELATION_TYPE_FRIEND, $name_search);
 } else {
-    $friends = SocialManager::get_friends($user_id);
+    $friends = SocialManager::get_friends($user_id, USER_RELATION_TYPE_FRIEND);
 }
 
 $social_right_content = '<div class="col-md-12">';

+ 4 - 2
main/social/group_topics.php

@@ -52,7 +52,9 @@ if (isset($_REQUEST['action']) && $_REQUEST['action'] == 'delete') {
 }
 
 // My friends
-$friend_html = SocialManager::listMyFriendsBlock(api_get_user_id());
+$friend_html = SocialManager::listMyFriendsBlock(
+    $user_id
+);
 $content = null;
 $social_right_content = '';
 
@@ -147,7 +149,7 @@ function add_image_form() {
 	}
 }
 
-$(document).ready(function() {
+$(function() {
 	if ($("#msg_'.$message_id.'").length) {
 		$("html,body").animate({
 			scrollTop: $("#msg_'.$message_id.'").offset().top

+ 15 - 5
main/social/group_view.php

@@ -211,7 +211,7 @@ if ($is_group_member || $groupInfo['visibility'] == GROUP_PERMISSION_OPEN) {
                 get_lang('YouShouldCreateATopic'),
                 $createThreadUrl,
                 [
-                    'class' => 'btn btn-primary',
+                    'class' => 'ajax btn btn-primary',
                     'title' => get_lang('ComposeMessage'),
                     'data-title' => get_lang('ComposeMessage'),
                     'data-size' => 'lg',
@@ -231,7 +231,7 @@ if ($is_group_member || $groupInfo['visibility'] == GROUP_PERMISSION_OPEN) {
                 get_lang('NewTopic'),
                 $createThreadUrl,
                 [
-                    'class' => 'btn btn-primary',
+                    'class' => 'ajax btn btn-default',
                     'title' => get_lang('ComposeMessage'),
                     'data-title' => get_lang('ComposeMessage'),
                     'data-size' => 'lg',
@@ -242,6 +242,13 @@ if ($is_group_member || $groupInfo['visibility'] == GROUP_PERMISSION_OPEN) {
     $members = $usergroup->get_users_by_group($group_id, true);
     $member_content = '';
 
+    // My friends
+    $friend_html = SocialManager::listMyFriendsBlock(
+        $user_id,
+        '',
+        ''
+    );
+
     // Members
     if (count($members) > 0) {
         if ($role == GROUP_USER_PERMISSION_ADMIN) {
@@ -295,9 +302,8 @@ if ($is_group_member || $groupInfo['visibility'] == GROUP_PERMISSION_OPEN) {
     if (!empty($create_thread_link)) {
         $create_thread_link = Display::div($create_thread_link, ['class' => 'float-right']);
     }
-
-    $listTopic = $content;
-    $listMembers = $member_content;
+    $headers = [get_lang('Discussions'), get_lang('Members')];
+    $socialForum = Display::tabs($headers, [$content, $member_content], 'tabs');
 } else {
     // if I already sent an invitation message
     if (!in_array(
@@ -321,6 +327,10 @@ SocialManager::setSocialUserBlock($tpl, api_get_user_id(), 'groups', $group_id);
 $tpl->setHelp('Groups');
 $tpl->assign('create_link', $create_thread_link);
 $tpl->assign('is_group_member', $is_group_member);
+$tpl->assign('group_info', $group_info);
+$tpl->assign('social_friend_block', $friend_html);
+$tpl->assign('social_menu_block', $social_menu_block);
+$tpl->assign('social_forum', $socialForum);
 
 $tpl->assign('list_members', $listMembers);
 $tpl->assign('list_topic', $listTopic);

+ 56 - 18
main/social/home.php

@@ -77,19 +77,33 @@ $userGroup = new UserGroup();
     }
 }*/
 
-$socialSearch = UserManager::getSearchForm('');
+SocialManager::handlePosts(api_get_self());
 
-// Top Last
-$results['newest'] = $userGroup->get_groups_by_age(2, true);
-// Top popular
-$results['popular'] = $userGroup->get_groups_by_popularity(2, true);
+$threadList = SocialManager::getThreadList($user_id);
+$threadIdList = [];
+if (!empty($threadList)) {
+    $threadIdList = array_column($threadList, 'id');
+}
 
-// My friends
-$friend_html = SocialManager::listMyFriendsBlock(
-    $user_id,
-    '',
-    $show_full_profile
+// Social Post Wall
+$posts = SocialManager::getMyWallMessages($user_id, 0, 10, $threadIdList);
+$countPost = $posts['count'];
+$posts = $posts['posts'];
+SocialManager::getScrollJs($countPost, $htmlHeadXtra);
+
+// Block Menu
+$social_menu_block = SocialManager::show_social_menu('home');
+
+$social_search_block = Display::panel(
+    UserManager::get_search_form(''),
+    get_lang('SearchUsers')
 );
+
+$social_group_block = SocialManager::getGroupBlock($user_id);
+
+// My friends
+$friend_html = SocialManager::listMyFriendsBlock($user_id);
+
 // Block Social Sessions
 $social_session_block = null;
 $user_info = api_get_user_info($user_id);
@@ -99,16 +113,40 @@ if (count($sessionList) > 0) {
     $social_session_block = $sessionList;
 }
 
-$tpl = new Template(get_lang('SocialNetwork'));
+$wallSocialAddPost = SocialManager::getWallForm(api_get_self());
+$socialAutoExtendLink = SocialManager::getAutoExtendLink($user_id, $countPost);
 
-SocialManager::setSocialUserBlock($tpl, api_get_user_id(), 'home');
+$formSearch = new FormValidator(
+    'find_friends_form',
+    'get',
+    api_get_path(WEB_CODE_PATH).'social/search.php?search_type=1',
+    null,
+    null,
+    FormValidator::LAYOUT_BOX_NO_LABEL
+);
+$formSearch->addHidden('search_type', 1);
+$formSearch->addText(
+    'q',
+    get_lang('Search'),
+    false,
+    [
+        'aria-label' => get_lang('SearchUsers'),
+        'custom' => true,
+        'placeholder' => get_lang('SearchUsersByName'),
+    ]
+);
 
+$tpl = new Template(get_lang('SocialNetwork'));
+SocialManager::setSocialUserBlock($tpl, $user_id, 'home');
+$tpl->assign('social_wall_block', $wallSocialAddPost);
+$tpl->assign('social_post_wall_block', $posts);
+$tpl->assign('social_menu_block', $social_menu_block);
+$tpl->assign('social_auto_extend_link', $socialAutoExtendLink);
+$tpl->assign('search_friends_form', $formSearch->returnForm());
 $tpl->assign('social_friend_block', $friend_html);
-$tpl->assign('session_list', $social_session_block);
-$tpl->assign('social_search', $socialSearch);
+$tpl->assign('social_search_block', $social_search_block);
 $tpl->assign('social_skill_block', SocialManager::getSkillBlock($user_id));
-$tpl->assign('groups', $results);
+$tpl->assign('social_group_block', $social_group_block);
+$tpl->assign('session_list', $social_session_block);
 $social_layout = $tpl->get_template('social/home.tpl');
-$content = $tpl->fetch($social_layout);
-$tpl->assign('content', $content);
-$tpl->display_one_col_template();
+$tpl->display($social_layout);

+ 148 - 0
main/social/map.php

@@ -0,0 +1,148 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+/**
+ * @package chamilo.social
+ *
+ * @author Julio Montoya <gugli100@gmail.com>
+ */
+$cidReset = true;
+
+require_once __DIR__.'/../inc/global.inc.php';
+
+api_block_anonymous_users();
+
+$fields = api_get_configuration_value('allow_social_map_fields');
+
+if (!$fields) {
+    api_not_allowed(true);
+}
+
+$fields = isset($fields['fields']) ? $fields['fields'] : '';
+
+if (empty($fields)) {
+    api_not_allowed(true);
+}
+
+$extraField = new ExtraField('user');
+$infoStage = $extraField->get_handler_field_info_by_field_variable($fields['0']);
+$infoVille = $extraField->get_handler_field_info_by_field_variable($fields['1']);
+
+if (empty($infoStage) || empty($infoVille)) {
+    api_not_allowed(true);
+}
+
+$gMapsPlugin = GoogleMapsPlugin::create();
+$localization = $gMapsPlugin->get('enable_api') === 'true';
+
+if ($localization) {
+    $apiKey = $gMapsPlugin->get('api_key');
+    if (empty($apiKey)) {
+        api_not_allowed(true);
+    }
+} else {
+    api_not_allowed(true);
+}
+
+$tableUser = Database::get_main_table(TABLE_MAIN_USER);
+$sql = "SELECT u.id, firstname, lastname, ev.value ville, ev2.value stage
+        FROM $tableUser u 
+        INNER JOIN extra_field_values ev
+        ON ev.item_id = u.id
+        INNER JOIN extra_field_values ev2
+        ON ev2.item_id = u.id
+        WHERE 
+            ev.field_id = ".$infoStage['id']." AND 
+            ev2.field_id = ".$infoVille['id']." AND 
+            u.status = ".STUDENT." AND
+            u.active = 1 AND
+            (ev.value <> '' OR ev2.value <> '') AND 
+            (ev.value LIKE '%::%' OR ev2.value LIKE '%::%')            
+";
+
+$cacheDriver = new \Doctrine\Common\Cache\ApcuCache();
+$keyDate = 'map_cache_date';
+$keyData = 'map_cache_data';
+
+$now = time();
+
+// Refresh cache every day
+//$tomorrow = strtotime('+1 day', $now);
+$tomorrow = strtotime('+5 minute', $now);
+
+$loadFromDatabase = true;
+if ($cacheDriver->contains($keyData) && $cacheDriver->contains($keyDate)) {
+    $savedDate = $cacheDriver->fetch($keyDate);
+    $loadFromDatabase = false;
+    if ($savedDate < $now) {
+        $loadFromDatabase = true;
+    }
+}
+
+$loadFromDatabase = true;
+if ($loadFromDatabase) {
+    $result = Database::query($sql);
+    $data = Database::store_result($result, 'ASSOC');
+
+    $cacheDriver->save($keyData, $data);
+    $cacheDriver->save($keyDate, $tomorrow);
+} else {
+    $data = $cacheDriver->fetch($keyData);
+}
+
+foreach ($data as &$result) {
+    $result['complete_name'] = addslashes(api_get_person_name($result['firstname'], $result['lastname']));
+    $parts = explode('::', $result['ville']);
+    if (isset($parts[1]) && !empty($parts[1])) {
+        $parts2 = explode(',', $parts[1]);
+        $result['ville_lat'] = $parts2[0];
+        $result['ville_long'] = $parts2[1];
+
+        unset($result['ville']);
+    }
+
+    $parts = explode('::', $result['stage']);
+    if (isset($parts[1]) && !empty($parts[1])) {
+        $parts2 = explode(',', $parts[1]);
+        $result['stage_lat'] = $parts2[0];
+        $result['stage_long'] = $parts2[1];
+        unset($result['stage']);
+    }
+}
+
+$htmlHeadXtra[] = '<script type="text/javascript" src="'.api_get_path(WEB_LIBRARY_JS_PATH).'map/markerclusterer.js"></script>';
+$htmlHeadXtra[] = '<script type="text/javascript" src="'.api_get_path(WEB_LIBRARY_JS_PATH).'map/oms.min.js"></script>';
+
+$tpl = new Template(null);
+$tpl->assign('url', api_get_path(WEB_CODE_PATH).'social/profile.php');
+$tpl->assign(
+    'image_city',
+    Display::return_icon(
+        'red-dot.png',
+        '',
+        [],
+        ICON_SIZE_SMALL,
+        false,
+        true
+    )
+);
+$tpl->assign(
+    'image_stage',
+    Display::return_icon(
+        'blue-dot.png',
+        '',
+        [],
+        ICON_SIZE_SMALL,
+        false,
+        true
+    )
+);
+
+$tpl->assign('places', json_encode($data));
+$tpl->assign('api_key', $apiKey);
+
+$tpl->assign('field_1', $infoStage['display_text']);
+$tpl->assign('field_2', $infoVille['display_text']);
+
+$layout = $tpl->get_template('social/map.tpl');
+$tpl->display($layout);

+ 1 - 1
main/social/myfiles.php

@@ -62,7 +62,7 @@ function register_friend(element_input) {
     }
 }
 
-$(document).on("ready", function () {
+$(function() {
     $("#el-finder")
         .elfinder({
             url: "'.api_get_path(WEB_LIBRARY_PATH).'elfinder/php/connector.php",

+ 20 - 349
main/social/profile.php

@@ -23,7 +23,7 @@ if (api_get_setting('allow_social_tool') != 'true') {
 }
 
 $user_id = api_get_user_id();
-$friendId = isset($_GET['u']) ? intval($_GET['u']) : api_get_user_id();
+$friendId = isset($_GET['u']) ? (int) $_GET['u'] : api_get_user_id();
 $isAdmin = api_is_platform_admin($user_id);
 $userGroup = new UserGroup();
 
@@ -32,71 +32,14 @@ $show_full_profile = true;
 $this_section = SECTION_SOCIAL;
 
 // Initialize blocks
-$social_extra_info_block = null;
 $social_course_block = null;
 $social_group_info_block = null;
 $social_rss_block = null;
 $social_session_block = null;
 
-if (!empty($_POST['social_wall_new_msg_main']) || !empty($_FILES['picture']['tmp_name'])) {
-    $messageId = 0;
-    $messageContent = $_POST['social_wall_new_msg_main'];
-    if (!empty($_POST['url_content'])) {
-        $messageContent = $_POST['social_wall_new_msg_main'].'<br /><br />'.$_POST['url_content'];
-    }
-    $idMessage = SocialManager::sendWallMessage(
-        api_get_user_id(),
-        $friendId,
-        $messageContent,
-        $messageId,
-        MESSAGE_STATUS_WALL_POST
-    );
-    if (!empty($_FILES['picture']['tmp_name']) && $idMessage > 0) {
-        $error = SocialManager::sendWallMessageAttachmentFile(
-            api_get_user_id(),
-            $_FILES['picture'],
-            $idMessage,
-            $fileComment = ''
-        );
-    }
-
-    Display::addFlash(Display::return_message(get_lang('MessageSent')));
+SocialManager::handlePosts(api_get_self().'?u='.$friendId);
 
-    $url = api_get_path(WEB_CODE_PATH).'social/profile.php';
-    $url .= empty($_SERVER['QUERY_STRING']) ? '' : '?'.Security::remove_XSS($_SERVER['QUERY_STRING']);
-    header('Location: '.$url);
-    exit;
-} elseif (!empty($_POST['social_wall_new_msg']) && !empty($_POST['messageId'])) {
-    $messageId = intval($_POST['messageId']);
-    $messageContent = $_POST['social_wall_new_msg'];
-
-    $res = SocialManager::sendWallMessage(
-        api_get_user_id(),
-        $friendId,
-        $messageContent,
-        $messageId,
-        MESSAGE_STATUS_WALL
-    );
-    Display::addFlash(Display::return_message(get_lang('MessageSent')));
-    $url = api_get_path(WEB_CODE_PATH).'social/profile.php';
-    $url .= empty($_SERVER['QUERY_STRING']) ? '' : '?'.Security::remove_XSS($_SERVER['QUERY_STRING']);
-    header('Location: '.$url);
-    exit;
-} elseif (isset($_GET['messageId'])) {
-    $messageId = intval($_GET['messageId']);
-    $messageInfo = MessageManager::get_message_by_id($messageId);
-    if (!empty($messageInfo)) {
-        // I can only delete messages of my own wall
-        if ($messageInfo['user_receiver_id'] == $user_id) {
-            $status = SocialManager::deleteMessage($messageId);
-
-            Display::addFlash(Display::return_message(get_lang('MessageDeleted')));
-            header('Location: '.api_get_path(WEB_CODE_PATH).'social/profile.php');
-            exit;
-        }
-    }
-    api_not_allowed(true);
-} elseif (isset($_GET['u'])) {
+if (isset($_GET['u'])) {
     //I'm your friend? I can see your profile?
     $user_id = intval($_GET['u']);
     if (api_is_anonymous($user_id, true)) {
@@ -147,43 +90,18 @@ if (!empty($_POST['social_wall_new_msg_main']) || !empty($_FILES['picture']['tmp
     $user_info = api_get_user_info($user_id);
 }
 
+$isSelfUser = false;
 if ($user_info['user_id'] == api_get_user_id()) {
     $isSelfUser = true;
-} else {
-    $isSelfUser = false;
 }
+
 $userIsOnline = user_is_online($user_id);
 $ajax_url = api_get_path(WEB_AJAX_PATH).'message.ajax.php';
 $socialAjaxUrl = api_get_path(WEB_AJAX_PATH).'social.ajax.php';
 $javascriptDir = api_get_path(LIBRARY_PATH).'javascript/';
 api_block_anonymous_users();
-$locale = api_get_language_isocode();
-// Add Jquery scroll pagination plugin
-$htmlHeadXtra[] = api_get_js('jscroll/jquery.jscroll.js');
-// Add Jquery Time ago plugin
-$htmlHeadXtra[] = api_get_asset('jquery-timeago/jquery.timeago.js');
-$timeAgoLocaleDir = $javascriptDir.'jquery-timeago/locales/jquery.timeago.'.$locale.'.js';
-if (file_exists($timeAgoLocaleDir)) {
-    $htmlHeadXtra[] = api_get_js('jquery-timeago/locales/jquery.timeago.'.$locale.'.js');
-}
-
-$htmlHeadXtra[] = '<script>
-$(document).ready(function(){
-    var container = $("#wallMessages");
-    container.jscroll({
-        loadingHtml: "<div class=\"well_border\">'.get_lang('Loading').' </div>",
-        nextSelector: "a.nextPage:last",
-        contentSelector: "",
-        callback: timeAgo
-    });
-    timeAgo();
-});
-
-function timeAgo() {
-    $(".timeago").timeago();
-}
-</script>';
-
+$countPost = SocialManager::getCountWallMessagesByUser($friendId);
+SocialManager::getScrollJs($countPost, $htmlHeadXtra);
 $link_shared = '';
 
 $nametool = get_lang('ViewMySharedProfile');
@@ -217,9 +135,8 @@ if (isset($_GET['u'])) {
 Session::write('social_user_id', (int) $user_id);
 
 // Setting some course info
-$my_user_id = isset($_GET['u']) ? intval($_GET['u']) : api_get_user_id();
 $personal_course_list = UserManager::get_personal_session_course_list(
-    $my_user_id,
+    $friendId,
     50
 );
 $course_list_code = [];
@@ -248,37 +165,22 @@ $social_menu_block = SocialManager::getMenuSocial(
 );
 
 //Setting some session info
-$user_info = api_get_user_info($my_user_id);
+$user_info = api_get_user_info($friendId);
 $sessionList = SessionManager::getSessionsFollowedByUser(
-    $my_user_id,
+    $friendId,
     $user_info['status']
 );
 
 // My friends
-$friend_html = SocialManager::listMyFriendsBlock(
-    $user_id,
-    $link_shared,
-    $show_full_profile
-);
-
-$wallSocialAddPost = SocialManager::getWallForm($show_full_profile);
-$social_wall_block = $wallSocialAddPost;
+$friend_html = SocialManager::listMyFriendsBlock($user_id, $link_shared);
+$wallSocialAddPost = SocialManager::getWallForm(api_get_self());
 
-// Social Post Wall
-$posts = SocialManager::getWallMessagesByUser($my_user_id, $friendId);
-$social_post_wall_block = empty($posts) ? '<p>'.get_lang("NoPosts").'</p>' : $posts;
-
-$socialAutoExtendLink = Display::url(
-    get_lang('SeeMore'),
-    $socialAjaxUrl.'?u='.$my_user_id.'&a=list_wall_message&start=10&length=5',
-    [
-        'class' => 'nextPage next',
-    ]
-);
+$posts = SocialManager::getWallMessagesByUser($friendId);
+$socialAutoExtendLink = SocialManager::getAutoExtendLink($user_id, $countPost);
 
 // Added a Jquery Function to return the Preview of OpenGraph URL Content
 $htmlHeadXtra[] = '<script>
-$(document).ready(function() {
+$(function() {
     var getUrl = $("[name=\'social_wall_new_msg_main\']");
     var matchUrl = /https?:\/\/w{0,3}\w*?\.(\w*?\.)?\w{2,3}\S*|www\.(\w*?\.)?\w*?\.\w{2,3}\S*|(\w*?\.)?\w*?\.\w{2,3}[\/\?]\S*/ ;
 
@@ -320,236 +222,7 @@ $social_right_content = '';
 $listInvitations = '';
 
 if ($show_full_profile) {
-    $t_ufo = Database::get_main_table(TABLE_EXTRA_FIELD_OPTIONS);
-    $extra_user_data = UserManager::get_extra_user_data($user_id, false, true);
-
-    $extra_information = '';
-    if (is_array($extra_user_data) && count($extra_user_data) > 0) {
-        $extra_information_value = '';
-        $extraField = new ExtraField('user');
-        foreach ($extra_user_data as $key => $data) {
-            if (empty($data)) {
-                continue;
-            }
-            // Avoiding parameters
-            if (in_array(
-                $key,
-                [
-                    'mail_notify_invitation',
-                    'mail_notify_message',
-                    'mail_notify_group_message',
-                ]
-            )) {
-                continue;
-            }
-            // get display text, visibility and type from user_field table
-            $field_variable = str_replace('extra_', '', $key);
-
-            $extraFieldInfo = $extraField->get_handler_field_info_by_field_variable(
-                $field_variable
-            );
-
-            if (in_array($extraFieldInfo['variable'], ['skype', 'linkedin_url'])) {
-                continue;
-            }
-
-            // if is not visible skip
-            if ($extraFieldInfo['visible_to_self'] != 1) {
-                continue;
-            }
-
-            // if is not visible to others skip also
-            if ($extraFieldInfo['visible_to_others'] != 1) {
-                continue;
-            }
-
-            if (is_array($data)) {
-                $extra_information_value .= '<li class="list-group-item">'.ucfirst($extraFieldInfo['display_text']).' '
-                    .' '.implode(',', $data).'</li>';
-            } else {
-                switch ($extraFieldInfo['field_type']) {
-                    case ExtraField::FIELD_TYPE_DOUBLE_SELECT:
-                        $id_options = explode('::', $data);
-                        $value_options = [];
-                        // get option display text from user_field_options table
-                        foreach ($id_options as $id_option) {
-                            $sql = "SELECT display_text 
-                                    FROM $t_ufo 
-                                    WHERE id = '$id_option'";
-                            $res_options = Database::query($sql);
-                            $row_options = Database::fetch_row($res_options);
-                            $value_options[] = $row_options[0];
-                        }
-                        $extra_information_value .= '<li class="list-group-item">'.ucfirst($extraFieldInfo['display_text']).': '
-                            .' '.implode(' ', $value_options).'</li>';
-                        break;
-                    case ExtraField::FIELD_TYPE_TAG:
-                        $user_tags = UserManager::get_user_tags($user_id, $extraFieldInfo['id']);
-
-                        $tag_tmp = [];
-                        foreach ($user_tags as $tags) {
-                            $tag_tmp[] = '<a class="label label_tag"'
-                                .' href="'.api_get_path(WEB_PATH).'main/social/search.php?q='.$tags['tag'].'">'
-                                .$tags['tag']
-                                .'</a>';
-                        }
-                        if (is_array($user_tags) && count($user_tags) > 0) {
-                            $extra_information_value .= '<li class="list-group-item">'.ucfirst($extraFieldInfo['display_text']).': '
-                                .' '.implode('', $tag_tmp).'</li>';
-                        }
-                        break;
-                    case ExtraField::FIELD_TYPE_SOCIAL_PROFILE:
-                        $icon_path = UserManager::get_favicon_from_url($data);
-                        if (SocialManager::verifyUrl($icon_path) == false) {
-                            break;
-                        }
-                        $bottom = '0.2';
-                        //quick hack for hi5
-                        $domain = parse_url($icon_path, PHP_URL_HOST);
-                        if ($domain == 'www.hi5.com' or $domain == 'hi5.com') {
-                            $bottom = '-0.8';
-                        }
-                        $data = '<a href="'.$data.'">'
-                            .'<img src="'.$icon_path.'" alt="icon"'
-                            .' style="margin-right:0.5em;margin-bottom:'.$bottom.'em;" />'
-                            .$extraFieldInfo['display_text']
-                            .'</a>';
-                        $extra_information_value .= '<li class="list-group-item">'.$data.'</li>';
-                        break;
-                    case ExtraField::FIELD_TYPE_SELECT_WITH_TEXT_FIELD:
-                        $parsedData = explode('::', $data);
-
-                        if (!$parsedData) {
-                            break;
-                        }
-
-                        $objEfOption = new ExtraFieldOption('user');
-                        $optionInfo = $objEfOption->get($parsedData[0]);
-
-                        $extra_information_value .= '<li class="list-group-item">'
-                            .$optionInfo['display_text'].': '
-                            .$parsedData[1].'</li>';
-                        break;
-                    case ExtraField::FIELD_TYPE_TRIPLE_SELECT:
-                        $optionIds = explode(';', $data);
-                        $optionValues = [];
-
-                        foreach ($optionIds as $optionId) {
-                            $objEfOption = new ExtraFieldOption('user');
-                            $optionInfo = $objEfOption->get($optionId);
-
-                            $optionValues[] = $optionInfo['display_text'];
-                        }
-                        $extra_information_value .= '<li class="list-group-item">'
-                            .ucfirst($extraFieldInfo['display_text']).': '
-                            .implode(' ', $optionValues).'</li>';
-                        break;
-                    default:
-                        $extra_information_value .= '<li class="list-group-item">'.ucfirst($extraFieldInfo['display_text']).': '.$data.'</li>';
-                        break;
-                }
-            }
-        }
-
-        // if there are information to show
-        if (!empty($extra_information_value)) {
-            $extra_information_value = '<ul class="list-group">'.$extra_information_value.'</ul>';
-            $extra_information .= Display::panelCollapse(
-                get_lang('ExtraInformation'),
-                $extra_information_value,
-                'sn-extra-information',
-                null,
-                'sn-extra-accordion',
-                'sn-extra-collapse'
-            );
-        }
-    }
-
-    // If there are information to show Block Extra Information
-    if (!empty($extra_information_value)) {
-        $social_extra_info_block = $extra_information;
-    }
-
-    // MY GROUPS
-    $results = $userGroup->get_groups_by_user($my_user_id, 0);
-    $grid_my_groups = [];
-    $max_numbers_of_group = 4;
-
-    if (is_array($results) && count($results) > 0) {
-        $i = 1;
-        foreach ($results as $result) {
-            if ($i > $max_numbers_of_group) {
-                break;
-            }
-            $id = $result['id'];
-            $url_open = '<a href="group_view.php?id='.$id.'">';
-            $url_close = '</a>';
-            $icon = '';
-            $name = cut($result['name'], CUT_GROUP_NAME, true);
-            if ($result['relation_type'] == GROUP_USER_PERMISSION_ADMIN) {
-                $icon = Display::return_icon(
-                    'social_group_admin.png',
-                    get_lang('Admin'),
-                    ['style' => 'vertical-align:middle;width:16px;height:16px;']
-                );
-            } elseif ($result['relation_type'] == GROUP_USER_PERMISSION_MODERATOR) {
-                $icon = Display::return_icon(
-                    'social_group_moderator.png',
-                    get_lang('Moderator'),
-                    ['style' => 'vertical-align:middle;width:16px;height:16px;']
-                );
-            }
-            $count_users_group = count($userGroup->get_all_users_by_group($id));
-            if ($count_users_group == 1) {
-                $count_users_group = $count_users_group.' '.get_lang('Member');
-            } else {
-                $count_users_group = $count_users_group.' '.get_lang('Members');
-            }
-            $item_name = $url_open.$name.$icon.$url_close;
-            $item_actions = '';
-            $grid_my_groups[] = [
-                $item_name,
-                $url_open.$result['picture'].$url_close,
-                $item_actions,
-            ];
-            $i++;
-        }
-    }
-
-    // Block My Groups
-    if (count($grid_my_groups) > 0) {
-        $my_groups = '';
-        $count_groups = 0;
-        if (count($results) == 1) {
-            $count_groups = count($results);
-        } else {
-            $count_groups = count($results);
-        }
-
-        if ($i > $max_numbers_of_group) {
-            if (api_get_user_id() == $user_id) {
-                $my_groups .= '<div class="box_shared_profile_group_actions">'
-                    .'<a href="groups.php?#tab_browse-1">'.get_lang('SeeAllMyGroups').'</a></div>';
-            } else {
-                $my_groups .= '<div class="box_shared_profile_group_actions">'
-                    .'<a href="'.api_get_path(WEB_CODE_PATH).'social/profile_friends_and_groups.inc.php'
-                    .'?view=mygroups&height=390&width=610&user_id='.$user_id.'"'
-                    .' class="ajax" title="'.get_lang('SeeAll').'" >'
-                    .get_lang('SeeAllMyGroups')
-                    .'</a></div>';
-            }
-        }
-
-        $total = count($grid_my_groups);
-        $i = 1;
-        foreach ($grid_my_groups as $group) {
-            $my_groups .= '<div class="panel-body">';
-            $my_groups .= $group[0];
-            $my_groups .= '</div>';
-            $i++;
-        }
-        $social_group_info_block = Display::panel($my_groups, get_lang('MyGroups').' ('.$count_groups.')');
-    }
+    $social_group_info_block = SocialManager::getGroupBlock($friendId);
 
     // Block Social Course
     $my_courses = null;
@@ -571,7 +244,6 @@ if ($show_full_profile) {
 
     // Block Social Sessions
     if (count($sessionList) > 0) {
-        //$social_session_block = $htmlSessionList;
         $social_session_block = $sessionList;
     }
 
@@ -601,7 +273,7 @@ if ($show_full_profile) {
     if (!isset($_GET['u']) ||
         (isset($_GET['u']) && $_GET['u'] == api_get_user_id())
     ) {
-        $pending_invitations = SocialManager::get_list_invitation_of_friends_by_user_id(api_get_user_id(), null);
+        $pending_invitations = SocialManager::get_list_invitation_of_friends_by_user_id(api_get_user_id());
         $list_get_path_web = SocialManager::get_list_web_path_user_invitation_by_user_id(api_get_user_id());
         $count_pending_invitations = count($pending_invitations);
     }
@@ -710,13 +382,12 @@ SocialManager::setSocialUserBlock(
 
 $tpl->assign('social_friend_block', $friend_html);
 $tpl->assign('social_menu_block', $social_menu_block);
-$tpl->assign('social_wall_block', $social_wall_block);
-$tpl->assign('social_post_wall_block', $social_post_wall_block);
-$tpl->assign('social_extra_info_block', $social_extra_info_block);
+$tpl->assign('social_wall_block', $wallSocialAddPost);
+$tpl->assign('social_post_wall_block', $posts);
 $tpl->assign('social_course_block', $social_course_block);
 $tpl->assign('social_group_info_block', $social_group_info_block);
 $tpl->assign('social_rss_block', $social_rss_block);
-$tpl->assign('social_skill_block', SocialManager::getSkillBlock($my_user_id));
+$tpl->assign('social_skill_block', SocialManager::getSkillBlock($friendId, 'vertical'));
 $tpl->assign('session_list', $social_session_block);
 $tpl->assign('invitations', $listInvitations);
 $tpl->assign('social_right_information', $socialRightInformation);

+ 8 - 17
main/social/profile_friends_and_groups.inc.php

@@ -17,7 +17,7 @@ if (api_get_setting('allow_social_tool') != 'true') {
 }
 
 $views = ['friends', 'mygroups'];
-$user_id = intval($_GET['user_id']);
+$user_id = (int) $_GET['user_id'];
 $userGroup = new UserGroup();
 
 if (isset($_GET['view']) && in_array($_GET['view'], $views)) {
@@ -92,17 +92,13 @@ if (isset($_GET['view']) && in_array($_GET['view'], $views)) {
                     $userGroup->get_all_users_by_group($id)
                 );
                 if ($count_users_group == 1) {
-                    $count_users_group = $count_users_group.' '.get_lang(
-                        'Member'
-                    );
+                    $count_users_group = $count_users_group.' '.get_lang('Member');
                 } else {
-                    $count_users_group = $count_users_group.' '.get_lang(
-                        'Members'
-                    );
+                    $count_users_group = $count_users_group.' '.get_lang('Members');
                 }
                 $picture = $userGroup->get_picture_group(
                     $result['id'],
-                    $result['picture_uri'],
+                    $result['picture'],
                     80
                 );
                 $item_name = '<div class="box_shared_profile_group_title">'.$url_open.api_xml_http_response_encode($name).$icon.$url_close.'</div>';
@@ -129,6 +125,7 @@ if (isset($_GET['view']) && in_array($_GET['view'], $views)) {
                 $i++;
             }
         }
+
         if (count($grid_my_groups) > 0) {
             echo '<div style="margin-top:20px">';
             echo '<div><h3>'.get_lang('MyGroups').'</h3></div>';
@@ -140,15 +137,9 @@ if (isset($_GET['view']) && in_array($_GET['view'], $views)) {
             }
             echo '<div>'.$count_groups.'</div>';
 
-            Display::display_sortable_grid(
-                'shared_profile_mygroups',
-                [],
-                $grid_my_groups,
-                ['hide_navigation' => true, 'per_page' => 2],
-                $query_vars,
-                false,
-                [true, true, true, false]
-            );
+            foreach ($grid_my_groups as $group) {
+                echo Display::panel($group[0], $group[1]);
+            }
             echo '</div>';
         }
     }

+ 47 - 42
main/social/search.php

@@ -100,6 +100,7 @@ if ($query != '' || ($query_vars['search_type'] == '1' && count($query_vars) > 2
                 'a' => 'get_user_popup',
                 'user_id' => $user_info['user_id'],
             ]);
+
             $sendMessage = Display::toolbarButton(
                 get_lang('SendMessage'),
                 $sendMessageUrl,
@@ -138,27 +139,29 @@ if ($query != '' || ($query_vars['search_type'] == '1' && count($query_vars) > 2
     $results .= '</div>';
 
     $visibility = [true, true, true, true, true];
-    $results .= Display::return_sortable_grid(
-        'users',
-        null,
-        null,
-        ['hide_navigation' => false, 'per_page' => $itemPerPage],
-        $query_vars,
-        false,
-        $visibility,
-        true,
-        [],
-        $totalUsers
-    );
-
-    $block_search .= Display::panelCollapse(
-        get_lang('Users'),
-        $results,
-        'search-friends',
-        null,
-        'friends-accordion',
-        'friends-collapse'
-    );
+
+    if (!empty($users)) {
+        $results .= Display::return_sortable_grid(
+            'users',
+            null,
+            null,
+            ['hide_navigation' => false, 'per_page' => $itemPerPage],
+            $query_vars,
+            false,
+            $visibility,
+            true,
+            [],
+            $totalUsers
+        );
+        $block_search .= Display::panelCollapse(
+            get_lang('Users'),
+            $results,
+            'search-friends',
+            null,
+            'friends-accordion',
+            'friends-collapse'
+        );
+    }
 
     $grid_groups = [];
     $block_groups = '<div id="whoisonline">';
@@ -210,27 +213,29 @@ if ($query != '' || ($query_vars['search_type'] == '1' && count($query_vars) > 2
     $block_groups .= '</div>';
 
     $visibility = [true, true, true, true, true];
-    $block_groups .= Display::return_sortable_grid(
-        'groups',
-        null,
-        $grid_groups,
-        ['hide_navigation' => false, 'per_page' => $itemPerPage],
-        $query_vars,
-        false,
-        $visibility,
-        true,
-        [],
-        $totalGroups
-    );
-
-    $block_search .= Display:: panelCollapse(
-        get_lang('Groups'),
-        $block_groups,
-        'search-groups',
-        null,
-        'groups-accordion',
-        'groups-collapse'
-    );
+
+    if (!empty($groups)) {
+        $block_groups .= Display::return_sortable_grid(
+            'groups',
+            null,
+            $grid_groups,
+            ['hide_navigation' => false, 'per_page' => $itemPerPage],
+            $query_vars,
+            false,
+            $visibility,
+            true,
+            [],
+            $totalGroups
+        );
+        $block_search .= Display:: panelCollapse(
+            get_lang('Groups'),
+            $block_groups,
+            'search-groups',
+            null,
+            'groups-accordion',
+            'groups-collapse'
+        );
+    }
 }
 
 $tpl = new Template($tool_name);

+ 1 - 1
main/survey/copy_survey.php

@@ -34,7 +34,7 @@ if (empty($survey)) {
     api_not_allowed(true);
 }
 
-$surveyTitle = str_replace("&nbsp;", '', strip_tags($survey['title'].' ('.$survey['code'].') '));
+$surveyTitle = str_replace('&nbsp;', '', strip_tags($survey['title'].' ('.$survey['code'].') '));
 
 $form = new FormValidator('copy_survey', 'post', api_get_self().'?survey_id='.$surveyId.'&'.api_get_cidreq());
 $form->addElement(

+ 22 - 18
main/survey/create_meeting.php

@@ -8,6 +8,7 @@ $this_section = SECTION_COURSES;
 if (!api_is_allowed_to_edit()) {
     api_not_allowed(true);
 }
+
 $htmlHeadXtra[] = api_get_css_asset('jt.timepicker/jquery.timepicker.css');
 $htmlHeadXtra[] = api_get_asset('jt.timepicker/jquery.timepicker.js');
 $htmlHeadXtra[] = api_get_asset('datepair.js/dist/datepair.js');
@@ -36,24 +37,25 @@ $form->addHidden('survey_thanks', '');
 $form->addHidden('visible_results', '0');
 $form->addHidden('survey_type', 3);
 
-// Setting the form elements
-/*if ($_GET['action'] == 'edit' && isset($survey_id) && is_numeric($survey_id)) {
-    $form->addElement('hidden', 'survey_id');
-}*/
-
-$text = $form->addElement(
-    'text',
+$text = $form->addText(
     'survey_title',
-    get_lang('Title'),
-    null,
-    ['ToolbarSet' => 'Survey', 'Width' => '100%', 'Height' => '200']
+    get_lang('Title')
 );
 
-$form->addDateTimePicker('start_date', get_lang('StartDate'));
-$form->addDateTimePicker('end_date', get_lang('EndDate'));
+$allowSurveyAvailabilityDatetime = api_get_configuration_value('allow_survey_availability_datetime');
+
+if ($allowSurveyAvailabilityDatetime) {
+    $startDateElement = $form->addDateTimePicker('start_date', get_lang('StartDate'));
+    $endDateElement = $form->addDateTimePicker('end_date', get_lang('EndDate'));
+    $form->addRule('start_date', get_lang('InvalidDate'), 'datetime');
+    $form->addRule('end_date', get_lang('InvalidDate'), 'datetime');
+} else {
+    $startDateElement = $form->addElement('date_picker', 'start_date', get_lang('StartDate'));
+    $endDateElement = $form->addElement('date_picker', 'end_date', get_lang('EndDate'));
+    $form->addRule('start_date', get_lang('InvalidDate'), 'date');
+    $form->addRule('end_date', get_lang('InvalidDate'), 'date');
+}
 
-$form->addRule('start_date', get_lang('InvalidDate'), 'datetime');
-$form->addRule('end_date', get_lang('InvalidDate'), 'datetime');
 $form->addRule(
     ['start_date', 'end_date'],
     get_lang('StartDateShouldBeBeforeEndDate'),
@@ -61,6 +63,9 @@ $form->addRule(
     'lte'
 );
 
+$form->setRequired($startDateElement);
+$form->setRequired($endDateElement);
+
 $form->addHtmlEditor('survey_introduction', get_lang('Description'), false);
 $form->setRequired($text);
 
@@ -70,7 +75,7 @@ for ($i = 1; $i <= $maxEvents; $i++) {
     $name = 'time_'.$i;
     $form->addDateTimeRangePicker($name, get_lang('Date'));
     if ($i > 3) {
-        $hideList .= "$('#".$name."_alt').parent().parent().parent().hide();";
+        $hideList .= "$('#".$name."_date_time_wrapper').hide();";
     }
 }
 
@@ -80,12 +85,12 @@ $(function() {
     var number = 3;    
     $("#add_button").on("click", function() {
         number++;
-        $("#time_" + number + "_alt").parent().parent().parent().show();
+        $("#time_" + number + "_date_time_wrapper").show();
     });
     
     $("#remove_button").on("click", function() {
         if (number > 1) {            
-            $("#time_" + number + "_alt").parent().parent().parent().hide();
+            $("#time_" + number + "_date_time_wrapper").hide();
             number--;
         }
     });
@@ -149,7 +154,6 @@ if ($form->validate()) {
     $counter = 1;
     if (!empty($surveyData['id'])) {
         foreach ($dates as $date) {
-            //SurveyManager::save_question();
             $params = [
                 'c_id' => api_get_course_int_id(),
                 'survey_id' => $surveyData['id'],

+ 25 - 18
main/survey/create_new_survey.php

@@ -36,12 +36,12 @@ if (!api_is_allowed_to_edit()) {
         !api_is_element_in_the_session(TOOL_SURVEY, $_GET['survey_id']))
     ) {
         api_not_allowed(true);
-        exit;
     }
 }
 
 // Getting the survey information
 $survey_id = isset($_GET['survey_id']) ? (int) $_GET['survey_id'] : null;
+$action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
 $survey_data = SurveyManager::get_survey($survey_id);
 
 // Additional information
@@ -51,14 +51,14 @@ $gradebook_link_type = 8;
 $urlname = isset($survey_data['title']) ? strip_tags($survey_data['title']) : null;
 
 // Breadcrumbs
-if ($_GET['action'] == 'add') {
+if ($action == 'add') {
     $interbreadcrumb[] = [
         'url' => api_get_path(WEB_CODE_PATH).'survey/survey_list.php?'.api_get_cidreq(),
         'name' => get_lang('SurveyList'),
     ];
     $tool_name = get_lang('CreateNewSurvey');
 }
-if ($_GET['action'] == 'edit' && is_numeric($survey_id)) {
+if ($action == 'edit' && is_numeric($survey_id)) {
     $interbreadcrumb[] = [
         'url' => api_get_path(WEB_CODE_PATH).'survey/survey_list.php?'.api_get_cidreq(),
         'name' => get_lang('SurveyList'),
@@ -71,7 +71,7 @@ if ($_GET['action'] == 'edit' && is_numeric($survey_id)) {
 }
 $gradebook_link_id = null;
 // Getting the default values
-if ($_GET['action'] == 'edit' && isset($survey_id) && is_numeric($survey_id)) {
+if ($action == 'edit' && isset($survey_id) && is_numeric($survey_id)) {
     $defaults = $survey_data;
     $defaults['survey_id'] = $survey_id;
     $defaults['anonymous'] = $survey_data['anonymous'];
@@ -113,8 +113,6 @@ if ($_GET['action'] == 'edit' && isset($survey_id) && is_numeric($survey_id)) {
         $allowSurveyAvailabilityDatetime ? 'Y-m-d 23:59:59' : 'Y-m-d',
         $startdateandxdays
     );
-    //$defaults['survey_share']['survey_share'] = 0;
-    //$form_share_value = 1;
     $defaults['anonymous'] = 0;
 }
 
@@ -122,13 +120,13 @@ if ($_GET['action'] == 'edit' && isset($survey_id) && is_numeric($survey_id)) {
 $form = new FormValidator(
     'survey',
     'post',
-    api_get_self().'?action='.Security::remove_XSS($_GET['action']).'&survey_id='.$survey_id.'&'.api_get_cidreq()
+    api_get_self().'?action='.$action.'&survey_id='.$survey_id.'&'.api_get_cidreq()
 );
 
 $form->addElement('header', $tool_name);
 
 // Setting the form elements
-if ($_GET['action'] == 'edit' && isset($survey_id) && is_numeric($survey_id)) {
+if ($action == 'edit' && isset($survey_id) && is_numeric($survey_id)) {
     $form->addElement('hidden', 'survey_id');
 }
 
@@ -139,7 +137,7 @@ $survey_code = $form->addElement(
     ['size' => '20', 'maxlength' => '20', 'autofocus' => 'autofocus']
 );
 
-if ($_GET['action'] == 'edit') {
+if ($action == 'edit') {
     $survey_code->freeze();
     $form->applyFilter('survey_code', 'api_strtoupper');
 }
@@ -167,14 +165,23 @@ $form->addElement(
 // Pass the language of the survey in the form
 $form->addElement('hidden', 'survey_language');
 
+$allowSurveyAvailabilityDatetime = api_get_configuration_value('allow_survey_availability_datetime');
+
 if ($allowSurveyAvailabilityDatetime) {
-    $form->addDateTimePicker('start_date', get_lang('StartDate'));
-    $form->addDateTimePicker('end_date', get_lang('EndDate'));
+    $startDateElement = $form->addDateTimePicker('start_date', get_lang('StartDate'));
+    $endDateElement = $form->addDateTimePicker('end_date', get_lang('EndDate'));
+    $form->addRule('start_date', get_lang('InvalidDate'), 'datetime');
+    $form->addRule('end_date', get_lang('InvalidDate'), 'datetime');
 } else {
-    $form->addElement('date_picker', 'start_date', get_lang('StartDate'));
-    $form->addElement('date_picker', 'end_date', get_lang('EndDate'));
+    $startDateElement = $form->addElement('date_picker', 'start_date', get_lang('StartDate'));
+    $endDateElement = $form->addElement('date_picker', 'end_date', get_lang('EndDate'));
+    $form->addRule('start_date', get_lang('InvalidDate'), 'date');
+    $form->addRule('end_date', get_lang('InvalidDate'), 'date');
 }
 
+$form->setRequired($startDateElement);
+$form->setRequired($endDateElement);
+
 $form->addElement('checkbox', 'anonymous', null, get_lang('Anonymous'));
 $visibleResults = [
     SURVEY_VISIBLE_TUTOR => get_lang('Coach'),
@@ -234,7 +241,7 @@ if (Gradebook::is_active()) {
     // Loading Gradebook select
     GradebookUtils::load_gradebook_select_in_tool($form);
 
-    if ($_GET['action'] == 'edit') {
+    if ($action == 'edit') {
         $element = $form->getElement('category_id');
         $element->freeze();
     }
@@ -245,7 +252,7 @@ if (Gradebook::is_active()) {
 $surveytypes[0] = get_lang('Normal');
 $surveytypes[1] = get_lang('Conditional');
 
-if ($_GET['action'] == 'add') {
+if ($action == 'add') {
     $form->addElement('hidden', 'survey_type', 0);
     $survey_tree = new SurveyTree();
     $list_surveys = $survey_tree->createList($survey_tree->surveylist);
@@ -259,7 +266,7 @@ $form->addElement('checkbox', 'shuffle', null, get_lang('ActivateShuffle'));
 
 $input_name_list = null;
 
-if (isset($_GET['action']) && $_GET['action'] == 'edit' && !empty($survey_id)) {
+if ($action == 'edit' && !empty($survey_id)) {
     if ($survey_data['anonymous'] == 0) {
         $form->addElement(
             'checkbox',
@@ -308,14 +315,14 @@ $skillList = Skill::addSkillsToForm($form, ITEM_TYPE_SURVEY, $survey_id);
 
 $form->addElement('html', '</div><br />');
 
-if (isset($_GET['survey_id']) && $_GET['action'] == 'edit') {
+if (isset($_GET['survey_id']) && $action == 'edit') {
     $form->addButtonUpdate(get_lang('ModifySurvey'), 'submit_survey');
 } else {
     $form->addButtonCreate(get_lang('CreateSurvey'), 'submit_survey');
 }
 
 // Setting the rules
-if ($_GET['action'] == 'add') {
+if ($action == 'add') {
     $form->addRule('survey_code', get_lang('ThisFieldIsRequired'), 'required');
     $form->addRule('survey_code', '', 'maxlength', 20);
 }

+ 255 - 0
main/survey/edit_meeting.php

@@ -0,0 +1,255 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+require_once __DIR__.'/../inc/global.inc.php';
+
+$this_section = SECTION_COURSES;
+
+if (!api_is_allowed_to_edit()) {
+    api_not_allowed(true);
+}
+
+if (!api_is_allowed_to_edit()) {
+    if (!api_is_session_general_coach() ||
+        (!empty($_GET['survey_id']) &&
+            !api_is_element_in_the_session(TOOL_SURVEY, $_GET['survey_id']))
+    ) {
+        api_not_allowed(true);
+    }
+}
+
+$htmlHeadXtra[] = api_get_css_asset('jt.timepicker/jquery.timepicker.css');
+$htmlHeadXtra[] = api_get_asset('jt.timepicker/jquery.timepicker.js');
+$htmlHeadXtra[] = api_get_asset('datepair.js/dist/datepair.js');
+$htmlHeadXtra[] = api_get_asset('datepair.js/dist/jquery.datepair.js');
+
+$interbreadcrumb[] = [
+    'url' => api_get_path(WEB_CODE_PATH).'survey/survey_list.php?'.api_get_cidreq(),
+    'name' => get_lang('SurveyList'),
+];
+
+$surveyId = isset($_GET['survey_id']) ? (int) $_GET['survey_id'] : null;
+$surveyData = SurveyManager::get_survey($surveyId);
+
+if (empty($surveyData)) {
+    api_not_allowed(true);
+}
+
+$courseInfo = api_get_course_info();
+
+$tool_name = get_lang('Edit');
+
+$form = new FormValidator(
+    'survey',
+    'post',
+    api_get_self().'?action=edit&'.api_get_cidreq().'&survey_id='.$surveyId
+);
+
+$form->addElement('header', $tool_name);
+$form->addHidden('anonymous', 0);
+$form->addHidden('survey_language', $courseInfo['language']);
+$form->addHidden('survey_subtitle', '');
+$form->addHidden('survey_thanks', '');
+$form->addHidden('visible_results', '0');
+$form->addHidden('survey_type', 3);
+
+$text = $form->addText(
+    'survey_title',
+    get_lang('Title')
+);
+
+$allowSurveyAvailabilityDatetime = api_get_configuration_value('allow_survey_availability_datetime');
+
+if ($allowSurveyAvailabilityDatetime) {
+    $startDateElement = $form->addDateTimePicker('start_date', get_lang('StartDate'));
+    $endDateElement = $form->addDateTimePicker('end_date', get_lang('EndDate'));
+    $form->addRule('start_date', get_lang('InvalidDate'), 'datetime');
+    $form->addRule('end_date', get_lang('InvalidDate'), 'datetime');
+} else {
+    $startDateElement = $form->addElement('date_picker', 'start_date', get_lang('StartDate'));
+    $endDateElement = $form->addElement('date_picker', 'end_date', get_lang('EndDate'));
+    $form->addRule('start_date', get_lang('InvalidDate'), 'date');
+    $form->addRule('end_date', get_lang('InvalidDate'), 'date');
+}
+
+$form->addRule(
+    ['start_date', 'end_date'],
+    get_lang('StartDateShouldBeBeforeEndDate'),
+    'date_compare',
+    'lte'
+);
+
+$form->addHtmlEditor('survey_introduction', get_lang('Description'), false);
+$form->setRequired($text);
+
+$questions = SurveyManager::get_questions($surveyData['iid']);
+$currentQuestionsCount = count($questions);
+$counter = 1;
+foreach ($questions as $question) {
+    $name = 'time_'.$counter;
+    $parts = explode('@@', $question['question']);
+    $surveyData[$name] = api_get_local_time($parts[0]).'@@'.api_get_local_time($parts[1]);
+
+    $form->addDateTimeRangePicker($name, get_lang('Date'));
+    $form->addHidden($name.'_question_id', $question['question_id']);
+    $counter++;
+}
+$currentQuestionsCount++;
+
+$hideList = '';
+$maxEvents = $currentQuestionsCount + 10;
+for ($i = $currentQuestionsCount; $i <= $maxEvents; $i++) {
+    $name = 'time_'.$i;
+    $form->addDateTimeRangePicker($name, get_lang('Date'));
+    $hideList .= "$('#".$name."_date_time_wrapper').hide();";
+}
+
+$form->addHtml('<script>
+$(function() {
+    '.$hideList.'
+    var number = "'.--$currentQuestionsCount.'";    
+    $("#add_button").on("click", function() {
+        number++;
+        $("#time_" + number + "_date_time_wrapper").show();
+        $("#time_" + number + "_time_range_start").val("");
+        $("#time_" + number + "_time_range_end").val("");
+        $("#time_" + number + "_alt").val("");
+    });
+    
+    $("#remove_button").on("click", function() {
+        if (number > 1) {      
+            console.log("#time_" + number + "_time_range_start");
+            $("#time_" + number + "_date_time_wrapper").hide();
+            $("#time_" + number).val("delete");
+            
+            $("#time_" + number + "_alt").val("delete");
+            $("#time_" + number + "_time_range_start").val("delete");                        
+            number--;
+        }
+    });
+});
+</script>');
+
+$form->addLabel(
+    '',
+    Display::url(get_lang('Add'), 'javascript:void(0)', ['id' => 'add_button', 'class' => 'btn btn-default'])
+    .' '.
+    Display::url(
+        get_lang('Remove'),
+        'javascript:void(0)',
+        ['id' => 'remove_button', 'class' => 'btn btn-danger']
+    )
+);
+
+$form->addButtonUpdate(get_lang('Edit'), 'submit_survey');
+
+$form->setDefaults($surveyData);
+
+// The validation or display
+if ($form->validate()) {
+    // Exporting the values
+    $values = $form->getSubmitValues();
+
+    $values['survey_id'] = $surveyId;
+    $values['survey_code'] = SurveyManager::generateSurveyCode($values['survey_title']);
+    // Storing the survey
+    SurveyManager::store_survey($values);
+
+    $dates = [];
+    $deleteItems = [];
+
+    for ($i = 1; $i <= $maxEvents; $i++) {
+        $name = 'time_'.$i;
+
+        if (isset($values[$name]) && !empty($values[$name])) {
+            $id = '';
+            if (isset($values[$name.'_question_id'])) {
+                $id = $values[$name.'_question_id'];
+            }
+
+            $date = $values[$name];
+
+            if ($date === 'delete' && !empty($id)) {
+                $deleteItems[] = $id;
+            }
+
+            if (empty($date)) {
+                continue;
+            }
+
+            $start = $name.'_time_range_start';
+            $end = $name.'_time_range_end';
+
+            $start = $values[$start];
+            $end = $values[$end];
+
+            $part = explode('@@', $values[$name]);
+            $firstDate = substr($part[0], 0, 10);
+
+            $start = api_get_utc_datetime($firstDate.' '.$start);
+            $end = api_get_utc_datetime($firstDate.' '.$end);
+
+            if (!empty($start) && !empty($start)) {
+                $row = [
+                    'id' => $id,
+                    'start' => $start,
+                    'end' => $end,
+                ];
+                $dates[] = $row;
+            }
+        }
+    }
+
+    $questionTable = Database::get_course_table(TABLE_SURVEY_QUESTION);
+    $counter = 1;
+    if (!empty($surveyData['iid'])) {
+        $questions = SurveyManager::get_questions($surveyData['iid']);
+        if (!empty($questions)) {
+            $questions = array_column($questions, 'question');
+        }
+
+        foreach ($dates as $date) {
+            $formattedDate = $date['start'].'@@'.$date['end'];
+
+            if (!empty($date['id'])) {
+                $questionId = $date['id'];
+                $sql = "UPDATE $questionTable SET survey_question = '$formattedDate'
+                        WHERE iid = $questionId";
+                Database::query($sql);
+            } else {
+                $params = [
+                    'c_id' => api_get_course_int_id(),
+                    'survey_id' => $surveyData['iid'],
+                    'survey_question' => $formattedDate,
+                    'survey_question_comment' => '',
+                    'type' => 'doodle',
+                    'display' => 'horizontal',
+                    'sort' => $counter,
+                    'shared_question_id' => '0',
+                    'max_value' => 0,
+                ];
+                $questionId = Database::insert($questionTable, $params);
+                if ($questionId) {
+                    $sql = "UPDATE $questionTable SET question_id = $questionId
+                            WHERE iid = $questionId";
+                    Database::query($sql);
+                }
+                $counter++;
+            }
+        }
+
+        foreach ($deleteItems as $deleteId) {
+            SurveyManager::delete_survey_question($surveyData['iid'], $deleteId);
+        }
+    }
+
+    // Redirecting to the survey page (whilst showing the return message)
+    header('Location: '.api_get_path(WEB_CODE_PATH).'survey/survey_list.php?'.api_get_cidreq());
+    exit;
+} else {
+    // Displaying the header
+    Display::display_header($tool_name);
+    $form->display();
+}
+
+Display::display_footer();

+ 15 - 13
main/survey/fillsurvey.php

@@ -109,8 +109,8 @@ if ($invitationcode == 'auto' && isset($_GET['scode'])) {
     $result = Database::query($sql);
     if (Database :: num_rows($result) > 0) {
         // Check availability
-        $row = Database :: fetch_array($result, 'ASSOC');
-        $tempdata = SurveyManager :: get_survey($row['survey_id']);
+        $row = Database::fetch_array($result, 'ASSOC');
+        $tempdata = SurveyManager::get_survey($row['survey_id']);
         SurveyManager::checkTimeAvailability($tempdata);
         // Check for double invitation records (insert should be done once)
         $sql = "SELECT user
@@ -166,8 +166,6 @@ $logInfo = [
     'tool_id_detail' => 0,
     'action' => 'invitationcode',
     'action_details' => $invitationcode,
-    'current_id' => 0,
-    'info' => '',
 ];
 Event::registerLog($logInfo);
 
@@ -184,7 +182,7 @@ if (Database::num_rows($result) > 1) {
     if ($_POST['language']) {
         $survey_invitation['survey_id'] = $_POST['language'];
     } else {
-        Display :: display_header(get_lang('ToolSurvey'));
+        Display::display_header(get_lang('ToolSurvey'));
         $frmLangUrl = api_get_self().'?'.api_get_cidreq().'&'
             .http_build_query([
                 'course' => Security::remove_XSS($_GET['course']),
@@ -225,6 +223,10 @@ if ($survey_data['survey_type'] == '3') {
     exit;
 }
 
+if (!empty($survey_data['anonymous'])) {
+    define('USER_IN_ANON_SURVEY', true);
+}
+
 // Storing the answers
 if (count($_POST) > 0) {
     if ($survey_data['survey_type'] === '0') {
@@ -526,7 +528,7 @@ if ($survey_data['form_fields'] != '' &&
     // the $jquery_ready_content variable collects all functions
     // that will be load in the $(document).ready javascript function
     $htmlHeadXtra[] = '<script>
-    $(document).ready(function(){
+    $(function() {
         '.$jquery_ready_content.'
     });
     </script>';
@@ -635,7 +637,7 @@ if (isset($_POST['finish_survey'])) {
         $survey_invitation['c_id']
     );
 
-    if ($courseInfo) {
+    if ($courseInfo && !api_is_anonymous()) {
         echo Display::toolbarButton(
             get_lang('ReturnToCourseHomepage'),
             api_get_course_url($courseInfo['code']),
@@ -663,7 +665,7 @@ if ((isset($_GET['show']) && $_GET['show'] != '') ||
     // As long as there is no pagebreak fount we keep adding questions to the page
     $questions_displayed = [];
     $counter = 0;
-    // If non-conditional survey
+    //$paged_questions = Session::read('paged_questions');
     $paged_questions = [];
     // If non-conditional survey
     if ($survey_data['survey_type'] == '0') {
@@ -749,9 +751,9 @@ if ((isset($_GET['show']) && $_GET['show'] != '') ||
                             ".($allowRequiredSurveyQuestions ? ', survey_question.is_required' : '')."
                         FROM $table_survey_question survey_question
                         LEFT JOIN $table_survey_question_option survey_question_option
-                            ON survey_question.question_id = survey_question_option.question_id AND
+                        ON survey_question.question_id = survey_question_option.question_id AND
                             survey_question_option.c_id = $course_id
-                        WHERE
+                        WHERE                        
                             survey_question NOT LIKE '%{{%' AND
                             survey_question.survey_id = '".intval($survey_invitation['survey_id'])."' AND
                             survey_question.question_id IN (".implode(',', $paged_questions[$_GET['show']]).") AND
@@ -816,7 +818,7 @@ if ((isset($_GET['show']) && $_GET['show'] != '') ||
                 $answer_list['group'] = $row['survey_group_pri'];
                 $results[] = $answer_list;
             }
-            //echo '<br />'; print_r($results); echo '<br />';
+
             // Get the total score for each group of questions
             $totals = [];
             $sql = "SELECT SUM(temp.value) as value, temp.survey_group_pri FROM
@@ -1188,7 +1190,7 @@ if ((isset($_GET['show']) && $_GET['show'] != '') ||
 }
 
 $numberOfPages = SurveyManager::getCountPages($survey_data);
-// Selecting the maximum number of pages
+
 // Displaying the form with the questions
 $show = 0;
 if (isset($_GET['show']) && $_GET['show'] != '') {
@@ -1347,7 +1349,7 @@ if ($survey_data['survey_type'] == '0') {
         }
     }
 } elseif ($survey_data['survey_type'] == '1') {
-    //conditional/personality-test type survey
+    // Conditional/personality-test type survey
     if (isset($_GET['show']) || isset($_POST['personality'])) {
         $numberOfPages = count($paged_questions);
         if (!empty($paged_questions_sec) && count($paged_questions_sec) > 0) {

+ 1 - 1
main/survey/link.php

@@ -40,7 +40,7 @@ if ($hashIsValid && $courseInfo) {
     $invitation_id = SurveyUtil::save_invitation($params);
 
     if ($invitation_id) {
-        $link = api_get_path(WEB_CODE_PATH).'survey/fillsurvey.php?invitationcode='.$invitation_code.'&course='.$courseInfo['code'].'&id_session='.$sessionId;
+        $link = SurveyUtil::generateFillSurveyLink($invitation_code, $courseInfo['code'], $sessionId);
         header('Location: '.$link);
         exit;
     }

+ 28 - 46
main/survey/meeting.php

@@ -14,7 +14,7 @@ $surveyId = isset($_REQUEST['survey_id']) ? (int) $_REQUEST['survey_id'] : 0;
 $invitationcode = isset($_REQUEST['invitationcode']) ? Database::escape_string($_REQUEST['invitationcode']) : 0;
 $action = isset($_REQUEST['action']) ? $_REQUEST['action'] : '';
 
-if (!api_is_allowed_to_edit() || !empty($invitationcode)) {
+if (!empty($invitationcode) || !api_is_allowed_to_edit()) {
     $table_survey_invitation = Database::get_course_table(TABLE_SURVEY_INVITATION);
     $table_survey = Database::get_course_table(TABLE_SURVEY);
 
@@ -38,26 +38,6 @@ if (!api_is_allowed_to_edit() || !empty($invitationcode)) {
     $surveyId = $result['iid'];
 }
 
-// getting all the students of the course
-/*if (empty($sessionId)) {
-    // Registered students in a course outside session.
-    $students = CourseManager:: get_student_list_from_course_code(
-        api_get_course_id(),
-        false,
-        0,
-        null,
-        null,
-        true
-    );
-} else {
-    // Registered students in session.
-    $students = CourseManager:: get_student_list_from_course_code(
-        api_get_course_id(),
-        true,
-        $sessionId
-    );
-}*/
-
 $surveyData = SurveyManager::get_survey($surveyId);
 
 if (empty($surveyData)) {
@@ -154,8 +134,16 @@ foreach ($questions as $item) {
     $date = explode(' ', $startDateTime);
     $endDate = explode(' ', $endDateTime);
     $mainDate = $date[0];
-    $startTime = $date[1];
-    $endTime = $endDate[1];
+
+    $startTime = isset($date[1]) && !empty($date[1]) ? sprintf(get_lang('FromTimeX'), $date[1]) : '';
+    $endTime = isset($endDate[1]) && !empty($endDate[1]) ? sprintf(get_lang('ToTimeX'), $endDate[1]) : '';
+
+    if (isset($date[1]) && isset($endDate[1])) {
+        if ($date[1] === $endDate[1]) {
+            $startTime = '';
+            $endTime = '';
+        }
+    }
 
     $mainDate = api_format_date($mainDate, DATE_FORMAT_SHORT);
     $table->setHeaderContents($row, $column, "<h4>$mainDate</h4> $startTime <br >$endTime");
@@ -280,29 +268,23 @@ if ($action === 'edit') {
     $content .= '</form>';
 }
 
-/*$ajaxUrl = api_get_path(WEB_AJAX_PATH).
-    'survey.ajax.php?a=save_question&'.api_get_cidreq().'&survey_id='.$surveyId.'&question_id=';
-
-$content .= '<script>
-$(function() {
-    $(".question").on("change", function() {
-        var questionId = $(this).attr("id");
-        var status = 0;
-        if ($(this).prop("checked")) {
-            status = 1;
-        }
-        $.ajax({
-            url: "'.$ajaxUrl.'" + questionId + "&status=" + status,
-            success: function (data) {
-                $("#ajax_result").html("<div class=\"alert alert-info\">"+ "'.get_lang('Saved').'" + "</div>");
-                $("#ajax_result").show();
-                return;
-            },
-        });
-    });
-
-});
-</script>';*/
+$actions = '';
+if (api_is_allowed_to_edit()) {
+    $actions .= Display::url(
+        Display::return_icon('edit.png', get_lang('EditSurvey'), '', ICON_SIZE_MEDIUM),
+        api_get_path(WEB_CODE_PATH).'survey/edit_meeting.php?'.api_get_cidreq().'&action=edit&survey_id='.$surveyId
+    );
+    $actions .= Display::url(
+        Display::return_icon('delete.png', get_lang('DeleteSurvey'), '', ICON_SIZE_MEDIUM),
+        api_get_path(WEB_CODE_PATH).'survey/survey_list.php?'.api_get_cidreq().'&action=delete&survey_id='.$surveyId,
+        ['onclick' => 'javascript:if(!confirm(\''.addslashes(api_htmlentities(get_lang('DeleteSurvey').'?', ENT_QUOTES)).'\')) return false;']
+    );
+    $actions .= Display::url(
+        Display::return_icon('mail_send.png', get_lang('Publish'), '', ICON_SIZE_MEDIUM),
+        api_get_path(WEB_CODE_PATH).'survey/survey_invite.php?'.api_get_cidreq().'&survey_id='.$surveyId
+    );
+}
 
+$template->assign('actions', Display::toolbarAction('toolbar', [$actions]));
 $template->assign('content', $content);
 $template->display_one_col_template();

+ 7 - 2
main/survey/pending.php

@@ -10,7 +10,7 @@ $cidReset = true;
 
 require_once __DIR__.'/../inc/global.inc.php';
 
-api_block_anonymous_users(true);
+api_block_anonymous_users();
 
 $em = Database::getManager();
 
@@ -36,13 +36,18 @@ foreach ($pending as $i => $item) {
 
     $course = $course ? ['id' => $course->getId(), 'title' => $course->getTitle(), 'code' => $course->getCode()] : null;
     $session = $session ? ['id' => $session->getId(), 'name' => $session->getName()] : null;
+    $courseInfo = api_get_course_info_by_id($course->getId());
     $surveysData[$survey->getSurveyId()] = [
         'title' => $survey->getTitle(),
-        'invitation_code' => $invitation->getInvitationCode(),
         'avail_from' => $survey->getAvailFrom(),
         'avail_till' => $survey->getAvailTill(),
         'course' => $course,
         'session' => $session,
+        'link' => SurveyUtil::generateFillSurveyLink(
+            $invitation->getInvitationCode(),
+            $courseInfo,
+            $survey->getSessionId()
+        ),
     ];
 }
 

+ 8 - 4
main/survey/preview.php

@@ -29,6 +29,8 @@ $surveyId = (int) $_GET['survey_id'];
 $userInvited = 0;
 $userAnonymous = 0;
 
+$allowRequiredSurveyQuestions = api_get_configuration_value('allow_required_survey_questions');
+
 //query to ask if logged user is allowed to see the preview (if he is invited of he is a teacher)
 $sql = "SELECT survey_invitation.user
         FROM $table_survey_invitation survey_invitation
@@ -84,7 +86,7 @@ if ($surveyAnonymous == 0 && api_is_anonymous()) {
     }
 }
 $show = 0;
-// Header
+
 Display::display_header(get_lang('SurveyPreview'));
 
 // We exit here is the first or last question is a pagebreak (which causes errors)
@@ -112,7 +114,7 @@ if (api_is_course_admin() ||
     if (isset($_POST['finish_survey'])) {
         echo Display::return_message(get_lang('SurveyFinished'), 'confirm');
         echo $survey_data['survey_thanks'];
-        Display :: display_footer();
+        Display::display_footer();
         exit;
     }
 
@@ -127,7 +129,7 @@ if (api_is_course_admin() ||
         $sql = "SELECT * FROM $table_survey_question
                 WHERE
                   survey_question NOT LIKE '%{{%' AND 
-                  c_id = $course_id AND 
+                  c_id = $course_id AND
                   survey_id = '".$surveyId."'
                 ORDER BY sort ASC";
         $result = Database::query($sql);
@@ -164,6 +166,7 @@ if (api_is_course_admin() ||
                         survey_question_option.question_option_id,
                         survey_question_option.option_text,
                         survey_question_option.sort as option_sort
+                        ".($allowRequiredSurveyQuestions ? ', survey_question.is_required' : '')."
                     FROM $table_survey_question survey_question
                     LEFT JOIN $table_survey_question_option survey_question_option
                     ON
@@ -189,6 +192,7 @@ if (api_is_course_admin() ||
                     //$questions[$sort]['options'][intval($row['option_sort'])] = $row['option_text'];
                     $questions[$sort]['options'][$row['question_option_id']] = $row['option_text'];
                     $questions[$sort]['maximum_score'] = $row['max_value'];
+                    $questions[$row['sort']]['is_required'] = $allowRequiredSurveyQuestions && $row['is_required'];
                 } else {
                     // If the type is a pagebreak we are finished loading the questions for this page
                     //break;
@@ -204,8 +208,8 @@ if (api_is_course_admin() ||
     }
 
     $numberOfPages = SurveyManager::getCountPages($survey_data);
-    // Selecting the maximum number of pages
 
+    // Displaying the form with the questions
     if (isset($_GET['show'])) {
         $show = (int) $_GET['show'] + 1;
     }

+ 16 - 16
main/survey/question.php

@@ -11,7 +11,7 @@
 require_once __DIR__.'/../inc/global.inc.php';
 
 $htmlHeadXtra[] = '<script>
-$(document).ready( function() {
+$(function() {
     $("button").click(function() {
         $("#is_executable").attr("value",$(this).attr("name"));
     });
@@ -100,7 +100,7 @@ if (!in_array($_GET['type'], $possible_types)) {
     Display :: display_header($tool_name, 'Survey');
     echo $actions;
     echo Display::return_message(get_lang('TypeDoesNotExist'), 'error', false);
-    Display :: display_footer();
+    Display::display_footer();
 }
 
 $error_message = '';
@@ -117,21 +117,21 @@ $formData['answers'] = ['', ''];
 
 switch ($_GET['type']) {
     case 'yesno':
-    $formData['answers'][0] = get_lang('Yes');
-    $formData['answers'][1] = get_lang('No');
+        $formData['answers'][0] = get_lang('Yes');
+        $formData['answers'][1] = get_lang('No');
         break;
     case 'personality':
-    $formData['answers'][0] = 1;
-    $formData['answers'][1] = 2;
-    $formData['answers'][2] = 3;
-    $formData['answers'][3] = 4;
-    $formData['answers'][4] = 5;
-
-    $formData['values'][0] = 0;
-    $formData['values'][1] = 0;
-    $formData['values'][2] = 1;
-    $formData['values'][3] = 2;
-    $formData['values'][4] = 3;
+        $formData['answers'][0] = 1;
+        $formData['answers'][1] = 2;
+        $formData['answers'][2] = 3;
+        $formData['answers'][3] = 4;
+        $formData['answers'][4] = 5;
+
+        $formData['values'][0] = 0;
+        $formData['values'][1] = 0;
+        $formData['values'][2] = 1;
+        $formData['values'][3] = 2;
+        $formData['values'][4] = 3;
         break;
     case 'open':
         Display::addFlash(Display::return_message(get_lang('QuestionTags')));
@@ -155,4 +155,4 @@ if ($surveyQuestion->getForm()->validate()) {
 
 Display::display_header($tool_name, 'Survey');
 echo $surveyQuestion->getForm()->returnForm();
-Display :: display_footer();
+Display::display_footer();

+ 3 - 10
main/survey/reporting.php

@@ -61,20 +61,13 @@ $format = isset($_REQUEST['export_format']) ? $_REQUEST['export_format'] : '';
 if (!empty($exportReport) && !empty($format)) {
     switch ($format) {
         case 'xls':
-            $filename = 'survey_results_'.$survey_id;
-            $data = SurveyUtil::export_complete_report_xls(
-                $survey_data,
-                $filename,
-                $userId
-            );
+            $filename = 'survey_results_'.$survey_id.'.xlsx';
+            $data = SurveyUtil::export_complete_report_xls($survey_data, $filename, $userId);
             exit;
             break;
         case 'csv':
         default:
-            $data = SurveyUtil::export_complete_report(
-                $survey_data,
-                $userId
-            );
+            $data = SurveyUtil::export_complete_report($survey_data, $userId);
             $filename = 'survey_results_'.$survey_id.'.csv';
             header('Content-type: application/octet-stream');
             header('Content-Type: application/force-download');

+ 77 - 47
main/survey/survey.lib.php

@@ -139,14 +139,15 @@ class SurveyManager
         $course_code = '',
         $simple_return = false
     ) {
+        $my_course_id = api_get_course_id();
+
         // Table definition
         if (!empty($course_code)) {
             $my_course_id = $course_code;
         } elseif (isset($_GET['course'])) {
             $my_course_id = Security::remove_XSS($_GET['course']);
-        } else {
-            $my_course_id = api_get_course_id();
         }
+
         $courseInfo = api_get_course_info($my_course_id);
         $survey_id = (int) $survey_id;
         $table_survey = Database::get_course_table(TABLE_SURVEY);
@@ -235,8 +236,8 @@ class SurveyManager
             $sql = 'SELECT 1 FROM '.$table_survey.'
 			        WHERE
 			            c_id = '.$course_id.' AND
-			            code="'.Database::escape_string($values['survey_code']).'" AND
-			            lang="'.Database::escape_string($values['survey_language']).'"';
+			            code = "'.Database::escape_string($values['survey_code']).'" AND
+			            lang = "'.Database::escape_string($values['survey_language']).'"';
             $rs = Database::query($sql);
             if (Database::num_rows($rs) > 0) {
                 Display::addFlash(
@@ -287,6 +288,9 @@ class SurveyManager
                 $survey->setFormFields(0);
             }
 
+            $extraParams['one_question_per_page'] = isset($values['one_question_per_page']) ? $values['one_question_per_page'] : 0;
+            $extraParams['shuffle'] = isset($values['shuffle']) ? $values['shuffle'] : 0;
+
             if ($values['survey_type'] == 1) {
                 $survey
                     ->setSurveyType(1)
@@ -429,8 +433,8 @@ class SurveyManager
             $extraParams['shuffle'] = isset($values['shuffle']) ? $values['shuffle'] : null;
 
             if ($values['anonymous'] == 0) {
-                $extraParams['show_form_profile'] = $values['show_form_profile'];
-                if ($values['show_form_profile'] == 1) {
+                $extraParams['show_form_profile'] = isset($values['show_form_profile']) ? $values['show_form_profile'] : '';
+                if ($extraParams['show_form_profile'] == 1) {
                     $fields = explode(',', $values['input_name_list']);
                     $field_values = '';
                     foreach ($fields as &$field) {
@@ -503,7 +507,7 @@ class SurveyManager
             $return['id'] = $values['survey_id'];
         }
 
-        $survey_id = intval($return['id']);
+        $survey_id = (int) $return['id'];
 
         // Gradebook
         $gradebook_option = false;
@@ -739,7 +743,7 @@ class SurveyManager
         }
 
         $sql = "SELECT * FROM $table_survey_question_group
-                WHERE c_id = $course_id AND  survey_id='".$survey_id."'";
+                WHERE c_id = $course_id AND survey_id='".$survey_id."'";
         $res = Database::query($sql);
         while ($row = Database::fetch_array($res, 'ASSOC')) {
             $params = [
@@ -776,6 +780,13 @@ class SurveyManager
                 'survey_group_sec1' => $row['survey_group_sec1'],
                 'survey_group_sec2' => $row['survey_group_sec2'],
             ];
+
+            if (api_get_configuration_value('allow_required_survey_questions')) {
+                if (isset($row['is_required'])) {
+                    $params['is_required'] = $row['is_required'];
+                }
+            }
+
             $insertId = Database::insert($table_survey_question, $params);
             if ($insertId) {
                 $sql = "UPDATE $table_survey_question SET question_id = iid WHERE iid = $insertId";
@@ -870,25 +881,28 @@ class SurveyManager
      */
     public static function update_survey_answered($survey_data, $user, $survey_code)
     {
+        if (empty($survey_data)) {
+            return false;
+        }
+
         // Database table definitions
         $table_survey = Database::get_course_table(TABLE_SURVEY);
         $table_survey_invitation = Database::get_course_table(TABLE_SURVEY_INVITATION);
 
-        $survey_id = $survey_data['survey_id'];
-        $course_id = $survey_data['c_id'];
+        $survey_id = (int) $survey_data['survey_id'];
+        $course_id = (int) $survey_data['c_id'];
         $session_id = $survey_data['session_id'];
 
         // Getting a list with all the people who have filled the survey
-        $people_filled = self::get_people_who_filled_survey($survey_id, false, $course_id);
-
-        $number = count($people_filled);
+        /*$people_filled = self::get_people_who_filled_survey($survey_id, false, $course_id);
+        $number = count($people_filled);*/
 
         // Storing this value in the survey table
         $sql = "UPDATE $table_survey
-		        SET answered = $number
+		        SET answered = answered + 1
 		        WHERE
                     c_id = $course_id AND
-		            survey_id = ".intval($survey_id);
+		            survey_id = ".$survey_id;
         Database::query($sql);
 
         $allow = api_get_configuration_value('survey_answered_at_field');
@@ -1391,7 +1405,7 @@ class SurveyManager
 
         $sql = "UPDATE $table_survey_question 
                 SET sort = '".Database::escape_string($question_sort_two)."'
-		        WHERE c_id = $course_id AND  question_id='".intval($question_id_one)."'";
+		        WHERE c_id = $course_id AND question_id='".intval($question_id_one)."'";
         Database::query($sql);
 
         $sql = "UPDATE $table_survey_question 
@@ -1455,7 +1469,10 @@ class SurveyManager
      */
     public static function delete_survey_question($survey_id, $question_id, $shared = false)
     {
+        $survey_id = (int) $survey_id;
+        $question_id = (int) $question_id;
         $course_id = api_get_course_int_id();
+
         if ($shared) {
             self::delete_shared_survey_question($survey_id, $question_id);
         }
@@ -1466,8 +1483,8 @@ class SurveyManager
         $sql = "DELETE FROM $table
 		        WHERE
 		            c_id = $course_id AND
-		            survey_id='".intval($survey_id)."' AND
-		            question_id='".intval($question_id)."'";
+		            survey_id='".$survey_id."' AND
+		            question_id='".$question_id."'";
         Database::query($sql);
 
         // Deleting the options of the question of the survey
@@ -1703,7 +1720,7 @@ class SurveyManager
         $table = Database::get_course_table(TABLE_SURVEY_ANSWER);
         $survey_id = (int) $survey_id;
         $sql = "DELETE FROM $table 
-                WHERE c_id = $course_id AND survey_id=$survey_id";
+                WHERE c_id = $course_id AND survey_id = $survey_id";
         Database::query($sql);
 
         return true;
@@ -1786,6 +1803,15 @@ class SurveyManager
         } else {
             $sql = "SELECT DISTINCT user FROM $table_survey_answer
 			        WHERE c_id = $course_id AND survey_id= '".$survey_id."'  ";
+
+            if (api_get_configuration_value('survey_anonymous_show_answered')) {
+                $tblInvitation = Database::get_course_table(TABLE_SURVEY_INVITATION);
+                $tblSurvey = Database::get_course_table(TABLE_SURVEY);
+
+                $sql = "SELECT i.user FROM $tblInvitation i
+                    INNER JOIN $tblSurvey s ON i.survey_code = s.code
+                    WHERE i.answered IS TRUE AND s.iid = $survey_id";
+            }
         }
 
         $res = Database::query($sql);
@@ -1932,16 +1958,17 @@ class SurveyManager
             return;
         }
 
-        $urlParams = http_build_query([
-            'course' => api_get_course_id(),
-            'invitationcode' => $invitation->getInvitationCode(),
-        ]);
-
         Display::addFlash(
             Display::return_message(get_lang('MandatorySurveyNoAnswered'), 'warning')
         );
 
-        header('Location: '.api_get_path(WEB_CODE_PATH).'survey/fillsurvey.php?'.$urlParams.'&'.api_get_cidreq());
+        $url = SurveyUtil::generateFillSurveyLink(
+            $invitation->getInvitationCode(),
+            api_get_course_info(),
+            api_get_session_id()
+        );
+
+        header('Location: '.$url);
         exit;
     }
 
@@ -2066,6 +2093,13 @@ class SurveyManager
                     'survey_group_sec1' => $row['survey_group_sec1'],
                     'survey_group_sec2' => $row['survey_group_sec2'],
                 ];
+
+                if (api_get_configuration_value('allow_required_survey_questions')) {
+                    if (isset($row['is_required'])) {
+                        $params['is_required'] = $row['is_required'];
+                    }
+                }
+
                 $insertId = Database::insert($surveyQuestionTable, $params);
                 if ($insertId) {
                     $sql = "UPDATE $surveyQuestionTable 
@@ -2246,43 +2280,39 @@ class SurveyManager
 
         $table = Database::get_course_table(TABLE_SURVEY_QUESTION);
 
-        // One question per page
-        $sql = "SELECT * FROM $table
+        // pagebreak
+        $sql = "SELECT COUNT(iid) FROM $table
                 WHERE
                     survey_question NOT LIKE '%{{%' AND
                     type = 'pagebreak' AND
                     c_id = $courseId AND
                     survey_id = $surveyId";
         $result = Database::query($sql);
-        $numberPageBreaks = Database::num_rows($result);
+        $numberPageBreaks = Database::result($result, 0, 0);
 
-        // One question per page
-        $sql = "SELECT * FROM $table
-                    WHERE
-                        survey_question NOT LIKE '%{{%' AND
-                        type != 'pagebreak' AND
-                        c_id = $courseId AND
+        // No pagebreak
+        $sql = "SELECT COUNT(iid) FROM $table
+                WHERE
+                    survey_question NOT LIKE '%{{%' AND
+                    type != 'pagebreak' AND
+                    c_id = $courseId AND
                     survey_id = $surveyId";
         $result = Database::query($sql);
-        $countOfQuestions = Database::num_rows($result);
-        $count = 1;
-        if (!empty($numberPageBreaks) && !empty($countOfQuestions)) {
-            $count = $countOfQuestions;
-        }
+        $countOfQuestions = Database::result($result, 0, 0);
 
         if ($survey['one_question_per_page'] == 1) {
-            $count = 1;
             if (!empty($countOfQuestions)) {
-                $count = $countOfQuestions;
-            }
-        } else {
-            $count = $numberPageBreaks * 2;
-            if (empty($numberPageBreaks)) {
-                $count = 1;
+                return $countOfQuestions;
             }
+
+            return 1;
+        }
+
+        if (empty($numberPageBreaks)) {
+            return 1;
         }
 
-        return $count;
+        return $numberPageBreaks + 1;
     }
 
     /**

+ 7 - 5
main/survey/survey.php

@@ -159,6 +159,8 @@ if ($survey_data['survey_type'] != 3) {
     }
 }
 
+$survey_actions .= SurveyUtil::getAdditionalTeacherActions($survey_id, ICON_SIZE_MEDIUM);
+
 echo '<div class="actions">'.$survey_actions.'</div>';
 
 $urlQuestion = api_get_path(WEB_CODE_PATH).'survey/question.php?'.api_get_cidreq().'&action=add';
@@ -374,11 +376,11 @@ if ($is_survey_type_1) {
     $rs = Database::query($sql);
     while ($row = Database::fetch_array($rs, 'ASSOC')) {
         $grouplist .= '<tr><td>'.$row['name'].'</td><td>'.$row['description'].'</td><td>'.
-            '<a href="'.api_get_path(WEB_CODE_PATH).'survey/survey.php?survey_id='.$survey_id.'&gid='.$row['id'].'&action=editgroup">'.
-            Display::return_icon('edit.png', get_lang('Edit'), '', ICON_SIZE_SMALL).'</a> '.
-            '<a href="'.api_get_path(WEB_CODE_PATH).'survey/survey.php?survey_id='.$survey_id.'&gid='.$row['id'].'&action=deletegroup" onclick="javascript:if(!confirm(\''.addslashes(api_htmlentities(sprintf(get_lang('DeleteSurveyGroup'), $row['name']).'?', ENT_QUOTES)).'\')) return false;">'.
-            Display::return_icon('delete.png', get_lang('Delete'), '', ICON_SIZE_SMALL).'</a>'.
-            '</td></tr>';
+        '<a href="'.api_get_path(WEB_CODE_PATH).'survey/survey.php?survey_id='.$survey_id.'&gid='.$row['id'].'&action=editgroup">'.
+        Display::return_icon('edit.png', get_lang('Edit'), '', ICON_SIZE_SMALL).'</a> '.
+        '<a href="'.api_get_path(WEB_CODE_PATH).'survey/survey.php?survey_id='.$survey_id.'&gid='.$row['id'].'&action=deletegroup" onclick="javascript:if(!confirm(\''.addslashes(api_htmlentities(sprintf(get_lang('DeleteSurveyGroup'), $row['name']).'?', ENT_QUOTES)).'\')) return false;">'.
+        Display::return_icon('delete.png', get_lang('Delete'), '', ICON_SIZE_SMALL).'</a>'.
+        '</td></tr>';
     }
     echo $grouplist.'</table>';
 }

+ 177 - 68
main/survey/surveyUtil.class.php

@@ -26,10 +26,11 @@ class SurveyUtil
         // Table definitions
         $tbl_survey_question = Database::get_course_table(TABLE_SURVEY_QUESTION);
         $course_id = api_get_course_int_id();
+        $survey_id = (int) $survey_id;
 
         // Getting the information of the question
         $sql = "SELECT * FROM $tbl_survey_question
-                WHERE c_id = $course_id AND survey_id='".Database::escape_string($survey_id)."'
+                WHERE c_id = $course_id AND survey_id='".$survey_id."'
                 ORDER BY sort ASC";
         $result = Database::query($sql);
         $total = Database::num_rows($result);
@@ -107,6 +108,7 @@ class SurveyUtil
         if (empty($question_id)) {
             return false;
         }
+
         // Table definition
         $table_survey_answer = Database::get_course_table(TABLE_SURVEY_ANSWER);
 
@@ -709,7 +711,7 @@ class SurveyUtil
                         echo ' </tr>';
                     }
                 }
-                // displaying the table: footer (totals)
+
                 $optionResult = '';
                 if (isset($option['question_id']) && isset($number_of_answers[$option['question_id']])) {
                     if ($number_of_answers[$option['question_id']] == 0) {
@@ -881,7 +883,7 @@ class SurveyUtil
         $table_survey_question = Database::get_course_table(TABLE_SURVEY_QUESTION);
         $table_survey_question_option = Database::get_course_table(TABLE_SURVEY_QUESTION_OPTION);
         $table_survey_answer = Database::get_course_table(TABLE_SURVEY_ANSWER);
-
+        $course_id = api_get_course_int_id();
         $surveyId = isset($_GET['survey_id']) ? (int) $_GET['survey_id'] : 0;
         $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
 
@@ -956,7 +958,6 @@ class SurveyUtil
             }
         }
 
-        $course_id = api_get_course_int_id();
         $sql = "SELECT 
                   q.question_id, 
                   q.type, 
@@ -968,7 +969,7 @@ class SurveyUtil
 				WHERE 
 				    survey_question NOT LIKE '%{{%' AND
 				    q.survey_id = '".$surveyId."' AND
-				    q.c_id = $course_id
+				    q.c_id = $course_id 
 				GROUP BY q.question_id
 				ORDER BY q.sort ASC";
         $result = Database::query($sql);
@@ -1042,7 +1043,7 @@ class SurveyUtil
             if (!(isset($_POST['submit_question_filter']) && $_POST['submit_question_filter']) ||
                 (is_array($_POST['questions_filter']) && in_array($row['question_id'], $_POST['questions_filter']))
             ) {
-                // <hub> modif 05-05-2010
+                // we do not show comment and pagebreak question types
                 if ($row['type'] == 'open' || $row['type'] == 'comment') {
                     echo '<th>&nbsp;-&nbsp;</th>';
                     $possible_answers[$row['question_id']][$row['question_option_id']] = $row['question_option_id'];
@@ -1229,6 +1230,8 @@ class SurveyUtil
             return false;
         }
 
+        $course_id = api_get_course_int_id();
+
         $table_survey_question = Database::get_course_table(TABLE_SURVEY_QUESTION);
         $table_survey_question_option = Database::get_course_table(TABLE_SURVEY_QUESTION_OPTION);
         $table_survey_answer = Database::get_course_table(TABLE_SURVEY_ANSWER);
@@ -1248,7 +1251,6 @@ class SurveyUtil
 
         $num = count($extra_user_fields);
         $return .= str_repeat(';', $num);
-        $course_id = api_get_course_int_id();
 
         $sql = "SELECT
                     questions.question_id,
@@ -1326,10 +1328,10 @@ class SurveyUtil
                     survey_question_option.sort as option_sort
 				FROM $table_survey_question survey_question
 				LEFT JOIN $table_survey_question_option survey_question_option
-				ON
+				ON	    
 				    survey_question.question_id = survey_question_option.question_id AND
 				    survey_question_option.c_id = survey_question.c_id
-				WHERE
+				WHERE				
 				    survey_question NOT LIKE '%{{%' AND			
 				    survey_question.type <> 'pagebreak' AND				    
 				    survey_question.survey_id = $surveyId AND
@@ -2320,14 +2322,9 @@ class SurveyUtil
             (!empty($params['user']) || !empty($params['group_id'])) &&
             !empty($params['survey_code'])
         ) {
-            $params['survey_invitation_id'] = $params['survey_invitation_id'] ?? 0;
-            $params['reminder_date'] = $params['reminder_date'] ?? null;
-            $params['answered'] = $params['answered'] ?? 0;
-            $params['group_id'] = $params['group_id'] ?? 0;
-
             $insertId = Database::insert($table, $params);
             if ($insertId) {
-                $sql = "UPDATE $table 
+                $sql = "UPDATE $table
                         SET survey_invitation_id = $insertId
                         WHERE iid = $insertId";
                 Database::query($sql);
@@ -2350,9 +2347,9 @@ class SurveyUtil
     public static function invitationExists($courseId, $sessionId, $groupId, $surveyCode)
     {
         $table = Database::get_course_table(TABLE_SURVEY_INVITATION);
-        $courseId = intval($courseId);
-        $sessionId = intval($sessionId);
-        $groupId = intval($groupId);
+        $courseId = (int) $courseId;
+        $sessionId = (int) $sessionId;
+        $groupId = (int) $groupId;
         $surveyCode = Database::escape_string($surveyCode);
 
         $sql = "SELECT survey_invitation_id FROM $table
@@ -2384,11 +2381,10 @@ class SurveyUtil
         $sessionId = api_get_session_id();
 
         // Replacing the **link** part with a valid link for the user
-        $link = api_get_path(WEB_CODE_PATH).'survey/fillsurvey.php?';
-        $link .= 'id_session='.$sessionId.'&course='.$_course['code'].'&invitationcode='.$invitation_code;
+        $link = self::generateFillSurveyLink($invitation_code, $_course, $sessionId);
 
         $text_link = '<a href="'.$link.'">'.get_lang('ClickHereToAnswerTheSurvey')."</a><br />\r\n<br />\r\n"
-            .get_lang('OrCopyPasteTheFollowingUrl')." <br />\r\n ".$link;
+            .get_lang('OrCopyPasteTheFollowingUrl')." <br /> \r\n <br /> \r\n ".$link;
 
         $replace_count = 0;
         $full_invitation_text = api_str_ireplace('**link**', $text_link, $invitation_text, $replace_count);
@@ -2424,14 +2420,13 @@ class SurveyUtil
                 null,
                 null,
                 null,
-                $sender_user_id
+                $sender_user_id,
+                true
             );
         } else {
-            /** @todo check if the address is a valid email */
-            $recipient_email = $invitedUser;
             @api_mail_html(
                 '',
-                $recipient_email,
+                $invitedUser,
                 $invitation_title,
                 $full_invitation_text,
                 $sender_name,
@@ -2457,6 +2452,10 @@ class SurveyUtil
      */
     public static function update_count_invited($survey_code, $courseId = 0, $sessionId = 0)
     {
+        $survey_code = Database::escape_string($survey_code);
+        $courseId = (int) $courseId;
+        $sessionId = (int) $sessionId;
+
         $courseId = $courseId ?: api_get_course_int_id();
         $sessionId = $sessionId ?: api_get_session_id();
         $sessionCondition = api_get_session_condition($sessionId);
@@ -2470,7 +2469,7 @@ class SurveyUtil
                 FROM $table_survey_invitation
 		        WHERE
 		            c_id = $courseId AND
-		            survey_code = '".Database::escape_string($survey_code)."' AND
+		            survey_code = '".$survey_code."' AND
 		            user <> ''
 		            $sessionCondition
                 ";
@@ -2483,7 +2482,7 @@ class SurveyUtil
 		        SET invited = '".Database::escape_string($total_invited)."'
 		        WHERE
 		            c_id = $courseId AND
-		            code = '".Database::escape_string($survey_code)."'
+		            code = '".$survey_code."'
 		            $sessionCondition
                 ";
         Database::query($sql);
@@ -2508,11 +2507,17 @@ class SurveyUtil
      */
     public static function get_invited_users($survey_code, $course_code = '', $session_id = 0)
     {
+        $session_id = (int) $session_id;
+        $survey_code = Database::escape_string($survey_code);
+        $course_code = Database::escape_string($course_code);
+
+        $course_id = api_get_course_int_id();
+
         if (!empty($course_code)) {
             $course_info = api_get_course_info($course_code);
-            $course_id = $course_info['real_id'];
-        } else {
-            $course_id = api_get_course_int_id();
+            if ($course_info) {
+                $course_id = $course_info['real_id'];
+            }
         }
 
         if (empty($session_id)) {
@@ -2528,7 +2533,7 @@ class SurveyUtil
 				FROM $table_survey_invitation as table_invitation
 				WHERE
 				    table_invitation.c_id = $course_id AND
-                    survey_code='".Database::escape_string($survey_code)."' AND
+                    survey_code='".$survey_code."' AND
                     session_id = $session_id
                 ";
 
@@ -2841,10 +2846,16 @@ class SurveyUtil
         if (api_is_allowed_to_edit() ||
             api_is_element_in_the_session(TOOL_SURVEY, $survey_id)
         ) {
+            $editUrl = $codePath.'survey/create_new_survey.php?'.
+                http_build_query($params + ['action' => 'edit', 'survey_id' => $survey_id]);
+            if ($survey->getSurveyType() == 3) {
+                $editUrl = $codePath.'survey/edit_meeting.php?'.
+                    http_build_query($params + ['action' => 'edit', 'survey_id' => $survey_id]);
+            }
+
             $actions[] = Display::url(
                 Display::return_icon('edit.png', get_lang('Edit')),
-                $codePath.'survey/create_new_survey.php?'
-                    .http_build_query($params + ['action' => 'edit', 'survey_id' => $survey_id])
+                $editUrl
             );
 
             if (SurveyManager::survey_generation_hash_available()) {
@@ -2863,7 +2874,7 @@ class SurveyUtil
                 $actions[] = Display::url(
                     Display::return_icon('copy.png', get_lang('DuplicateSurvey')),
                     $codePath.'survey/survey_list.php?'
-                        .http_build_query($params + ['action' => 'copy_survey', 'survey_id' => $survey_id])
+                    .http_build_query($params + ['action' => 'copy_survey', 'survey_id' => $survey_id])
                 );
 
                 $actions[] = Display::url(
@@ -2882,7 +2893,7 @@ class SurveyUtil
                 $actions[] = Display::url(
                     Display::return_icon('clean.png', get_lang('EmptySurvey')),
                     $codePath.'survey/survey_list.php?'
-                        .http_build_query($params + ['action' => 'empty', 'survey_id' => $survey_id]),
+                    .http_build_query($params + ['action' => 'empty', 'survey_id' => $survey_id]),
                     [
                         'onclick' => "javascript: if (!confirm('".$warning."')) return false;",
                     ]
@@ -2898,9 +2909,9 @@ class SurveyUtil
         }
 
         $actions[] = Display::url(
-                Display::return_icon('mail_send.png', get_lang('Publish')),
-                $codePath.'survey/survey_invite.php?'.http_build_query($params + ['survey_id' => $survey_id])
-            );
+            Display::return_icon('mail_send.png', get_lang('Publish')),
+            $codePath.'survey/survey_invite.php?'.http_build_query($params + ['survey_id' => $survey_id])
+        );
 
         if ($type != 3) {
             $actions[] = $hideReportingButton ? null : $reportingLink;
@@ -2909,13 +2920,15 @@ class SurveyUtil
         if (api_is_allowed_to_edit() ||
             api_is_element_in_the_session(TOOL_SURVEY, $survey_id)
         ) {
+            $actions[] = self::getAdditionalTeacherActions($survey_id);
+
             $warning = addslashes(api_htmlentities(get_lang('DeleteSurvey').'?', ENT_QUOTES));
             $actions[] = Display::url(
                 Display::return_icon('delete.png', get_lang('Delete')),
                 $codePath.'survey/survey_list.php?'
-                    .http_build_query($params + ['action' => 'delete', 'survey_id' => $survey_id]),
+                .http_build_query($params + ['action' => 'delete', 'survey_id' => $survey_id]),
                 [
-                    'onclick' => "javascript: if(!confirm('".$warning."')) return false;",
+                    'onclick' => "javascript: if (!confirm('".$warning."')) return false;",
                 ]
             );
         }
@@ -2923,6 +2936,33 @@ class SurveyUtil
         return implode(PHP_EOL, $actions);
     }
 
+    /**
+     * Get the additional actions added in survey_additional_teacher_modify_actions configuration.
+     *
+     * @param int $surveyId
+     * @param int $iconSize
+     *
+     * @return string
+     */
+    public static function getAdditionalTeacherActions($surveyId, $iconSize = ICON_SIZE_SMALL)
+    {
+        $additionalActions = api_get_configuration_value('survey_additional_teacher_modify_actions') ?: [];
+
+        if (empty($additionalActions)) {
+            return '';
+        }
+
+        $actions = [];
+        foreach ($additionalActions as $additionalAction) {
+            $actions[] = call_user_func(
+                $additionalAction,
+                ['survey_id' => $surveyId, 'icon_size' => $iconSize]
+            );
+        }
+
+        return implode(PHP_EOL, $actions);
+    }
+
     /**
      * @param int $survey_id
      *
@@ -2943,7 +2983,7 @@ class SurveyUtil
             Display::return_icon('mail_send.png', get_lang('Publish')),
             $codePath.'survey/survey_invite.php?'.http_build_query($params + ['survey_id' => $survey_id])
         );
-        $warning = addslashes(api_htmlentities(get_lang("EmptySurvey").'?', ENT_QUOTES));
+        $warning = addslashes(api_htmlentities(get_lang('EmptySurvey').'?', ENT_QUOTES));
         $actions[] = Display::url(
             Display::return_icon('clean.png', get_lang('EmptySurvey')),
             $codePath.'survey/survey_list.php?'
@@ -3113,9 +3153,9 @@ class SurveyUtil
             ON (survey.author = user.user_id)
             WHERE survey.c_id = $course_id
             $search_restriction
-            $condition_session 
+            $condition_session
             GROUP BY survey.survey_id
-            ORDER BY col$column $direction 
+            ORDER BY col$column $direction
             LIMIT $from,$number_of_items
         ";
         $res = Database::query($sql);
@@ -3135,9 +3175,9 @@ class SurveyUtil
                     );
                 } else {
                     $array[1] = Display::url(
-                    $survey[1],
-                    api_get_path(WEB_CODE_PATH).'survey/survey.php?survey_id='.$survey[0].'&'.api_get_cidreq()
-                );
+                        $survey[1],
+                        api_get_path(WEB_CODE_PATH).'survey/survey.php?survey_id='.$survey[0].'&'.api_get_cidreq()
+                    );
                 }
             }
 
@@ -3146,14 +3186,25 @@ class SurveyUtil
             $array[2] = $survey[2].$session_img;
             $array[3] = $survey[3];
             $array[4] = $survey[4];
-            $array[5] = api_convert_and_format_date(
-                $survey[5],
-                $allowSurveyAvailabilityDatetime ? DATE_TIME_FORMAT_LONG : DATE_FORMAT_LONG
-            );
-            $array[6] = api_convert_and_format_date(
-                $survey[6],
-                $allowSurveyAvailabilityDatetime ? DATE_TIME_FORMAT_LONG : DATE_FORMAT_LONG
-            );
+
+            // Dates
+            $array[5] = '';
+
+            if (!empty($survey[5]) && $survey[5] !== '0000-00-00' && $survey[5] !== '0000-00-00 00:00:00') {
+                $array[5] = api_convert_and_format_date(
+                    $survey[5],
+                    $allowSurveyAvailabilityDatetime ? DATE_TIME_FORMAT_LONG : DATE_FORMAT_LONG
+                );
+            }
+
+            $array[6] = '';
+            if (!empty($survey[6]) && $survey[6] !== '0000-00-00' && $survey[6] !== '0000-00-00 00:00:00') {
+                $array[6] = api_convert_and_format_date(
+                    $survey[6],
+                    $allowSurveyAvailabilityDatetime ? DATE_TIME_FORMAT_LONG : DATE_FORMAT_LONG
+                );
+            }
+
             $array[7] =
                 Display::url(
                     $survey['answered'],
@@ -3230,11 +3281,11 @@ class SurveyUtil
         $efv = new ExtraFieldValue('survey');
 
         $sql = "
-            SELECT 
-            survey.survey_id AS col0, 
-                survey.title AS col1, 
-                survey.code AS col2, 
-                count(survey_question.question_id) AS col3, 
+            SELECT
+            survey.survey_id AS col0,
+                survey.title AS col1,
+                survey.code AS col2,
+                count(survey_question.question_id) AS col3,
         "
             .(api_is_western_name_order()
                 ? "CONCAT(user.firstname, ' ', user.lastname)"
@@ -3300,7 +3351,6 @@ class SurveyUtil
         $allowSurveyAvailabilityDatetime = api_get_configuration_value('allow_survey_availability_datetime');
 
         // Database table definitions
-        $table_survey_question = Database::get_course_table(TABLE_SURVEY_QUESTION);
         $table_survey_invitation = Database::get_course_table(TABLE_SURVEY_INVITATION);
         $table_survey = Database::get_course_table(TABLE_SURVEY);
 
@@ -3321,16 +3371,16 @@ class SurveyUtil
         $filterDate = $allowSurveyAvailabilityDatetime ? $now->format('Y-m-d H:i') : $now->format('Y-m-d');
 
         $sql = "SELECT *
-                FROM $table_survey survey 
+                FROM $table_survey survey
                 INNER JOIN
                 $table_survey_invitation survey_invitation
                 ON (
                     survey.code = survey_invitation.survey_code AND
-                    survey.c_id = survey_invitation.c_id AND 
+                    survey.c_id = survey_invitation.c_id AND
                     survey.session_id = survey_invitation.session_id
                 )
 				WHERE
-                    survey_invitation.user = $user_id AND                    
+                    survey_invitation.user = $user_id AND
                     survey.avail_from <= '$filterDate' AND
                     survey.avail_till >= '$filterDate' AND
                     survey.c_id = $course_id AND
@@ -3351,8 +3401,7 @@ class SurveyUtil
                     [],
                     ICON_SIZE_TINY
                 );
-                $url = api_get_path(WEB_CODE_PATH).'survey/fillsurvey.php?course='.$_course['sysCode']
-                    .'&invitationcode='.$row['invitation_code'].'&cidReq='.$_course['sysCode'].'&id_session='.$row['session_id'];
+                $url = self::generateFillSurveyLink($row['invitation_code'], $_course, $row['session_id']);
                 echo '<a href="'.$url.'">
                     '.$row['title']
                     .'</a></td>';
@@ -3631,9 +3680,9 @@ class SurveyUtil
         $chartContainerId = 'chartContainer'
     ) {
         $htmlChart = '';
-        if (api_browser_support("svg")) {
-            $htmlChart .= api_get_js("d3/d3.v3.5.4.min.js");
-            $htmlChart .= api_get_js("dimple.v2.1.2.min.js").'
+        if (api_browser_support('svg')) {
+            $htmlChart .= api_get_js('d3/d3.v3.5.4.min.js');
+            $htmlChart .= api_get_js('dimple.v2.1.2.min.js').'
             <script>
             var svg = dimple.newSvg("#'.$chartContainerId.'", "100%", 400);
             var data = [';
@@ -3794,4 +3843,64 @@ class SurveyUtil
 
         return $pendingSurveys;
     }
+
+    /**
+     * @param string $surveyCode
+     * @param int    $courseId
+     * @param int    $sessionId
+     *
+     * @return array
+     */
+    public static function getSentInvitations($surveyCode, $courseId, $sessionId = 0)
+    {
+        $tblUser = Database::get_main_table(TABLE_MAIN_USER);
+        $tblSurveyInvitation = Database::get_course_table(TABLE_SURVEY_INVITATION);
+
+        $sessionCondition = api_get_session_condition($sessionId);
+        $surveyCode = Database::escape_string($surveyCode);
+        $courseId = (int) $courseId;
+
+        $sql = "SELECT survey_invitation.*, user.firstname, user.lastname, user.email
+                FROM $tblSurveyInvitation survey_invitation
+                LEFT JOIN $tblUser user
+                ON (survey_invitation.user = user.id AND survey_invitation.c_id = $courseId)
+                WHERE
+                    survey_invitation.survey_code = '$surveyCode'
+                    $sessionCondition";
+
+        $query = Database::query($sql);
+
+        return Database::store_result($query);
+    }
+
+    /**
+     * @param string $code       invitation code
+     * @param array  $courseInfo
+     * @param int    $sessionId
+     * @param string $surveyCode
+     *
+     * @return string
+     */
+    public static function generateFillSurveyLink($code, $courseInfo, $sessionId, $surveyCode = '')
+    {
+        $code = Security::remove_XSS($code);
+        $sessionId = (int) $sessionId;
+
+        if (empty($courseInfo)) {
+            return '';
+        }
+
+        $params = [
+            'invitationcode' => $code,
+            'cidReq' => $courseInfo['code'],
+            'course' => $courseInfo['code'],
+            'id_session' => $sessionId,
+        ];
+
+        if (!empty($surveyCode)) {
+            $params['scode'] = Security::remove_XSS($surveyCode);
+        }
+
+        return api_get_path(WEB_CODE_PATH).'survey/fillsurvey.php?'.http_build_query($params);
+    }
 }

+ 118 - 42
main/survey/survey_invitation.php

@@ -25,6 +25,7 @@ $table_course = Database::get_main_table(TABLE_MAIN_COURSE);
 $table_user = Database::get_main_table(TABLE_MAIN_USER);
 $table_survey_invitation = Database::get_course_table(TABLE_SURVEY_INVITATION);
 $tool_name = get_lang('SurveyInvitations');
+$courseInfo = api_get_course_info();
 
 // Getting the survey information
 $survey_id = Security::remove_XSS($_GET['survey_id']);
@@ -33,6 +34,8 @@ if (empty($survey_data)) {
     api_not_allowed(true);
 }
 
+$view = isset($_GET['view']) ? $_GET['view'] : 'invited';
+
 $urlname = strip_tags(
     api_substr(api_html_entity_decode($survey_data['title'], ENT_QUOTES), 0, 40)
 );
@@ -53,62 +56,86 @@ $interbreadcrumb[] = [
 // Displaying the header
 Display::display_header($tool_name);
 
+$course_id = api_get_course_int_id();
+$sessionId = api_get_session_id();
+
+$sentInvitations = SurveyUtil::getSentInvitations($survey_data['code'], $course_id, $sessionId);
+
 // Getting all the people who have filled this survey
 $answered_data = SurveyManager::get_people_who_filled_survey($survey_id);
-if ($survey_data['anonymous'] == 1) {
+
+$invitationsCount = count($sentInvitations);
+$answeredCount = count($answered_data);
+$unasnweredCount = count($sentInvitations) - count($answered_data);
+
+if ($survey_data['anonymous'] == 1 && !api_get_configuration_value('survey_anonymous_show_answered')) {
     echo Display::return_message(
-        get_lang('AnonymousSurveyCannotKnowWhoAnswered').' '.count(
-            $answered_data
-        ).' '.get_lang('PeopleAnswered')
+        get_lang('AnonymousSurveyCannotKnowWhoAnswered').' '.$answeredCount.' '.get_lang('PeopleAnswered')
     );
     $answered_data = [];
 }
+if ($survey_data['anonymous'] == 1) {
+    if ($answeredCount < 2) {
+        $answeredCount = 0;
+        $unasnweredCount = $invitationsCount;
+    }
+}
 $url = api_get_self().'?survey_id='.$survey_id.'&'.api_get_cidreq();
 
-if (!isset($_GET['view']) || $_GET['view'] == 'invited') {
-    echo get_lang('ViewInvited').' | ';
+echo '<ul class="nav nav-tabs">';
+
+if ($view == 'invited') {
+    echo '<li role="presentation" class="active"><a href="#">'.get_lang('ViewInvited');
 } else {
-    echo '	<a href="'.$url.'&view=invited">'.
-        get_lang('ViewInvited').'</a> |';
+    echo '<li role="presentation"><a href="'.$url.'&view=invited">'.
+        get_lang('ViewInvited');
 }
-if ($_GET['view'] == 'answered') {
-    echo get_lang('ViewAnswered').' | ';
+echo ' <span class="badge badge-default">'.$invitationsCount.'</span>';
+echo '</a></li>';
+if ($view == 'answered') {
+    echo '<li role="presentation" class="active"><a href="#">'.get_lang('ViewAnswered');
 } else {
-    echo '	<a href="'.$url.'&view=answered">'.
-        get_lang('ViewAnswered').'</a> |';
+    echo '<li role="presentation"><a href="'.$url.'&view=answered">'.
+        get_lang('ViewAnswered');
 }
+echo ' <span class="badge badge-default">'.$answeredCount.'</span>';
+echo '</a></li>';
 
-if ($_GET['view'] == 'unanswered') {
-    echo get_lang('ViewUnanswered');
+if ($view == 'unanswered') {
+    echo '<li role="presentation" class="active"><a href="#">'.get_lang('ViewUnanswered');
 } else {
-    echo '	<a href="'.$url.'&view=unanswered">'.
-        get_lang('ViewUnanswered').'</a>';
+    echo '<li role="presentation"><a href="'.$url.'&view=unanswered">'.
+        get_lang('ViewUnanswered');
 }
+echo ' <span class="badge badge-default">'.$unasnweredCount.'</span>';
+echo '</a></li>';
+echo '</ul>';
 
 // Table header
-echo '<table class="data_table">';
+echo '<table class="data_table" style="margin-top: 5px;">';
 echo '	<tr>';
 echo '		<th>'.get_lang('User').'</th>';
 echo '		<th>'.get_lang('InvitationDate').'</th>';
-echo '		<th>'.get_lang('Answered').'</th>';
+
+switch ($view) {
+    case 'unanswered':
+        echo '		<th>'.get_lang('SurveyInviteLink').'</th>';
+        break;
+    default:
+        echo '		<th>'.get_lang('Answered').'</th>';
+        break;
+}
+
 echo '	</tr>';
 
-$course_id = api_get_course_int_id();
-$sessionId = api_get_session_id();
-$sessionCondition = api_get_session_condition($sessionId);
-
-$sql = "SELECT survey_invitation.*, user.firstname, user.lastname, user.email
-        FROM $table_survey_invitation survey_invitation
-        LEFT JOIN $table_user user
-        ON (survey_invitation.user = user.id AND survey_invitation.c_id = $course_id)
-        WHERE
-            survey_invitation.survey_code = '".Database::escape_string($survey_data['code'])."' $sessionCondition";
-
-$res = Database::query($sql);
-while ($row = Database::fetch_assoc($res)) {
-    if (!$_GET['view'] || $_GET['view'] == 'invited' ||
-        ($_GET['view'] == 'answered' && in_array($row['user'], $answered_data)) ||
-        ($_GET['view'] == 'unanswered' && !in_array($row['user'], $answered_data))
+$surveyAnonymousShowAnswered = api_get_configuration_value('survey_anonymous_show_answered');
+$hideSurveyReportingButton = api_get_configuration_value('hide_survey_reporting_button');
+
+foreach ($sentInvitations as $row) {
+    $id = $row['iid'];
+    if ($view == 'invited' ||
+        ($view == 'answered' && in_array($row['user'], $answered_data) && $answeredCount > 1) ||
+        ($view == 'unanswered' && !in_array($row['user'], $answered_data) && $answeredCount > 1)
     ) {
         echo '<tr>';
         if (is_numeric($row['user'])) {
@@ -120,17 +147,66 @@ while ($row = Database::fetch_assoc($res)) {
             echo '<td>'.$row['user'].'</td>';
         }
         echo '	<td>'.Display::dateToStringAgoAndLongDate($row['invitation_date']).'</td>';
-        echo '	<td>';
 
-        if (in_array($row['user'], $answered_data) && !api_get_configuration_value('hide_survey_reporting_button')) {
-            echo '<a href="'.
-                api_get_path(WEB_CODE_PATH).
-                'survey/reporting.php?action=userreport&survey_id='.$survey_id.'&user='.$row['user'].'&'.api_get_cidreq().'">'.
-                get_lang('ViewAnswers').'</a>';
+        if (in_array($row['user'], $answered_data)) {
+            if (!$surveyAnonymousShowAnswered && !$hideSurveyReportingButton) {
+                echo '<td>';
+                echo '<a href="'.
+                    api_get_path(WEB_CODE_PATH).
+                    'survey/reporting.php?action=userreport&survey_id='.$survey_id.'&user='.$row['user'].'&'.api_get_cidreq().'">'.
+                    get_lang('ViewAnswers').'</a>';
+                echo '</td>';
+            } else {
+                if ($survey_data['anonymous'] == 1 && $answeredCount > 1) {
+                    echo '<td>'.get_lang('Answered').'</td>';
+                } else {
+                    echo '<td>-</td>';
+                }
+            }
         } else {
-            echo '-';
+            if ($view == 'unanswered') {
+                echo '	<td>';
+                $code = $row['invitation_code'];
+
+                $link = SurveyUtil::generateFillSurveyLink($code, $courseInfo, $sessionId);
+                $link = Display::input('text', 'copy_'.$id, $link, ['id' => 'copy_'.$id, 'class' => '']);
+                $link .= ' '.Display::url(
+                        Display::returnFontAwesomeIcon('copy').get_lang('CopyTextToClipboard'),
+                        'javascript:void()',
+                        ['onclick' => "copyTextToClipBoard('copy_".$id."')", 'class' => 'btn btn-primary btn-sm']
+                    );
+
+                echo $link;
+                echo '	</td>';
+            } else {
+                echo '<td>-</td>';
+            }
         }
 
+        echo '</tr>';
+    } elseif ($view === 'unanswered' && $answeredCount == 0) {
+        echo '<tr>';
+        if (is_numeric($row['user'])) {
+            $userInfo = api_get_user_info($row['user']);
+            echo '<td>';
+            echo UserManager::getUserProfileLink($userInfo);
+            echo '</td>';
+        } else {
+            echo '<td>'.$row['user'].'</td>';
+        }
+        echo '	<td>'.Display::dateToStringAgoAndLongDate($row['invitation_date']).'</td>';
+        echo '	<td>';
+        $code = $row['invitation_code'];
+
+        $link = SurveyUtil::generateFillSurveyLink($code, $courseInfo, $sessionId);
+        $link = Display::input('text', 'copy_'.$id, $link, ['id' => 'copy_'.$id, 'class' => '']);
+        $link .= ' '.Display::url(
+                Display::returnFontAwesomeIcon('copy').get_lang('CopyTextToClipboard'),
+                'javascript:void()',
+                ['onclick' => "copyTextToClipBoard('copy_".$id."')", 'class' => 'btn btn-primary btn-sm']
+            );
+
+        echo $link;
         echo '	</td>';
         echo '</tr>';
     }
@@ -138,5 +214,5 @@ while ($row = Database::fetch_assoc($res)) {
 
 // Closing the table
 echo '</table>';
-// Footer
+
 Display::display_footer();

+ 23 - 13
main/survey/survey_invite.php

@@ -26,7 +26,7 @@ $course_id = api_get_course_int_id();
 $_course = api_get_course_info();
 
 // Getting the survey information
-$survey_id = Security::remove_XSS($_GET['survey_id']);
+$survey_id = (int) $_GET['survey_id'];
 $survey_data = SurveyManager::get_survey($survey_id);
 if (empty($survey_data)) {
     api_not_allowed(true);
@@ -50,10 +50,17 @@ $interbreadcrumb[] = [
     'name' => get_lang('SurveyList'),
 ];
 if (api_is_course_admin()) {
-    $interbreadcrumb[] = [
-        'url' => api_get_path(WEB_CODE_PATH).'survey/survey.php?survey_id='.$survey_id.'&'.api_get_cidreq(),
-        'name' => $urlname,
-    ];
+    if ($survey_data['survey_type'] == 3) {
+        $interbreadcrumb[] = [
+            'url' => api_get_path(WEB_CODE_PATH).'survey/meeting.php?survey_id='.$survey_id.'&'.api_get_cidreq(),
+            'name' => $urlname,
+        ];
+    } else {
+        $interbreadcrumb[] = [
+            'url' => api_get_path(WEB_CODE_PATH).'survey/survey.php?survey_id='.$survey_id.'&'.api_get_cidreq(),
+            'name' => $urlname,
+        ];
+    }
 } else {
     $interbreadcrumb[] = [
         'url' => api_get_path(WEB_CODE_PATH).'survey/survey_invite.php?survey_id='.$survey_id.'&'.api_get_cidreq(),
@@ -84,12 +91,15 @@ if (Database::num_rows($result) > 1) {
 
 // Invited / answered message
 if ($survey_data['invited'] > 0 && !isset($_POST['submit'])) {
-    $message = '<a href="'.api_get_path(WEB_CODE_PATH).'survey/survey_invitation.php?view=answered&survey_id='.$survey_data['survey_id'].'&'.api_get_cidreq().'">'.
-        $survey_data['answered'].'</a> ';
-    $message .= get_lang('HaveAnswered').' ';
-    $message .= '<a href="'.api_get_path(WEB_CODE_PATH).'survey/survey_invitation.php?view=invited&survey_id='.$survey_data['survey_id'].'&'.api_get_cidreq().'">'.
-        $survey_data['invited'].'</a> ';
-    $message .= get_lang('WereInvited');
+    $message = Display::url(
+        $survey_data['answered'],
+        api_get_path(WEB_CODE_PATH).'survey/survey_invitation.php?view=answered&survey_id='.$survey_data['survey_id'].'&'.api_get_cidreq()
+    );
+    $message .= ' '.get_lang('HaveAnswered').' ';
+    $message .= Display::url(
+        $survey_data['invited'],
+        api_get_path(WEB_CODE_PATH).'survey/survey_invitation.php?view=invited&survey_id='.$survey_data['survey_id'].'&'.api_get_cidreq());
+    $message .= ' '.get_lang('WereInvited');
     echo Display::return_message($message, 'normal', false);
 }
 
@@ -142,7 +152,7 @@ $form->addHtmlEditor(
 );
 $form->addElement('html', '</div>');
 // You cab send a reminder to unanswered people if the survey is not anonymous
-if ($survey_data['anonymous'] != 1) {
+if ($survey_data['anonymous'] != 1 || api_get_configuration_value('survey_anonymous_show_answered')) {
     $form->addElement('checkbox', 'remindUnAnswered', '', get_lang('RemindUnanswered'));
 }
 // Allow resending to all selected users
@@ -165,7 +175,7 @@ if (api_is_multiple_url_enabled()) {
 }
 
 // Show the URL that can be used by users to fill a survey without invitation
-$auto_survey_link = $portal_url.'main/survey/fillsurvey.php?course='.$_course['sysCode'].'&invitationcode=auto&scode='.$survey_data['survey_code'].'&id_session='.$survey_data['session_id'];
+$auto_survey_link = SurveyUtil::generateFillSurveyLink('auto', $_course, $survey_data['session_id'], $survey_data['survey_code']);
 
 $form->addElement('label', null, get_lang('AutoInviteLink'));
 $form->addElement('label', null, $auto_survey_link);

+ 5 - 5
main/survey/survey_list.php

@@ -23,7 +23,7 @@ $current_course_tool = TOOL_SURVEY;
 $currentUserId = api_get_user_id();
 
 api_protect_course_script(true);
-$action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : null;
+$action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
 
 // Tracking
 Event::event_access_tool(TOOL_SURVEY);
@@ -32,10 +32,6 @@ $logInfo = [
     'tool' => TOOL_SURVEY,
     'tool_id' => 0,
     'tool_id_detail' => 0,
-    'action' => '',
-    'action_details' => '',
-    'current_id' => 0,
-    'info' => '',
 ];
 Event::registerLog($logInfo);
 
@@ -52,6 +48,8 @@ $isDrhOfCourse = CourseManager::isUserSubscribedInCourseAsDrh(
 
 if ($isDrhOfCourse) {
     Display::display_header(get_lang('SurveyList'));
+    // Tool introduction
+    Display::display_introduction_section('survey', 'left');
     SurveyUtil::displaySurveyListForDrh();
     Display::display_footer();
     exit;
@@ -60,6 +58,8 @@ if ($isDrhOfCourse) {
 if (!api_is_allowed_to_edit(false, true)) {
     // Coach can see this
     Display::display_header(get_lang('SurveyList'));
+    // Tool introduction
+    Display::display_introduction_section('survey', 'left');
     SurveyUtil::getSurveyList($currentUserId);
     Display::display_footer();
     exit;

+ 4 - 0
main/survey/survey_question.php

@@ -289,6 +289,10 @@ class survey_question
         $formData['answers'] = isset($formData['answers']) ? $formData['answers'] : [];
         Session::write('answer_list', $formData['answers']);
 
+        if (!isset($formData['is_required']) && api_get_configuration_value('survey_mark_question_as_required')) {
+            $formData['is_required'] = true;
+        }
+
         return $formData;
     }
 

+ 2 - 0
main/ticket/course_user_list.php

@@ -1,6 +1,8 @@
 <?php
 /* For licensing terms, see /license.txt */
 
+exit;
+
 /**
  * @package chamilo.plugin.ticket
  */

+ 1 - 1
main/ticket/new_ticket.php

@@ -43,7 +43,7 @@ function updateCourseList(sessionId) {
     }, "json");    
 }
 
-$(document).on("ready", function () {    
+$(function() {  
     $("select#session_id").on("change", function () {        
         var sessionId = parseInt(this.value, 10);
         updateCourseList(sessionId);

+ 1 - 0
main/ticket/report.php

@@ -1,6 +1,7 @@
 <?php
 /* For licensing terms, see /license.txt */
 
+exit;
 use ChamiloSession as Session;
 
 /**

+ 1 - 1
main/ticket/ticket_details.php

@@ -20,7 +20,7 @@ $interbreadcrumb[] = ['url' => '#', 'name' => get_lang('TicketDetail')];
 
 $disableReponseButtons = '';
 $htmlHeadXtra[] = '<script>
-$(document).ready(function() {
+$(function() {
 	$("#dialog-form").dialog({
 		autoOpen: false,
 		height: 450,

+ 1 - 1
main/ticket/tickets.php

@@ -36,7 +36,7 @@ function clear_course_list(div_course) {
     $("div#"+div_course).hide("");
 }
 
-$(document).ready(function() {
+$(function() {
     $("#advanced_search_form").css("display","none");
 });
 

+ 1 - 1
main/ticket/tutor.php

@@ -9,7 +9,7 @@ require_once __DIR__.'/../inc/global.inc.php';
 require_once __DIR__.'/tutor_report.lib.php';
 
 $htmlHeadXtra[] = '<script>
-$(document).ready(function (){
+$(function() {
     $(".ajax").click(function() {
         var url     = this.href;
         var dialog  = $("#dialog");

+ 1 - 1
main/timeline/view.php

@@ -21,7 +21,7 @@ $htmlHeadXtra[] = api_get_css(api_get_path(WEB_LIBRARY_PATH).'javascript/timelin
 $htmlHeadXtra[] = api_get_js('timeline/timeline-min.js');
 $htmlHeadXtra[] = '
 <script>
-	$(document).ready(function() {
+	$(function() {
 		var timeline = new VMM.Timeline();
 		timeline.init("'.$url.'");
 	});

+ 91 - 94
main/tracking/courseLog.php

@@ -8,19 +8,21 @@ use ChamiloSession as Session;
  * @package chamilo.tracking
  */
 require_once __DIR__.'/../inc/global.inc.php';
+
 $current_course_tool = TOOL_TRACKING;
 $courseId = api_get_course_id();
 $courseInfo = api_get_course_info($courseId);
-
+//keep course_code form as it is loaded (global) by the table's get_user_data
 $course_code = $courseCode = $courseInfo['code'];
-$session_id = $sessionId = api_get_session_id();
-
+$sessionId = api_get_session_id();
+// PERSON_NAME_DATA_EXPORT is buggy
+$sortByFirstName = api_sort_by_first_name();
 $from_myspace = false;
 $from = isset($_GET['from']) ? $_GET['from'] : null;
 
 // Starting the output buffering when we are exporting the information.
 $export_csv = isset($_GET['export']) && $_GET['export'] == 'csv' ? true : false;
-$_course = api_get_course_info();
+
 $htmlHeadXtra[] = api_get_js('chartjs/Chart.min.js');
 $htmlHeadXtra[] = ' ';
 
@@ -31,7 +33,7 @@ if ($from == 'myspace') {
 }
 
 // Access restrictions.
-$is_allowedToTrack = Tracking::isAllowToTrack($session_id);
+$is_allowedToTrack = Tracking::isAllowToTrack($sessionId);
 
 if (!$is_allowedToTrack) {
     api_not_allowed(true);
@@ -55,7 +57,6 @@ if (api_is_drh()) {
         }
 
         $coursesFollowedList = CourseManager::get_courses_followed_by_drh(api_get_user_id());
-
         if (!empty($coursesFollowedList)) {
             $coursesFollowedList = array_keys($coursesFollowedList);
         }
@@ -77,8 +78,8 @@ if (api_is_drh()) {
 }
 
 if ($export_csv) {
-    if (!empty($session_id)) {
-        Session::write('id_session', $session_id);
+    if (!empty($sessionId)) {
+        Session::write('id_session', $sessionId);
     }
     ob_start();
 }
@@ -120,7 +121,7 @@ $js = "<script>
     // be sure that these columns always exists
     // see headers = array();
     // tab of header texts
-    $(document).ready( function() {
+    $(function() {
         init_hide();
         var columnsToHide = ".$columnsToHide.";
         if (columnsToHide) {
@@ -134,7 +135,6 @@ $htmlHeadXtra[] = $js;
 
 // Database table definitions.
 //@todo remove this calls
-$TABLETRACK_DOWNLOADS = Database::get_main_table(TABLE_STATISTIC_TRACK_E_DOWNLOADS);
 $TABLETRACK_EXERCISES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
 $TABLECOURSUSER = Database::get_main_table(TABLE_MAIN_COURSE_USER);
 $TABLECOURSE = Database::get_main_table(TABLE_MAIN_COURSE);
@@ -160,11 +160,11 @@ if (isset($_GET['origin']) && $_GET['origin'] == 'resume_session') {
 $view = isset($_REQUEST['view']) ? $_REQUEST['view'] : '';
 $nameTools = get_lang('Tracking');
 
-// getting all the students of the course
+//Template Tracking
 $tpl = new Template($nameTools);
 
 // getting all the students of the course
-if (empty($session_id)) {
+if (empty($sessionId)) {
     // Registered students in a course outside session.
     $a_students = CourseManager::get_student_list_from_course_code($courseId);
 } else {
@@ -190,9 +190,7 @@ if (isset($_GET['additional_profile_field'])) {
             $user_array
         );
 
-        $extra_info[$fieldId] = UserManager::get_extra_field_information(
-            $fieldId
-        );
+        $extra_info[$fieldId] = UserManager::get_extra_field_information($fieldId);
     }
 }
 
@@ -202,8 +200,6 @@ Session::write('extra_field_info', $extra_info);
 // Display the header.
 Display::display_header($nameTools, 'Tracking');
 
-/* MAIN CODE */
-
 $actionsLeft = TrackingCourseLog::actionsLeft('users', $sessionId);
 
 $actionsRight = '<div class="float-right">';
@@ -221,6 +217,7 @@ $users_tracking_per_page = '';
 if (isset($_GET['users_tracking_per_page'])) {
     $users_tracking_per_page = '&users_tracking_per_page='.intval($_GET['users_tracking_per_page']);
 }
+
 $actionsRight .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&export=csv&'.$addional_param.$users_tracking_per_page.'">
      '.Display::return_icon('export_csv.png', get_lang('ExportAsCSV'), '', ICON_SIZE_MEDIUM).'</a>';
 $actionsRight .= '</div>';
@@ -245,13 +242,13 @@ echo Display::toolbarAction(
 
 $course_name = get_lang('Course').' '.$courseInfo['name'];
 
-if ($session_id) {
+if ($sessionId) {
     $titleSession = Display::return_icon(
         'session.png',
         get_lang('Session'),
         [],
         ICON_SIZE_SMALL
-    ).' '.api_get_session_name($session_id);
+    ).' '.api_get_session_name($sessionId);
     $titleCourse = Display::return_icon(
         'course.png',
         get_lang('Course'),
@@ -275,9 +272,9 @@ $teacherList = CourseManager::getTeacherListFromCourseCodeToString(
 );
 
 $coaches = null;
-if (!empty($session_id)) {
+if (!empty($sessionId)) {
     $coaches = CourseManager::get_coachs_from_course_to_string(
-        $session_id,
+        $sessionId,
         $courseInfo['real_id'],
         ',',
         true,
@@ -285,7 +282,6 @@ if (!empty($session_id)) {
     );
 }
 $html = '';
-
 if (!empty($teacherList)) {
     $html .= Display::page_subheader2(get_lang('Teachers'));
     $html .= $teacherList;
@@ -338,6 +334,7 @@ $trackingDirection = isset($_GET['users_tracking_direction']) ? $_GET['users_tra
 // Show the charts part only if there are students subscribed to this course/session
 if ($nbStudents > 0) {
     $usersTracking = TrackingCourseLog::get_user_data(null, $nbStudents, $trackingColumn, $trackingDirection, false);
+
     $numberStudentsCompletedLP = 0;
     $averageStudentsTestScore = 0;
     $scoresDistribution = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
@@ -352,77 +349,78 @@ if ($nbStudents > 0) {
         $course_code,
         null,
         null,
-        $session_id
+        $sessionId
     );
 
-    foreach ($usersTracking as $userTracking) {
-        $userInfo = api_get_user_info_from_username($userTracking[3]);
-        if (empty($userInfo)) {
-            continue;
-        }
-        $userId = $userInfo['user_id'];
-        if ($userTracking[5] === '100%') {
-            $numberStudentsCompletedLP++;
-        }
-        $averageStudentTestScore = substr($userTracking[7], 0, -1);
-        $averageStudentsTestScore += $averageStudentTestScore;
+    $hideReports = api_get_configuration_value('hide_course_report_graph');
 
-        if ($averageStudentTestScore === '100') {
-            $reducedAverage = 9;
-        } else {
-            $reducedAverage = floor($averageStudentTestScore / 10);
-        }
-        if (isset($scoresDistribution[$reducedAverage])) {
-            $scoresDistribution[$reducedAverage]++;
-        }
-        $scoreStudent = substr($userTracking[5], 0, -1) + substr($userTracking[7], 0, -1);
-        list($hours, $minutes, $seconds) = preg_split('/:/', $userTracking[4]);
-        $minutes = round((3600 * $hours + 60 * $minutes + $seconds) / 60);
-
-        $certificate = false;
-        if (isset($category[0]) && $category[0]->is_certificate_available($userId)) {
-            $certificate = true;
-            $certificateCount++;
-        }
+    if ($hideReports == false) {
+        foreach ($usersTracking as $userTracking) {
+            $userInfo = api_get_user_info_from_username($userTracking[3]);
+            if (empty($userInfo)) {
+                continue;
+            }
+            $userId = $userInfo['user_id'];
+            if ($userTracking[5] === '100%') {
+                $numberStudentsCompletedLP++;
+            }
+            $averageStudentTestScore = substr($userTracking[7], 0, -1);
+            $averageStudentsTestScore += $averageStudentTestScore;
 
-        $listStudent = [
-            'id' => $userId,
-            'fullname' => $userInfo['complete_name'],
-            'score' => floor($scoreStudent / 2),
-            'total_time' => $minutes,
-            'avatar' => $userInfo['avatar'],
-            'certicate' => $certificate,
-        ];
-        $listStudentIds[] = $userId;
-        $userScoreList[] = $listStudent;
-    }
+            if ($averageStudentTestScore === '100') {
+                $reducedAverage = 9;
+            } else {
+                $reducedAverage = floor($averageStudentTestScore / 10);
+            }
+            if (isset($scoresDistribution[$reducedAverage])) {
+                $scoresDistribution[$reducedAverage]++;
+            }
+            $scoreStudent = substr($userTracking[5], 0, -1) + substr($userTracking[7], 0, -1);
+            list($hours, $minutes, $seconds) = preg_split('/:/', $userTracking[4]);
+            $minutes = round((3600 * $hours + 60 * $minutes + $seconds) / 60);
+
+            $certificate = false;
+            if (isset($category[0]) && $category[0]->is_certificate_available($userId)) {
+                $certificate = true;
+                $certificateCount++;
+            }
 
-    uasort($userScoreList, 'sort_by_order');
-    $averageStudentsTestScore = round($averageStudentsTestScore / $nbStudents);
+            $listStudent = [
+                'id' => $userId,
+                'fullname' => $userInfo['complete_name'],
+                'score' => floor($scoreStudent / 2),
+                'total_time' => $minutes,
+                'avatar' => $userInfo['avatar'],
+                'certicate' => $certificate,
+            ];
+            $listStudentIds[] = $userId;
+            $userScoreList[] = $listStudent;
+        }
 
-    $colors = ChamiloApi::getColorPalette(true, true, 10);
+        uasort($userScoreList, 'sort_by_order');
+        $averageStudentsTestScore = round($averageStudentsTestScore / $nbStudents);
 
-    $tpl->assign('chart_colors', json_encode($colors));
-    $tpl->assign('certificate_count', $certificateCount);
-    $tpl->assign('score_distribution', json_encode($scoresDistribution));
-    $tpl->assign('json_time_student', json_encode($userScoreList));
-    $tpl->assign('students_test_score', $averageStudentsTestScore);
-    $tpl->assign('students_completed_lp', $numberStudentsCompletedLP);
-    $tpl->assign('number_students', $nbStudents);
-    $tpl->assign('top_students', $userScoreList);
+        $colors = ChamiloApi::getColorPalette(true, true, 10);
 
-    $trackingSummaryLayout = $tpl->get_template('tracking/tracking_course_log.html.twig');
-    $content = $tpl->fetch($trackingSummaryLayout);
+        $tpl->assign('chart_colors', json_encode($colors));
+        $tpl->assign('certificate_count', $certificateCount);
+        $tpl->assign('score_distribution', json_encode($scoresDistribution));
+        $tpl->assign('json_time_student', json_encode($userScoreList));
+        $tpl->assign('students_test_score', $averageStudentsTestScore);
+        $tpl->assign('students_completed_lp', $numberStudentsCompletedLP);
+        $tpl->assign('number_students', $nbStudents);
+        $tpl->assign('top_students', $userScoreList);
 
-    echo $content;
+        $trackingSummaryLayout = $tpl->get_template('tracking/tracking_course_log.tpl');
+        $content = $tpl->fetch($trackingSummaryLayout);
+
+        echo $content;
+    }
 }
 
 $html .= Display::page_subheader2(get_lang('StudentList'));
 
-// PERSON_NAME_DATA_EXPORT is buggy
-$sortByFirstName = api_sort_by_first_name();
-
-if (count($a_students) > 0) {
+if ($nbStudents > 0) {
     $getLangXDays = get_lang('XDays');
     $form = new FormValidator(
         'reminder_form',
@@ -447,7 +445,7 @@ if (count($a_students) > 0) {
         'since',
         Display::returnFontAwesomeIcon('warning').get_lang('RemindInactivesLearnersSince'),
         $options,
-        ['class' => 'col-sm-3']
+        ['disable_js' => true]
     );
     $el->setSelected(7);
 
@@ -474,17 +472,16 @@ if (count($a_students) > 0) {
     $all_datas = [];
     $course_code = $_course['id'];
 
-    $table = new SortableTable(
-        'users_tracking',
-        ['TrackingCourseLog', 'get_number_of_users'],
-        function () use ($usersTracking) {
-            return $usersTracking;
-        },
-        1
+    $table = new SortableTableFromArray(
+        $usersTracking,
+        1,
+        20,
+        'users_tracking'
     );
+    $table->total_number_of_items = $nbStudents;
 
     $parameters['cidReq'] = isset($_GET['cidReq']) ? Security::remove_XSS($_GET['cidReq']) : '';
-    $parameters['id_session'] = $session_id;
+    $parameters['id_session'] = $sessionId;
     $parameters['from'] = isset($_GET['myspace']) ? Security::remove_XSS($_GET['myspace']) : null;
 
     $table->set_additional_parameters($parameters);
@@ -559,7 +556,7 @@ if (count($a_students) > 0) {
     $table->set_header(11, get_lang('Classes'));
     $headers['clasess'] = get_lang('Classes');
 
-    if (empty($session_id)) {
+    if (empty($sessionId)) {
         $table->set_header(12, get_lang('Survey'), false);
         $headers['survey'] = get_lang('Survey');
         $table->set_header(13, get_lang('FirstLoginInCourse'), false);
@@ -647,7 +644,7 @@ if ($export_csv) {
     $csv_headers[] = get_lang('Student_publication');
     $csv_headers[] = get_lang('Messages');
 
-    if (empty($session_id)) {
+    if (empty($sessionId)) {
         $csv_headers[] = get_lang('Survey');
     }
 
@@ -662,13 +659,13 @@ if ($export_csv) {
     ob_end_clean();
 
     $csvContentInSession = Session::read('csv_content');
-    // Adding headers before the content.
+
     // Adding headers before the content.
     array_unshift($csvContentInSession, $csv_headers);
 
-    if ($session_id) {
+    if ($sessionId) {
         $sessionData = [];
-        $sessionInfo = api_get_session_info($session_id);
+        $sessionInfo = api_get_session_info($sessionId);
         $sessionDates = SessionManager::parseSessionDates($sessionInfo);
 
         array_unshift($csvContentInSession, [get_lang('Date'), $sessionDates['access']]);

+ 1 - 1
main/tracking/course_log_resources.php

@@ -9,7 +9,7 @@ $current_course_tool = TOOL_TRACKING;
 
 $from_myspace = false;
 $from = isset($_GET['from']) ? $_GET['from'] : null;
-
+// Starting the output buffering when we are exporting the information.
 $export_csv = isset($_GET['export']) && $_GET['export'] == 'csv' ? true : false;
 $exportXls = isset($_GET['export']) && $_GET['export'] == 'xls' ? true : false;
 $session_id = intval($_REQUEST['id_session']);

+ 1 - 1
main/tracking/course_log_tools.php

@@ -174,7 +174,7 @@ if ($lpReporting) {
 }
 
 if ($exerciseReporting) {
-    // Exercices tracking.
+    // Exercises tracking.
     echo '<div class="report_section">';
     echo Display::page_subheader(
         Display::return_icon(

+ 0 - 1
main/tracking/total_time.php

@@ -86,7 +86,6 @@ $columnsToHide = json_encode($columnsToHide);
 $csv_content = [];
 // Database table definitions.
 //@todo remove this calls
-$TABLETRACK_DOWNLOADS = Database::get_main_table(TABLE_STATISTIC_TRACK_E_DOWNLOADS);
 $TABLETRACK_EXERCISES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
 $TABLECOURSUSER = Database::get_main_table(TABLE_MAIN_COURSE_USER);
 $TABLECOURSE = Database::get_main_table(TABLE_MAIN_COURSE);

+ 1 - 1
main/user/class.php

@@ -30,7 +30,7 @@ $groupFilter = isset($_GET['group_filter']) ? intval($_GET['group_filter']) : 0;
 
 $htmlHeadXtra[] = '
 <script>
-$(document).ready( function() {
+$(function() {
     $("#group_filter").change(function() {
         window.location = "class.php?'.api_get_cidreq().'&type='.$type.'" +"&group_filter=" + $(this).val();
     });

+ 1 - 1
main/user/user_import.php

@@ -107,7 +107,7 @@ if ($form->validate()) {
                     CourseManager::subscribeUser($userId, $course_code, $userType, $session_id);
                     if (empty($session_id)) {
                         //just to make sure
-                        if (CourseManager :: is_user_subscribed_in_course($userId, $course_code)) {
+                        if (CourseManager::is_user_subscribed_in_course($userId, $course_code)) {
                             $user_to_show[] = $userInfo['complete_name'];
                         }
                     } else {

+ 4 - 0
main/webservices/api/v2.php

@@ -123,6 +123,10 @@ try {
             $data = $restApi->addCourse($_POST);
             $restResponse->setData($data);
             break;
+        case Rest::SAVE_USER:
+            $data = $restApi->addUser($_POST);
+            $restResponse->setData($data);
+            break;
         case Rest::SUBSCRIBE_USER_TO_COURSE:
             $data = $restApi->subscribeUserToCourse($_POST);
             $restResponse->setData($data);

+ 136 - 0
main/webservices/cm_soap_course.php

@@ -12,6 +12,142 @@ require_once __DIR__.'/cm_soap.php';
  */
 $s = WSCMSoapServer::singleton();
 
+$s->wsdl->addComplexType(
+    'course_id',
+    'complexType',
+    'struct',
+    'all',
+    '',
+    [
+        'course_id_field_name' => ['name' => 'course_id_field_name', 'type' => 'xsd:string'],
+        'course_id_value' => ['name' => 'course_id_value', 'type' => 'xsd:string'],
+    ]
+);
+
+$s->wsdl->addComplexType(
+    'course_result',
+    'complexType',
+    'struct',
+    'all',
+    '',
+    [
+        'course_id_value' => ['name' => 'course_id_value', 'type' => 'xsd:string'],
+        'result' => ['name' => 'result', 'type' => 'tns:result'],
+    ]
+);
+
+$s->wsdl->addComplexType(
+    'course_result_array',
+    'complexType',
+    'array',
+    '',
+    'SOAP-ENC:Array',
+    [],
+    [['ref' => 'SOAP-ENC:arrayType', 'wsdl:arrayType' => 'tns:course_result[]']],
+    'tns:course_result'
+);
+
+$s->register(
+    'WSCMCourse.DeleteCourse',
+    ['secret_key' => 'xsd:string', 'course_id_field_name' => 'xsd:string', 'course_id_value' => 'xsd:string']
+);
+
+$s->register(
+    'WSCMCourse.DeleteCourses',
+    ['secret_key' => 'xsd:string', 'courses' => 'tns:course_id[]'],
+    ['return' => 'tns:course_result_array']
+);
+
+$s->register(
+    'WSCMCourse.CreateCourse',
+    [
+        'secret_key' => 'xsd:string',
+        'title' => 'xsd:string',
+        'category_code' => 'xsd:string',
+        'wanted_code' => 'xsd:string',
+        'tutor_name' => 'xsd:string',
+        'course_admin_user_id_field_name' => 'xsd:string',
+        'course_admin_user_id_value' => 'xsd:string',
+        'language' => 'xsd:string',
+        'course_id_field_name' => 'xsd:string',
+        'course_id_value' => 'xsd:string',
+        'extras' => 'tns:extra_field[]',
+    ],
+    ['return' => 'xsd:int']
+);
+
+$s->wsdl->addComplexType(
+    'course_create',
+    'complexType',
+    'struct',
+    'all',
+    '',
+    [
+        'title' => ['name' => 'title', 'type' => 'xsd:string'],
+        'category_code' => ['name' => 'category_code', 'type' => 'xsd:string'],
+        'wanted_code' => ['name' => 'wanted_code', 'type' => 'xsd:int'],
+        'tutor_name' => ['name' => 'tutor_name', 'type' => 'xsd:string'],
+        'course_admin_user_id_field_name' => ['name' => 'course_admin_user_id_field_name', 'type' => 'xsd:string'],
+        'course_admin_user_id_value' => ['name' => 'course_admin_user_id_value', 'type' => 'xsd:string'],
+        'language' => ['name' => 'language', 'type' => 'xsd:string'],
+        'course_id_field_name' => ['name' => 'course_id_field_name', 'type' => 'xsd:string'],
+        'course_id_value' => ['name' => 'course_id_value', 'type' => 'xsd:string'],
+        'extras' => ['name' => 'extras', 'type' => 'tns:extra_field[]'],
+    ]
+);
+
+$s->wsdl->addComplexType(
+    'course_create_result',
+    'complexType',
+    'struct',
+    'all',
+    '',
+    [
+        'course_id_value' => ['name' => 'course_id_value', 'type' => 'xsd:string'],
+        'course_id_generated' => ['name' => 'course_id_generated', 'type' => 'xsd:int'],
+        'result' => ['name' => 'result', 'type' => 'tns:result'],
+    ]
+);
+
+$s->wsdl->addComplexType(
+    'course_create_result_array',
+    'complexType',
+    'array',
+    '',
+    'SOAP-ENC:Array',
+    [],
+    [['ref' => 'SOAP-ENC:arrayType', 'wsdl:arrayType' => 'tns:course_create_result[]']],
+    'tns:course_create_result'
+);
+
+$s->register(
+    'WSCMCourse.CreateCourses',
+    [
+        'secret_key' => 'xsd:string',
+        'courses' => 'tns:course_create[]',
+    ],
+    ['return' => 'tns:course_create_result_array']
+);
+
+$s->register(
+    'WSCMCourse.EditCourse',
+    [
+        'secret_key' => 'xsd:string',
+        'course_id_field_name' => 'xsd:string',
+        'course_id_value' => 'xsd:string',
+        'title' => 'xsd:string',
+        'category_code' => 'xsd:string',
+        'department_name' => 'xsd:string',
+        'department_url' => 'xsd:string',
+        'language' => 'xsd:string',
+        'visibility' => 'xsd:int',
+        'subscribe' => 'xsd:int',
+        'unsubscribe' => 'xsd:int',
+        'visual_code' => 'xsd:string',
+        'extras' => 'tns:extra_field[]',
+    ]
+);
+
 $s->wsdl->addComplexType(
     'course',
     'complexType',

+ 400 - 0
main/webservices/cm_webservice_course.php

@@ -12,6 +12,229 @@ require_once __DIR__.'/cm_webservice.php';
  */
 class WSCMCourse extends WSCM
 {
+    /**
+     * Deletes a course.
+     *
+     * @param string API secret key
+     * @param string Course id field name
+     * @param string Course id value
+     */
+    public function DeleteCourse(
+        $secret_key,
+        $course_id_field_name,
+        $course_id_value
+    ) {
+        $verifKey = $this->verifyKey($secret_key);
+        if ($verifKey instanceof WSError) {
+            $this->handleError($verifKey);
+        } else {
+            $result = $this->deleteCourseHelper(
+                $course_id_field_name,
+                $course_id_value
+            );
+            if ($result instanceof WSError) {
+                $this->handleError($result);
+            }
+        }
+    }
+
+    /**
+     * Deletes multiple courses.
+     *
+     * @param string API secret key
+     * @param array Array of courses with elements of the form
+     * array('course_id_field_name' => 'name_of_field', 'course_id_value' => 'value')
+     *
+     * @return array Array with elements like
+     *               array('course_id_value' => 'value', 'result' => array('code' => 0, 'message' => 'Operation was successful')).
+     *               Note that if the result array contains a code different
+     *               than 0, an error occured
+     */
+    public function DeleteCourses($secret_key, $courses)
+    {
+        $verifKey = $this->verifyKey($secret_key);
+        if ($verifKey instanceof WSError) {
+            $this->handleError($verifKey);
+        } else {
+            $results = [];
+            foreach ($courses as $course) {
+                $result_tmp = [];
+                $result_op = $this->deleteCourseHelper($course['course_id_field_name'], $course['course_id_value']);
+                $result_tmp['course_id_value'] = $course['course_id_value'];
+                if ($result_op instanceof WSCMError) {
+                    // Return the error in the results
+                    $result_tmp['result'] = $result_op->toArray();
+                } else {
+                    $result_tmp['result'] = $this->getSuccessfulResult();
+                }
+                $results[] = $result_tmp;
+            }
+
+            return $results;
+        }
+    }
+
+    /**
+     * Creates a course.
+     *
+     * @param string API secret key
+     * @param string Title
+     * @param string Category code
+     * @param string Wanted code. If it's not defined, it will be generated automatically
+     * @param string Tutor name
+     * @param string Course admin user id field name
+     * @param string Course admin user id value
+     * @param string Course language
+     * @param string Course id field name
+     * @param string Course id value
+     * @param array Course extra fields
+     *
+     * @return int Course id generated
+     */
+    public function CreateCourse(
+        $secret_key,
+        $title,
+        $category_code,
+        $wanted_code,
+        $tutor_name,
+        $course_admin_user_id_field_name,
+        $course_admin_user_id_value,
+        $language,
+        $course_id_field_name,
+        $course_id_value,
+        $extras
+    ) {
+        // First, verify the secret key
+        $verifKey = $this->verifyKey($secret_key);
+        if ($verifKey instanceof WSError) {
+            $this->handleError($verifKey);
+        } else {
+            $result = $this->createCourseHelper(
+                $title,
+                $category_code,
+                $wanted_code,
+                $tutor_name,
+                $course_admin_user_id_field_name,
+                $course_admin_user_id_value,
+                $language,
+                $course_id_field_name,
+                $course_id_value,
+                $extras
+            );
+            if ($result instanceof WSError) {
+                $this->handleError($result);
+            } else {
+                return $result;
+            }
+        }
+    }
+
+    /**
+     * Create multiple courses.
+     *
+     * @param string API secret key
+     * @param array Courses to be created, with elements following the structure presented in CreateCourse
+     *
+     * @return array Array with elements of the form
+     *               array('course_id_value' => 'original value sent', 'course_id_generated' => 'value_generated', 'result' => array('code' => 0, 'message' => 'Operation was successful'))
+     */
+    public function CreateCourses($secret_key, $courses)
+    {
+        // First, verify the secret key
+        $verifKey = $this->verifyKey($secret_key);
+        if ($verifKey instanceof WSCMError) {
+            $this->handleError($verifKey);
+        } else {
+            $results = [];
+            foreach ($courses as $course) {
+                $result_tmp = [];
+                //reinitialize variables just in case
+                $title = $category_code = $wanted_code = $tutor_name = $course_admin_user_id_field_name = $course_admin_user_id_value = $language = $course_id_field_name = $course_id_value = $extras = null;
+                extract($course);
+                $result = $this->createCourseHelper(
+                    $title,
+                    $category_code,
+                    $wanted_code,
+                    $tutor_name,
+                    $course_admin_user_id_field_name,
+                    $course_admin_user_id_value,
+                    $language,
+                    $course_id_field_name,
+                    $course_id_value,
+                    $extras
+                );
+                if ($result instanceof WSCMError) {
+                    $result_tmp['result'] = $result->toArray();
+                    $result_tmp['course_id_value'] = $course_id_value;
+                    $result_tmp['course_id_generated'] = 0;
+                } else {
+                    $result_tmp['result'] = $this->getSuccessfulResult();
+                    $result_tmp['course_id_value'] = $course_id_value;
+                    $result_tmp['course_id_generated'] = $result;
+                }
+                $results[] = $result_tmp;
+            }
+
+            return $results;
+        }
+    }
+
+    /**
+     * Edits a course.
+     *
+     * @param string API secret key
+     * @param string Course id field name
+     * @param string Course id value
+     * @param string Title
+     * @param string Category code
+     * @param string Department name
+     * @param string Department url
+     * @param string Course language
+     * @param int Visibility
+     * @param int Subscribe (0 = denied, 1 = allowed)
+     * @param int Unsubscribe (0 = denied, 1 = allowed)
+     * @param string Visual code
+     * @param array Course extra fields
+     */
+    public function EditCourse(
+        $secret_key,
+        $course_id_field_name,
+        $course_id_value,
+        $title,
+        $category_code,
+        $department_name,
+        $department_url,
+        $language,
+        $visibility,
+        $subscribe,
+        $unsubscribe,
+        $visual_code,
+        $extras
+    ) {
+        $verifKey = $this->verifyKey($secret_key);
+        if ($verifKey instanceof WSCMError) {
+            $this->handleError($verifKey);
+        } else {
+            $result = $this->editCourseHelper(
+                $course_id_field_name,
+                $course_id_value,
+                $title,
+                $category_code,
+                $department_name,
+                $department_url,
+                $language,
+                $visibility,
+                $subscribe,
+                $unsubscribe,
+                $visual_code,
+                $extras
+            );
+            if ($result instanceof WSCMError) {
+                $this->handleError($result);
+            }
+        }
+    }
+
     /**
      * List courses.
      *
@@ -260,6 +483,183 @@ class WSCMCourse extends WSCM
         return $username;
     }
 
+    /**
+     * Deletes a course (helper method).
+     *
+     * @param string Course id field name
+     * @param string Course id value
+     *
+     * @return mixed True if the course was successfully deleted, WSError otherwise
+     */
+    protected function deleteCourseHelper(
+        $course_id_field_name,
+        $course_id_value
+    ) {
+        $course_id = $this->getCourseId(
+            $course_id_field_name,
+            $course_id_value
+        );
+        if ($course_id instanceof WSCMError) {
+            return $course_id;
+        } else {
+            $course_code = CourseManager::get_course_code_from_course_id(
+                $course_id
+            );
+            CourseManager::delete_course($course_code);
+
+            return true;
+        }
+    }
+
+    /**
+     * Creates a course (helper method).
+     *
+     * @param string Title
+     * @param string Category code
+     * @param string Wanted code. If it's not defined, it will be generated automatically
+     * @param string Tutor name
+     * @param string Course admin user id field name
+     * @param string Course admin user id value
+     * @param string Course language
+     * @param string Course id field name
+     * @param string Course id value
+     * @param array Course extra fields
+     *
+     * @return mixed Generated id if creation was successful, WSError otherwise
+     */
+    protected function createCourseHelper(
+        $title,
+        $category_code,
+        $wanted_code,
+        $tutor_name,
+        $course_admin_user_id_field_name,
+        $course_admin_user_id_value,
+        $language,
+        $course_id_field_name,
+        $course_id_value,
+        $extras
+    ) {
+        // Add the original course id field name and value to the extra fields if needed
+        $extras_associative = [];
+        if ($course_id_field_name != "chamilo_course_id") {
+            $extras_associative[$course_id_field_name] = $course_id_value;
+        }
+        foreach ($extras as $extra) {
+            $extras_associative[$extra['field_name']] = $extra['field_value'];
+        }
+        $course_admin_id = $this->getUserId($course_admin_user_id_field_name, $course_admin_user_id_value);
+        if ($course_admin_id instanceof WSError) {
+            return $course_admin_id;
+        }
+        if ($wanted_code == '') {
+            $wanted_code = CourseManager::generate_course_code($title);
+        }
+        $result = create_course(
+            $wanted_code,
+            $title,
+            $tutor_name,
+            $category_code,
+            $language,
+            $course_admin_id,
+            $this->_configuration['db_prefix'],
+            0
+        );
+        if (!$result) {
+            return new WSError(202, 'There was an error creating the course');
+        } else {
+            // Update extra fields
+            foreach ($extras_associative as $fname => $fvalue) {
+                CourseManager::update_course_extra_field_value($result, $fname, $fvalue);
+            }
+            // Get course id
+            $course_info = CourseManager::get_course_information($result);
+
+            return $course_info['real_id'];
+        }
+    }
+
+    /**
+     * Edits a course (helper method).
+     *
+     * @param string Course id field name
+     * @param string Course id value
+     * @param string Title
+     * @param string Category code
+     * @param string Department name
+     * @param string Department url
+     * @param string Course language
+     * @param int Visibility
+     * @param int Subscribe (0 = denied, 1 = allowed)
+     * @param int Unsubscribe (0 = denied, 1 = allowed)
+     * @param string Visual code
+     * @param array Course extra fields
+     *
+     * @return mixed True in case of success, WSError otherwise
+     */
+    protected function editCourseHelper(
+        $course_id_field_name,
+        $course_id_value,
+        $title,
+        $category_code,
+        $department_name,
+        $department_url,
+        $language,
+        $visibility,
+        $subscribe,
+        $unsubscribe,
+        $visual_code,
+        $extras
+    ) {
+        $course_id = $this->getCourseId($course_id_field_name, $course_id_value);
+        if ($course_id instanceof WSCMError) {
+            return $course_id;
+        } else {
+            $attributes = [];
+            if (!empty($title)) {
+                $attributes['title'] = $title;
+            }
+            if (!empty($category_code)) {
+                $attributes['category_code'] = $category_code;
+            }
+            if (!empty($department_name)) {
+                $attributes['department_name'] = $department_name;
+            }
+            if (!empty($department_url)) {
+                $attributes['department_url'] = $department_url;
+            }
+            if (!empty($language)) {
+                $attributes['course_language'] = $language;
+            }
+            if ($visibility != '') {
+                $attributes['visibility'] = (int) $visibility;
+            }
+            if ($subscribe != '') {
+                $attributes['subscribe'] = (int) $subscribe;
+            }
+            if ($unsubscribe != '') {
+                $attributes['unsubscribe'] = (int) $unsubscribe;
+            }
+            if (!empty($visual_code)) {
+                $attributes['visual_code'] = $visual_code;
+            }
+            if (!empty($attributes)) {
+                CourseManager::update_attributes($course_id, $attributes);
+            }
+            if (!empty($extras)) {
+                $course_code = CourseManager::get_course_code_from_course_id($course_id);
+                $extras_associative = [];
+                foreach ($extras as $extra) {
+                    $extras_associative[$extra['field_name']] = $extra['field_value'];
+                }
+                foreach ($extras_associative as $fname => $fvalue) {
+                    CourseManager::update_extra_field_value($course_code, $fname, $fvalue);
+                }
+            }
+
+            return true;
+        }
+    }
+
     /**
      * Subscribe or unsubscribe user to a course (helper method).
      *

+ 0 - 1
main/webservices/cm_webservice_forum.php

@@ -4,7 +4,6 @@
  * @package chamilo.webservices
  */
 require_once __DIR__.'/../inc/global.inc.php';
-require_once __DIR__.'/../forum/forumconfig.inc.php';
 require_once __DIR__.'/../forum/forumfunction.inc.php';
 require_once __DIR__.'/cm_webservice.php';
 

+ 1 - 0
main/webservices/soap.php

@@ -122,6 +122,7 @@ $s->register(
     ['return' => 'xsd:string']
 );
 
+require_once __DIR__.'/soap_user.php';
 require_once __DIR__.'/soap_course.php';
 require_once __DIR__.'/soap_session.php';
 require_once __DIR__.'/soap_report.php';

+ 142 - 0
main/webservices/soap_course.php

@@ -13,6 +13,148 @@ require_once __DIR__.'/soap.php';
  */
 $s = WSSoapServer::singleton();
 
+$s->wsdl->addComplexType(
+    'course_id',
+    'complexType',
+    'struct',
+    'all',
+    '',
+    [
+        'course_id_field_name' => ['name' => 'course_id_field_name', 'type' => 'xsd:string'],
+        'course_id_value' => ['name' => 'course_id_value', 'type' => 'xsd:string'],
+    ]
+);
+
+$s->wsdl->addComplexType(
+    'course_result',
+    'complexType',
+    'struct',
+    'all',
+    '',
+    [
+        'course_id_value' => ['name' => 'course_id_value', 'type' => 'xsd:string'],
+        'result' => ['name' => 'result', 'type' => 'tns:result'],
+    ]
+);
+
+$s->wsdl->addComplexType(
+    'course_result_array',
+    'complexType',
+    'array',
+    '',
+    'SOAP-ENC:Array',
+    [],
+    [['ref' => 'SOAP-ENC:arrayType', 'wsdl:arrayType' => 'tns:course_result[]']],
+    'tns:course_result'
+);
+
+$s->register(
+    'WSCourse.DeleteCourse',
+    ['secret_key' => 'xsd:string', 'course_id_field_name' => 'xsd:string', 'course_id_value' => 'xsd:string'],
+    [],
+    'urn:WSService', // namespace
+    'urn:WSService#WSCourse.DeleteCourse', // soapaction
+    'rpc', // style
+    'encoded', // use
+    'Delete a course in chamilo'                   // documentation
+);
+
+$s->register(
+    'WSCourse.DeleteCourses',
+    ['secret_key' => 'xsd:string', 'courses' => 'tns:course_id[]'],
+    ['return' => 'tns:course_result_array']
+);
+
+$s->register(
+    'WSCourse.CreateCourse',
+    [
+        'secret_key' => 'xsd:string',
+        'title' => 'xsd:string',
+        'category_code' => 'xsd:string',
+        'wanted_code' => 'xsd:string',
+        'tutor_name' => 'xsd:string',
+        'course_admin_user_id_field_name' => 'xsd:string',
+        'course_admin_user_id_value' => 'xsd:string',
+        'language' => 'xsd:string',
+        'course_id_field_name' => 'xsd:string',
+        'course_id_value' => 'xsd:string',
+        'extras' => 'tns:extra_field',
+    ],
+    ['return' => 'xsd:int']
+);
+
+$s->wsdl->addComplexType(
+    'course_create',
+    'complexType',
+    'struct',
+    'all',
+    '',
+    [
+        'title' => ['name' => 'title', 'type' => 'xsd:string'],
+        'category_code' => ['name' => 'category_code', 'type' => 'xsd:string'],
+        'wanted_code' => ['name' => 'wanted_code', 'type' => 'xsd:int'],
+        'tutor_name' => ['name' => 'tutor_name', 'type' => 'xsd:string'],
+        'course_admin_user_id_field_name' => ['name' => 'course_admin_user_id_field_name', 'type' => 'xsd:string'],
+        'course_admin_user_id_value' => ['name' => 'course_admin_user_id_value', 'type' => 'xsd:string'],
+        'language' => ['name' => 'language', 'type' => 'xsd:string'],
+        'course_id_field_name' => ['name' => 'course_id_field_name', 'type' => 'xsd:string'],
+        'course_id_value' => ['name' => 'course_id_value', 'type' => 'xsd:string'],
+        'extras' => ['name' => 'extras', 'type' => 'tns:extra_field'],
+    ]
+);
+
+$s->wsdl->addComplexType(
+    'course_create_result',
+    'complexType',
+    'struct',
+    'all',
+    '',
+    [
+        'course_id_value' => ['name' => 'course_id_value', 'type' => 'xsd:string'],
+        'course_id_generated' => ['name' => 'course_id_generated', 'type' => 'xsd:int'],
+        'result' => ['name' => 'result', 'type' => 'tns:result'],
+    ]
+);
+
+$s->wsdl->addComplexType(
+    'course_create_result_array',
+    'complexType',
+    'array',
+    '',
+    'SOAP-ENC:Array',
+    [],
+    [['ref' => 'SOAP-ENC:arrayType', 'wsdl:arrayType' => 'tns:course_create_result[]']],
+    'tns:course_create_result'
+);
+
+$s->register(
+    'WSCourse.CreateCourses',
+    [
+        'secret_key' => 'xsd:string',
+        'courses' => 'tns:course_create[]',
+    ],
+    ['return' => 'tns:course_create_result_array']
+);
+
+$s->register(
+    'WSCourse.EditCourse',
+    [
+        'secret_key' => 'xsd:string',
+        'course_id_field_name' => 'xsd:string',
+        'course_id_value' => 'xsd:string',
+        'title' => 'xsd:string',
+        'category_code' => 'xsd:string',
+        'department_name' => 'xsd:string',
+        'department_url' => 'xsd:string',
+        'language' => 'xsd:string',
+        'visibility' => 'xsd:int',
+        'subscribe' => 'xsd:int',
+        'unsubscribe' => 'xsd:int',
+        'visual_code' => 'xsd:string',
+        'extras' => 'tns:extra_field',
+    ]
+);
+
 $s->wsdl->addComplexType(
     'course',
     'complexType',

+ 307 - 0
main/webservices/soap_user.php

@@ -0,0 +1,307 @@
+<?php
+/* For licensing terms, see /license.txt */
+/**
+ * Configures the WSUser SOAP service.
+ *
+ * @package chamilo.webservices
+ */
+require_once __DIR__.'/webservice_user.php';
+require_once __DIR__.'/soap.php';
+
+/**
+ * Configures the WSUser SOAP service.
+ *
+ * @package chamilo.webservices
+ */
+$s = WSSoapServer::singleton();
+
+$s->wsdl->addComplexType(
+    'user_id',
+    'complexType',
+    'struct',
+    'all',
+    '',
+    [
+        'user_id_field_name' => [
+            'name' => 'user_id_field_name',
+            'type' => 'xsd:string',
+        ],
+        'user_id_value' => [
+            'name' => 'user_id_value',
+            'type' => 'xsd:string',
+        ],
+    ]
+);
+
+$s->wsdl->addComplexType(
+    'user_result',
+    'complexType',
+    'struct',
+    'all',
+    '',
+    [
+        'user_id_value' => [
+            'name' => 'user_id_value',
+            'type' => 'xsd:string',
+        ],
+        'result' => ['name' => 'result', 'type' => 'tns:result'],
+    ]
+);
+
+$s->wsdl->addComplexType(
+    'user_result_array',
+    'complexType',
+    'array',
+    '',
+    'SOAP-ENC:Array',
+    [],
+    [
+        [
+            'ref' => 'SOAP-ENC:arrayType',
+            'wsdl:arrayType' => 'tns:user_result[]',
+        ],
+    ],
+    'tns:user_result'
+);
+
+$s->register(
+    'WSUser.DisableUser',
+    [
+        'secret_key' => 'xsd:string',
+        'user_id_field_name' => 'xsd:string',
+        'user_id_value' => 'xsd:string',
+    ]
+);
+
+$s->register(
+    'WSUser.DisableUsers',
+    ['secret_key' => 'xsd:string', 'users' => 'tns:user_id[]'],
+    ['return' => 'tns:user_result_array']
+);
+
+$s->register(
+    'WSUser.EnableUser',
+    [
+        'secret_key' => 'xsd:string',
+        'user_id_field_name' => 'xsd:string',
+        'user_id_value' => 'xsd:string',
+    ]
+);
+
+$s->register(
+    'WSUser.EnableUsers',
+    ['secret_key' => 'xsd:string', 'users' => 'tns:user_id[]'],
+    ['return' => 'tns:user_result_array']
+);
+
+$s->register(
+    'WSUser.DeleteUser',
+    [
+        'secret_key' => 'xsd:string',
+        'user_id_field_name' => 'xsd:string',
+        'user_id_value' => 'xsd:string',
+    ]
+);
+
+$s->register(
+    'WSUser.DeleteUsers',
+    ['secret_key' => 'xsd:string', 'users' => 'tns:user_id[]'],
+    ['return' => 'tns:user_result_array']
+);
+
+$s->register(
+    'WSUser.CreateUser',
+    [
+        'secret_key' => 'xsd:string',
+        'firstname' => 'xsd:string',
+        'lastname' => 'xsd:string',
+        'status' => 'xsd:int',
+        'loginname' => 'xsd:string',
+        'password' => 'xsd:string',
+        'encrypt_method' => 'xsd:string',
+        'user_id_field_name' => 'xsd:string',
+        'user_id_value' => 'xsd:string',
+        'visibility' => 'xsd:int',
+        'email' => 'xsd:string',
+        'language' => 'xsd:string',
+        'phone' => 'xsd:string',
+        'expiration_date' => 'xsd:string',
+        'extras' => 'tns:extra_field',
+    ],
+    ['return' => 'xsd:int']
+);
+
+$s->wsdl->addComplexType(
+    'user_create',
+    'complexType',
+    'struct',
+    'all',
+    '',
+    [
+        'firstname' => ['name' => 'firstname', 'type' => 'xsd:string'],
+        'lastname' => ['name' => 'lastname', 'type' => 'xsd:string'],
+        'status' => ['name' => 'status', 'type' => 'xsd:int'],
+        'loginname' => ['name' => 'loginname', 'type' => 'xsd:string'],
+        'password' => ['name' => 'password', 'type' => 'xsd:string'],
+        'encrypt_method' => [
+            'name' => 'encrypt_method',
+            'type' => 'xsd:string',
+        ],
+        'user_id_field_name' => [
+            'name' => 'user_id_field_name',
+            'type' => 'xsd:string',
+        ],
+        'user_id_value' => [
+            'name' => 'user_id_value',
+            'type' => 'xsd:string',
+        ],
+        'visibility' => ['name' => 'visibility', 'type' => 'xsd:int'],
+        'email' => ['name' => 'email', 'type' => 'xsd:string'],
+        'language' => ['name' => 'language', 'type' => 'xsd:string'],
+        'phone' => ['name' => 'phone', 'type' => 'xsd:string'],
+        'expiration_date' => [
+            'name' => 'expiration_date',
+            'type' => 'xsd:string',
+        ],
+        'extras' => ['name' => 'extras', 'type' => 'tns:extra_field'],
+    ]
+);
+
+$s->wsdl->addComplexType(
+    'user_create_result',
+    'complexType',
+    'struct',
+    'all',
+    '',
+    [
+        'user_id_value' => [
+            'name' => 'user_id_value',
+            'type' => 'xsd:string',
+        ],
+        'user_id_generated' => [
+            'name' => 'user_id_generated',
+            'type' => 'xsd:int',
+        ],
+        'result' => ['name' => 'result', 'type' => 'tns:result'],
+    ]
+);
+
+$s->wsdl->addComplexType(
+    'user_create_result_array',
+    'complexType',
+    'array',
+    '',
+    'SOAP-ENC:Array',
+    [],
+    [
+        [
+            'ref' => 'SOAP-ENC:arrayType',
+            'wsdl:arrayType' => 'tns:user_create_result[]',
+        ],
+    ],
+    'tns:user_create_result'
+);
+
+$s->register(
+    'WSUser.CreateUsers',
+    [
+        'secret_key' => 'xsd:string',
+        'users' => 'tns:user_create[]',
+    ],
+    ['return' => 'tns:user_create_result_array']
+);
+
+$s->register(
+    'WSUser.EditUser',
+    [
+        'secret_key' => 'xsd:string',
+        'user_id_field_name' => 'xsd:string',
+        'user_id_value' => 'xsd:string',
+        'firstname' => 'xsd:string',
+        'lastname' => 'xsd:string',
+        'status' => 'xsd:int',
+        'loginname' => 'xsd:string',
+        'password' => 'xsd:string',
+        'encrypt_method' => 'xsd:string',
+        'email' => 'xsd:string',
+        'language' => 'xsd:string',
+        'phone' => 'xsd:string',
+        'expiration_date' => 'xsd:string',
+        'extras' => 'tns:extra_field',
+    ]
+);
+
+$s->wsdl->addComplexType(
+    'user_edit',
+    'complexType',
+    'struct',
+    'all',
+    '',
+    [
+        'user_id_field_name' => [
+            'name' => 'user_id_field_name',
+            'type' => 'xsd:string',
+        ],
+        'user_id_value' => [
+            'name' => 'user_id_value',
+            'type' => 'xsd:string',
+        ],
+        'firstname' => ['name' => 'firstname', 'type' => 'xsd:string'],
+        'lastname' => ['name' => 'lastname', 'type' => 'xsd:string'],
+        'status' => ['name' => 'status', 'type' => 'xsd:int'],
+        'loginname' => ['name' => 'loginname', 'type' => 'xsd:string'],
+        'password' => ['name' => 'password', 'type' => 'xsd:string'],
+        'encrypt_method' => [
+            'name' => 'encrypt_method',
+            'type' => 'xsd:string',
+        ],
+        'email' => ['name' => 'email', 'type' => 'xsd:string'],
+        'language' => ['name' => 'language', 'type' => 'xsd:string'],
+        'phone' => ['name' => 'phone', 'type' => 'xsd:string'],
+        'expiration_date' => [
+            'name' => 'expiration_date',
+            'type' => 'xsd:string',
+        ],
+        'extras' => ['name' => 'extras', 'type' => 'tns:extra_field'],
+    ]
+);
+
+$s->wsdl->addComplexType(
+    'user_edit_result',
+    'complexType',
+    'struct',
+    'all',
+    '',
+    [
+        'user_id_value' => [
+            'name' => 'user_id_value',
+            'type' => 'xsd:string',
+        ],
+        'result' => ['name' => 'result', 'type' => 'tns:result'],
+    ]
+);
+
+$s->wsdl->addComplexType(
+    'user_edit_result_array',
+    'complexType',
+    'array',
+    '',
+    'SOAP-ENC:Array',
+    [],
+    [
+        [
+            'ref' => 'SOAP-ENC:arrayType',
+            'wsdl:arrayType' => 'tns:user_edit_result[]',
+        ],
+    ],
+    'tns:user_edit_result'
+);
+
+$s->register(
+    'WSUser.EditUsers',
+    [
+        'secret_key' => 'xsd:string',
+        'users' => 'tns:user_edit[]',
+    ],
+    ['return' => 'tns:user_edit_result_array']
+);

+ 419 - 0
main/webservices/webservice_course.php

@@ -11,6 +11,232 @@ require_once __DIR__.'/webservice.php';
  */
 class WSCourse extends WS
 {
+    /**
+     * Deletes a course.
+     *
+     * @param string API secret key
+     * @param string Course id field name
+     * @param string Course id value
+     */
+    public function DeleteCourse(
+        $secret_key,
+        $course_id_field_name,
+        $course_id_value
+    ) {
+        $verifKey = $this->verifyKey($secret_key);
+        if ($verifKey instanceof WSError) {
+            $this->handleError($verifKey);
+        } else {
+            $result = $this->deleteCourseHelper(
+                $course_id_field_name,
+                $course_id_value
+            );
+            if ($result instanceof WSError) {
+                $this->handleError($result);
+            }
+        }
+    }
+
+    /**
+     * Deletes multiple courses.
+     *
+     * @param string API secret key
+     * @param array Array of courses with elements of the form
+     * array('course_id_field_name' => 'name_of_field', 'course_id_value' => 'value')
+     *
+     * @return array Array with elements like
+     *               array('course_id_value' => 'value', 'result' => array('code' => 0, 'message' => 'Operation was successful')).
+     *               Note that if the result array contains a code different
+     *               than 0, an error occured
+     */
+    public function DeleteCourses($secret_key, $courses)
+    {
+        $verifKey = $this->verifyKey($secret_key);
+        if ($verifKey instanceof WSError) {
+            $this->handleError($verifKey);
+        } else {
+            $results = [];
+            foreach ($courses as $course) {
+                $result_tmp = [];
+                $result_op = $this->deleteCourseHelper(
+                    $course['course_id_field_name'],
+                    $course['course_id_value']
+                );
+                $result_tmp['course_id_value'] = $course['course_id_value'];
+                if ($result_op instanceof WSError) {
+                    // Return the error in the results
+                    $result_tmp['result'] = $result_op->toArray();
+                } else {
+                    $result_tmp['result'] = $this->getSuccessfulResult();
+                }
+                $results[] = $result_tmp;
+            }
+
+            return $results;
+        }
+    }
+
+    /**
+     * Creates a course.
+     *
+     * @param string API secret key
+     * @param string Title
+     * @param string Category code
+     * @param string Wanted code. If it's not defined, it will be generated automatically
+     * @param string Tutor name
+     * @param string Course admin user id field name
+     * @param string Course admin user id value
+     * @param string Course language
+     * @param string Course id field name
+     * @param string Course id value
+     * @param array Course extra fields
+     *
+     * @return int Course id generated
+     */
+    public function CreateCourse(
+        $secret_key,
+        $title,
+        $category_code,
+        $wanted_code,
+        $tutor_name,
+        $course_admin_user_id_field_name,
+        $course_admin_user_id_value,
+        $language,
+        $course_id_field_name,
+        $course_id_value,
+        $extras
+    ) {
+        // First, verify the secret key
+        $verifKey = $this->verifyKey($secret_key);
+        if ($verifKey instanceof WSError) {
+            $this->handleError($verifKey);
+        } else {
+            $result = $this->createCourseHelper(
+                $title,
+                $category_code,
+                $wanted_code,
+                $tutor_name,
+                $course_admin_user_id_field_name,
+                $course_admin_user_id_value,
+                $language,
+                $course_id_field_name,
+                $course_id_value,
+                $extras
+            );
+            if ($result instanceof WSError) {
+                $this->handleError($result);
+            } else {
+                return $result;
+            }
+        }
+    }
+
+    /**
+     * Create multiple courses.
+     *
+     * @param string API secret key
+     * @param array Courses to be created, with elements following the structure presented in CreateCourse
+     *
+     * @return array Array with elements of the form
+     *               array('course_id_value' => 'original value sent', 'course_id_generated' => 'value_generated', 'result' => array('code' => 0, 'message' => 'Operation was successful'))
+     */
+    public function CreateCourses($secret_key, $courses)
+    {
+        // First, verify the secret key
+        $verifKey = $this->verifyKey($secret_key);
+        if ($verifKey instanceof WSError) {
+            $this->handleError($verifKey);
+        } else {
+            $results = [];
+            foreach ($courses as $course) {
+                $result_tmp = [];
+                // re-initialize variables just in case
+                $title = $category_code = $wanted_code = $tutor_name = $course_admin_user_id_field_name = $course_admin_user_id_value = $language = $course_id_field_name = $course_id_value = $extras = 0;
+                extract($course);
+                $result = $this->createCourseHelper(
+                    $title,
+                    $category_code,
+                    $wanted_code,
+                    $tutor_name,
+                    $course_admin_user_id_field_name,
+                    $course_admin_user_id_value,
+                    $language,
+                    $course_id_field_name,
+                    $course_id_value,
+                    $extras
+                );
+                if ($result instanceof WSError) {
+                    $result_tmp['result'] = $result->toArray();
+                    $result_tmp['course_id_value'] = $course_id_value;
+                    $result_tmp['course_id_generated'] = 0;
+                } else {
+                    $result_tmp['result'] = $this->getSuccessfulResult();
+                    $result_tmp['course_id_value'] = $course_id_value;
+                    $result_tmp['course_id_generated'] = $result;
+                }
+                $results[] = $result_tmp;
+            }
+
+            return $results;
+        }
+    }
+
+    /**
+     * Edits a course.
+     *
+     * @param string API secret key
+     * @param string Course id field name
+     * @param string Course id value
+     * @param string Title
+     * @param string Category code
+     * @param string Department name
+     * @param string Department url
+     * @param string Course language
+     * @param int Visibility
+     * @param int Subscribe (0 = denied, 1 = allowed)
+     * @param int Unsubscribe (0 = denied, 1 = allowed)
+     * @param string Visual code
+     * @param array Course extra fields
+     */
+    public function EditCourse(
+        $secret_key,
+        $course_id_field_name,
+        $course_id_value,
+        $title,
+        $category_code,
+        $department_name,
+        $department_url,
+        $language,
+        $visibility,
+        $subscribe,
+        $unsubscribe,
+        $visual_code,
+        $extras
+    ) {
+        $verifKey = $this->verifyKey($secret_key);
+        if ($verifKey instanceof WSError) {
+            $this->handleError($verifKey);
+        } else {
+            $result = $this->editCourseHelper(
+                $course_id_field_name,
+                $course_id_value,
+                $title,
+                $category_code,
+                $department_name,
+                $department_url,
+                $language,
+                $visibility,
+                $subscribe,
+                $unsubscribe,
+                $visual_code,
+                $extras
+            );
+            if ($result instanceof WSError) {
+                $this->handleError($result);
+            }
+        }
+    }
+
     /**
      * List courses.
      *
@@ -253,6 +479,199 @@ class WSCourse extends WS
         }
     }
 
+    /**
+     * Deletes a course (helper method).
+     *
+     * @param string Course id field name
+     * @param string Course id value
+     *
+     * @return mixed True if the course was successfully deleted, WSError otherwise
+     */
+    protected function deleteCourseHelper(
+        $course_id_field_name,
+        $course_id_value
+    ) {
+        $course_id = $this->getCourseId(
+            $course_id_field_name,
+            $course_id_value
+        );
+        if ($course_id instanceof WSError) {
+            return $course_id;
+        } else {
+            $course_code = CourseManager::get_course_code_from_course_id(
+                $course_id
+            );
+            CourseManager::delete_course($course_code);
+
+            return true;
+        }
+    }
+
+    /**
+     * Creates a course (helper method).
+     *
+     * @param string Title
+     * @param string Category code
+     * @param string Wanted code. If it's not defined, it will be generated automatically
+     * @param string Tutor name
+     * @param string Course admin user id field name
+     * @param string Course admin user id value
+     * @param string Course language
+     * @param string Course id field name
+     * @param string Course id value
+     * @param array Course extra fields
+     *
+     * @return mixed Generated id if creation was successful, WSError otherwise
+     */
+    protected function createCourseHelper(
+        $title,
+        $category_code,
+        $wanted_code,
+        $tutor_name,
+        $course_admin_user_id_field_name,
+        $course_admin_user_id_value,
+        $language,
+        $course_id_field_name,
+        $course_id_value,
+        $extras
+    ) {
+        // Add the original course id field name and value to the extra fields if needed
+        $extras_associative = [];
+        if ($course_id_field_name != "chamilo_course_id") {
+            $extras_associative[$course_id_field_name] = $course_id_value;
+        }
+        foreach ($extras as $extra) {
+            $extras_associative[$extra['field_name']] = $extra['field_value'];
+        }
+        $course_admin_id = $this->getUserId(
+            $course_admin_user_id_field_name,
+            $course_admin_user_id_value
+        );
+        if ($course_admin_id instanceof WSError) {
+            return $course_admin_id;
+        }
+        if ($wanted_code == '') {
+            $wanted_code = CourseManager::generate_course_code($title);
+        }
+        $result = create_course(
+            $wanted_code,
+            $title,
+            $tutor_name,
+            $category_code,
+            $language,
+            $course_admin_id,
+            $this->_configuration['db_prefix'],
+            0
+        );
+        if (!$result) {
+            return new WSError(202, 'There was an error creating the course');
+        } else {
+            // Update extra fields
+            foreach ($extras_associative as $fname => $fvalue) {
+                CourseManager::update_course_extra_field_value(
+                    $result,
+                    $fname,
+                    $fvalue
+                );
+            }
+            // Get course id
+            $course_info = CourseManager::get_course_information($result);
+
+            return $course_info['real_id'];
+        }
+    }
+
+    /**
+     * Edits a course (helper method).
+     *
+     * @param string Course id field name
+     * @param string Course id value
+     * @param string Title
+     * @param string Category code
+     * @param string Department name
+     * @param string Department url
+     * @param string Course language
+     * @param int Visibility
+     * @param int Subscribe (0 = denied, 1 = allowed)
+     * @param int Unsubscribe (0 = denied, 1 = allowed)
+     * @param string Visual code
+     * @param array Course extra fields
+     *
+     * @return mixed True in case of success, WSError otherwise
+     */
+    protected function editCourseHelper(
+        $course_id_field_name,
+        $course_id_value,
+        $title,
+        $category_code,
+        $department_name,
+        $department_url,
+        $language,
+        $visibility,
+        $subscribe,
+        $unsubscribe,
+        $visual_code,
+        $extras
+    ) {
+        $course_id = $this->getCourseId(
+            $course_id_field_name,
+            $course_id_value
+        );
+        if ($course_id instanceof WSError) {
+            return $course_id;
+        } else {
+            $attributes = [];
+            if (!empty($title)) {
+                $attributes['title'] = $title;
+            }
+            if (!empty($category_code)) {
+                $attributes['category_code'] = $category_code;
+            }
+            if (!empty($department_name)) {
+                $attributes['department_name'] = $department_name;
+            }
+            if (!empty($department_url)) {
+                $attributes['department_url'] = $department_url;
+            }
+            if (!empty($language)) {
+                $attributes['course_language'] = $language;
+            }
+            if ($visibility != '') {
+                $attributes['visibility'] = (int) $visibility;
+            }
+            if ($subscribe != '') {
+                $attributes['subscribe'] = (int) $subscribe;
+            }
+            if ($unsubscribe != '') {
+                $attributes['unsubscribe'] = (int) $unsubscribe;
+            }
+            if (!empty($visual_code)) {
+                $attributes['visual_code'] = $visual_code;
+            }
+            if (!empty($attributes)) {
+                CourseManager::update_attributes($course_id, $attributes);
+            }
+            if (!empty($extras)) {
+                $course_code = CourseManager::get_course_code_from_course_id(
+                    $course_id
+                );
+                $extras_associative = [];
+                foreach ($extras as $extra) {
+                    $extras_associative[$extra['field_name']] = $extra['field_value'];
+                }
+                foreach ($extras_associative as $fname => $fvalue) {
+                    CourseManager::update_extra_field_value(
+                        $course_code,
+                        $fname,
+                        $fvalue
+                    );
+                }
+            }
+
+            return true;
+        }
+    }
+
     /**
      * Subscribe or unsubscribe user to a course (helper method).
      *

+ 649 - 0
main/webservices/webservice_user.php

@@ -0,0 +1,649 @@
+<?php
+/* For licensing terms, see /license.txt */
+/**
+ * @package chamilo.webservices
+ */
+require_once __DIR__.'/../inc/global.inc.php';
+require_once __DIR__.'/webservice.php';
+
+/**
+ * Web services available for the User module. This class extends the WS class.
+ */
+class WSUser extends WS
+{
+    /**
+     * Disables a user.
+     *
+     * @param string API secret key
+     * @param string User id field name. Use "chamilo_user_id" as the field name if you want to use the internal user_id
+     * @param string User id value
+     */
+    public function DisableUser(
+        $secret_key,
+        $user_id_field_name,
+        $user_id_value
+    ) {
+        $verifKey = $this->verifyKey($secret_key);
+        if ($verifKey instanceof WSError) {
+            // Let the implementation handle it
+            $this->handleError($verifKey);
+        } else {
+            $result = $this->changeUserActiveState(
+                $user_id_field_name,
+                $user_id_value,
+                0
+            );
+            if ($result instanceof WSError) {
+                $this->handleError($result);
+            }
+        }
+    }
+
+    /**
+     * Disables multiple users.
+     *
+     * @param string API secret key
+     * @param array Array of users with elements of the form
+     * array('user_id_field_name' => 'name_of_field', 'user_id_value' => 'value')
+     *
+     * @return array Array with elements like
+     *               array('user_id_value' => 'value', 'result' => array('code' => 0, 'message' => 'Operation was successful')).
+     *               Note that if the result array contains a code different
+     *               than 0, an error occured
+     */
+    public function DisableUsers($secret_key, $users)
+    {
+        $verifKey = $this->verifyKey($secret_key);
+        if ($verifKey instanceof WSError) {
+            // Let the implementation handle it
+            $this->handleError($verifKey);
+        } else {
+            return $this->changeUsersActiveState($users, 0);
+        }
+    }
+
+    /**
+     * Enables a user.
+     *
+     * @param string API secret key
+     * @param string User id field name. Use "chamilo_user_id" as the field name if you want to use the internal user_id
+     * @param string User id value
+     */
+    public function EnableUser($secret_key, $user_id_field_name, $user_id_value)
+    {
+        $verifKey = $this->verifyKey($secret_key);
+        if ($verifKey instanceof WSError) {
+            $this->handleError($verifKey);
+        } else {
+            $result = $this->changeUserActiveState(
+                $user_id_field_name,
+                $user_id_value,
+                1
+            );
+            if ($result instanceof WSError) {
+                $this->handleError($result);
+            }
+        }
+    }
+
+    /**
+     * Enables multiple users.
+     *
+     * @param string API secret key
+     * @param array Array of users with elements of the form
+     * array('user_id_field_name' => 'name_of_field', 'user_id_value' => 'value')
+     *
+     * @return array Array with elements like
+     *               array('user_id_value' => 'value', 'result' => array('code' => 0, 'message' => 'Operation was successful')).
+     *               Note that if the result array contains a code different
+     *               than 0, an error occured
+     */
+    public function EnableUsers($secret_key, $users)
+    {
+        $verifKey = $this->verifyKey($secret_key);
+        if ($verifKey instanceof WSError) {
+            // Let the implementation handle it
+            $this->handleError($verifKey);
+        } else {
+            return $this->changeUsersActiveState($users, 1);
+        }
+    }
+
+    /**
+     * Deletes a user.
+     *
+     * @param string API secret key
+     * @param string User id field name. Use "chamilo_user_id" as the field name if you want to use the internal user_id
+     * @param string User id value
+     */
+    public function DeleteUser($secret_key, $user_id_field_name, $user_id_value)
+    {
+        $verifKey = $this->verifyKey($secret_key);
+        if ($verifKey instanceof WSError) {
+            $this->handleError($verifKey);
+        } else {
+            $result = $this->deleteUserHelper(
+                $user_id_field_name,
+                $user_id_value
+            );
+            if ($result instanceof WSError) {
+                $this->handleError($result);
+            }
+        }
+    }
+
+    /**
+     * Deletes multiple users.
+     *
+     * @param string API secret key
+     * @param array Array of users with elements of the form
+     * array('user_id_field_name' => 'name_of_field', 'user_id_value' => 'value')
+     *
+     * @return array Array with elements like
+     *               array('user_id_value' => 'value', 'result' => array('code' => 0, 'message' => 'Operation was successful')).
+     *               Note that if the result array contains a code different
+     *               than 0, an error occured
+     */
+    public function DeleteUsers($secret_key, $users)
+    {
+        $verifKey = $this->verifyKey($secret_key);
+        if ($verifKey instanceof WSError) {
+            $this->handleError($verifKey);
+        } else {
+            $results = [];
+            foreach ($users as $user) {
+                $result_tmp = [];
+                $result_op = $this->deleteUserHelper(
+                    $user['user_id_field_name'],
+                    $user['user_id_value']
+                );
+                $result_tmp['user_id_value'] = $user['user_id_value'];
+                if ($result_op instanceof WSError) {
+                    // Return the error in the results
+                    $result_tmp['result'] = $result_op->toArray();
+                } else {
+                    $result_tmp['result'] = $this->getSuccessfulResult();
+                }
+                $results[] = $result_tmp;
+            }
+
+            return $results;
+        }
+    }
+
+    /**
+     * Creates a user.
+     *
+     * @param string API secret key
+     * @param string User first name
+     * @param string User last name
+     * @param int User status
+     * @param string Login name
+     * @param string Password (encrypted or not)
+     * @param string Encrypt method. Leave blank if you are passing the password in clear text,
+     * set to the encrypt method used to encrypt the password otherwise. Remember
+     * to include the salt in the extra fields if you are encrypting the password
+     * @param string User id field name. Use "chamilo_user_id" as the field name if you want to use the internal user_id
+     * @param string User id value. Leave blank if you are using the internal user_id
+     * @param int Visibility. Set by default to 1
+     * @param string User email. Set by default to an empty string
+     * @param string Language. Set by default to english
+     * @param string Phone. Set by default to an empty string
+     * @param string Expiration date. Set to null by default
+     * @param array Extra fields. An array with elements of the form
+     * array('field_name' => 'name_of_the_field', 'field_value' => 'value_of_the_field'). Set to an empty array by default
+     *
+     * @return int New user id generated by the system
+     */
+    public function CreateUser(
+        $secret_key,
+        $firstname,
+        $lastname,
+        $status,
+        $login,
+        $password,
+        $encrypt_method,
+        $user_id_field_name,
+        $user_id_value,
+        $visibility = 1,
+        $email = '',
+        $language = 'english',
+        $phone = '',
+        $expiration_date = '0000-00-00 00:00:00',
+        $extras = []
+    ) {
+        // First, verify the secret key
+        $verifKey = $this->verifyKey($secret_key);
+        if ($verifKey instanceof WSError) {
+            $this->handleError($verifKey);
+        } else {
+            $result = $this->createUserHelper(
+                $firstname,
+                $lastname,
+                $status,
+                $login,
+                $password,
+                $encrypt_method,
+                $user_id_field_name,
+                $user_id_value,
+                $visibility,
+                $email,
+                $language,
+                $phone,
+                $expiration_date,
+                $extras
+            );
+            if ($result instanceof WSError) {
+                $this->handleError($result);
+            } else {
+                return $result;
+            }
+        }
+    }
+
+    /**
+     * Creates multiple users.
+     *
+     * @param string API secret key
+     * @param array Users array. Each member of this array must follow the structure imposed by the CreateUser method
+     *
+     * @return array Array with elements of the form
+     *               array('user_id_value' => 'original value sent', 'user_id_generated' => 'value_generated', 'result' => array('code' => 0, 'message' => 'Operation was successful'))
+     */
+    public function CreateUsers($secret_key, $users)
+    {
+        $verifKey = $this->verifyKey($secret_key);
+        if ($verifKey instanceof WSError) {
+            $this->handleError($verifKey);
+        } else {
+            $results = [];
+            foreach ($users as $user) {
+                $result_tmp = [];
+                // re-initialize variables just in case
+                $firstname = $lastname = $status = $login = $password = $encrypt_method = $user_id_field_name = $user_id_value = $visibility = $email = $language = $phone = $expiration_date = $extras = null;
+                extract($user);
+                $result = $this->createUserHelper(
+                    $firstname,
+                    $lastname,
+                    $status,
+                    $login,
+                    $password,
+                    $encrypt_method,
+                    $user_id_field_name,
+                    $user_id_value,
+                    $visibility,
+                    $email,
+                    $language,
+                    $phone,
+                    $expiration_date,
+                    $extras
+                );
+                if ($result instanceof WSError) {
+                    $result_tmp['result'] = $result->toArray();
+                    $result_tmp['user_id_value'] = $user_id_value;
+                    $result_tmp['user_id_generated'] = 0;
+                } else {
+                    $result_tmp['result'] = $this->getSuccessfulResult();
+                    $result_tmp['user_id_value'] = $user_id_value;
+                    $result_tmp['user_id_generated'] = $result;
+                }
+                $results[] = $result_tmp;
+            }
+
+            return $results;
+        }
+    }
+
+    /**
+     * Edits user info.
+     *
+     * @param string API secret key
+     * @param string User id field name. Use "chamilo_user_id" in order to use internal system id
+     * @param string User id value
+     * @param string First name
+     * @param string Last name
+     * @param int User status
+     * @param string Login name
+     * @param string Password. Leave blank if you don't want to update it
+     * @param string Encrypt method
+     * @param string User email
+     * @param string Language. Set by default to english
+     * @param string Phone. Set by default to an empty string
+     * @param string Expiration date. Set to null by default
+     * @param array Extra fields. An array with elements of the form
+     * ('field_name' => 'name_of_the_field', 'field_value' => 'value_of_the_field'). Leave empty if you don't want to update
+     */
+    public function EditUser(
+        $secret_key,
+        $user_id_field_name,
+        $user_id_value,
+        $firstname,
+        $lastname,
+        $status,
+        $loginname,
+        $password,
+        $encrypt_method,
+        $email,
+        $language,
+        $phone,
+        $expiration_date,
+        $extras
+    ) {
+        // First, verify the secret key
+        $verifKey = $this->verifyKey($secret_key);
+        if ($verifKey instanceof WSError) {
+            $this->handleError($verifKey);
+        } else {
+            $extras_associative = [];
+            if (!empty($extras)) {
+                foreach ($extras as $extra) {
+                    $extras_associative[$extra['field_name']] = $extra['field_value'];
+                }
+            }
+
+            $result = $this->editUserHelper(
+                $user_id_field_name,
+                $user_id_value,
+                $firstname,
+                $lastname,
+                $status,
+                $loginname,
+                $password,
+                $encrypt_method,
+                $email,
+                $language,
+                $phone,
+                $expiration_date,
+                $extras_associative
+            );
+            if ($result instanceof WSError) {
+                $this->handleError($result);
+            }
+        }
+    }
+
+    /**
+     * Edits multiple users.
+     *
+     * @param string API secret key
+     * @param array Users array. Each member of this array must follow the structure imposed by the EditUser method
+     *
+     * @return array Array with elements like
+     *               array('user_id_value' => 'value', 'result' => array('code' => 0, 'message' => 'Operation was successful')).
+     *               Note that if the result array contains a code different
+     *               than 0, an error occured
+     */
+    public function EditUsers($secret_key, $users)
+    {
+        $verifKey = $this->verifyKey($secret_key);
+        if ($verifKey instanceof WSError) {
+            $this->handleError($verifKey);
+        } else {
+            $results = [];
+            foreach ($users as $user) {
+                $result_tmp = [];
+                // re-initialize variables just in case
+                $user_id_field_name = $user_id_value = $firstname = $lastname = $status = $loginname = $password = $encrypt_method = $email = $language = $phone = $expiration_date = $extras = null;
+                extract($user);
+                $result_op = $this->editUserHelper(
+                    $user_id_field_name,
+                    $user_id_value,
+                    $firstname,
+                    $lastname,
+                    $status,
+                    $loginname,
+                    $password,
+                    $encrypt_method,
+                    $email,
+                    $language,
+                    $phone,
+                    $expiration_date,
+                    $extras
+                );
+                $result_tmp['user_id_value'] = $user['user_id_value'];
+                if ($result_op instanceof WSError) {
+                    // Return the error in the results
+                    $result_tmp['result'] = $result_op->toArray();
+                } else {
+                    $result_tmp['result'] = $this->getSuccessfulResult();
+                }
+                $results[] = $result_tmp;
+            }
+
+            return $results;
+        }
+    }
+
+    /**
+     * Enables or disables a user.
+     *
+     * @param string User id field name
+     * @param string User id value
+     * @param int Set to 1 to enable and to 0 to disable
+     *
+     * @return int
+     */
+    protected function changeUserActiveState(
+        $user_id_field_name,
+        $user_id_value,
+        $state
+    ) {
+        $user_id = $this->getUserId($user_id_field_name, $user_id_value);
+        if ($user_id instanceof WSError) {
+            return $user_id;
+        } else {
+            if ($state == 0) {
+                UserManager::disable($user_id);
+            } else {
+                if ($state == 1) {
+                    UserManager::enable($user_id);
+                }
+            }
+        }
+    }
+
+    /**
+     * Enables or disables multiple users.
+     *
+     * @param array Users
+     * @param int Set to 1 to enable and to 0 to disable
+     *
+     * @return array Array of results
+     */
+    protected function changeUsersActiveState($users, $state)
+    {
+        $results = [];
+        foreach ($users as $user) {
+            $result_tmp = [];
+            $result_op = $this->changeUserActiveState(
+                $user['user_id_field_name'],
+                $user['user_id_value'],
+                $state
+            );
+            $result_tmp['user_id_value'] = $user['user_id_value'];
+            if ($result_op instanceof WSError) {
+                // Return the error in the results
+                $result_tmp['result'] = $result_op->toArray();
+            } else {
+                $result_tmp['result'] = $this->getSuccessfulResult();
+            }
+            $results[] = $result_tmp;
+        }
+
+        return $results;
+    }
+
+    /**
+     * Deletes a user (helper method).
+     *
+     * @param string User id field name. Use "chamilo_user_id" as the field name if you want to use the internal user_id
+     * @param string User id value
+     *
+     * @return mixed True if user was successfully deleted, WSError otherwise
+     */
+    protected function deleteUserHelper($user_id_field_name, $user_id_value)
+    {
+        $user_id = $this->getUserId($user_id_field_name, $user_id_value);
+        if ($user_id instanceof WSError) {
+            return $user_id;
+        } else {
+            if (!UserManager::delete_user($user_id)) {
+                return new WSError(
+                    101,
+                    "There was a problem while deleting this user"
+                );
+            } else {
+                return true;
+            }
+        }
+    }
+
+    /**
+     * Creates a user (helper method).
+     *
+     * @param string User first name
+     * @param string User last name
+     * @param int User status
+     * @param string Login name
+     * @param string Password (encrypted or not)
+     * @param string Encrypt method. Leave blank if you are passing the password in clear text,
+     * set to the encrypt method used to encrypt the password otherwise. Remember
+     * to include the salt in the extra fields if you are encrypting the password
+     * @param string User id field name. Use "chamilo_user_id" as the field name if you want to use the internal user_id
+     * @param string User id value. Leave blank if you are using the internal user_id
+     * @param int visibility
+     * @param string user email
+     * @param string language
+     * @param string phone
+     * @param string Expiration date
+     * @param array Extra fields. An array with elements of the form
+     * array('field_name' => 'name_of_the_field', 'field_value' => 'value_of_the_field').
+     *
+     * @return mixed New user id generated by the system, WSError otherwise
+     */
+    protected function createUserHelper(
+        $firstname,
+        $lastname,
+        $status,
+        $login,
+        $password,
+        $encrypt_method,
+        $user_id_field_name,
+        $user_id_value,
+        $visibility,
+        $email,
+        $language,
+        $phone,
+        $expiration_date,
+        $extras = []
+    ) {
+        // Add the original user id field name and value to the extra fields if needed
+        $extras_associative = [];
+        if ($user_id_field_name != "chamilo_user_id") {
+            $extras_associative[$user_id_field_name] = $user_id_value;
+        }
+        if (!empty($extras)) {
+            foreach ($extras as $extra) {
+                $extras_associative[$extra['field_name']] = $extra['field_value'];
+            }
+        }
+        $result = UserManager::create_user(
+            $firstname,
+            $lastname,
+            $status,
+            $email,
+            $login,
+            $password,
+            '',
+            $language,
+            $phone,
+            '',
+            PLATFORM_AUTH_SOURCE,
+            $expiration_date,
+            $visibility,
+            0,
+            $extras_associative,
+            $encrypt_method
+        );
+        if (!$result) {
+            return new WSError(104, 'There was an error creating the user');
+        } else {
+            return $result;
+        }
+    }
+
+    /**
+     * Edits user info (helper method).
+     *
+     * @param string User id field name. Use "chamilo_user_id" in order to use internal system id
+     * @param string User id value
+     * @param string First name
+     * @param string Last name
+     * @param int User status
+     * @param string Login name
+     * @param string Password. Leave blank if you don't want to update it
+     * @param string Encrypt method
+     * @param string User email
+     * @param string Language. Set by default to english
+     * @param string Phone. Set by default to an empty string
+     * @param string Expiration date. Set to null by default
+     * @param array Extra fields. An array with elements of the form
+     * ('field_name' => 'name_of_the_field', 'field_value' => 'value_of_the_field').
+     * Leave empty if you don't want to update
+     *
+     * @return mixed True if user was successfully updated, WSError otherwise
+     */
+    protected function editUserHelper(
+        $user_id_field_name,
+        $user_id_value,
+        $firstname,
+        $lastname,
+        $status,
+        $loginname,
+        $password,
+        $encrypt_method,
+        $email,
+        $language,
+        $phone,
+        $expiration_date,
+        $extras
+    ) {
+        $user_id = $this->getUserId($user_id_field_name, $user_id_value);
+        if ($user_id instanceof WSError) {
+            return $user_id;
+        } else {
+            if ($password == '') {
+                $password = null;
+            }
+            $user_info = api_get_user_info($user_id);
+            if (count($extras) == 0) {
+                $extras = null;
+            }
+
+            $result = UserManager::update_user(
+                $user_id,
+                $firstname,
+                $lastname,
+                $loginname,
+                $password,
+                PLATFORM_AUTH_SOURCE,
+                $email,
+                $status,
+                '',
+                $phone,
+                $user_info['picture_uri'],
+                $expiration_date,
+                $user_info['active'],
+                null,
+                $user_info['hr_dept_id'],
+                $extras,
+                $encrypt_method
+            );
+            if (!$result) {
+                return new WSError(105, 'There was an error updating the user');
+            } else {
+                return $result;
+            }
+        }
+    }
+}

+ 1 - 1
main/wiki/index.php

@@ -34,7 +34,7 @@ function setFocus() {
     $("#search_title").focus();
 }
 
-$(document).ready(function() {
+$(function() {
     setFocus();
     $("#start_date_toggle").click(function() {
         $("#start_date").toggle();

+ 0 - 1
main/work/add_document.php

@@ -60,7 +60,6 @@ if (empty($docId)) {
     if (!empty($documents)) {
         echo Display::page_subheader(get_lang('DocumentsAdded'));
         echo '<div class="well">';
-        $urlDocument = api_get_path(WEB_CODE_PATH).'work/add_document.php';
         foreach ($documents as $doc) {
             $documentId = $doc['document_id'];
             $docData = DocumentManager::get_document_data_by_id($documentId, $courseInfo['code']);

+ 1 - 1
main/work/upload.php

@@ -45,7 +45,7 @@ $token = Security::get_token();
 $student_can_edit_in_session = api_is_allowed_to_session_edit(false, true);
 
 $onlyOnePublication = api_get_configuration_value('allow_only_one_student_publication_per_user');
-//  @todo add an option to allow/block multiple attempts.
+
 if ($onlyOnePublication) {
     $count = get_work_count_by_student($user_id, $work_id);
     if ($count >= 1) {

+ 1 - 1
main/work/view.php

@@ -6,7 +6,7 @@ $current_course_tool = TOOL_STUDENTPUBLICATION;
 
 require_once 'work.lib.php';
 
-$id = isset($_GET['id']) ? intval($_GET['id']) : null;
+$id = isset($_GET['id']) ? (int) $_GET['id'] : null;
 $work = get_work_data_by_id($id);
 
 if (empty($id) || empty($work)) {

+ 13 - 16
main/work/work.lib.php

@@ -137,12 +137,14 @@ function get_work_data_by_id($id, $courseId = 0, $sessionId = 0)
         $work['show_url'] = $webCodePath.'work/show_file.php?id='.$work['id'].'&'.api_get_cidreq();
         $work['show_content'] = '';
         if ($work['contains_file']) {
-            $fileType = mime_content_type(
-                api_get_path(SYS_COURSE_PATH).$course->getDirectory().'/'.$work['url']
-            );
+            $fileType = '';
+            $file = api_get_path(SYS_COURSE_PATH).$course->getDirectory().'/'.$work['url'];
+            if (file_exists($file)) {
+                $fileType = mime_content_type($file);
+            }
 
             if (in_array($fileType, ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'])) {
-                $work['show_content'] = Display::img($work['show_url'], $work['title']);
+                $work['show_content'] = Display::img($work['show_url'], $work['title'], null, false);
             } elseif (false !== strpos($fileType, 'video/')) {
                 $work['show_content'] = Display::tag(
                     'video',
@@ -153,10 +155,7 @@ function get_work_data_by_id($id, $courseId = 0, $sessionId = 0)
         }
 
         $fieldValue = new ExtraFieldValue('work');
-        $work['extra'] = $fieldValue->getAllValuesForAnItem(
-            $id,
-            true
-        );
+        $work['extra'] = $fieldValue->getAllValuesForAnItem($id, true);
     }
 
     return $work;
@@ -1006,7 +1005,7 @@ function to_javascript_work()
             $("#work_title").focus();
         }
 
-        $(document).ready(function() {
+        $(function() {
             setFocus();
             var checked = $("#expiry_date").attr("checked");
             if (checked) {
@@ -2033,7 +2032,7 @@ function get_work_user_list(
                     $feedback .= '<a href="'.$url.'view.php?'.api_get_cidreq().'&id='.$item_id.'" title="'.get_lang(
                             'View'
                         ).'">'.
-                    $count.' '.Display::returnFontAwesomeIcon('comments-o').'</a> ';
+                        $count.' '.Display::returnFontAwesomeIcon('comments-o').'</a> ';
                 }
 
                 $correction = '';
@@ -2102,7 +2101,7 @@ function get_work_user_list(
                     ';
 
                     $correction .= "<script>
-                    $(document).ready(function() {
+                    $(function() {
                         $('.work_correction_file_upload').each(function () {
                             $(this).fileupload({
                                 dropZone: $(this)
@@ -2155,13 +2154,13 @@ function get_work_user_list(
                                     ).'&item_id='.$item_id.'&id='.$work['parent_id'].'" title="'.get_lang(
                                         'Edit'
                                     ).'"  >'.
-                                Display::return_icon('edit.png', get_lang('Edit'), [], ICON_SIZE_SMALL).'</a>';
+                                    Display::return_icon('edit.png', get_lang('Edit'), [], ICON_SIZE_SMALL).'</a>';
                             } else {
                                 $editLink = '<a href="'.$url.'edit.php?'.api_get_cidreq(
                                     ).'&item_id='.$item_id.'&id='.$work['parent_id'].'" title="'.get_lang(
                                         'Modify'
                                     ).'">'.
-                                Display::return_icon('edit.png', get_lang('Edit'), [], ICON_SIZE_SMALL).'</a>';
+                                    Display::return_icon('edit.png', get_lang('Edit'), [], ICON_SIZE_SMALL).'</a>';
                             }
                         }
                         $action .= $editLink;
@@ -4010,12 +4009,10 @@ function processWorkForm(
                         $courseId,
                         $sessionId
                     );
-
                     if (count($userWorks) == 1) {
                         // The student only uploaded one doc so far, so add the
                         // considered work time to his course connection time
-                        $ip = api_get_real_ip();
-                        Event::eventAddVirtualCourseTime($courseId, $userId, $sessionId, $workingTime, $ip);
+                        Event::eventAddVirtualCourseTime($courseId, $userId, $sessionId, $workingTime);
                     }
                 }
             }