Quellcode durchsuchen

Update from 1.11.x

Julio vor 6 Jahren
Ursprung
Commit
20df33657d
57 geänderte Dateien mit 3266 neuen und 2341 gelöschten Zeilen
  1. 1 1
      main/inc/global.inc.php
  2. 17 17
      main/inc/lib/AnnouncementManager.php
  3. 73 45
      main/inc/lib/CoursesAndSessionsCatalog.class.php
  4. 466 418
      main/inc/lib/MoodleImport.php
  5. 4 4
      main/inc/lib/TicketManager.php
  6. 7 1
      main/inc/lib/UnserializeApi.php
  7. 6 12
      main/inc/lib/add_course.lib.inc.php
  8. 35 55
      main/inc/lib/agenda.lib.php
  9. 167 11
      main/inc/lib/api.lib.php
  10. 1 1
      main/inc/lib/array.lib.php
  11. 1 1
      main/inc/lib/attendance.lib.php
  12. 1 1
      main/inc/lib/auth.lib.php
  13. 84 93
      main/inc/lib/chat.lib.php
  14. 29 62
      main/inc/lib/course.lib.php
  15. 11 9
      main/inc/lib/course_category.lib.php
  16. 218 206
      main/inc/lib/course_home.lib.php
  17. 63 26
      main/inc/lib/display.lib.php
  18. 35 35
      main/inc/lib/document.lib.php
  19. 295 68
      main/inc/lib/exercise.lib.php
  20. 159 125
      main/inc/lib/exercise_show_functions.lib.php
  21. 6 2
      main/inc/lib/export.lib.inc.php
  22. 195 231
      main/inc/lib/extra_field.lib.php
  23. 2 2
      main/inc/lib/glossary.lib.php
  24. 36 28
      main/inc/lib/groupmanager.lib.php
  25. 6 2
      main/inc/lib/internationalization.lib.php
  26. 4 0
      main/inc/lib/javascript/chat/video.php
  27. 265 119
      main/inc/lib/message.lib.php
  28. 18 9
      main/inc/lib/notification.lib.php
  29. 4 4
      main/inc/lib/online.inc.php
  30. 31 12
      main/inc/lib/pdf.lib.php
  31. 4 4
      main/inc/lib/pear/HTML/Common.php
  32. 2 2
      main/inc/lib/pear/HTML/QuickForm/Renderer.php
  33. 23 23
      main/inc/lib/pear/HTML/QuickForm/button.php
  34. 4 3
      main/inc/lib/pear/HTML/QuickForm/checkbox.php
  35. 16 16
      main/inc/lib/pear/HTML/QuickForm/element.php
  36. 32 32
      main/inc/lib/pear/HTML/QuickForm/file.php
  37. 8 8
      main/inc/lib/pear/HTML/QuickForm/input.php
  38. 2 2
      main/inc/lib/pear/HTML/QuickForm/link.php
  39. 2 2
      main/inc/lib/pear/HTML/QuickForm/reset.php
  40. 9 9
      main/inc/lib/pear/HTML/QuickForm/static.php
  41. 3 3
      main/inc/lib/pear/HTML/QuickForm/submit.php
  42. 1 1
      main/inc/lib/pear/HTML/QuickForm/text.php
  43. 8 8
      main/inc/lib/pear/HTML/QuickForm/textarea.php
  44. 4 2
      main/inc/lib/plugin.class.php
  45. 3 2
      main/inc/lib/plugin.lib.php
  46. 286 226
      main/inc/lib/sessionmanager.lib.php
  47. 26 33
      main/inc/lib/skill.lib.php
  48. 104 104
      main/inc/lib/social.lib.php
  49. 1 1
      main/inc/lib/sortable_table.class.php
  50. 97 8
      main/inc/lib/statistics.lib.php
  51. 1 1
      main/inc/lib/system/session.class.php
  52. 10 43
      main/inc/lib/template.lib.php
  53. 72 174
      main/inc/lib/tracking.lib.php
  54. 1 1
      main/inc/lib/usergroup.lib.php
  55. 267 23
      main/inc/lib/usermanager.lib.php
  56. 38 8
      main/inc/lib/userportal.lib.php
  57. 2 2
      main/inc/lib/webservices/Rest.php

+ 1 - 1
main/inc/global.inc.php

@@ -307,7 +307,7 @@ require_once $libraryPath.'formvalidator/Rule/allowed_tags.inc.php';
 // which will then be usable from the banner and header scripts
 $this_section = SECTION_GLOBAL;
 
-// Include Chamilo Mail conf this is added here because the api_get_setting works
+// Including configuration files
 $configurationFiles = [
     'mail.conf.php',
     'profile.conf.php',

+ 17 - 17
main/inc/lib/AnnouncementManager.php

@@ -739,8 +739,8 @@ class AnnouncementManager
             }
 
             $send_to_users = CourseManager::separateUsersGroups($to_users);
-            // Store in item_property (first the groups, then the users)
-            //if (!isset($to_users)) {
+
+            // if nothing was selected in the menu then send to all the group
             $sentToAllGroup = false;
             if (empty($send_to_users['groups']) && empty($send_to_users['users'])) {
                 $groupInfo = GroupManager::get_group_properties($groupId);
@@ -754,7 +754,7 @@ class AnnouncementManager
                 );
                 $sentToAllGroup = true;
             }
-            // when no user is selected we send it to everyone
+
             if ($sentToAllGroup === false) {
                 if (!empty($send_to_users['groups'])) {
                     foreach ($send_to_users['groups'] as $group) {
@@ -769,19 +769,19 @@ class AnnouncementManager
                         );
                     }
                 }
-                // storing the selected users
+
                 $groupInfo = GroupManager::get_group_properties($groupId);
                 if (!empty($send_to_users['users'])) {
                     foreach ($send_to_users['users'] as $user) {
                         api_item_property_update(
-                                $courseInfo,
-                                TOOL_ANNOUNCEMENT,
-                                $last_id,
-                                'AnnouncementAdded',
-                                api_get_user_id(),
-                                $groupInfo,
-                                $user
-                            );
+                            $courseInfo,
+                            TOOL_ANNOUNCEMENT,
+                            $last_id,
+                            'AnnouncementAdded',
+                            api_get_user_id(),
+                            $groupInfo,
+                            $user
+                        );
                     }
                 }
             }
@@ -1633,8 +1633,8 @@ class AnnouncementManager
             ($allowUserEditSetting && !api_is_anonymous()) ||
             ($allowDrhAccess && api_is_drh())
         ) {
+            // A.1. you are a course admin with a USER filter
             // => see only the messages of this specific user + the messages of the group (s)he is member of.
-
             //if (!empty($user_id)) {
             if (0) {
                 if (is_array($group_memberships) &&
@@ -1771,13 +1771,13 @@ class AnnouncementManager
                             $condition_session
                             $searchCondition AND 
                             ip.visibility='1'
-                        $groupBy
+                            $groupBy
                         ORDER BY display_order DESC";
             } else {
                 if ($user_id) {
                     if ($allowUserEditSetting && !api_is_anonymous()) {
                         $cond_user_id = " AND (
-                            ip.lastedit_user_id = '".api_get_user_id()."' OR
+                                ip.lastedit_user_id = '".api_get_user_id()."' OR
                                 ((ip.to_user_id='$user_id' OR ip.to_user_id IS NULL) AND 
                                 (ip.to_group_id='0' OR ip.to_group_id IS NULL)
                             )
@@ -1824,7 +1824,7 @@ class AnnouncementManager
                                 $searchCondition  AND
                                 ip.visibility='1' AND
                                 announcement.session_id IN ( 0,".api_get_session_id().")
-                            $groupBy
+                                $groupBy
                             ";
                 }
             }
@@ -2000,7 +2000,7 @@ class AnnouncementManager
                                         api_get_system_encoding()
                                     )
                                 )."')) return false;\">".
-                            $deleteIcon."</a>";
+                                $deleteIcon."</a>";
                         }
                     }
                     $iterator++;

+ 73 - 45
main/inc/lib/CoursesAndSessionsCatalog.class.php

@@ -180,21 +180,21 @@ class CoursesAndSessionsCatalog
         $allCategories = CourseCategory::getAllCategories();
 
         foreach ($allCategories as $category) {
-            $subList = [];
             if (empty($category['parent_id'])) {
                 $list[$category['code']] = $category;
                 $list[$category['code']]['level'] = 0;
                 list($subList, $childrenCount) = self::buildCourseCategoryTree($allCategories, $category['code'], 0);
-                //$list = array($list, $subList);
                 foreach ($subList as $item) {
                     $list[$item['code']] = $item;
                 }
-                $list[$category['code']]['number_courses'] = $childrenCount + $category['number_courses'];
+                // Real course count
+                $countCourses = CourseCategory::countCoursesInCategory($category['code']);
+                $list[$category['code']]['number_courses'] = $childrenCount + $countCourses;
             }
         }
 
         // count courses that are in no category
-        $count_courses = CourseCategory::countCoursesInCategory();
+        $countCourses = CourseCategory::countCoursesInCategory();
         $categories['NONE'] = [
             'id' => 0,
             'name' => get_lang('WithoutCategory'),
@@ -204,7 +204,7 @@ class CoursesAndSessionsCatalog
             'children_count' => 0,
             'auth_course_child' => true,
             'auth_cat_child' => true,
-            'number_courses' => $count_courses,
+            'number_courses' => $countCourses,
             'level' => 0,
         ];
 
@@ -236,16 +236,19 @@ class CoursesAndSessionsCatalog
         ];
 
         $categoriesFromDatabase = CourseCategory::getCategories();
+
         foreach ($categoriesFromDatabase as $row) {
-            $count_courses = CourseCategory::countCoursesInCategory($row['code']);
-            $row['count_courses'] = $count_courses;
+            $countCourses = CourseCategory::countCoursesInCategory($row['code']);
+            $row['count_courses'] = $countCourses;
             if (empty($row['parent_id'])) {
                 $categories[0][$row['tree_pos']] = $row;
             } else {
                 $categories[$row['parent_id']][$row['tree_pos']] = $row;
             }
         }
-        $count_courses = CourseCategory::countCoursesInCategory();
+
+        // count courses that are in no category
+        $countCourses = CourseCategory::countCoursesInCategory();
         $categories[0][count($categories[0]) + 1] = [
             'id' => 0,
             'name' => get_lang('None'),
@@ -255,7 +258,7 @@ class CoursesAndSessionsCatalog
             'children_count' => 0,
             'auth_course_child' => true,
             'auth_cat_child' => true,
-            'count_courses' => $count_courses,
+            'count_courses' => $countCourses,
         ];
 
         return $categories;
@@ -292,26 +295,29 @@ class CoursesAndSessionsCatalog
     {
         $tbl_course = Database::get_main_table(TABLE_MAIN_COURSE);
         $avoidCoursesCondition = self::getAvoidCourseCondition();
-        $visibilityCondition = CourseManager::getCourseVisibilitySQLCondition(
-            'course',
-            true
-        );
+        $visibilityCondition = CourseManager::getCourseVisibilitySQLCondition('course', true);
 
         if (!empty($random_value)) {
-            $random_value = intval($random_value);
+            $random_value = (int) $random_value;
 
             $sql = "SELECT COUNT(*) FROM $tbl_course";
             $result = Database::query($sql);
             list($num_records) = Database::fetch_row($result);
 
             if (api_is_multiple_url_enabled()) {
-                $url_access_id = api_get_current_access_url_id();
+                $urlId = api_get_current_access_url_id();
                 $tbl_url_rel_course = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
 
+                $urlCondition = ' access_url_id = '.$urlId.' ';
+                $allowBaseCategories = api_get_configuration_value('allow_base_course_category');
+                if ($allowBaseCategories) {
+                    $urlCondition = ' (access_url_id = '.$urlId.' OR access_url_id = 1)  ';
+                }
+
                 $sql = "SELECT COUNT(*) FROM $tbl_course course
                         INNER JOIN $tbl_url_rel_course as url_rel_course
                         ON (url_rel_course.c_id = course.id)
-                        WHERE access_url_id = $url_access_id ";
+                        WHERE access_url_id = '.$urlId.' ";
                 $result = Database::query($sql);
                 list($num_records) = Database::fetch_row($result);
 
@@ -320,7 +326,7 @@ class CoursesAndSessionsCatalog
                         INNER JOIN $tbl_url_rel_course as url_rel_course
                         ON (url_rel_course.c_id = course.id)
                         WHERE
-                            access_url_id = $url_access_id AND
+                            $urlCondition AND
                             RAND()*$num_records< $random_value
                             $avoidCoursesCondition 
                             $visibilityCondition
@@ -352,7 +358,23 @@ class CoursesAndSessionsCatalog
         } else {
             $limitFilter = self::getLimitFilterFromArray($limit);
             $category_code = Database::escape_string($category_code);
-            if (empty($category_code) || $category_code == "ALL") {
+            $listCode = self::childrenCategories($category_code);
+            $conditionCode = ' ';
+
+            if (empty($listCode)) {
+                if ($category_code === 'NONE') {
+                    $conditionCode .= " category_code='' ";
+                } else {
+                    $conditionCode .= " category_code='$category_code' ";
+                }
+            } else {
+                foreach ($listCode as $code) {
+                    $conditionCode .= " category_code='$code' OR ";
+                }
+                $conditionCode .= " category_code='$category_code' ";
+            }
+
+            if (empty($category_code) || $category_code == 'ALL') {
                 $sql = "SELECT *, id as real_id 
                         FROM $tbl_course course
                         WHERE
@@ -361,12 +383,9 @@ class CoursesAndSessionsCatalog
                           $visibilityCondition
                     ORDER BY title $limitFilter ";
             } else {
-                if ($category_code == 'NONE') {
-                    $category_code = '';
-                }
                 $sql = "SELECT *, id as real_id FROM $tbl_course course
                         WHERE
-                            category_code='$category_code'
+                            $conditionCode 
                             $avoidCoursesCondition
                             $visibilityCondition
                         ORDER BY title $limitFilter ";
@@ -374,15 +393,17 @@ class CoursesAndSessionsCatalog
 
             // Showing only the courses of the current Chamilo access_url_id
             if (api_is_multiple_url_enabled()) {
-                $url_access_id = api_get_current_access_url_id();
+                $urlId = api_get_current_access_url_id();
                 $tbl_url_rel_course = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
-                if ($category_code != "ALL") {
+
+                $urlCondition = ' access_url_id = '.$urlId.' ';
+                if ($category_code != 'ALL') {
                     $sql = "SELECT *, course.id real_id FROM $tbl_course as course
                             INNER JOIN $tbl_url_rel_course as url_rel_course
                             ON (url_rel_course.c_id = course.id)
                             WHERE
-                                access_url_id = $url_access_id AND
-                                category_code='$category_code'
+                                $urlCondition AND
+                                $conditionCode
                                 $avoidCoursesCondition
                                 $visibilityCondition
                             ORDER BY title $limitFilter";
@@ -391,7 +412,7 @@ class CoursesAndSessionsCatalog
                             INNER JOIN $tbl_url_rel_course as url_rel_course
                             ON (url_rel_course.c_id = course.id)
                             WHERE
-                                access_url_id = $url_access_id
+                                $urlCondition
                                 $avoidCoursesCondition
                                 $visibilityCondition
                             ORDER BY title $limitFilter";
@@ -453,28 +474,36 @@ class CoursesAndSessionsCatalog
         $avoidCoursesCondition = self::getAvoidCourseCondition();
         $visibilityCondition = $justVisible ? CourseManager::getCourseVisibilitySQLCondition('course', true) : '';
         $search_term_safe = Database::escape_string($search_term);
-        $sql_find = "SELECT * FROM $courseTable course
-                      WHERE (
-                            course.code LIKE '%".$search_term_safe."%' OR
-                            course.title LIKE '%".$search_term_safe."%' OR
-                            course.tutor_name LIKE '%".$search_term_safe."%'
-                        )
-                        $avoidCoursesCondition
-                        $visibilityCondition
-                    ORDER BY title, visual_code ASC
-                    $limitFilter
-                    ";
+        $sql = "SELECT * FROM $courseTable course
+                WHERE (
+                        course.code LIKE '%".$search_term_safe."%' OR
+                        course.title LIKE '%".$search_term_safe."%' OR
+                        course.tutor_name LIKE '%".$search_term_safe."%'
+                    )
+                    $avoidCoursesCondition
+                    $visibilityCondition
+                ORDER BY title, visual_code ASC
+                $limitFilter
+                ";
 
         if (api_is_multiple_url_enabled()) {
-            $url_access_id = api_get_current_access_url_id();
-            if ($url_access_id != -1) {
+            $urlId = api_get_current_access_url_id();
+            if ($urlId != -1) {
                 $tbl_url_rel_course = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
-                $sql_find = "SELECT *
+
+                $urlCondition = ' access_url_id = '.$urlId.' AND';
+                $allowBaseCategories = api_get_configuration_value('allow_base_course_category');
+                if ($allowBaseCategories) {
+                    $urlCondition = ' (access_url_id = '.$urlId.' OR access_url_id = 1) AND ';
+                }
+
+                $sql = "SELECT *
                             FROM $courseTable as course
                             INNER JOIN $tbl_url_rel_course as url_rel_course
                             ON (url_rel_course.c_id = course.id)
                             WHERE
-                                access_url_id = $url_access_id AND (
+                                access_url_id = $urlId AND 
+                                (
                                     code LIKE '%".$search_term_safe."%' OR
                                     title LIKE '%".$search_term_safe."%' OR
                                     tutor_name LIKE '%".$search_term_safe."%'
@@ -486,7 +515,7 @@ class CoursesAndSessionsCatalog
                             ";
             }
         }
-        $result_find = Database::query($sql_find);
+        $result_find = Database::query($sql);
         $courses = [];
         while ($row = Database::fetch_array($result_find)) {
             $row['registration_code'] = !empty($row['registration_code']);
@@ -534,6 +563,7 @@ class CoursesAndSessionsCatalog
     {
         $em = Database::getManager();
         $urlId = api_get_current_access_url_id();
+
         $sql = "SELECT s.id FROM session s ";
         $sql .= "
             INNER JOIN access_url_rel_session ars
@@ -687,8 +717,6 @@ class CoursesAndSessionsCatalog
         $count = 0;
         $level++;
         foreach ($categories as $category) {
-            $childrenCount = 0;
-            $subList = [];
             if (empty($category['parent_id'])) {
                 continue;
             }

Datei-Diff unterdrückt, da er zu groß ist
+ 466 - 418
main/inc/lib/MoodleImport.php


+ 4 - 4
main/inc/lib/TicketManager.php

@@ -644,12 +644,12 @@ class TicketManager
         if ($sendConfirmation) {
             $form =
                 '<form action="ticket_details.php?ticket_id='.$ticketId.'" id="confirmticket" method="POST" >
-                         <p>'.get_lang('TicketWasThisAnswerSatisfying').'</p>
+                     <p>'.get_lang('TicketWasThisAnswerSatisfying').'</p>
                      <button class="btn btn-primary responseyes" name="response" id="responseyes" value="1">'.
                 get_lang('Yes').'</button>
                      <button class="btn btn-danger responseno" name="response" id="responseno" value="0">'.
                 get_lang('No').'</button>
-                     </form>';
+                 </form>';
             $content .= $form;
         }
 
@@ -672,7 +672,7 @@ class TicketManager
             $sql = "UPDATE $table_support_tickets
                     SET 
                         sys_lastedit_user_id = $userId,
-                        sys_lastedit_datetime ='$now',
+                        sys_lastedit_datetime = '$now',
                         total_messages = (
                             SELECT COUNT(*) as total_messages
                             FROM $table_support_messages
@@ -1510,7 +1510,7 @@ class TicketManager
         $sql = "UPDATE $table_support_tickets SET
                   priority_id = '".self::PRIORITY_HIGH."',
                   sys_lastedit_user_id = $userId,
-                  sys_lastedit_datetime ='$now'
+                  sys_lastedit_datetime = '$now'
                 WHERE id = $ticketId";
         Database::query($sql);
     }

+ 7 - 1
main/inc/lib/UnserializeApi.php

@@ -29,13 +29,17 @@ class UnserializeApi
                     \Fhaculty\Graph\Set\VerticesMap::class,
                     \Fhaculty\Graph\Set\Vertices::class,
                     \Fhaculty\Graph\Set\Edges::class,
+                    \Fhaculty\Graph\Vertex::class,
+                    \Fhaculty\Graph\Edge\Base::class,
+                    \Fhaculty\Graph\Edge\Directed::class,
+                    \Fhaculty\Graph\Edge\Undirected::class,
                 ];
                 break;
             case 'course':
                 $allowedClasses = [
                     \Chamilo\CourseBundle\Component\CourseCopy\Course::class,
-                    \Chamilo\CourseBundle\Component\CourseCopy\Resources\Asset::class,
                     \Chamilo\CourseBundle\Component\CourseCopy\Resources\Announcement::class,
+                    \Chamilo\CourseBundle\Component\CourseCopy\Resources\Asset::class,
                     \Chamilo\CourseBundle\Component\CourseCopy\Resources\Attendance::class,
                     \Chamilo\CourseBundle\Component\CourseCopy\Resources\CalendarEvent::class,
                     \Chamilo\CourseBundle\Component\CourseCopy\Resources\CourseCopyLearnpath::class,
@@ -49,6 +53,7 @@ class UnserializeApi
                     \Chamilo\CourseBundle\Component\CourseCopy\Resources\ForumTopic::class,
                     \Chamilo\CourseBundle\Component\CourseCopy\Resources\Glossary::class,
                     \Chamilo\CourseBundle\Component\CourseCopy\Resources\GradeBookBackup::class,
+                    \Chamilo\CourseBundle\Component\CourseCopy\Resources\LearnPathCategory::class,
                     \Chamilo\CourseBundle\Component\CourseCopy\Resources\Link::class,
                     \Chamilo\CourseBundle\Component\CourseCopy\Resources\LinkCategory::class,
                     \Chamilo\CourseBundle\Component\CourseCopy\Resources\Quiz::class,
@@ -62,6 +67,7 @@ class UnserializeApi
                     \Chamilo\CourseBundle\Component\CourseCopy\Resources\ToolIntro::class,
                     \Chamilo\CourseBundle\Component\CourseCopy\Resources\Wiki::class,
                     \Chamilo\CourseBundle\Component\CourseCopy\Resources\Work::class,
+                    \Chamilo\CourseBundle\Entity\CLpCategory::class,
                     stdClass::class,
                     Category::class,
                     AttendanceLink::class,

+ 6 - 12
main/inc/lib/add_course.lib.inc.php

@@ -661,6 +661,7 @@ class AddCourse
             ],
             'documents_default_visibility' => ['default' => 'visible', 'category' => 'document'],
             'show_course_in_user_language' => ['default' => 2, 'category' => null],
+            'email_to_teachers_on_new_work_feedback' => ['default' => 1, 'category' => null],
         ];
 
         $counter = 1;
@@ -743,7 +744,7 @@ class AddCourse
                 'audio',
                 'flash',
                 'video',
-                'certificates',
+                'certificates', //ofaj
             ];
 
             $default_course_path = api_get_path(SYS_CODE_PATH).'default_course_document/';
@@ -1193,14 +1194,15 @@ class AddCourse
     /**
      * Function register_course to create a record in the course table of the main database.
      *
-     * @param array Course details (see code for details)
+     * @param array $params      Course details (see code for details).
+     * @param int   $accessUrlId Optional.
      *
      * @return int Created course ID
      *
      * @todo use an array called $params instead of lots of params
      * @assert (null) === false
      */
-    public static function register_course($params)
+    public static function register_course($params, $accessUrlId = 1)
     {
         global $error_msg, $firstExpirationDelay;
         $title = $params['title'];
@@ -1371,15 +1373,7 @@ class AddCourse
                 }
 
                 // Adding the course to an URL.
-                if (api_is_multiple_url_enabled()) {
-                    $url_id = 1;
-                    if (api_get_current_access_url_id() != -1) {
-                        $url_id = api_get_current_access_url_id();
-                    }
-                    UrlManager::add_course_to_url($course_id, $url_id);
-                } else {
-                    UrlManager::add_course_to_url($course_id, 1);
-                }
+                UrlManager::add_course_to_url($course_id, $accessUrlId);
 
                 // Add event to the system log.
                 $user_id = api_get_user_id();

+ 35 - 55
main/inc/lib/agenda.lib.php

@@ -2942,62 +2942,41 @@ class Agenda
      */
     public function displayActions($view, $filter = 0)
     {
-        $courseInfo = api_get_course_info();
         $groupInfo = GroupManager::get_group_properties(api_get_group_id());
         $groupIid = isset($groupInfo['iid']) ? $groupInfo['iid'] : 0;
 
-        $courseCondition = '';
-        if (!empty($courseInfo)) {
-            $courseCondition = api_get_cidreq();
-        }
+        $codePath = api_get_path(WEB_CODE_PATH);
 
-        $actionsLeft = '';
-        $actionsLeft .= "<a href='".api_get_path(WEB_CODE_PATH)."calendar/agenda_js.php?type={$this->type}&".$courseCondition."'>".
-            Display::return_icon(
-                'calendar.png',
-                get_lang('Calendar'),
-                '',
-                ICON_SIZE_MEDIUM
-            )."</a>";
+        $currentUserId = api_get_user_id();
+        $cidReq = api_get_cidreq();
 
-        $actionsLeft .= "<a href='".api_get_path(WEB_CODE_PATH)."calendar/agenda_list.php?type={$this->type}&".$courseCondition."'>".
-            Display::return_icon(
-                'week.png',
-                get_lang('AgendaList'),
-                '',
-                ICON_SIZE_MEDIUM
-            )."</a>";
+        $actionsLeft = '';
+        $actionsLeft .= Display::url(
+            Display::return_icon('calendar.png', get_lang('Calendar'), [], ICON_SIZE_MEDIUM),
+            $codePath."calendar/agenda_js.php?type={$this->type}&$cidReq"
+        );
+        $actionsLeft .= Display::url(
+            Display::return_icon('week.png', get_lang('AgendaList'), [], ICON_SIZE_MEDIUM),
+            $codePath."calendar/agenda_list.php?type={$this->type}&$cidReq"
+        );
 
         $form = '';
         if (api_is_allowed_to_edit(false, true) ||
-            (api_get_course_setting('allow_user_edit_agenda') == '1' &&
-                !api_is_anonymous()) &&
-            api_is_allowed_to_session_edit(false, true) ||
-            (GroupManager::user_has_access(
-                api_get_user_id(),
-                $groupIid,
-                GroupManager::GROUP_TOOL_CALENDAR
-            ) &&
-            GroupManager::is_tutor_of_group(api_get_user_id(), $groupInfo))
+            (api_get_course_setting('allow_user_edit_agenda') == '1' && !api_is_anonymous()) &&
+            api_is_allowed_to_session_edit(false, true)
+            || (
+                GroupManager::user_has_access($currentUserId, $groupIid, GroupManager::GROUP_TOOL_CALENDAR)
+                && GroupManager::is_tutor_of_group($currentUserId, $groupInfo)
+            )
         ) {
             $actionsLeft .= Display::url(
-                Display::return_icon(
-                    'new_event.png',
-                    get_lang('AgendaAdd'),
-                    '',
-                    ICON_SIZE_MEDIUM
-                ),
-                api_get_path(WEB_CODE_PATH)."calendar/agenda.php?".api_get_cidreq()."&action=add&type=".$this->type
+                Display::return_icon('new_event.png', get_lang('AgendaAdd'), [], ICON_SIZE_MEDIUM),
+                $codePath."calendar/agenda.php?action=add&type={$this->type}&$cidReq"
             );
 
             $actionsLeft .= Display::url(
-                Display::return_icon(
-                    'import_calendar.png',
-                    get_lang('ICalFileImport'),
-                    '',
-                    ICON_SIZE_MEDIUM
-                ),
-                api_get_path(WEB_CODE_PATH)."calendar/agenda.php?".api_get_cidreq()."&action=importical&type=".$this->type
+                Display::return_icon('import_calendar.png', get_lang('ICalFileImport'), [], ICON_SIZE_MEDIUM),
+                $codePath."calendar/agenda.php?action=importical&type={$this->type}&$cidReq"
             );
 
             if ($this->type === 'course') {
@@ -3021,6 +3000,13 @@ class Agenda
             }
         }
 
+        if ($this->type == 'personal' && !api_is_anonymous()) {
+            $actionsLeft .= Display::url(
+                Display::return_icon('1day.png', get_lang('SessionsPlanCalendar'), [], ICON_SIZE_MEDIUM),
+                $codePath."calendar/planification.php"
+            );
+        }
+
         if (api_is_platform_admin() ||
             api_is_teacher() ||
             api_is_student_boss() ||
@@ -3040,26 +3026,20 @@ class Agenda
                         FormValidator::LAYOUT_INLINE
                     );
 
+                    $sessions = [];
+
                     if (api_is_drh()) {
-                        $sessionList = SessionManager::get_sessions_followed_by_drh(
-                            api_get_user_id()
-                        );
+                        $sessionList = SessionManager::get_sessions_followed_by_drh($currentUserId);
                         if (!empty($sessionList)) {
-                            $sessions = [];
                             foreach ($sessionList as $sessionItem) {
                                 $sessions[$sessionItem['id']] = strip_tags($sessionItem['name']);
                             }
                         }
                     } else {
-                        $sessions = SessionManager::get_sessions_by_user(
-                            api_get_user_id()
-                        );
-                        $sessions = array_column(
-                            $sessions,
-                            'session_name',
-                            'session_id'
-                        );
+                        $sessions = SessionManager::get_sessions_by_user($currentUserId);
+                        $sessions = array_column($sessions, 'session_name', 'session_id');
                     }
+
                     $form->addHidden('type', 'personal');
                     $sessions = ['0' => get_lang('SelectAnOption')] + $sessions;
 

+ 167 - 11
main/inc/lib/api.lib.php

@@ -476,7 +476,9 @@ define('RESULT_DISABLE_SHOW_FINAL_SCORE_ONLY_WITH_CATEGORIES', 3); //Show final
 define('RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT', 4);
 define('RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK', 5);
 define('RESULT_DISABLE_RANKING', 6);
-// 4: Show final score only with categories and show expected answers only on the last attempt
+define('RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER', 7);
+
+// 4: Show final score only with  and show expected answers only on the last attempt
 
 define('EXERCISE_MAX_NAME_SIZE', 80);
 
@@ -1147,17 +1149,16 @@ function api_valid_email($address)
  */
 function api_protect_course_script($print_headers = false, $allow_session_admins = false, $allow_drh = false)
 {
-    $is_allowed_in_course = api_is_allowed_in_course();
-
-    $is_visible = false;
     $course_info = api_get_course_info();
-
     if (empty($course_info)) {
         api_not_allowed($print_headers);
 
         return false;
     }
 
+    $is_allowed_in_course = api_is_allowed_in_course();
+    $is_visible = false;
+
     if (api_is_drh()) {
         return true;
     }
@@ -2771,8 +2772,11 @@ function api_get_plugin_setting($plugin, $variable)
 
     if (isset($result[$plugin])) {
         $value = $result[$plugin];
-        if (@unserialize($value) !== false) {
-            $value = unserialize($value);
+
+        $unserialized = UnserializeApi::unserialize('not_allowed_classes', $value, true);
+
+        if (false !== $unserialized) {
+            $value = $unserialized;
         }
 
         return $value;
@@ -3320,7 +3324,6 @@ function api_is_allowed_to_edit(
 ) {
     $allowSessionAdminEdit = api_get_configuration_value('session_admins_edit_courses_content') === true;
     // Admins can edit anything.
-    // Admins can edit anything.
     if (api_is_platform_admin($allowSessionAdminEdit)) {
         //The student preview was on
         if ($check_student_view && api_is_student_view_active()) {
@@ -6218,7 +6221,6 @@ function api_is_course_visible_for_user($userid = null, $cid = null)
  * @param string the tool of the element
  * @param int the element id in database
  * @param int the session_id to compare with element session id
- * @param string $tool
  *
  * @return bool true if the element is in the session, false else
  */
@@ -6257,7 +6259,7 @@ function api_is_element_in_the_session($tool, $element_id, $session_id = null)
     }
     $course_id = api_get_course_int_id();
 
-    $sql = "SELECT session_id FROM $table_tool 
+    $sql = "SELECT session_id FROM $table_tool
             WHERE c_id = $course_id AND $key_field =  ".$element_id;
     $rs = Database::query($sql);
     if ($element_session_id = Database::result($rs, 0, 0)) {
@@ -8082,7 +8084,7 @@ function api_get_password_checker_js($usernameInputId, $passwordInputId)
     ];
 
     $js = api_get_asset('pwstrength-bootstrap/dist/pwstrength-bootstrap.min.js');
-    $js .= "<script>    
+    $js .= "<script>
     var errorMessages = {
         password_to_short : \"".get_lang('PasswordIsTooShort')."\",
         same_as_username : \"".get_lang('YourPasswordCannotBeTheSameAsYourUsername')."\"
@@ -9304,3 +9306,157 @@ function api_get_relative_path($from, $to)
 
     return implode('/', $relPath);
 }
+
+/**
+ * Unserialize content using Brummann\Polyfill\Unserialize.
+ *
+ * @param string $type
+ * @param string $serialized
+ * @param bool   $ignoreErrors. Optional.
+ *
+ * @return mixed
+ */
+function api_unserialize_content($type, $serialized, $ignoreErrors = false)
+{
+    switch ($type) {
+        case 'career':
+        case 'sequence_graph':
+            $allowedClasses = [Graph::class, VerticesMap::class, Vertices::class, Edges::class];
+            break;
+        case 'lp':
+            $allowedClasses = [
+                learnpath::class,
+                learnpathItem::class,
+                aicc::class,
+                aiccBlock::class,
+                aiccItem::class,
+                aiccObjective::class,
+                aiccResource::class,
+                scorm::class,
+                scormItem::class,
+                scormMetadata::class,
+                scormOrganization::class,
+                scormResource::class,
+                Link::class,
+                LpItem::class,
+            ];
+            break;
+        case 'course':
+            $allowedClasses = [
+                Course::class,
+                Announcement::class,
+                Attendance::class,
+                CalendarEvent::class,
+                CourseCopyLearnpath::class,
+                CourseCopyTestCategory::class,
+                CourseDescription::class,
+                CourseSession::class,
+                Document::class,
+                Forum::class,
+                ForumCategory::class,
+                ForumPost::class,
+                ForumTopic::class,
+                Glossary::class,
+                GradeBookBackup::class,
+                Link::class,
+                LinkCategory::class,
+                Quiz::class,
+                QuizQuestion::class,
+                QuizQuestionOption::class,
+                ScormDocument::class,
+                Survey::class,
+                SurveyInvitation::class,
+                SurveyQuestion::class,
+                Thematic::class,
+                ToolIntro::class,
+                Wiki::class,
+                Work::class,
+                stdClass::class,
+            ];
+            break;
+        case 'not_allowed_classes':
+        default:
+            $allowedClasses = false;
+    }
+
+    if ($ignoreErrors) {
+        return @Unserialize::unserialize(
+            $serialized,
+            ['allowed_classes' => $allowedClasses]
+        );
+    }
+
+    return Unserialize::unserialize(
+        $serialized,
+        ['allowed_classes' => $allowedClasses]
+    );
+}
+
+/**
+ * Set the From and ReplyTo properties to PHPMailer instance.
+ *
+ * @param PHPMailer $mailer
+ * @param array     $sender
+ * @param array     $replyToAddress
+ *
+ * @throws phpmailerException
+ */
+function api_set_noreply_and_from_address_to_mailer(PHPMailer $mailer, array $sender, array $replyToAddress = [])
+{
+    $platformEmail = $GLOBALS['platform_email'];
+
+    $noReplyAddress = api_get_setting('noreply_email_address');
+    $avoidReplyToAddress = false;
+
+    if (!empty($noReplyAddress)) {
+        $avoidReplyToAddress = api_get_configuration_value('mail_no_reply_avoid_reply_to');
+    }
+
+    $notification = new Notification();
+
+    // If the parameter is set don't use the admin.
+    $senderName = !empty($sender['name']) ? $sender['name'] : $notification->getDefaultPlatformSenderName();
+    $senderEmail = !empty($sender['email']) ? $sender['email'] : $notification->getDefaultPlatformSenderEmail();
+
+    // Reply to first
+    if (!$avoidReplyToAddress) {
+        $mailer->AddCustomHeader('Errors-To: '.$notification->getDefaultPlatformSenderEmail());
+
+        if (
+            !empty($replyToAddress) &&
+            $platformEmail['SMTP_UNIQUE_REPLY_TO'] &&
+            PHPMailer::ValidateAddress($replyToAddress['mail'])
+        ) {
+            $mailer->AddReplyTo($replyToAddress['email'], $replyToAddress['name']);
+            // Errors to sender
+            $mailer->AddCustomHeader('Errors-To: '.$replyToAddress['mail']);
+            $mailer->Sender = $replyToAddress['mail'];
+        }
+    }
+
+    //If the SMTP configuration only accept one sender
+    if (
+        isset($platformEmail['SMTP_UNIQUE_SENDER']) &&
+        $platformEmail['SMTP_UNIQUE_SENDER']
+    ) {
+        $senderName = $platformEmail['SMTP_FROM_NAME'];
+        $senderEmail = $platformEmail['SMTP_FROM_EMAIL'];
+
+        if (PHPMailer::ValidateAddress($senderEmail)) {
+            //force-set Sender to $senderEmail, otherwise SetFrom only does it if it is currently empty
+            $mailer->Sender = $senderEmail;
+        }
+    }
+
+    $mailer->SetFrom($senderEmail, $senderName, !$avoidReplyToAddress);
+}
+
+/**
+ * @param string $template
+ *
+ * @return string
+ */
+function api_find_template($template)
+{
+    return Template::findTemplateFilePath($template);
+}

+ 1 - 1
main/inc/lib/array.lib.php

@@ -27,7 +27,7 @@ function array_unique_dimensional($array)
     $array = array_unique($array);
 
     foreach ($array as &$myvalue) {
-        $myvalue = unserialize($myvalue);
+        $myvalue = UnserializeApi::unserialize('not_allowed_clases', $myvalue);
     }
 
     return $array;

+ 1 - 1
main/inc/lib/attendance.lib.php

@@ -1675,7 +1675,7 @@ class Attendance
      */
     public static function is_locked_attendance($attendanceId)
     {
-        //use gradebook lock
+        //  Use gradebook lock
         $result = api_resource_is_locked_by_gradebook($attendanceId, LINK_ATTENDANCE);
 
         return $result;

+ 1 - 1
main/inc/lib/auth.lib.php

@@ -51,7 +51,7 @@ class Auth
                 FROM $TABLECOURS course, $TABLECOURSUSER  course_rel_user
                 WHERE
                     course.id = course_rel_user.c_id AND
-                    course_rel_user.relation_type<>".COURSE_RELATION_TYPE_RRHH." AND
+                    course_rel_user.relation_type <> ".COURSE_RELATION_TYPE_RRHH." AND
                     course_rel_user.user_id = '".$user_id."' 
                     $avoidCoursesCondition
                 ORDER BY course_rel_user.sort ASC";

+ 84 - 93
main/inc/lib/chat.lib.php

@@ -75,7 +75,7 @@ class Chat extends Model
      */
     public function getLatestChat($currentUserId, $userId, $latestMessages)
     {
-        $items = self::getPreviousMessages(
+        $items = $this->getPreviousMessages(
             $currentUserId,
             $userId,
             0,
@@ -115,14 +115,12 @@ class Chat extends Model
 
         $chats = [];
         foreach ($chatHistory as $userId => $time) {
-            $total = self::getCountMessagesExchangeBetweenUsers($userId, $currentUserId);
-
+            $total = $this->getCountMessagesExchangeBetweenUsers($userId, $currentUserId);
             $start = $total - $latestMessages;
-
             if ($start < 0) {
                 $start = 0;
             }
-            $items = $this->getMessages($userId, $currentUserId, 1, $start, $latestMessages);
+            $items = $this->getMessages($userId, $currentUserId, $start, $latestMessages);
             $chats[$userId]['items'] = $items;
             $chats[$userId]['window_user_info'] = api_get_user_info($userId);
         }
@@ -142,7 +140,7 @@ class Chat extends Model
         $chat->setUserStatus(1);
 
         $chatList = Session::read('openChatBoxes');
-        $chats = self::getAllLatestChats($chatList);
+        $chats = $this->getAllLatestChats($chatList);
         $return = [
             'user_status' => $this->getUserStatus(),
             'me' => get_lang('Me'),
@@ -158,7 +156,7 @@ class Chat extends Model
      * @param int $fromUserId
      * @param int $toUserId
      *
-     * @return mixed
+     * @return int
      */
     public function getCountMessagesExchangeBetweenUsers($fromUserId, $toUserId)
     {
@@ -178,7 +176,7 @@ class Chat extends Model
             'first'
         );
 
-        return $row['count'];
+        return (int) $row['count'];
     }
 
     /**
@@ -193,33 +191,39 @@ class Chat extends Model
         $fromUserId,
         $toUserId,
         $visibleMessages = 1,
-        $previousMessageCount = 5
-    ) {        
+        $previousMessageCount = 5,
+        $orderBy = ''
+    ) {
         $toUserId = (int) $toUserId;
         $fromUserId = (int) $fromUserId;
+        $visibleMessages = (int) $visibleMessages;
+        $previousMessageCount = (int) $previousMessageCount;
 
         $total = $this->getCountMessagesExchangeBetweenUsers($fromUserId, $toUserId);
-
         $show = $total - $visibleMessages;
+
+        if ($show < $previousMessageCount) {
+            $show = $previousMessageCount;
+        }
         $from = $show - $previousMessageCount;
 
         if ($from < 0) {
             return [];
         }
 
-        return $this->getMessages($fromUserId, $toUserId, $visibleMessages, $from, $previousMessageCount);
+        return $this->getMessages($fromUserId, $toUserId, $from, $previousMessageCount, $orderBy);
     }
 
     /**
-     * @param int $fromUserId
-     * @param int $toUserId
-     * @param int $visibleMessages
-     * @param int $start
-     * @param int $end
+     * @param int    $fromUserId
+     * @param int    $toUserId
+     * @param int    $start
+     * @param int    $end
+     * @param string $orderBy
      *
      * @return array
      */
-    public function getMessages($fromUserId, $toUserId, $visibleMessages, $start, $end, $orderBy = '')
+    public function getMessages($fromUserId, $toUserId, $start, $end, $orderBy = '')
     {
         $toUserId = (int) $toUserId;
         $fromUserId = (int) $fromUserId;
@@ -229,8 +233,8 @@ class Chat extends Model
         if (empty($toUserId) || empty($fromUserId)) {
             return [];
         }
-        $orderBy = Database::escape_string($orderBy);
 
+        $orderBy = Database::escape_string($orderBy);
         if (empty($orderBy)) {
             $orderBy = 'ORDER BY id ASC';
         }
@@ -264,17 +268,14 @@ class Chat extends Model
             $userInfo = $users[$fromUserId];
             $toUserInfo = $users[$toUserId];
 
-            $item = [
+            $items[$chat['id']] = [
                 'id' => $chat['id'],
-                's' => '0',
-                'f' => $fromUserId,
-                'm' => Security::remove_XSS($chat['message']),
+                'message' => Security::remove_XSS($chat['message']),
+                'date' => api_strtotime($chat['sent'], 'UTC'),
                 'recd' => $chat['recd'],
                 'from_user_info' => $userInfo,
                 'to_user_info' => $toUserInfo,
-                'date' => api_strtotime($chat['sent'], 'UTC'),
             ];
-            $items[$chat['id']] = $item;
             $_SESSION['openChatBoxes'][$fromUserId] = api_strtotime($chat['sent'], 'UTC');
         }
 
@@ -286,81 +287,52 @@ class Chat extends Model
      */
     public function heartbeat()
     {
+        $chatHistory = Session::read('chatHistory');
         $currentUserId = api_get_user_id();
 
+        // update current chats
+        if (!empty($chatHistory) && is_array($chatHistory)) {
+            foreach ($chatHistory as $fromUserId => &$data) {
+                $userInfo = api_get_user_info($fromUserId, true);
+                $count = $this->getCountMessagesExchangeBetweenUsers($fromUserId, $currentUserId);
+                $chatItems = $this->getLatestChat($fromUserId, $currentUserId, 5);
+                $data['window_user_info'] = $userInfo;
+                $data['items'] = $chatItems;
+                $data['total_messages'] = $count;
+            }
+        }
+
         $sql = "SELECT * FROM ".$this->table."
-                WHERE 
+                WHERE
                     to_user = '".$currentUserId."' AND recd = 0
                 ORDER BY id ASC";
         $result = Database::query($sql);
 
         $chatList = [];
         while ($chat = Database::fetch_array($result, 'ASSOC')) {
-            $chatList[$chat['from_user']]['items'][] = $chat;
+            $chatList[$chat['from_user']][] = $chat;
         }
 
-        $items = [];
-        $chatHistory = Session::read('chatHistory');
-
-        // update current chats
-        if (is_array($chatHistory)) {
-            foreach ($chatHistory as $fromUserId => $items) {
-                $user_info = api_get_user_info($fromUserId, true);
-                $count = $this->getCountMessagesExchangeBetweenUsers(
-                    $fromUserId,
-                    $currentUserId
-                );
-                $chatItems = self::getLatestChat($fromUserId, $currentUserId, 5);
-                $item = [
-                    'window_user_info' => api_get_user_info($fromUserId),
-                    'items' => $chatItems,
-                    'total_messages' => $count,
-                    'user_info' => [
-                        'user_name' => $user_info['complete_name'],
-                        'online' => $user_info['user_is_online'],
-                        'avatar' => $user_info['avatar_small'],
-                        'user_id' => $user_info['user_id'],
-                    ],
-                ];
-
-                $items[$fromUserId] = $item;
-            }
-        }
-
-        foreach ($chatList as $fromUserId => $rows) {
-            $rows = $rows['items'];
-            $user_info = api_get_user_info($fromUserId, true);
-            $count = $this->getCountMessagesExchangeBetweenUsers(
-                $fromUserId,
-                $currentUserId
-            );
-
-            $chatItems = self::getLatestChat($fromUserId, $currentUserId, 5);
+        foreach ($chatList as $fromUserId => $messages) {
+            $userInfo = api_get_user_info($fromUserId, true);
+            $count = $this->getCountMessagesExchangeBetweenUsers($fromUserId, $currentUserId);
+            $chatItems = $this->getLatestChat($fromUserId, $currentUserId, 5);
 
             // Cleaning tsChatBoxes
             unset($_SESSION['tsChatBoxes'][$fromUserId]);
 
-            foreach ($rows as $chat) {
+            foreach ($messages as $chat) {
                 $_SESSION['openChatBoxes'][$fromUserId] = api_strtotime($chat['sent'], 'UTC');
             }
 
-            $item = [
-                'window_user_info' => api_get_user_info($fromUserId),
-                'items' => $chatItems,
+            $chatHistory[$fromUserId] = [
+                'window_user_info' => $userInfo,
                 'total_messages' => $count,
-                'user_info' => [
-                    'user_name' => $user_info['complete_name'],
-                    'online' => $user_info['user_is_online'],
-                    'avatar' => $user_info['avatar_small'],
-                    'user_id' => $user_info['user_id'],
-                ],
+                'items' => $chatItems,
             ];
-
-            $items[$fromUserId] = $item;
-            $chatHistory[$fromUserId] = $item;
         }
 
-        if (!empty($_SESSION['openChatBoxes'])) {
+        /*if (!empty($_SESSION['openChatBoxes'])) {
             foreach ($_SESSION['openChatBoxes'] as $userId => $time) {
                 if (!isset($_SESSION['tsChatBoxes'][$userId])) {
                     $now = time() - $time;
@@ -368,12 +340,6 @@ class Chat extends Model
                     $message = sprintf(get_lang('SentAtX'), $time);
 
                     if ($now > 180) {
-                        $item = [
-                            's' => '2',
-                            'f' => $userId,
-                            'm' => $message,
-                        ];
-
                         if (isset($chatHistory[$userId])) {
                             $chatHistory[$userId]['items'][] = $item;
                         }
@@ -381,16 +347,15 @@ class Chat extends Model
                     }
                 }
             }
-        }
-
+        }*/
         Session::write('chatHistory', $chatHistory);
 
-        $sql = "UPDATE ".$this->table." 
+        $sql = "UPDATE ".$this->table."
                 SET recd = 1
                 WHERE to_user = '".$currentUserId."' AND recd = 0";
         Database::query($sql);
 
-        echo json_encode(['items' => $items]);
+        echo json_encode(['items' => $chatHistory]);
     }
 
     /**
@@ -429,7 +394,7 @@ class Chat extends Model
             $_SESSION['openChatBoxes'][$to_user_id] = api_strtotime($now, 'UTC');
 
             if ($sanitize) {
-                $messagesan = self::sanitize($message);
+                $messagesan = $this->sanitize($message);
             } else {
                 $messagesan = $message;
             }
@@ -475,13 +440,39 @@ class Chat extends Model
 
     /**
      * Close a specific chat box (user ID taken from $_POST['chatbox']).
+     *
+     * @param $userId
+     */
+    public function closeWindow($userId)
+    {
+        if (empty($userId)) {
+            return false;
+        }
+
+        $list = Session::read('openChatBoxes');
+        if (isset($list[$userId])) {
+            unset($list[$userId]);
+            Session::write('openChatBoxes', $list);
+        }
+
+        $list = Session::read('chatHistory');
+        if (isset($list[$userId])) {
+            unset($list[$userId]);
+            Session::write('chatHistory', $list);
+        }
+
+        return true;
+    }
+
+    /**
+     * Close chat - disconnects the user.
      */
     public function close()
     {
-        unset($_SESSION['openChatBoxes'][$_POST['chatbox']]);
-        unset($_SESSION['chatHistory'][$_POST['chatbox']]);
-        echo '1';
-        exit;
+        Session::erase('tsChatBoxes');
+        Session::erase('openChatBoxes');
+        Session::erase('chatHistory');
+        Session::erase('window_list');
     }
 
     /**

+ 29 - 62
main/inc/lib/course.lib.php

@@ -35,8 +35,8 @@ class CourseManager
      * Creates a course.
      *
      * @param array $params      Columns in the main.course table.
-     * @param int   $authorId    optional
-     * @param int   $accessUrlId optional
+     * @param int   $authorId    Optional.
+     * @param int   $accessUrlId Optional.
      *
      * @return mixed false if the course was not created, array with the course info
      */
@@ -400,11 +400,11 @@ class CourseManager
         }
 
         $userList = [];
-
+        // Cleaning the $user_id variable
         if (is_array($user_id)) {
             $new_user_id_list = [];
             foreach ($user_id as $my_user_id) {
-                $new_user_id_list[] = intval($my_user_id);
+                $new_user_id_list[] = (int) $my_user_id;
             }
             $new_user_id_list = array_filter($new_user_id_list);
             $userList = $new_user_id_list;
@@ -424,8 +424,8 @@ class CourseManager
         $sql = "DELETE FROM ".Database::get_course_table(TABLE_GROUP_TUTOR)."
                 WHERE c_id = $course_id AND user_id IN (".$user_ids.")";
         Database::query($sql);
-        // Erase user student publications (works) in the course - by André Boivin
 
+        // Erase user student publications (works) in the course - by André Boivin
         if (!empty($userList)) {
             require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
             foreach ($userList as $userId) {
@@ -465,53 +465,19 @@ class CourseManager
 
         // Unsubscribe user from the course.
         if (!empty($session_id)) {
-            // Delete in table session_rel_course_rel_user
-            $sql = "DELETE FROM ".Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER)."
-                    WHERE
-                        session_id = $session_id AND
-                        c_id = $course_id AND
-                        user_id IN ($user_ids)";
-            Database::query($sql);
-
             foreach ($userList as $uid) {
+                SessionManager::unSubscribeUserFromCourseSession($uid, $course_id, $session_id);
+
                 // check if a user is register in the session with other course
                 $sql = "SELECT user_id FROM ".Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER)."
                         WHERE session_id = $session_id AND user_id = $uid";
                 $rs = Database::query($sql);
 
                 if (Database::num_rows($rs) == 0) {
-                    // Delete in table session_rel_user
-                    $sql = "DELETE FROM ".Database::get_main_table(TABLE_MAIN_SESSION_USER)."
-                            WHERE
-                                session_id = $session_id AND
-                                user_id = $uid AND
-                                relation_type<>".SESSION_RELATION_TYPE_RRHH;
-                    Database::query($sql);
+                    SessionManager::unsubscribe_user_from_session($uid, $session_id);
                 }
             }
 
-            // Update the table session
-            $sql = "SELECT COUNT(*) FROM ".Database::get_main_table(TABLE_MAIN_SESSION_USER)."
-                    WHERE session_id = '".$session_id."' AND relation_type <> ".SESSION_RELATION_TYPE_RRHH;
-            $row = Database::fetch_array(Database::query($sql));
-            $count = $row[0];
-            // number of users by session
-            $sql = "UPDATE ".Database::get_main_table(TABLE_MAIN_SESSION)." SET nbr_users = $count
-                    WHERE id = $session_id";
-            Database::query($sql);
-
-            // Update the table session_rel_course
-            $sql = "SELECT COUNT(*) FROM ".Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER)."
-                    WHERE session_id = $session_id AND c_id = $course_id AND status <> 2";
-            $row = Database::fetch_array(@Database::query($sql));
-            $count = $row[0];
-
-            // number of users by session and course
-            $sql = "UPDATE ".Database::get_main_table(TABLE_MAIN_SESSION_COURSE)."
-                    SET nbr_users = $count
-                    WHERE session_id = $session_id AND c_id = $course_id";
-            Database::query($sql);
-
             Event::addEvent(
                 LOG_UNSUBSCRIBE_USER_FROM_COURSE,
                 LOG_COURSE_CODE,
@@ -622,7 +588,7 @@ class CourseManager
         $status = (int) $status;
 
         if (empty($userId) || empty($courseCode)) {
-            return false; //detected possible SQL injection
+            return false;
         }
 
         $courseInfo = api_get_course_info($courseCode);
@@ -638,17 +604,17 @@ class CourseManager
         if (empty($userInfo)) {
             Display::addFlash(Display::return_message(get_lang('UserDoesNotExist'), 'warning'));
 
-            return false; // The user has not been registered to the platform.
+            return false;
         }
 
         $courseId = $courseInfo['real_id'];
         $courseCode = $courseInfo['code'];
         $userCourseCategoryId = (int) $userCourseCategoryId;
-        // Check whether the user has not been already subscribed to the course.
+
         $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
         $status = $status === STUDENT || $status === COURSEMANAGER ? $status : STUDENT;
         $courseUserTable = Database::get_main_table(TABLE_MAIN_COURSE_USER);
-        // The user has been already subscribed to the course.
+
         if (!empty($sessionId)) {
             SessionManager::subscribe_users_to_session_course(
                 [$userId],
@@ -709,14 +675,14 @@ class CourseManager
                     if ($maxStudents !== '') {
                         $maxStudents = (int) $maxStudents;
                         $count = self::get_user_list_from_course_code(
-                $courseCode,
+                            $courseCode,
                             0,
                             null,
                             null,
                             STUDENT,
                             true,
                             false
-            );
+                        );
 
                         if ($count >= $maxStudents) {
                             Display::addFlash(Display::return_message(get_lang('MaxNumberSubscribedStudentsReached'), 'warning'));
@@ -767,22 +733,22 @@ class CourseManager
 
                 // Add event to the system log
                 Event::addEvent(
-                LOG_SUBSCRIBE_USER_TO_COURSE,
-                LOG_COURSE_CODE,
-                $courseCode,
-                api_get_utc_datetime(),
-                api_get_user_id(),
-                $courseId
-            );
+                    LOG_SUBSCRIBE_USER_TO_COURSE,
+                    LOG_COURSE_CODE,
+                    $courseCode,
+                    api_get_utc_datetime(),
+                    api_get_user_id(),
+                    $courseId
+                );
 
                 Event::addEvent(
-                LOG_SUBSCRIBE_USER_TO_COURSE,
-                LOG_USER_OBJECT,
+                    LOG_SUBSCRIBE_USER_TO_COURSE,
+                    LOG_USER_OBJECT,
                     $userInfo,
-                api_get_utc_datetime(),
-                api_get_user_id(),
-                $courseId
-            );
+                    api_get_utc_datetime(),
+                    api_get_user_id(),
+                    $courseId
+                );
 
                 return true;
             }
@@ -5652,6 +5618,7 @@ class CourseManager
             'email_to_teachers_on_new_work_feedback',
             'student_delete_own_publication',
             'hide_forum_notifications',
+            'quiz_question_limit_per_day',
         ];
 
         $courseModels = ExerciseLib::getScoreModels();
@@ -6164,7 +6131,7 @@ class CourseManager
                         "GROUP:".$groupId,
                         $alreadySelected
                     )
-                    ) { // $alreadySelected is the array containing the groups (and users) that are already selected
+                    ) {
                         $userCount = isset($thisGroup['userNb']) ? $thisGroup['userNb'] : 0;
                         if (empty($userCount)) {
                             $userCount = isset($thisGroup['count_users']) ? $thisGroup['count_users'] : 0;

+ 11 - 9
main/inc/lib/course_category.lib.php

@@ -600,7 +600,7 @@ class CourseCategory
     {
         $table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE_CATEGORY);
         $conditions = " INNER JOIN $table a ON (c.id = a.course_category_id)";
-        $whereCondition = " AND a.access_url_id = ".api_get_current_access_url_id();
+        $whereCondition = ' AND a.access_url_id = '.api_get_current_access_url_id();
 
         $tbl_category = Database::get_main_table(TABLE_MAIN_CATEGORY);
         $sql = "SELECT code, name
@@ -629,13 +629,12 @@ class CourseCategory
     public static function countCoursesInCategory($category_code = '', $searchTerm = '')
     {
         $tbl_course = Database::get_main_table(TABLE_MAIN_COURSE);
+
         $categoryCode = Database::escape_string($category_code);
         $searchTerm = Database::escape_string($searchTerm);
+
         $avoidCoursesCondition = CoursesAndSessionsCatalog::getAvoidCourseCondition();
-        $visibilityCondition = CourseManager::getCourseVisibilitySQLCondition(
-            'course',
-            true
-        );
+        $visibilityCondition = CourseManager::getCourseVisibilitySQLCondition('course', true);
 
         $categoryFilter = '';
         if ($categoryCode === 'ALL') {
@@ -655,14 +654,14 @@ class CourseCategory
             ) ';
         }
 
-        $url_access_id = api_get_current_access_url_id();
+        $urlCondition = ' access_url_id = '.api_get_current_access_url_id().' AND';
         $tbl_url_rel_course = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
-        $sql = "SELECT * 
+        $sql = "SELECT count(*) as count 
                 FROM $tbl_course as course
                 INNER JOIN $tbl_url_rel_course as url_rel_course
                 ON (url_rel_course.c_id = course.id)
                 WHERE
-                    access_url_id = $url_access_id AND
+                    $urlCondition
                     course.visibility != '0' AND
                     course.visibility != '4'
                     $categoryFilter
@@ -671,7 +670,10 @@ class CourseCategory
                     $visibilityCondition
             ";
 
-        return Database::num_rows(Database::query($sql));
+        $result = Database::query($sql);
+        $row = Database::fetch_array($result);
+
+        return (int) $row['count'];
     }
 
     /**

+ 218 - 206
main/inc/lib/course_home.lib.php

@@ -664,8 +664,8 @@ class CourseHome
                         $add = learnpath::is_lp_visible_for_student(
                             $lpId,
                             $userId,
-                                $courseInfo['code'],
-                                $sessionId
+                            $courseInfo['code'],
+                            $sessionId
                         );
                     }
                     if ($path) {
@@ -765,7 +765,7 @@ class CourseHome
             $tbl_blogs_rel_user = Database::get_course_table(TABLE_BLOGS_REL_USER);
             foreach ($tmp_all_tools_list as $tool) {
                 if ($tool['image'] == 'blog.gif') {
-                    // Init
+                    // Get blog id
                     $blog_id = substr($tool['link'], strrpos($tool['link'], '=') + 1, strlen($tool['link']));
 
                     // Get blog members
@@ -844,7 +844,7 @@ class CourseHome
                 $studentview = false;
                 $tool['original_link'] = $tool['link'];
                 if ($tool['image'] == 'scormbuilder.gif') {
-                    // check if the published learnpath is visible for student
+                    // Check if the published learnpath is visible for student
                     $lpId = self::getPublishedLpIdFromLink($tool['link']);
                     if (api_is_allowed_to_edit(null, true)) {
                         $studentview = true;
@@ -992,8 +992,8 @@ class CourseHome
                 }
 
                 $qm_or_amp = strpos($tool['link'], '?') === false ? '?' : '&';
-                // If it's a link, we don't add the cidReq
 
+                // If it's a link, we don't add the cidReq
                 if ($tool['image'] == 'file_html.png' || $tool['image'] == 'file_html_na.png') {
                     $tool['link'] = $tool['link'];
                 } else {
@@ -1048,6 +1048,7 @@ class CourseHome
                         .$tool['original_link'].$qm_or_amp.api_get_cidreq();
                 }
 
+                // Use in the course home
                 $icon = Display::return_icon(
                     $tool['image'],
                     $tool_name,
@@ -1109,20 +1110,21 @@ class CourseHome
                     $tool_link_params
                 );
                 $items[] = $item;
-            } // end of foreach
-        }
-
-        if (api_get_setting('homepage_view') != 'activity_big') {
-            return $items;
+            }
         }
 
         foreach ($items as &$item) {
-            $originalImage = self::getToolIcon($item);
-            $item['tool']['image'] = Display::url(
-                $originalImage,
-                $item['url_params']['href'],
-                $item['url_params']
-            );
+            $originalImage = self::getToolIcon($item, ICON_SIZE_BIG);
+            $item['tool']['only_icon_medium'] = self::getToolIcon($item, ICON_SIZE_MEDIUM, false);
+            $item['tool']['only_icon_small'] = self::getToolIcon($item, ICON_SIZE_SMALL, false);
+
+            if ($theme === 'activity_big') {
+                $item['tool']['image'] = Display::url(
+                    $originalImage,
+                    $item['url_params']['href'],
+                    $item['url_params']
+                );
+            }
         }
 
         return $items;
@@ -1256,109 +1258,12 @@ class CourseHome
         return $category;
     }
 
-    /**
-     * @param bool $include_admin_tools
-     *
-     * @return array
-     */
-    public static function get_navigation_items($include_admin_tools = false)
-    {
-        $navigation_items = [];
-        $course_id = api_get_course_int_id();
-        $courseInfo = api_get_course_info();
-        $sessionId = api_get_session_id();
-
-        if (!empty($course_id)) {
-            $course_tools_table = Database::get_course_table(TABLE_TOOL_LIST);
-            /* 	Link to the Course homepage */
-            $navigation_items['home']['image'] = 'home.gif';
-            $navigation_items['home']['link'] = $courseInfo['course_public_url'];
-            $navigation_items['home']['name'] = get_lang('CourseHomepageLink');
-
-            $sql = "SELECT * FROM $course_tools_table
-                    WHERE c_id = $course_id AND visibility='1' and admin='0'
-                    ORDER BY id ASC";
-            $result = Database::query($sql);
-
-            $hideTools = [];
-            $hideToolsKeys = [];
-            if (!api_is_platform_admin()) {
-                $hideTools = api_get_setting('course_hide_tools');
-                $hideToolsKeys = array_keys($hideTools);
-            }
-
-            while ($row = Database::fetch_array($result)) {
-                if (!empty($hideTools)) {
-                    if (in_array($row['name'], $hideToolsKeys)) {
-                        // Tool is hidden
-                        if ($hideTools[$row['name']] == 'true') {
-                            continue;
-                        }
-                    }
-                }
-
-                $navigation_items[$row['id']] = $row;
-                if (stripos($row['link'], 'http://') === false &&
-                    stripos($row['link'], 'https://') === false
-                ) {
-                    $navigation_items[$row['id']]['link'] = api_get_path(WEB_CODE_PATH);
-
-                    if ($row['category'] == 'plugin') {
-                        $plugin = new AppPlugin();
-                        $pluginInfo = $plugin->getPluginInfo($row['name']);
-                        if (isset($pluginInfo['title'])) {
-                            $navigation_items[$row['id']]['link'] = api_get_path(WEB_PLUGIN_PATH);
-                            $navigation_items[$row['id']]['name'] = $pluginInfo['title'];
-                        }
-                    } else {
-                        $navigation_items[$row['id']]['name'] = self::translate_tool_name($row);
-                    }
-
-                    $navigation_items[$row['id']]['link'] .= $row['link'];
-                }
-            }
-
-            /* Admin (edit rights) only links
-            - Course settings (course admin only)
-            - Course rights (roles & rights overview) */
-            if ($include_admin_tools) {
-                $sql = "SELECT name, image FROM $course_tools_table
-                        WHERE c_id = $course_id  AND link='course_info/infocours.php'";
-                $sql_result = Database::query($sql);
-                $course_setting_info = Database::fetch_array($sql_result);
-                $course_setting_visual_name = self::translate_tool_name($course_setting_info);
-                if ($sessionId == 0) {
-                    // course settings item
-                    $navigation_items['course_settings']['image'] = $course_setting_info['image'];
-                    $navigation_items['course_settings']['link'] = api_get_path(WEB_CODE_PATH).'course_info/infocours.php';
-                    $navigation_items['course_settings']['name'] = $course_setting_visual_name;
-                }
-            }
-        }
-
-        foreach ($navigation_items as $key => $navigation_item) {
-            if (strstr($navigation_item['link'], '?')) {
-                //link already contains a parameter, add course id parameter with &
-                $parameter_separator = '&amp;';
-            } else {
-                //link doesn't contain a parameter yet, add course id parameter with ?
-                $parameter_separator = '?';
-            }
-            //$navigation_items[$key]['link'] .= $parameter_separator.api_get_cidreq();
-            $navigation_items[$key]['link'] .= $parameter_separator.'cidReq='.api_get_course_id().'&gidReq=0&id_session='.$sessionId;
-        }
-
-        return $navigation_items;
-    }
-
     /**
      * Show a navigation menu.
      */
     public static function show_navigation_menu()
     {
-        $navigation_items = self::get_navigation_items(true);
-        $course_id = api_get_course_id();
-
+        $blocks = self::getUserBlocks();
         $class = null;
         $idLearn = null;
         $item = null;
@@ -1366,62 +1271,37 @@ class CourseHome
 
         $html = '<div id="toolnav">';
         $html .= '<ul id="toolnavbox">';
-        $count = 0;
-        foreach ($navigation_items as $key => $navigation_item) {
-            //students can't see the course settings option
-            $count++;
-            if (!api_is_allowed_to_edit() && $key == 'course_settings') {
-                continue;
-            }
-            $html .= '<li>';
-            $url_item = parse_url($navigation_item['link']);
-            $url_current = parse_url($_SERVER['REQUEST_URI']);
-
-            if (api_get_setting('show_navigation_menu') == 'text') {
-                $class = 'text';
-                $marginLeft = 170;
-                $item = $navigation_item['name'];
-            } elseif (api_get_setting('show_navigation_menu') == 'icons') {
-                $class = 'icons';
-                $marginLeft = 25;
-                $item = Display::return_icon(
-                    substr($navigation_item['image'], 0, -3)."png",
-                    $navigation_item['name'],
-                    ['class' => 'tool-img'],
-                    ICON_SIZE_SMALL
-                );
-            } else {
-                $class = 'icons-text';
-                $item = $navigation_item['name'].
-                    Display::return_icon(
-                        substr($navigation_item['image'], 0, -3)."png",
-                        $navigation_item['name'],
-                        ['class' => 'tool-img'],
-                        ICON_SIZE_SMALL
-                    );
-            }
 
-            if (stristr($url_item['path'], $url_current['path'])) {
-                if (!isset($_GET['learnpath_id']) || strpos($url_item['query'], 'learnpath_id='.intval($_GET['learnpath_id'])) === 0) {
-                    $idLearn = ' id="here"';
+        $showOnlyText = api_get_setting('show_navigation_menu') === 'text';
+        $showOnlyIcons = api_get_setting('show_navigation_menu') === 'icons';
+
+        foreach ($blocks as $block) {
+            $blockItems = $block['content'];
+            foreach ($blockItems as $item) {
+                $html .= '<li>';
+                if ($showOnlyText) {
+                    $class = 'text';
+                    $marginLeft = 170;
+                    $show = $item['name'];
+                } elseif ($showOnlyIcons) {
+                    $class = 'icons';
+                    $marginLeft = 25;
+                    $show = $item['tool']['only_icon_small'];
+                } else {
+                    $class = 'icons-text';
+                    $show = $item['name'].$item['tool']['only_icon_small'];
                 }
-            }
 
-            if (strpos($navigation_item['link'], 'chat') !== false &&
-                api_get_course_setting('allow_open_chat_window', $course_id)
-            ) {
-                $html .= '<a '.$idLearn.' class="btn btn-default text-left '.$class.' " href="javascript: void(0);" onclick="javascript: window.open(\''.$navigation_item['link'].'\',\'window_chat'.api_get_course_id().'\',config=\'height=\'+600+\', width=\'+825+\', left=2, top=2, toolbar=no, menubar=no, scrollbars=yes, resizable=yes, location=no, directories=no, status=no\')" target="'.$navigation_item['target'].'"';
-                $html .= ' title="'.$navigation_item['name'].'">';
-                $html .= $item;
-                $html .= '</a>';
-            } else {
-                $html .= '<a '.$idLearn.' class="btn btn-default text-left '.$class.'" href="'.$navigation_item['link'].'" target="_top" title="'.$navigation_item['name'].'">';
-                $html .= $item;
-                $html .= '</a>';
+                $item['url_params']['class'] = 'btn btn-default text-left '.$class;
+                $html .= Display::url(
+                    $show,
+                    $item['only_href'],
+                    $item['url_params']
+                );
+                $html .= '</li>';
             }
-
-            $html .= '</li>';
         }
+
         $html .= '</ul>';
         $html .= '<script>$(function() {
                 $("#toolnavbox a").stop().animate({"margin-left":"-'.$marginLeft.'px"},1000);
@@ -1451,53 +1331,39 @@ class CourseHome
     public static function show_navigation_tool_shortcuts($orientation = SHORTCUTS_HORIZONTAL)
     {
         $origin = api_get_origin();
+        $courseInfo = api_get_course_info();
         if ($origin === 'learnpath') {
             return '';
         }
 
-        $navigation_items = self::get_navigation_items(false);
+        $blocks = self::getUserBlocks();
         $html = '';
-        if (!empty($navigation_items)) {
+        if (!empty($blocks)) {
+            $styleId = 'toolshortcuts_vertical';
             if ($orientation == SHORTCUTS_HORIZONTAL) {
-                $style_id = "toolshortcuts_horizontal";
-            } else {
-                $style_id = "toolshortcuts_vertical";
+                $styleId = 'toolshortcuts_horizontal';
             }
-            $html .= '<div id="'.$style_id.'">';
-            foreach ($navigation_items as $key => $navigation_item) {
-                if (strpos($navigation_item['link'], 'chat') !== false &&
-                    api_get_course_setting('allow_open_chat_window')
-                ) {
-                    $html .= '<a class="items-icon" href="javascript: void(0);" onclick="javascript: window.open(\''.$navigation_item['link'].'\',\'window_chat'.api_get_course_id().'\',config=\'height=\'+600+\', width=\'+825+\', left=2, top=2, toolbar=no, menubar=no, scrollbars=yes, resizable=yes, location=no, directories=no, status=no\')" target="'.$navigation_item['target'].'"';
-                } else {
-                    $html .= '<a class="items-icon" href="'.$navigation_item['link'].'"';
-                }
-                if (strpos(api_get_self(), $navigation_item['link']) !== false) {
-                    $html .= ' id="here"';
-                }
-                $html .= ' target="_top" title="'.$navigation_item['name'].'">';
+            $html .= '<div id="'.$styleId.'">';
 
-                if (isset($navigation_item['category']) && $navigation_item['category'] == 'plugin') {
-                    /*$plugin_info = $app_plugin->getPluginInfo($navigation_item['name']);
-                    if (isset($plugin_info) && isset($plugin_info['title'])) {
-                        $tool_name = $plugin_info['title'];
-                    }*/
-                    if (!file_exists(api_get_path(SYS_CODE_PATH).'img/'.$navigation_item['image']) &&
-                        !file_exists(api_get_path(SYS_CODE_PATH).'img/icons/'.ICON_SIZE_MEDIUM.'/'.$navigation_item['image'])
-                    ) {
-                        $navigation_item['image'] = 'plugins.png';
-                    }
-                }
+            $html .= Display::url(
+                Display::return_icon('home.png', get_lang('CourseHomepageLink'), '', ICON_SIZE_MEDIUM),
+                $courseInfo['course_public_url'],
+                ['class' => 'items-icon']
+            );
 
-                $html .= Display::return_icon(
-                    substr($navigation_item['image'], 0, -3).'png',
-                    $navigation_item['name'],
-                    [],
-                    ICON_SIZE_MEDIUM
-                );
-                $html .= '</a> ';
-                if ($orientation == SHORTCUTS_VERTICAL) {
-                    $html .= '<br />';
+            foreach ($blocks as $block) {
+                $blockItems = $block['content'];
+                foreach ($blockItems as $item) {
+                    $item['url_params']['id'] = '';
+                    $item['url_params']['class'] = 'items-icon';
+                    $html .= Display::url(
+                        $item['tool']['only_icon_medium'],
+                        $item['only_href'],
+                        $item['url_params']
+                    );
+                    if ($orientation == SHORTCUTS_VERTICAL) {
+                        $html .= '<br />';
+                    }
                 }
             }
             $html .= '</div>';
@@ -1697,6 +1563,145 @@ class CourseHome
         }
     }
 
+    /**
+     * @return array
+     */
+    public static function getCourseAdminBlocks()
+    {
+        $blocks = [];
+        $my_list = self::get_tools_category(TOOL_AUTHORING);
+
+        $blocks[] = [
+            'title' => get_lang('Authoring'),
+            'class' => 'course-tools-author',
+            'content' => self::show_tools_category($my_list),
+        ];
+
+        $list1 = self::get_tools_category(TOOL_INTERACTION);
+        $list2 = self::get_tools_category(TOOL_COURSE_PLUGIN);
+        $my_list = array_merge($list1, $list2);
+
+        $blocks[] = [
+            'title' => get_lang('Interaction'),
+            'class' => 'course-tools-interaction',
+            'content' => self::show_tools_category($my_list),
+        ];
+
+        $my_list = self::get_tools_category(TOOL_ADMIN_PLATFORM);
+
+        $blocks[] = [
+            'title' => get_lang('Administration'),
+            'class' => 'course-tools-administration',
+            'content' => self::show_tools_category($my_list),
+        ];
+
+        return $blocks;
+    }
+
+    /**
+     * @return array
+     */
+    public static function getCoachBlocks()
+    {
+        $blocks = [];
+        $my_list = self::get_tools_category(TOOL_STUDENT_VIEW);
+
+        $blocks[] = [
+            'content' => self::show_tools_category($my_list),
+        ];
+
+        $sessionsCopy = api_get_setting('allow_session_course_copy_for_teachers');
+        if ($sessionsCopy === 'true') {
+            // Adding only maintenance for coaches.
+            $myList = self::get_tools_category(TOOL_ADMIN_PLATFORM);
+            $onlyMaintenanceList = [];
+
+            foreach ($myList as $item) {
+                if ($item['name'] === 'course_maintenance') {
+                    $item['link'] = 'course_info/maintenance_coach.php';
+
+                    $onlyMaintenanceList[] = $item;
+                }
+            }
+
+            $blocks[] = [
+                'title' => get_lang('Administration'),
+                'content' => self::show_tools_category($onlyMaintenanceList),
+            ];
+        }
+
+        return $blocks;
+    }
+
+    /**
+     * @return array
+     */
+    public static function getStudentBlocks()
+    {
+        $blocks = [];
+        $tools = self::get_tools_category(TOOL_STUDENT_VIEW);
+        $isDrhOfCourse = CourseManager::isUserSubscribedInCourseAsDrh(
+            api_get_user_id(),
+            api_get_course_info()
+        );
+
+        // Force user icon for DRH
+        if ($isDrhOfCourse) {
+            $addUserTool = true;
+            foreach ($tools as $tool) {
+                if ($tool['name'] === 'user') {
+                    $addUserTool = false;
+                    break;
+                }
+            }
+
+            if ($addUserTool) {
+                $tools[] = [
+                    'c_id' => api_get_course_int_id(),
+                    'name' => 'user',
+                    'link' => 'user/user.php',
+                    'image' => 'members.gif',
+                    'visibility' => '1',
+                    'admin' => '0',
+                    'address' => 'squaregrey.gif',
+                    'added_tool' => '0',
+                    'target' => '_self',
+                    'category' => 'interaction',
+                    'session_id' => api_get_session_id(),
+                ];
+            }
+        }
+
+        if (count($tools) > 0) {
+            $blocks[] = ['content' => self::show_tools_category($tools)];
+        }
+
+        if ($isDrhOfCourse) {
+            $drhTool = self::get_tools_category(TOOL_DRH);
+            $blocks[] = ['content' => self::show_tools_category($drhTool)];
+        }
+
+        return $blocks;
+    }
+
+    /**
+     * @return array
+     */
+    public static function getUserBlocks()
+    {
+        $sessionId = api_get_session_id();
+        // Start of tools for CourseAdmins (teachers/tutors)
+        if ($sessionId === 0 && api_is_course_admin() && api_is_allowed_to_edit(null, true)) {
+            $blocks = self::getCourseAdminBlocks();
+        } elseif (api_is_coach()) {
+            $blocks = self::getCoachBlocks();
+        } else {
+            $blocks = self::getStudentBlocks();
+        }
+
+        return $blocks;
+    }
+
     /**
      * Filter tool icons. Only show if $patronKey is = :teacher
      * Example dataIcons[i]['name']: parameter titleIcons1:teacher || titleIcons2 || titleIcons3:teacher.
@@ -1758,10 +1763,12 @@ class CourseHome
      * Find the tool icon when homepage_view is activity_big.
      *
      * @param array $item
+     * @param int   $iconSize
+     * @param bool  $generateId
      *
      * @return string
      */
-    private static function getToolIcon(array $item)
+    private static function getToolIcon(array $item, $iconSize, $generateId = true)
     {
         $image = str_replace('.gif', '.png', $item['tool']['image']);
         $toolIid = isset($item['tool']['iid']) ? $item['tool']['iid'] : null;
@@ -1788,11 +1795,16 @@ class CourseHome
             );
         }
 
+        $id = '';
+        if ($generateId) {
+            $id = 'toolimage_'.$toolIid;
+        }
+
         return Display::return_icon(
             $image,
             $item['name'],
-            ['id' => 'toolimage_'.$toolIid],
-            ICON_SIZE_BIG,
+            ['id' => $id],
+            $iconSize,
             false
         );
     }

+ 63 - 26
main/inc/lib/display.lib.php

@@ -1325,7 +1325,7 @@ class Display
         $obj->viewrecords = 'true';
         $all_value = 10000000;
 
-        // Default row quantity
+        // Sets how many records we want to view in the grid
         $obj->rowNum = 20;
 
         // Default row quantity
@@ -1708,27 +1708,20 @@ class Display
         }
         $output = [];
         if (!$nosession) {
-            $main_user_table = Database::get_main_table(TABLE_MAIN_USER);
-            $tbl_session = Database::get_main_table(TABLE_MAIN_SESSION);
-            // Request for the name of the general coach
-            $sql = 'SELECT tu.lastname, tu.firstname, ts.*
-                    FROM '.$tbl_session.' ts
-                    LEFT JOIN '.$main_user_table.' tu
-                    ON ts.id_coach = tu.user_id
-                    WHERE ts.id = '.intval($session_id);
-            $rs = Database::query($sql);
-            $session_info = Database::store_result($rs, 'ASSOC');
-            $session_info = $session_info[0];
+            $session_info = api_get_session_info($session_id);
+            $coachInfo = [];
+            if (!empty($session['id_coach'])) {
+                $coachInfo = api_get_user_info($session['id_coach']);
+            }
 
             $session = [];
             $session['category_id'] = $session_info['session_category_id'];
             $session['title'] = $session_info['name'];
             $session['id_coach'] = $session_info['id_coach'];
-            $session['coach'] = '';
             $session['dates'] = '';
-
-            if (api_get_setting('show_session_coach') === 'true') {
-                $session['coach'] = get_lang('GeneralCoach').': '.api_get_person_name($session_info['firstname'], $session_info['lastname']);
+            $session['coach'] = '';
+            if (api_get_setting('show_session_coach') === 'true' && isset($coachInfo['complete_name'])) {
+                $session['coach'] = get_lang('GeneralCoach').': '.$coachInfo['complete_name'];
             }
 
             if (($session_info['access_end_date'] == '0000-00-00 00:00:00' &&
@@ -1745,11 +1738,8 @@ class Display
             } else {
                 $dates = SessionManager::parseSessionDates($session_info, true);
                 $session['dates'] = $dates['access'];
-                if (api_get_setting('show_session_coach') === 'true') {
-                    $session['coach'] = api_get_person_name(
-                        $session_info['firstname'],
-                        $session_info['lastname']
-                    );
+                if (api_get_setting('show_session_coach') === 'true' && isset($coachInfo['complete_name'])) {
+                    $session['coach'] = $coachInfo['complete_name'];
                 }
                 $active = $date_start <= $now && $date_end >= $now;
             }
@@ -2604,7 +2594,7 @@ class Display
     /**
      * Get a HTML code for a icon by Font Awesome.
      *
-     * @param string     $name            The icon name
+     * @param string     $name            The icon name. Example: "mail-reply"
      * @param int|string $size            Optional. The size for the icon. (Example: lg, 2, 3, 4, 5)
      * @param bool       $fixWidth        Optional. Whether add the fw class
      * @param string     $additionalClass Optional. Additional class
@@ -2796,7 +2786,7 @@ HTML;
             case 'jpeg':
             case 'gif':
             case 'png':
-                $content = '<img width="400px" src="'.$fileUrl.'" />';
+                $content = '<img class="img-responsive" src="'.$fileUrl.'" />';
                 break;
             default:
                 //$html = self::url($data['basename'], $fileUrl);
@@ -2816,20 +2806,42 @@ HTML;
     {
         $defaultFeatures = ['playpause', 'current', 'progress', 'duration', 'tracks', 'volume', 'fullscreen', 'vrview'];
         $features = api_get_configuration_value('video_features');
+        $bowerJsFiles = [];
+        $bowerCSSFiles = [];
         if (!empty($features) && isset($features['features'])) {
             foreach ($features['features'] as $feature) {
                 if ($feature === 'vrview') {
                     continue;
                 }
                 $defaultFeatures[] = $feature;
+                $bowerJsFiles[] = "mediaelement/plugins/$feature/$feature.js";
+                $bowerCSSFiles[] = "mediaelement/plugins/$feature/$feature.css";
             }
         }
 
+        $translateHtml = '';
+        $translate = api_get_configuration_value('translate_html');
+        if ($translate) {
+            $translateHtml = '{type:"script", id:"_fr4", src:"'.api_get_path(WEB_AJAX_PATH).'lang.ajax.php?a=translate_html&'.api_get_cidreq().'"},';
+        }
+
+        $counter = 10;
+        $extraMediaFiles = '';
+        foreach ($bowerJsFiles as $file) {
+            $extraMediaFiles .= '{type: "script", id: "media_'.$counter.'", src: "'.api_get_path(WEB_PUBLIC_PATH).'assets/'.$file.'"},';
+            $counter++;
+        }
+
+        foreach ($bowerCSSFiles as $file) {
+            $extraMediaFiles .= '{type: "stylesheet", id: "media_'.$counter.'", src: "'.api_get_path(WEB_PUBLIC_PATH).'assets/'.$file.'"},';
+            $counter++;
+        }
+
         $defaultFeatures = implode("','", $defaultFeatures);
         $frameReady = '
           $.frameReady(function() {
-            $(document).ready(function () {
-                $("video:not(.skip), audio:not(.skip)").mediaelementplayer({
+            $(function() {
+                $("video:not(.skip), audio:not(.skip)").mediaelementplayer({                    
                     pluginPath: "'.api_get_path(WEB_PUBLIC_PATH).'assets/mediaelement/build/",            
                     features: ["'.$defaultFeatures.'"],
                     success: function(mediaElement, originalNode, instance) {
@@ -2846,15 +2858,40 @@ HTML;
                 { type:"stylesheet", id:"_fr5", src:"'.api_get_path(WEB_PUBLIC_PATH).'assets/jquery-ui/themes/smoothness/jquery-ui.min.css"},
                 { type:"stylesheet", id:"_fr6", src:"'.api_get_path(WEB_PUBLIC_PATH).'assets/jquery-ui/themes/smoothness/theme.css"},
                 { type:"script", id:"_fr2", src:"'.api_get_path(WEB_LIBRARY_PATH).'javascript/jquery.highlight.js"},
+                { type:"stylesheet", id:"_fr7", src:"'.api_get_path(WEB_PUBLIC_PATH).'css/dialog.css"},
                 { type:"script", id:"_fr3", src:"'.api_get_path(WEB_CODE_PATH).'glossary/glossary.js.php?'.api_get_cidreq().'"},
                 {type: "script", id: "_media1", src: "'.api_get_path(WEB_PUBLIC_PATH).'assets/mediaelement/build/mediaelement-and-player.min.js"},
                 {type: "stylesheet", id: "_media2", src: "'.api_get_path(WEB_PUBLIC_PATH).'assets/mediaelement/build/mediaelementplayer.min.css"},                
                 {type: "stylesheet", id: "_media4", src: "'.api_get_path(WEB_PUBLIC_PATH).'assets/mediaelement/plugins/vrview/vrview.css"},
                 {type: "script", id: "_media4", src: "'.api_get_path(WEB_PUBLIC_PATH).'assets/mediaelement/plugins/vrview/vrview.js"},
-                {type:"script", id:"_fr4", src:"'.api_get_path(WEB_AJAX_PATH).'lang.ajax.php?a=translate_html&'.api_get_cidreq().'"},
+                '.$extraMediaFiles.'
+                '.$translateHtml.'
             ]
           });';
 
         return $frameReady;
     }
+
+    /**
+     * @param string $image
+     * @param int    $size
+     *
+     * @return string
+     */
+    public static function get_icon_path($image, $size = ICON_SIZE_SMALL)
+    {
+        return self::return_icon($image, '', [], $size, false, true);
+    }
+
+    /**
+     * @param string $image
+     * @param int    $size
+     * @param string $name
+     *
+     * @return string
+     */
+    public static function get_image($image, $size = ICON_SIZE_SMALL, $name = '')
+    {
+        return self::return_icon($image, $name, [], $size);
+    }
 }

+ 35 - 35
main/inc/lib/document.lib.php

@@ -273,7 +273,7 @@ class DocumentManager
         if (isset($mimeTypes[$extension])) {
             return $mimeTypes[$extension];
         }
-        //else return octet-stream
+
         return 'application/octet-stream';
     }
 
@@ -717,7 +717,7 @@ class DocumentManager
     ) {
         $TABLE_ITEMPROPERTY = Database::get_course_table(TABLE_ITEM_PROPERTY);
         $TABLE_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
-        $groupIid = intval($groupIid);
+        $groupIid = (int) $groupIid;
         $document_folders = [];
 
         $students = CourseManager::get_user_list_from_course_code(
@@ -734,7 +734,7 @@ class DocumentManager
 
         $groupCondition = " last.to_group_id = $groupIid";
         if (empty($groupIid)) {
-            $groupCondition = " (last.to_group_id = 0 OR last.to_group_id IS NULL)";
+            $groupCondition = ' (last.to_group_id = 0 OR last.to_group_id IS NULL)';
         }
 
         $show_users_condition = '';
@@ -821,7 +821,7 @@ class DocumentManager
             $condition_session = api_get_session_condition(
                 $session_id,
                 true,
-                false,
+                true, // needed to don't show files in elfinder browser
                 'docs.session_id'
             );
 
@@ -3212,19 +3212,21 @@ class DocumentManager
      *
      * @return string
      */
-    public static function generateVideoPreview($file, $extension)
+    public static function generateMediaPreview($file, $extension)
     {
-        $type = '';
-        /*if ($extension != 'flv') {
-
-        }*/
-        //$type = "video/$extension";
-        //$fileInfo = parse_url($file);
-        //$type = self::file_get_mime_type(basename($fileInfo['path']));
-
-        $html = '<video id="myvideo" controls>';
-        $html .= '<source src="'.$file.'" >';
-        $html .= '</video>';
+        $id = api_get_unique_id();
+        switch ($extension) {
+            case 'mp3':
+                $document_data['file_extension'] = $extension;
+                $html = '<div style="margin: 0; position: absolute; top: 50%; left: 35%;">';
+                $html .= '<audio id="'.$id.'" controls="controls" src="'.$file.'" type="audio/mp3" ></audio></div>';
+                break;
+            default:
+                $html = '<video id="'.$id.'" controls>';
+                $html .= '<source src="'.$file.'" >';
+                $html .= '</video>';
+                break;
+        }
 
         return $html;
     }
@@ -3443,17 +3445,17 @@ class DocumentManager
             $url = $lpAjaxUrl.'?a=get_documents&lp_id=&cidReq='.$course_info['code'];
             $return .= "<script>
             $(document).ready(function () {
-            $('.close_div').click(function() {
-                var course_id = this.id.split('_')[2];
-                var session_id = this.id.split('_')[3];
-                $('#document_result_'+course_id+'_'+session_id).hide();
-                $('.lp_resource').remove();
-                $('.document_preview_container').html('');
+                $('.close_div').click(function() {
+                    var course_id = this.id.split('_')[2];
+                    var session_id = this.id.split('_')[3];
+                    $('#document_result_'+course_id+'_'+session_id).hide();
+                    $('.lp_resource').remove();
+                    $('.document_preview_container').html('');
                 });
             });
             </script>";
         } else {
-            //For LPs
+            // For LPs
             $url = $lpAjaxUrl.'?a=get_documents&lp_id='.$lp_id.'&'.api_get_cidreq();
         }
 
@@ -3647,12 +3649,12 @@ class DocumentManager
                 } else {
                     if ($checkParentVisibility) {
                         return self::check_visibility_tree(
-                        $document_data['parent_id'],
+                            $document_data['parent_id'],
                             $courseInfo,
                             $sessionId,
-                        $user_id,
-                        $groupId
-                    );
+                            $user_id,
+                            $groupId
+                        );
                     }
 
                     return true;
@@ -6548,16 +6550,14 @@ class DocumentManager
         if ($lp_id) {
             // LP URL
             $url = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&action=add_item&type='.TOOL_DOCUMENT.'&file='.$documentId.'&lp_id='.$lp_id;
-            if (!empty($overwrite_url)) {
-                $url = $overwrite_url.'&cidReq='.$course_info['code'].'&id_session='.$session_id.'&document_id='.$documentId.'';
-            }
         } else {
             // Direct document URL
             $url = $web_code_path.'document/document.php?cidReq='.$course_info['code'].'&id_session='.$session_id.'&id='.$documentId;
-            if (!empty($overwrite_url)) {
-                $overwrite_url = Security::remove_XSS($overwrite_url);
-                $url = $overwrite_url.'&cidReq='.$course_info['code'].'&id_session='.$session_id.'&document_id='.$documentId;
-            }
+        }
+
+        if (!empty($overwrite_url)) {
+            $overwrite_url = Security::remove_XSS($overwrite_url);
+            $url = $overwrite_url.'&cidReq='.$course_info['code'].'&id_session='.$session_id.'&document_id='.$documentId;
         }
 
         $img = Display::returnIconPath($icon);
@@ -6653,7 +6653,7 @@ class DocumentManager
             $return = '<ul class="lp_resource">';
         }
 
-        $return .= '<li class="doc_folder '.$folder_class_hidden.'" id="doc_id_'.$resource['id'].'"  style="margin-left:'.($num * 18).'px; ">';
+        $return .= '<li class="doc_folder'.$folder_class_hidden.'" id="doc_id_'.$resource['id'].'"  style="margin-left:'.($num * 18).'px; ">';
 
         $image = Display::returnIconPath('nolines_plus.gif');
         if (empty($path)) {

+ 295 - 68
main/inc/lib/exercise.lib.php

@@ -1,6 +1,8 @@
 <?php
 /* For licensing terms, see /license.txt */
 
+use Chamilo\CoreBundle\Component\Utils\ChamiloApi;
+use Chamilo\CoreBundle\Entity\TrackEExercises;
 use ChamiloSession as Session;
 
 /**
@@ -671,7 +673,7 @@ class ExerciseLib
                                     // radio button selection
                                     if (isset($myChoiceDegreeCertainty[$numAnswer]) &&
                                         $id == $myChoiceDegreeCertainty[$numAnswer]
-                                       ) {
+                                    ) {
                                         $attributes1 = ['checked' => 1, 'selected' => 1];
                                     } else {
                                         $attributes1 = [];
@@ -695,8 +697,8 @@ class ExerciseLib
                                             ),
                                             ['style' => 'text-align:center; background-color:#F7E1D7;',
                                                 'onclick' => 'handleRadioRow(event, '.
-                                                $questionId.', '.
-                                                $numAnswer.')',
+                                                    $questionId.', '.
+                                                    $numAnswer.')',
                                             ]
                                         );
                                     } else {
@@ -708,8 +710,8 @@ class ExerciseLib
                                             ),
                                             ['style' => 'text-align:center; background-color:#EFEFFC;',
                                                 'onclick' => 'handleRadioRow(event, '.
-                                                $questionId.', '.
-                                                $numAnswer.')',
+                                                    $questionId.', '.
+                                                    $numAnswer.')',
                                             ]
                                         );
                                     }
@@ -1081,8 +1083,8 @@ class ExerciseLib
                             if (isset($select_items[$lines_count])) {
                                 $s .= '<div class="text-right">
                                         <p class="indent">'.
-                                            $select_items[$lines_count]['letter'].'.&nbsp; '.
-                                            $select_items[$lines_count]['answer'].'
+                                    $select_items[$lines_count]['letter'].'.&nbsp; '.
+                                    $select_items[$lines_count]['answer'].'
                                         </p>
                                         </div>';
                             } else {
@@ -1100,7 +1102,7 @@ class ExerciseLib
                                       <td colspan="2"></td>
                                       <td valign="top">';
                                     $s .= '<b>'.$select_items[$lines_count]['letter'].'.</b> '.
-                                                $select_items[$lines_count]['answer'];
+                                        $select_items[$lines_count]['answer'];
                                     $s .= "</td>
                                 </tr>";
                                     $lines_count++;
@@ -2469,14 +2471,14 @@ HOTSPOT;
                                 $filterByUser = isset($_GET['filter_by_user']) ? (int) $_GET['filter_by_user'] : 0;
                                 $delete_link = '<a href="exercise_report.php?'.api_get_cidreq().'&filter_by_user='.$filterByUser.'&filter='.$filter.'&exerciseId='.$exercise_id.'&delete=delete&did='.$id.'"
                                 onclick="javascript:if(!confirm(\''.sprintf(
-                                    addslashes(get_lang('DeleteAttempt')),
-                                    $results[$i]['username'],
-                                    $dt
-                                ).'\')) return false;">';
+                                        addslashes(get_lang('DeleteAttempt')),
+                                        $results[$i]['username'],
+                                        $dt
+                                    ).'\')) return false;">';
                                 $delete_link .= Display::return_icon(
-                                    'delete.png',
+                                        'delete.png',
                                         addslashes(get_lang('Delete'))
-                                ).'</a>';
+                                    ).'</a>';
 
                                 if (api_is_drh() && !api_is_platform_admin()) {
                                     $delete_link = null;
@@ -2802,18 +2804,10 @@ HOTSPOT;
             return '-';
         }
 
-        $maxNote = api_get_setting('exercise_max_score');
-        $minNote = api_get_setting('exercise_min_score');
-
         if ($use_platform_settings) {
-            if ($maxNote != '' && $minNote != '') {
-                if (!empty($weight) && intval($weight) != 0) {
-                    $score = $minNote + ($maxNote - $minNote) * $score / $weight;
-                } else {
-                    $score = $minNote;
-                }
-                $weight = $maxNote;
-            }
+            $result = self::convertScoreToPlatformSetting($score, $weight);
+            $score = $result['score'];
+            $weight = $result['weight'];
         }
         $percentage = (100 * $score) / ($weight != 0 ? $weight : 1);
         // Formats values
@@ -2853,7 +2847,6 @@ HOTSPOT;
             $weight = float_format($weight, 1, $decimalSeparator, $thousandSeparator);
         }
 
-        $html = '';
         if ($show_percentage) {
             $percentageSign = '%';
             if ($hidePercentageSign) {
@@ -3212,7 +3205,7 @@ EOT;
             // only end is set
             $time_conditions .= " (start_time IS NULL AND end_time <> '' AND end_time > '$now') OR ";
             // nothing is set
-            $time_conditions .= " (start_time IS NULL AND end_time IS NULL))  ";
+            $time_conditions .= ' (start_time IS NULL AND end_time IS NULL)) ';
         }
 
         $needle_where = !empty($search) ? " AND title LIKE '?' " : '';
@@ -3909,9 +3902,7 @@ EOT;
         $track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
         $courseUser = Database::get_main_table(TABLE_MAIN_COURSE_USER);
         $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
-        $courseUserSession = Database::get_main_table(
-            TABLE_MAIN_SESSION_COURSE_USER
-        );
+        $courseUserSession = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
 
         $question_id = intval($question_id);
         $exercise_id = intval($exercise_id);
@@ -4007,15 +3998,13 @@ EOT;
         $courseUser = Database::get_main_table(TABLE_MAIN_COURSE_USER);
         $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
 
-        $courseUserSession = Database::get_main_table(
-            TABLE_MAIN_SESSION_COURSE_USER
-        );
+        $courseUserSession = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
 
-        $question_id = intval($question_id);
-        $answer_id = intval($answer_id);
-        $exercise_id = intval($exercise_id);
+        $question_id = (int) $question_id;
+        $answer_id = (int) $answer_id;
+        $exercise_id = (int) $exercise_id;
         $course_code = Database::escape_string($course_code);
-        $session_id = intval($session_id);
+        $session_id = (int) $session_id;
 
         if (empty($session_id)) {
             $courseCondition = "
@@ -4082,26 +4071,24 @@ EOT;
         $track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
         $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
         $courseUser = Database::get_main_table(TABLE_MAIN_COURSE_USER);
-        $courseUserSession = Database::get_main_table(
-            TABLE_MAIN_SESSION_COURSE_USER
-        );
+        $courseUserSession = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
 
-        $question_id = intval($question_id);
-        $answer_id = intval($answer_id);
-        $exercise_id = intval($exercise_id);
+        $question_id = (int) $question_id;
+        $answer_id = (int) $answer_id;
+        $exercise_id = (int) $exercise_id;
         $courseId = api_get_course_int_id($course_code);
-        $session_id = intval($session_id);
+        $session_id = (int) $session_id;
 
         switch ($question_type) {
             case FILL_IN_BLANKS:
-                $answer_condition = "";
+                $answer_condition = '';
                 $select_condition = " e.exe_id, answer ";
                 break;
             case MATCHING:
             case MATCHING_DRAGGABLE:
             default:
                 $answer_condition = " answer = $answer_id AND ";
-                $select_condition = " DISTINCT exe_user_id ";
+                $select_condition = ' DISTINCT exe_user_id ';
         }
 
         if (empty($session_id)) {
@@ -4113,7 +4100,7 @@ EOT;
             $courseCondition = "
             INNER JOIN $courseUserSession cu
             ON cu.c_id = a.c_id AND cu.user_id = exe_user_id";
-            $courseConditionWhere = " AND cu.status = 0 ";
+            $courseConditionWhere = ' AND cu.status = 0 ';
         }
 
         $sql = "SELECT $select_condition
@@ -4314,9 +4301,9 @@ EOT;
         $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
         $track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
 
-        $exercise_id = intval($exercise_id);
+        $exercise_id = (int) $exercise_id;
         $course_code = Database::escape_string($course_code);
-        $session_id = intval($session_id);
+        $session_id = (int) $session_id;
 
         $sql = "SELECT DISTINCT exe_user_id
                 FROM $track_exercises e
@@ -4439,16 +4426,20 @@ EOT;
         // Hide results
         $show_results = false;
         $show_only_score = false;
-
         if ($objExercise->results_disabled == RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS) {
             $show_results = true;
         }
 
+        if ($objExercise->results_disabled == RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER) {
+            $show_results = true;
+        }
+
         if (in_array(
             $objExercise->results_disabled,
             [
                 RESULT_DISABLE_SHOW_SCORE_ONLY,
                 RESULT_DISABLE_SHOW_FINAL_SCORE_ONLY_WITH_CATEGORIES,
+                RESULT_DISABLE_RANKING,
             ]
         )
         ) {
@@ -4466,9 +4457,19 @@ EOT;
         }
 
         $showTotalScoreAndUserChoicesInLastAttempt = true;
-        if ($objExercise->results_disabled == RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT) {
+        $showTotalScore = true;
+        $showQuestionScore = true;
+
+        if (in_array(
+            $objExercise->results_disabled,
+            [
+                RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT,
+                RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK,
+            ])
+        ) {
             $show_only_score = true;
             $show_results = true;
+            $numberAttempts = 0;
             if ($objExercise->attempts > 0) {
                 $attempts = Event::getExerciseResultsByUser(
                     api_get_user_id(),
@@ -4479,22 +4480,32 @@ EOT;
                     $exercise_stat_info['orig_lp_item_id'],
                     'desc'
                 );
-
                 if ($attempts) {
                     $numberAttempts = count($attempts);
-                } else {
-                    $numberAttempts = 0;
                 }
 
                 if ($save_user_result) {
                     $numberAttempts++;
                 }
+                $showTotalScore = false;
+                $showTotalScoreAndUserChoicesInLastAttempt = false;
                 if ($numberAttempts >= $objExercise->attempts) {
+                    $showTotalScore = true;
                     $show_results = true;
                     $show_only_score = false;
                     $showTotalScoreAndUserChoicesInLastAttempt = true;
-                } else {
-                    $showTotalScoreAndUserChoicesInLastAttempt = false;
+                }
+            }
+
+            if ($objExercise->results_disabled == RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK) {
+                $show_only_score = false;
+                $show_results = true;
+                $show_all_but_expected_answer = false;
+                $showTotalScore = false;
+                $showQuestionScore = false;
+                if ($numberAttempts >= $objExercise->attempts) {
+                    $showTotalScore = true;
+                    $showQuestionScore = true;
                 }
             }
         }
@@ -4648,14 +4659,18 @@ EOT;
 
                 $score = [];
                 if ($show_results) {
+                    $scorePassed = $my_total_score >= $my_total_weight;
+                    if (function_exists('bccomp')) {
+                        $compareResult = bccomp($my_total_score, $my_total_weight, 3);
+                        $scorePassed = $compareResult === 1 || $compareResult === 0;
+                    }
                     $score = [
                         'result' => self::show_score(
                             $my_total_score,
                             $my_total_weight,
-                            false,
-                            true
+                            false
                         ),
-                        'pass' => $my_total_score >= $my_total_weight ? true : false,
+                        'pass' => $scorePassed,
                         'score' => $my_total_score,
                         'weight' => $my_total_weight,
                         'comments' => $comnt,
@@ -4677,6 +4692,10 @@ EOT;
                 $question_content = '';
                 if ($show_results) {
                     $question_content = '<div class="question_row_answer">';
+                    if ($showQuestionScore == false) {
+                        $score = [];
+                    }
+
                     // Shows question title an description
                     $question_content .= $objQuestionTmp->return_header(
                         $objExercise,
@@ -4708,7 +4727,7 @@ EOT;
         }
 
         $totalScoreText = null;
-        if ($show_results || $show_only_score) {
+        if (($show_results || $show_only_score) && $showTotalScore) {
             if ($result['answer_type'] == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
                 echo '<h1 style="text-align : center; margin : 20px 0;">'.get_lang('YourResults').'</h1><br />';
             }
@@ -4766,7 +4785,9 @@ EOT;
         echo $totalScoreText;
 
         // Ofaj change BT#11784
-        if (!empty($objExercise->description)) {
+        if (api_get_configuration_value('quiz_show_description_on_results_page') &&
+            !empty($objExercise->description)
+        ) {
             echo Display::div($objExercise->description, ['class' => 'exercise_description']);
         }
 
@@ -4776,10 +4797,6 @@ EOT;
             echo $totalScoreText;
         }
 
-        if (!empty($remainingMessage)) {
-            echo Display::return_message($remainingMessage, 'normal', false);
-        }
-
         if ($save_user_result) {
             // Tracking of results
             if ($exercise_stat_info) {
@@ -4798,9 +4815,7 @@ EOT;
                         $learnpath_item_id,
                         $learnpath_item_view_id,
                         $exercise_stat_info['exe_duration'],
-                        $question_list,
-                        '',
-                        []
+                        $question_list
                     );
                 }
             }
@@ -4819,6 +4834,136 @@ EOT;
                 );
             }
         }
+
+        if (RESULT_DISABLE_RANKING == $objExercise->selectResultsDisabled()) {
+            echo Display::page_header(get_lang('Ranking'), null, 'h4');
+            echo self::displayResultsInRanking(
+                $objExercise->iId,
+                api_get_user_id(),
+                api_get_course_int_id(),
+                api_get_session_id()
+            );
+        }
+
+        if (!empty($remainingMessage)) {
+            echo Display::return_message($remainingMessage, 'normal', false);
+        }
+    }
+
+    /**
+     * Display the ranking of results in a exercise.
+     *
+     * @param int $exerciseId
+     * @param int $currentUserId
+     * @param int $courseId
+     * @param int $sessionId
+     *
+     * @return string
+     */
+    public static function displayResultsInRanking($exerciseId, $currentUserId, $courseId, $sessionId = 0)
+    {
+        $data = self::exerciseResultsInRanking($exerciseId, $courseId, $sessionId);
+
+        $table = new HTML_Table(['class' => 'table table-hover table-bordered']);
+        $table->setHeaderContents(0, 0, get_lang('Position'), ['class' => 'text-right']);
+        $table->setHeaderContents(0, 1, get_lang('Username'));
+        $table->setHeaderContents(0, 2, get_lang('Score'), ['class' => 'text-right']);
+        $table->setHeaderContents(0, 3, get_lang('Date'), ['class' => 'text-center']);
+
+        foreach ($data as $r => $item) {
+            $selected = $item[1]->getId() == $currentUserId;
+
+            foreach ($item as $c => $value) {
+                $table->setCellContents($r + 1, $c, $value);
+
+                $attrClass = '';
+
+                if (in_array($c, [0, 2])) {
+                    $attrClass = 'text-right';
+                } elseif (3 == $c) {
+                    $attrClass = 'text-center';
+                }
+
+                if ($selected) {
+                    $attrClass .= ' warning';
+                }
+
+                $table->setCellAttributes($r + 1, $c, ['class' => $attrClass]);
+            }
+        }
+
+        return $table->toHtml();
+    }
+
+    /**
+     * Get the ranking for results in a exercise.
+     * Function used internally by ExerciseLib::displayResultsInRanking.
+     *
+     * @param int $exerciseId
+     * @param int $courseId
+     * @param int $sessionId
+     *
+     * @return array
+     */
+    public static function exerciseResultsInRanking($exerciseId, $courseId, $sessionId = 0)
+    {
+        $em = Database::getManager();
+
+        $dql = 'SELECT DISTINCT te.exeUserId FROM ChamiloCoreBundle:TrackEExercises te WHERE te.exeExoId = :id AND te.cId = :cId';
+        $dql .= api_get_session_condition($sessionId, true, false, 'te.sessionId');
+
+        $result = $em
+            ->createQuery($dql)
+            ->setParameters(['id' => $exerciseId, 'cId' => $courseId])
+            ->getScalarResult();
+
+        $data = [];
+
+        /** @var TrackEExercises $item */
+        foreach ($result as $item) {
+            $bestAttemp = self::get_best_attempt_by_user($item['exeUserId'], $exerciseId, $courseId, $sessionId = 0);
+
+            $data[] = $bestAttemp;
+        }
+
+        usort(
+            $data,
+            function ($a, $b) {
+                if ($a['exe_result'] != $b['exe_result']) {
+                    return $a['exe_result'] > $b['exe_result'] ? -1 : 1;
+                }
+
+                if ($a['exe_date'] != $b['exe_date']) {
+                    return $a['exe_date'] < $b['exe_date'] ? -1 : 1;
+                }
+
+                return 0;
+            }
+        );
+
+        // flags to display the same position in case of tie
+        $lastScore = $data[0]['exe_result'];
+        $position = 1;
+
+        $data = array_map(
+            function ($item) use (&$lastScore, &$position) {
+                if ($item['exe_result'] < $lastScore) {
+                    $position++;
+                }
+
+                $lastScore = $item['exe_result'];
+
+                return [
+                    $position,
+                    api_get_user_entity($item['exe_user_id']),
+                    self::show_score($item['exe_result'], $item['exe_weighting'], true, true, true),
+                    api_convert_and_format_date($item['exe_date'], DATE_TIME_FORMAT_SHORT),
+                ];
+            },
+            $data
+        );
+
+        return $data;
     }
 
     /**
@@ -5087,4 +5232,86 @@ EOT;
 
         return $emailAlerts;
     }
+
+    /**
+     * Get the additional actions added in exercise_additional_teacher_modify_actions configuration.
+     *
+     * @param int $exerciseId
+     * @param int $iconSize
+     *
+     * @return string
+     */
+    public static function getAdditionalTeacherActions($exerciseId, $iconSize = ICON_SIZE_SMALL)
+    {
+        $additionalActions = api_get_configuration_value('exercise_additional_teacher_modify_actions') ?: [];
+        $actions = [];
+
+        foreach ($additionalActions as $additionalAction) {
+            $actions[] = call_user_func(
+                $additionalAction,
+                $exerciseId,
+                $iconSize
+            );
+        }
+
+        return implode(PHP_EOL, $actions);
+    }
+
+    /**
+     * @param DateTime $time
+     * @param int      $userId
+     * @param int      $courseId
+     * @param int      $sessionId
+     *
+     * @throws \Doctrine\ORM\Query\QueryException
+     *
+     * @return int
+     */
+    public static function countAnsweredQuestionsByUserAfterTime(DateTime $time, $userId, $courseId, $sessionId)
+    {
+        $em = Database::getManager();
+
+        $time = api_get_utc_datetime($time->format('Y-m-d H:i:s'), false, true);
+
+        $result = $em
+            ->createQuery('
+                SELECT COUNT(ea) FROM ChamiloCoreBundle:TrackEAttempt ea
+                WHERE ea.userId = :user AND ea.cId = :course AND ea.sessionId = :session
+                    AND ea.tms > :time
+                GROUP BY ea.questionId
+            ')
+            ->setParameters(['user' => $userId, 'course' => $courseId, 'session' => $sessionId, 'time' => $time])
+            ->getResult();
+
+        return count($result);
+    }
+
+    /**
+     * @param int $userId
+     * @param int $numberOfQuestions
+     * @param int $courseId
+     * @param int $sessionId
+     *
+     * @return bool
+     * @throws \Doctrine\ORM\Query\QueryException
+     */
+    public static function isQuestionsLimitPerDayReached($userId, $numberOfQuestions, $courseId, $sessionId)
+    {
+        $questionsLimitPerDay = (int) api_get_course_setting('quiz_question_limit_per_day');
+
+        if ($questionsLimitPerDay <= 0) {
+            return false;
+        }
+
+        $midnightTime = ChamiloApi::getServerMidnightTime();
+
+        $answeredQuestionsCount = ExerciseLib::countAnsweredQuestionsByUserAfterTime(
+            $midnightTime,
+            $userId,
+            $courseId,
+            $sessionId
+        );
+
+        return ($answeredQuestionsCount + $numberOfQuestions) > $questionsLimitPerDay;
+    }
 }

+ 159 - 125
main/inc/lib/exercise_show_functions.lib.php

@@ -57,11 +57,6 @@ class ExerciseShowFunctions
             echo '<tr><td>';
             echo Security::remove_XSS($answerHTML, COURSEMANAGERLOWSECURITY);
             echo '</td>';
-            if (!api_is_allowed_to_edit(null, true) && $feedbackType != EXERCISE_FEEDBACK_TYPE_EXAM) {
-                echo '<td>';
-                $comm = Event::get_comments($id, $questionId);
-                echo '</td>';
-            }
             echo '</tr>';
         }
     }
@@ -80,7 +75,7 @@ class ExerciseShowFunctions
         $answer,
         $id,
         $questionId,
-        $results_disabled,
+        $resultsDisabled,
         $showTotalScoreAndUserChoices,
         $expectedChoice = '',
         $choice = '',
@@ -103,11 +98,6 @@ class ExerciseShowFunctions
                 echo '</td><td>';
                 echo Security::remove_XSS($status);
                 echo '</td>';
-                if (!api_is_allowed_to_edit(null, true) && $feedback_type != EXERCISE_FEEDBACK_TYPE_EXAM) {
-                    echo '<td>';
-                    $comm = Event::get_comments($id, $questionId);
-                    echo '</td>';
-                }
                 echo '</tr>';
             }
         } else {
@@ -116,11 +106,6 @@ class ExerciseShowFunctions
             } else {
                 echo '<tr><td>';
                 echo Security::remove_XSS($answer);
-                if (!api_is_allowed_to_edit(null, true) && $feedback_type != EXERCISE_FEEDBACK_TYPE_EXAM) {
-                    echo '<td>';
-                    $comm = Event::get_comments($id, $questionId);
-                    echo '</td>';
-                }
                 echo '</tr>';
             }
         }
@@ -139,7 +124,7 @@ class ExerciseShowFunctions
         $exe_id,
         $questionId,
         $questionScore = null,
-        $results_disabled = 0
+        $resultsDisabled = 0
     ) {
         $comments = Event::get_comments($exe_id, $questionId);
 
@@ -165,7 +150,7 @@ class ExerciseShowFunctions
      * @param $id
      * @param $questionId
      * @param null $fileUrl
-     * @param int  $results_disabled
+     * @param int  $resultsDisabled
      * @param int  $questionScore
      */
     public static function display_oral_expression_answer(
@@ -174,7 +159,7 @@ class ExerciseShowFunctions
         $id,
         $questionId,
         $fileUrl = null,
-        $results_disabled = 0,
+        $resultsDisabled = 0,
         $questionScore = 0
     ) {
         if (isset($fileUrl)) {
@@ -187,7 +172,9 @@ class ExerciseShowFunctions
 
         if (empty($id)) {
             echo '<tr>';
-            echo Display::tag('td', Security::remove_XSS($answer), ['width' => '55%']);
+            if (!empty($answer)) {
+                echo Display::tag('td', Security::remove_XSS($answer), ['width' => '55%']);
+            }
             echo '</tr>';
             if (!$questionScore && $feedback_type != EXERCISE_FEEDBACK_TYPE_EXAM) {
                 echo '<tr>';
@@ -236,67 +223,78 @@ class ExerciseShowFunctions
         $showTotalScoreAndUserChoices
     ) {
         $hide_expected_answer = false;
-        if ($feedback_type == 0 && $resultsDisabled == 2) {
-            $hide_expected_answer = true;
-        }
-
-        if ($resultsDisabled == RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT) {
-            $hide_expected_answer = true;
-            if ($showTotalScoreAndUserChoices) {
-                $hide_expected_answer = false;
-            }
+        switch ($resultsDisabled) {
+            case RESULT_DISABLE_SHOW_SCORE_ONLY:
+                if ($feedback_type == 0) {
+                    $hide_expected_answer = true;
+                }
+                break;
+            case RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK:
+            case RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT:
+                $hide_expected_answer = true;
+                if ($showTotalScoreAndUserChoices) {
+                    $hide_expected_answer = false;
+                }
+                break;
         }
 
         $hotspot_colors = [
-            "", // $i starts from 1 on next loop (ugly fix)
-            "#4271B5",
-            "#FE8E16",
-            "#45C7F0",
-            "#BCD631",
-            "#D63173",
-            "#D7D7D7",
-            "#90AFDD",
-            "#AF8640",
-            "#4F9242",
-            "#F4EB24",
-            "#ED2024",
-            "#3B3B3B",
-            "#F7BDE2",
+            '', // $i starts from 1 on next loop (ugly fix)
+            '#4271B5',
+            '#FE8E16',
+            '#45C7F0',
+            '#BCD631',
+            '#D63173',
+            '#D7D7D7',
+            '#90AFDD',
+            '#AF8640',
+            '#4F9242',
+            '#F4EB24',
+            '#ED2024',
+            '#3B3B3B',
+            '#F7BDE2',
         ];
-        echo '<table class="data_table"><tr>';
-        echo '<td class="text-center" width="5%">';
-        echo '<span class="fa fa-square fa-fw fa-2x" aria-hidden="true" style="color:'.
+
+        $content = '<table class="data_table"><tr>';
+        $content .= '<td class="text-center" width="5%">';
+        $content .= '<span class="fa fa-square fa-fw fa-2x" aria-hidden="true" style="color:'.
             $hotspot_colors[$orderColor].'"></span>';
-        echo '</td>';
-        echo '<td class="text-left" width="25%">';
-        echo "$answerId - $answer";
-        echo '</td>';
-        echo '<td class="text-left" width="10%">';
+        $content .= '</td>';
+        $content .= '<td class="text-left" width="25%">';
+        $content .= "$answerId - $answer";
+        $content .= '</td>';
+        $content .= '<td class="text-left" width="10%">';
         if (!$hide_expected_answer) {
             $status = Display::label(get_lang('Incorrect'), 'danger');
             if ($studentChoice) {
                 $status = Display::label(get_lang('Correct'), 'success');
+            } else {
+                if ($resultsDisabled == RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER) {
+                    return '';
+                }
             }
-            echo $status;
+            $content .= $status;
         }
-        echo '</td>';
+        $content .= '</td>';
         if ($feedback_type != EXERCISE_FEEDBACK_TYPE_EXAM) {
-            echo '<td class="text-left" width="60%">';
+            $content .= '<td class="text-left" width="60%">';
             if ($studentChoice) {
-                echo '<span style="font-weight: bold; color: #008000;">'.nl2br($answerComment).'</span>';
+                $content .= '<span style="font-weight: bold; color: #008000;">'.nl2br($answerComment).'</span>';
             }
-            echo '</td>';
+            $content .= '</td>';
         } else {
-            echo '<td class="text-left" width="60%">&nbsp;</td>';
+            $content .= '<td class="text-left" width="60%">&nbsp;</td>';
         }
-        echo '</tr>';
+        $content .= '</tr>';
+
+        echo $content;
     }
 
     /**
      * Display the answers to a multiple choice question.
      *
      * @param Exercise $exercise
-     * @param int      $feedback_type                Feedback type
+     * @param int      $feedbackType                 Feedback type
      * @param int      $answerType                   Answer type
      * @param int      $studentChoice                Student choice
      * @param string   $answer                       Textual answer
@@ -311,7 +309,7 @@ class ExerciseShowFunctions
      */
     public static function display_unique_or_multiple_answer(
         $exercise,
-        $feedback_type,
+        $feedbackType,
         $answerType,
         $studentChoice,
         $answer,
@@ -336,16 +334,32 @@ class ExerciseShowFunctions
             $answer = str_replace($tags, '', $answer);
         }
 
+        $studentChoiceInt = (int) $studentChoice;
+        $answerCorrectChoice = (int) $answerCorrect;
+
         $hide_expected_answer = false;
-        if ($feedback_type == 0 && ($resultsDisabled == RESULT_DISABLE_SHOW_SCORE_ONLY)) {
-            $hide_expected_answer = true;
-        }
+        switch ($resultsDisabled) {
+            case RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER:
+                if ($studentChoiceInt !== $answerCorrectChoice) {
+                    return '';
+                }
 
-        if ($resultsDisabled == RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT) {
-            $hide_expected_answer = true;
-            if ($showTotalScoreAndUserChoices) {
-                $hide_expected_answer = false;
-            }
+                if (!$answerCorrect) {
+                    return '';
+                }
+                break;
+            case RESULT_DISABLE_SHOW_SCORE_ONLY:
+                if ($feedbackType == 0) {
+                    $hide_expected_answer = true;
+                }
+                break;
+            case RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK:
+            case RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT:
+                $hide_expected_answer = true;
+                if ($showTotalScoreAndUserChoices) {
+                    $hide_expected_answer = false;
+                }
+                break;
         }
 
         $icon = in_array($answerType, [UNIQUE_ANSWER, UNIQUE_ANSWER_NO_OPTION]) ? 'radio' : 'checkbox';
@@ -362,7 +376,7 @@ class ExerciseShowFunctions
         if (!$hide_expected_answer) {
             echo Display::return_icon($iconAnswer, null, null, ICON_SIZE_TINY);
         } else {
-            echo "-";
+            echo '-';
         }
         echo '</td><td width="40%">';
         echo $answer;
@@ -370,17 +384,15 @@ class ExerciseShowFunctions
 
         if ($exercise->showExpectedChoice()) {
             $status = Display::label(get_lang('Incorrect'), 'danger');
-            if ($studentChoice) {
-                if ($answerCorrect) {
-                    $status = Display::label(get_lang('Correct'), 'success');
-                }
+            if ($studentChoiceInt === $answerCorrectChoice) {
+                $status = Display::label(get_lang('Correct'), 'success');
             }
             echo '<td width="20%">';
             echo $status;
             echo '</td>';
         }
 
-        if ($feedback_type != EXERCISE_FEEDBACK_TYPE_EXAM) {
+        if ($feedbackType != EXERCISE_FEEDBACK_TYPE_EXAM) {
             echo '<td width="20%">';
             if ($studentChoice) {
                 $color = 'black';
@@ -390,8 +402,15 @@ class ExerciseShowFunctions
                 if ($hide_expected_answer) {
                     $color = '';
                 }
-                echo '<span style="font-weight: bold; color: '.$color.';">'.
-                    Security::remove_XSS($answerComment).'</span>';
+
+                $comment = '<span style="font-weight: bold; color: '.$color.';">'.
+                    Security::remove_XSS($answerComment).
+                    '</span>';
+
+                if (!$answerCorrect && $resultsDisabled == RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER) {
+                    $comment = '';
+                }
+                echo $comment;
             }
             echo '</td>';
             if ($ans == 1) {
@@ -418,7 +437,7 @@ class ExerciseShowFunctions
      */
     public static function display_multiple_answer_true_false(
         $exercise,
-        $feedback_type,
+        $feedbackType,
         $answerType,
         $studentChoice,
         $answer,
@@ -431,39 +450,44 @@ class ExerciseShowFunctions
         $showTotalScoreAndUserChoices
     ) {
         $hide_expected_answer = false;
-        if ($feedback_type == 0 && ($resultsDisabled == RESULT_DISABLE_SHOW_SCORE_ONLY)) {
-            $hide_expected_answer = true;
+        switch ($resultsDisabled) {
+            case RESULT_DISABLE_SHOW_SCORE_ONLY:
+                if ($feedbackType == 0) {
+                    $hide_expected_answer = true;
+                }
+                break;
+            case RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK:
+            case RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT:
+                $hide_expected_answer = true;
+                if ($showTotalScoreAndUserChoices) {
+                    $hide_expected_answer = false;
+                }
+                break;
         }
 
-        if ($resultsDisabled == RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT) {
-            $hide_expected_answer = true;
-            if ($showTotalScoreAndUserChoices) {
-                $hide_expected_answer = false;
-            }
-        }
-        echo '<tr><td width="5%">';
+        $content = '<tr><td width="5%">';
         $course_id = api_get_course_int_id();
         $new_options = Question::readQuestionOption($questionId, $course_id);
         // Your choice
         if (isset($new_options[$studentChoice])) {
-            echo get_lang($new_options[$studentChoice]['name']);
+            $content .= get_lang($new_options[$studentChoice]['name']);
         } else {
-            echo '-';
+            $content .= '-';
         }
         echo '</td><td width="5%">';
         // Expected choice
         if (!$hide_expected_answer) {
             if (isset($new_options[$answerCorrect])) {
-                echo get_lang($new_options[$answerCorrect]['name']);
+                $content .= get_lang($new_options[$answerCorrect]['name']);
             } else {
-                echo '-';
+                $content .= '-';
             }
         } else {
-            echo '-';
+            $content .= '-';
         }
-        echo '</td><td width="40%">';
-        echo $answer;
-        echo '</td>';
+        $content .= '</td><td width="40%">';
+        $content .= $answer;
+        $content .= '</td>';
         if ($exercise->showExpectedChoice()) {
             $status = Display::label(get_lang('Incorrect'), 'danger');
             if (isset($new_options[$studentChoice])) {
@@ -471,31 +495,35 @@ class ExerciseShowFunctions
                     $status = Display::label(get_lang('Correct'), 'success');
                 }
             }
-            echo '<td width="20%">';
-            echo $status;
-            echo '</td>';
+            $content .= '<td width="20%">';
+            $content .= $status;
+            $content .= '</td>';
         }
-        if ($feedback_type != EXERCISE_FEEDBACK_TYPE_EXAM) {
-            echo '<td width="20%">';
-            $color = "black";
+        if ($feedbackType != EXERCISE_FEEDBACK_TYPE_EXAM) {
+            if ($resultsDisabled == RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER) {
+                if ($studentChoice != $answerCorrect) {
+                    return '';
+                }
+            }
+            $content .= '<td width="20%">';
+            $color = 'black';
             if (isset($new_options[$studentChoice])) {
                 if ($studentChoice == $answerCorrect) {
-                    $color = "green";
+                    $color = 'green';
                 }
 
                 if ($hide_expected_answer) {
                     $color = '';
                 }
-                echo '<span style="font-weight: bold; color: '.$color.';">'.nl2br($answerComment).'</span>';
-            }
-            echo '</td>';
-            if ($ans == 1) {
-                $comm = Event::get_comments($id, $questionId);
+                $content .= '<span style="font-weight: bold; color: '.$color.';">'.nl2br($answerComment).'</span>';
             }
+            $content .= '</td>';
         } else {
-            echo '<td>&nbsp;</td>';
+            $content .= '<td>&nbsp;</td>';
         }
-        echo '</tr>';
+        $content .= '</tr>';
+
+        echo $content;
     }
 
     /**
@@ -552,7 +580,9 @@ class ExerciseShowFunctions
         echo '</td><td width="20%">';
         echo $answer;
         echo '</td><td width="5%" style="text-align:center;">';
-        echo $newOptions[$studentChoiceDegree]['name'];
+        if (isset($newOptions[$studentChoiceDegree])) {
+            echo $newOptions[$studentChoiceDegree]['name'];
+        }
         echo '</td>';
 
         $degreeInfo = $question->getResponseDegreeInfo(
@@ -598,7 +628,7 @@ class ExerciseShowFunctions
      */
     public static function display_multiple_answer_combination_true_false(
         $exercise,
-        $feedback_type,
+        $feedbackType,
         $answerType,
         $studentChoice,
         $answer,
@@ -611,15 +641,19 @@ class ExerciseShowFunctions
         $showTotalScoreAndUserChoices
     ) {
         $hide_expected_answer = false;
-        if ($feedback_type == 0 && ($resultsDisabled == RESULT_DISABLE_SHOW_SCORE_ONLY)) {
-            $hide_expected_answer = true;
-        }
-
-        if ($resultsDisabled == RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT) {
-            $hide_expected_answer = true;
-            if ($showTotalScoreAndUserChoices) {
-                $hide_expected_answer = false;
-            }
+        switch ($resultsDisabled) {
+            case RESULT_DISABLE_SHOW_SCORE_ONLY:
+                if ($feedbackType == 0) {
+                    $hide_expected_answer = true;
+                }
+                break;
+            case RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK:
+            case RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT:
+                $hide_expected_answer = true;
+                if ($showTotalScoreAndUserChoices) {
+                    $hide_expected_answer = false;
+                }
+                break;
         }
 
         echo '<tr><td width="5%">';
@@ -660,7 +694,7 @@ class ExerciseShowFunctions
             echo '</td>';
         }
 
-        if ($feedback_type != EXERCISE_FEEDBACK_TYPE_EXAM) {
+        if ($feedbackType != EXERCISE_FEEDBACK_TYPE_EXAM) {
             echo '<td width="20%">';
             //@todo replace this harcoded value
             if ($studentChoice) {
@@ -684,21 +718,21 @@ class ExerciseShowFunctions
     }
 
     /**
-     * @param $feedback_type
+     * @param $feedbackType
      * @param $exe_id
      * @param $questionId
      * @param null $questionScore
-     * @param int  $results_disabled
+     * @param int  $resultsDisabled
      */
     public static function displayAnnotationAnswer(
-        $feedback_type,
+        $feedbackType,
         $exe_id,
         $questionId,
         $questionScore = null,
-        $results_disabled = 0
+        $resultsDisabled = 0
     ) {
         $comments = Event::get_comments($exe_id, $questionId);
-        if ($feedback_type != EXERCISE_FEEDBACK_TYPE_EXAM) {
+        if ($feedbackType != EXERCISE_FEEDBACK_TYPE_EXAM) {
             if ($questionScore <= 0 && empty($comments)) {
                 echo '<br />'.ExerciseLib::getNotCorrectedYetText();
             }

+ 6 - 2
main/inc/lib/export.lib.inc.php

@@ -33,19 +33,23 @@ class Export
      * @param array  $data
      * @param string $filename
      * @param bool   $writeOnly Whether to only write on disk or also send for download
+     * @param string $enclosure
      *
      * @return mixed csv raw data | false if no data to export | string file path if success in $writeOnly mode
      */
-    public static function arrayToCsv($data, $filename = 'export', $writeOnly = false)
+    public static function arrayToCsv($data, $filename = 'export', $writeOnly = false, $enclosure = '"')
     {
         if (empty($data)) {
             return false;
         }
 
+        $enclosure = !empty($enclosure) ? $enclosure : '"';
+
         $filePath = api_get_path(SYS_ARCHIVE_PATH).uniqid('').'.csv';
         $stream = fopen($filePath, 'w');
-        $writer = new CsvWriter(';', '"', $stream, true);
+        $writer = new CsvWriter(';', $enclosure, $stream, true);
         $writer->prepare();
+
         foreach ($data as $item) {
             if (empty($item)) {
                 $writer->writeItem([]);

+ 195 - 231
main/inc/lib/extra_field.lib.php

@@ -3035,6 +3035,200 @@ JAVASCRIPT;
         return $result;
     }
 
+    /**
+     * @param string $variable
+     * @param string $dataValue
+     *
+     * @return string
+     */
+    public static function getLocalizationJavascript($variable, $dataValue)
+    {
+        $dataValue = addslashes($dataValue);
+        $html = "<script>
+            $(function() {
+                if (typeof google === 'object') {
+                    var address = '$dataValue';
+                    initializeGeo{$variable}(address, false);
+    
+                    $('#geolocalization_extra_{$variable}').on('click', function() {
+                        var address = $('#{$variable}').val();
+                        initializeGeo{$variable}(address, false);
+                        return false;
+                    });
+    
+                    $('#myLocation_extra_{$variable}').on('click', function() {
+                        myLocation{$variable}();
+                        return false;
+                    });
+    
+                    // When clicking enter
+                    $('#{$variable}').keypress(function(event) {                        
+                        if (event.which == 13) {                            
+                            $('#geolocalization_extra_{$variable}').click();
+                            return false;
+                        }
+                    });
+                    
+                    // On focus out update city
+                    $('#{$variable}').focusout(function() {                                                 
+                        $('#geolocalization_extra_{$variable}').click();
+                        return false;                        
+                    });
+                    
+                    return;
+                }
+    
+                $('#map_extra_{$variable}')
+                    .html('<div class=\"alert alert-info\">"
+                .addslashes(get_lang('YouNeedToActivateTheGoogleMapsPluginInAdminPlatformToSeeTheMap'))
+                ."</div>');
+            });
+    
+            function myLocation{$variable}() 
+            {                                                    
+                if (navigator.geolocation) {
+                    var geoPosition = function(position) {
+                        var lat = position.coords.latitude;
+                        var lng = position.coords.longitude;
+                        var latLng = new google.maps.LatLng(lat, lng);
+                        initializeGeo{$variable}(false, latLng);
+                    };
+    
+                    var geoError = function(error) {                        
+                        alert('Geocode ".get_lang('Error').": ' + error);
+                    };
+    
+                    var geoOptions = {
+                        enableHighAccuracy: true
+                    };
+                    navigator.geolocation.getCurrentPosition(geoPosition, geoError, geoOptions);
+                }
+            }
+    
+            function initializeGeo{$variable}(address, latLng)
+            {                
+                var geocoder = new google.maps.Geocoder();
+                var latlng = new google.maps.LatLng(-34.397, 150.644);
+                var myOptions = {
+                    zoom: 15,
+                    center: latlng,
+                    mapTypeControl: true,
+                    mapTypeControlOptions: {
+                        style: google.maps.MapTypeControlStyle.DROPDOWN_MENU
+                    },
+                    navigationControl: true,
+                    mapTypeId: google.maps.MapTypeId.ROADMAP
+                };
+    
+                map_{$variable} = new google.maps.Map(
+                    document.getElementById('map_extra_{$variable}'),
+                    myOptions
+                );
+    
+                var parameter = address ? {'address': address} : latLng ? {'latLng': latLng} : false;
+    
+                if (geocoder && parameter) {
+                    geocoder.geocode(parameter, function(results, status) {
+                        if (status == google.maps.GeocoderStatus.OK) {
+                            if (status != google.maps.GeocoderStatus.ZERO_RESULTS) {                                
+                                map_{$variable}.setCenter(results[0].geometry.location);
+                                
+                                // get city and country                                
+                                var defaultAddress = results[0].formatted_address;
+                                var city = '';
+                                var country = '';
+                                
+                                for (var i=0; i<results[0].address_components.length; i++) {
+                                    if (results[0].address_components[i].types[0] == \"locality\") {
+                                        //this is the object you are looking for City
+                                        city = results[0].address_components[i];
+                                    }
+                                    /*if (results[j].address_components[i].types[0] == \"administrative_area_level_1\") {
+                                        //this is the object you are looking for State
+                                        region = results[0].address_components[i];
+                                    }*/
+                                    if (results[0].address_components[i].types[0] == \"country\") {
+                                        //this is the object you are looking for
+                                        country = results[0].address_components[i];
+                                    }
+                                }
+                        
+                                if (city && city.long_name && country && country.long_name) {
+                                    defaultAddress = city.long_name + ', ' + country.long_name;
+                                }
+                                $('#{$variable}').val(defaultAddress);                                
+                                $('#{$variable}_coordinates').val(
+                                    results[0].geometry.location.lat()+','+results[0].geometry.location.lng()
+                                );
+                                
+                                var infowindow = new google.maps.InfoWindow({
+                                    content: '<b>' + $('#extra_{$variable}').val() + '</b>',
+                                    size: new google.maps.Size(150, 50)
+                                });
+    
+                                var marker = new google.maps.Marker({
+                                    position: results[0].geometry.location,
+                                    map: map_{$variable},
+                                    title: $('#extra_{$variable}').val()
+                                });
+                                google.maps.event.addListener(marker, 'click', function() {
+                                    infowindow.open(map_{$variable}, marker);
+                                });
+                            } else {
+                                alert('".get_lang('NotFound')."');
+                            }
+                        } else {
+                            alert('Geocode ".get_lang('Error').": ".get_lang("AddressField")." ".get_lang('NotFound')."');
+                        }
+                    });
+                }
+            }
+            </script>";
+
+        return $html;
+    }
+
+    /**
+     * @param string $variable
+     * @param string $text
+     *
+     * @return string
+     */
+    public static function getLocalizationInput($variable, $text)
+    {
+        $html = '
+                <div class="form-group">
+                    <label for="geolocalization_extra_'.$variable.'"
+                        class="col-sm-2 control-label"></label>
+                    <div class="col-sm-8">
+                        <button class="btn btn-default"
+                            id="geolocalization_extra_'.$variable.'"
+                            name="geolocalization_extra_'.$variable.'"
+                            type="submit">
+                            <em class="fa fa-map-marker"></em> '.get_lang('SearchGeolocalization').'
+                        </button>
+                        <button class="btn btn-default" id="myLocation_extra_'.$variable.'"
+                            name="myLocation_extra_'.$variable.'"
+                            type="submit">
+                            <em class="fa fa-crosshairs"></em> '.get_lang('MyLocation').'
+                        </button>
+                    </div>
+                </div>                   
+                <div class="form-group">
+                    <label for="map_extra_'.$variable.'" class="col-sm-2 control-label">
+                        '.$text.' - '.get_lang('Map').'
+                    </label>
+                    <div class="col-sm-8">
+                        <div name="map_extra_'.$variable.'"
+                            id="map_extra_'.$variable.'" style="width:100%; height:300px;">
+                        </div>
+                    </div>
+                </div>
+            ';
+
+        return $html;
+    }
+
     /**
      * @param array $options
      * @param int   $parentId
@@ -3069,31 +3263,6 @@ JAVASCRIPT;
         $addOptions = [];
         $optionsExists = false;
         global $app;
-        // Check if exist $app['orm.em'] object
-        if (isset($app['orm.em']) && is_object($app['orm.em'])) {
-            $optionsExists = $app['orm.em']
-                ->getRepository('ChamiloLMS\Entity\ExtraFieldOptionRelFieldOption')
-                ->findOneBy(['fieldId' => $fieldDetails['id']]);
-        }
-
-        if ($optionsExists) {
-            if (isset($userInfo['status'])
-                && !empty($userInfo['status'])
-            ) {
-                $fieldWorkFlow = $app['orm.em']
-                    ->getRepository('ChamiloLMS\Entity\ExtraFieldOptionRelFieldOption')
-                    ->findBy(
-                        [
-                            'fieldId' => $fieldDetails['id'],
-                            'relatedFieldOptionId' => $defaultValueId,
-                            'roleId' => $userInfo['status'],
-                        ]
-                    );
-                foreach ($fieldWorkFlow as $item) {
-                    $addOptions[] = $item->getFieldOptionId();
-                }
-            }
-        }
 
         $options = [];
         if (empty($defaultValueId)) {
@@ -3126,17 +3295,6 @@ JAVASCRIPT;
                 }
             }
 
-            if (isset($optionList[$defaultValueId])) {
-                if (isset($optionList[$defaultValueId]['option_value'])
-                    && $optionList[$defaultValueId]['option_value'] == 'aprobada'
-                ) {
-                    // @todo function don't exists api_is_question_manager
-                    /*if (api_is_question_manager() == false) {
-                        $form->freeze();
-                    }*/
-                }
-            }
-
             // Setting priority message
             if (isset($optionList[$defaultValueId])
                 && isset($optionList[$defaultValueId]['priority'])
@@ -3162,7 +3320,7 @@ JAVASCRIPT;
             'select',
             'extra_'.$fieldDetails['variable'],
             $fieldDetails['display_text'],
-            [],
+            $options,
             ['id' => 'extra_'.$fieldDetails['variable']]
         );
 
@@ -3546,198 +3704,4 @@ JAVASCRIPT;
 
         return $js;
     }
-
-    /**
-     * @param string $variable
-     * @param string $dataValue
-     *
-     * @return string
-     */
-    public static function getLocalizationJavascript($variable, $dataValue)
-    {
-        $dataValue = addslashes($dataValue);
-        $html = "<script>
-            $(function() {
-                if (typeof google === 'object') {
-                    var address = '$dataValue';
-                    initializeGeo{$variable}(address, false);
-    
-                    $('#geolocalization_extra_{$variable}').on('click', function() {
-                        var address = $('#{$variable}').val();
-                        initializeGeo{$variable}(address, false);
-                        return false;
-                    });
-    
-                    $('#myLocation_extra_{$variable}').on('click', function() {
-                        myLocation{$variable}();
-                        return false;
-                    });
-    
-                    // When clicking enter
-                    $('#{$variable}').keypress(function(event) {                        
-                        if (event.which == 13) {                            
-                            $('#geolocalization_extra_{$variable}').click();
-                            return false;
-                        }
-                    });
-                    
-                    // On focus out update city
-                    $('#{$variable}').focusout(function() {                                                 
-                        $('#geolocalization_extra_{$variable}').click();
-                        return false;                        
-                    });
-                    
-                    return;
-                }
-    
-                $('#map_extra_{$variable}')
-                    .html('<div class=\"alert alert-info\">"
-                .addslashes(get_lang('YouNeedToActivateTheGoogleMapsPluginInAdminPlatformToSeeTheMap'))
-                ."</div>');
-            });
-    
-            function myLocation{$variable}() 
-            {                                                    
-                if (navigator.geolocation) {
-                    var geoPosition = function(position) {
-                        var lat = position.coords.latitude;
-                        var lng = position.coords.longitude;
-                        var latLng = new google.maps.LatLng(lat, lng);
-                        initializeGeo{$variable}(false, latLng);
-                    };
-    
-                    var geoError = function(error) {                        
-                        alert('Geocode ".get_lang('Error').": ' + error);
-                    };
-    
-                    var geoOptions = {
-                        enableHighAccuracy: true
-                    };
-                    navigator.geolocation.getCurrentPosition(geoPosition, geoError, geoOptions);
-                }
-            }
-    
-            function initializeGeo{$variable}(address, latLng)
-            {                
-                var geocoder = new google.maps.Geocoder();
-                var latlng = new google.maps.LatLng(-34.397, 150.644);
-                var myOptions = {
-                    zoom: 15,
-                    center: latlng,
-                    mapTypeControl: true,
-                    mapTypeControlOptions: {
-                        style: google.maps.MapTypeControlStyle.DROPDOWN_MENU
-                    },
-                    navigationControl: true,
-                    mapTypeId: google.maps.MapTypeId.ROADMAP
-                };
-    
-                map_{$variable} = new google.maps.Map(
-                    document.getElementById('map_extra_{$variable}'),
-                    myOptions
-                );
-    
-                var parameter = address ? {'address': address} : latLng ? {'latLng': latLng} : false;
-    
-                if (geocoder && parameter) {
-                    geocoder.geocode(parameter, function(results, status) {
-                        if (status == google.maps.GeocoderStatus.OK) {
-                            if (status != google.maps.GeocoderStatus.ZERO_RESULTS) {                                
-                                map_{$variable}.setCenter(results[0].geometry.location);
-                                
-                                // get city and country                                
-                                var defaultAddress = results[0].formatted_address;
-                                var city = '';
-                                var country = '';
-                                
-                                for (var i=0; i<results[0].address_components.length; i++) {
-                                    if (results[0].address_components[i].types[0] == \"locality\") {
-                                        //this is the object you are looking for City
-                                        city = results[0].address_components[i];
-                                    }
-                                    /*if (results[j].address_components[i].types[0] == \"administrative_area_level_1\") {
-                                        //this is the object you are looking for State
-                                        region = results[0].address_components[i];
-                                    }*/
-                                    if (results[0].address_components[i].types[0] == \"country\") {
-                                        //this is the object you are looking for
-                                        country = results[0].address_components[i];
-                                    }
-                                }
-                        
-                                if (city && city.long_name && country && country.long_name) {
-                                    defaultAddress = city.long_name + ', ' + country.long_name;
-                                }
-                                $('#{$variable}').val(defaultAddress);                                
-                                $('#{$variable}_coordinates').val(
-                                    results[0].geometry.location.lat()+','+results[0].geometry.location.lng()
-                                );
-                                
-                                var infowindow = new google.maps.InfoWindow({
-                                    content: '<b>' + $('#extra_{$variable}').val() + '</b>',
-                                    size: new google.maps.Size(150, 50)
-                                });
-    
-                                var marker = new google.maps.Marker({
-                                    position: results[0].geometry.location,
-                                    map: map_{$variable},
-                                    title: $('#extra_{$variable}').val()
-                                });
-                                google.maps.event.addListener(marker, 'click', function() {
-                                    infowindow.open(map_{$variable}, marker);
-                                });
-                            } else {
-                                alert('".get_lang('NotFound')."');
-                            }
-                        } else {
-                            alert('Geocode ".get_lang('Error').": ".get_lang("AddressField")." ".get_lang('NotFound')."');
-                        }
-                    });
-                }
-            }
-            </script>";
-
-        return $html;
-    }
-
-    /**
-     * @param string $variable
-     * @param string $text
-     *
-     * @return string
-     */
-    public static function getLocalizationInput($variable, $text)
-    {
-        $html = '
-                <div class="form-group">
-                    <label for="geolocalization_extra_'.$variable.'"
-                        class="col-sm-2 control-label"></label>
-                    <div class="col-sm-8">
-                        <button class="btn btn-default"
-                            id="geolocalization_extra_'.$variable.'"
-                            name="geolocalization_extra_'.$variable.'"
-                            type="submit">
-                            <em class="fa fa-map-marker"></em> '.get_lang('SearchGeolocalization').'
-                        </button>
-                        <button class="btn btn-default" id="myLocation_extra_'.$variable.'"
-                            name="myLocation_extra_'.$variable.'"
-                            type="submit">
-                            <em class="fa fa-crosshairs"></em> '.get_lang('MyLocation').'
-                        </button>
-                    </div>
-                </div>                   
-                <div class="form-group">
-                    <label for="map_extra_'.$variable.'" class="col-sm-2 control-label">
-                        '.$text.' - '.get_lang('Map').'
-                    </label>
-                    <div class="col-sm-8">
-                        <div name="map_extra_'.$variable.'"
-                            id="map_extra_'.$variable.'" style="width:100%; height:300px;">
-                        </div>
-                    </div>
-                </div>
-            ';
-
-        return $html;
-    }
 }

+ 2 - 2
main/inc/lib/glossary.lib.php

@@ -813,9 +813,9 @@ class GlossaryManager
             return;
         }
 
-        $data = GlossaryManager::get_glossary_data(
+        $data = self::get_glossary_data(
             0,
-            GlossaryManager::get_number_glossary_terms(api_get_session_id()),
+            self::get_number_glossary_terms(api_get_session_id()),
             0,
             'ASC'
         );

+ 36 - 28
main/inc/lib/groupmanager.lib.php

@@ -68,9 +68,11 @@ class GroupManager
     }
 
     /**
+     * @param int $courseId
+     *
      * @return array
      */
-    public static function get_groups($courseId = null)
+    public static function get_groups($courseId = 0)
     {
         $table_group = Database::get_course_table(TABLE_GROUP);
         $courseId = !empty($courseId) ? (int) $courseId : api_get_course_int_id();
@@ -191,7 +193,7 @@ class GroupManager
         $course_id = $_course['real_id'];
         $currentCourseRepository = $_course['path'];
         $category = self::get_category($category_id);
-        $places = intval($places);
+        $places = (int) $places;
 
         // Default values
         $docState = self::TOOL_PRIVATE;
@@ -249,7 +251,7 @@ class GroupManager
                 self_registration_allowed = '".$selfRegAllowed."',
                 self_unregistration_allowed = '".$selfUnregAllwoed."',
                 $documentCondition
-                session_id='".intval($session_id)."'";
+                session_id='".$session_id."'";
 
         Database::query($sql);
         $lastId = Database::insert_id();
@@ -285,7 +287,6 @@ class GroupManager
 
             // create a forum if needed
             if ($forumState >= 0) {
-                require_once api_get_path(SYS_CODE_PATH).'forum/forumconfig.inc.php';
                 require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
 
                 $forum_categories = get_forum_categories();
@@ -390,27 +391,31 @@ class GroupManager
     /**
      * Create a group for every class subscribed to the current course.
      *
-     * @param int $category_id The category in which the groups should be created
+     * @param int $categoryId The category in which the groups should be created
      *
      * @return array
      */
-    public static function create_class_groups($category_id)
+    public static function create_class_groups($categoryId)
     {
-        $options['where'] = [" usergroup.course_id = ? " => api_get_course_int_id()];
+        $options['where'] = [' usergroup.course_id = ? ' => api_get_course_int_id()];
         $obj = new UserGroup();
         $classes = $obj->getUserGroupInCourse($options);
         $group_ids = [];
+
         foreach ($classes as $class) {
-            $users_ids = $obj->get_users_by_usergroup($class['id']);
-            $group_id = self::create_group(
+            $userList = $obj->get_users_by_usergroup($class['id']);
+            $groupId = self::create_group(
                 $class['name'],
-                $category_id,
+                $categoryId,
                 0,
-                count($users_ids)
+                null
             );
-            $groupInfo = self::get_group_properties($group_id);
-            self::subscribe_users($users_ids, $groupInfo);
-            $group_ids[] = $group_id;
+
+            if ($groupId) {
+                $groupInfo = self::get_group_properties($groupId);
+                self::subscribe_users($userList, $groupInfo);
+                $group_ids[] = $groupId;
+            }
         }
 
         return $group_ids;
@@ -547,17 +552,19 @@ class GroupManager
     public static function get_group_properties($group_id, $useIid = false)
     {
         $course_id = api_get_course_int_id();
-        if (empty($group_id) || !is_int(intval($group_id))) {
+        $group_id = (int) $group_id;
+
+        if (empty($group_id)) {
             return null;
         }
 
         $table_group = Database::get_course_table(TABLE_GROUP);
         $sql = "SELECT * FROM $table_group
-                WHERE c_id = $course_id AND id = ".intval($group_id);
+                WHERE c_id = $course_id AND id = ".$group_id;
 
         if ($useIid) {
             $sql = "SELECT * FROM $table_group
-                    WHERE c_id = $course_id AND iid = ".intval($group_id);
+                    WHERE c_id = $course_id AND iid = ".$group_id;
         }
         $db_result = Database::query($sql);
         $db_object = Database::fetch_object($db_result);
@@ -645,7 +652,7 @@ class GroupManager
             return [];
         }
         $name = Database::escape_string($name);
-        $courseId = intval($courseId);
+        $courseId = (int) $courseId;
         $table_group = Database::get_course_table(TABLE_GROUP);
         $sql = "SELECT * FROM $table_group
                 WHERE c_id = $courseId AND name LIKE '%$name%'";
@@ -701,8 +708,8 @@ class GroupManager
     ) {
         $table_group = Database::get_course_table(TABLE_GROUP);
         $table_forum = Database::get_course_table(TABLE_FORUM);
-        $categoryId = intval($categoryId);
-        $group_id = intval($group_id);
+        $categoryId = (int) $categoryId;
+        $group_id = (int) $group_id;
         $courseId = api_get_course_int_id();
 
         $allowDocumentAccess = api_get_configuration_value('group_document_access');
@@ -726,7 +733,7 @@ class GroupManager
                     self_registration_allowed = '".Database::escape_string($selfRegistrationAllowed)."',
                     self_unregistration_allowed = '".Database::escape_string($selfUnRegistrationAllowed)."',
                     $documentCondition
-                    category_id = ".intval($categoryId)."
+                    category_id = ".$categoryId."
                 WHERE c_id = $courseId AND id=".$group_id;
         $result = Database::query($sql);
 
@@ -1054,7 +1061,7 @@ class GroupManager
         $documentAccess
     ) {
         $table = Database::get_course_table(TABLE_GROUP_CATEGORY);
-        $id = intval($id);
+        $id = (int) $id;
 
         $courseId = api_get_course_int_id();
 
@@ -1547,9 +1554,10 @@ class GroupManager
     public static function is_self_registration_allowed($user_id, $groupInfo)
     {
         $course_id = api_get_course_int_id();
-        if (!$user_id > 0) {
+        if (empty($user_id)) {
             return false;
         }
+
         $groupIid = $groupInfo['iid'];
         $table = Database::get_course_table(TABLE_GROUP);
         if (isset($groupIid)) {
@@ -1579,7 +1587,7 @@ class GroupManager
      */
     public static function is_self_unregistration_allowed($user_id, $groupInfo)
     {
-        if (!$user_id > 0 || empty($groupInfo)) {
+        if (empty($user_id) || empty($groupInfo)) {
             return false;
         }
         $groupIid = $groupInfo['iid'];
@@ -1920,7 +1928,7 @@ class GroupManager
             return false;
         }
 
-        if (!empty($groupId) > 0) {
+        if (!empty($groupId)) {
             $table_group_tutor = Database::get_course_table(TABLE_GROUP_TUTOR);
             $sql = "DELETE FROM $table_group_tutor
                     WHERE group_id = $groupId AND c_id = $courseId";
@@ -3091,7 +3099,7 @@ class GroupManager
         }
 
         // Tutor can also make any change
-        $isTutor = GroupManager::is_tutor_of_group($userId, $groupInfo, $courseId);
+        $isTutor = self::is_tutor_of_group($userId, $groupInfo, $courseId);
 
         if ($isTutor) {
             return true;
@@ -3132,7 +3140,7 @@ class GroupManager
             case self::DOCUMENT_MODE_SHARE:
                 // Default chamilo behaviour
                 // Student can upload his own content, cannot modify another content.
-                $isMember = GroupManager::is_subscribed($userId, $groupInfo);
+                $isMember = self::is_subscribed($userId, $groupInfo);
                 if ($isMember) {
                     // No document to check, allow access to document feature.
                     if (empty($documentInfoToBeCheck)) {
@@ -3175,7 +3183,7 @@ class GroupManager
                 break;
             case self::DOCUMENT_MODE_COLLABORATION:
                 // Student can upload content, can modify another content.
-                $isMember = GroupManager::is_subscribed($userId, $groupInfo);
+                $isMember = self::is_subscribed($userId, $groupInfo);
                 if ($isMember) {
                     $result = true;
                 }

+ 6 - 2
main/inc/lib/internationalization.lib.php

@@ -148,8 +148,12 @@ function get_lang($variable, $returnEmptyIfNotFound = false, $language = null)
             $langvar = $GLOBALS[$variable];
         } elseif (isset($GLOBALS["lang$variable"])) {
             $langvar = $GLOBALS["lang$variable"];
-        } elseif (!$returnEmptyIfNotFound) {
-            $langvar = $show_special_markup ? SPECIAL_OPENING_TAG.$variable.SPECIAL_CLOSING_TAG : $variable;
+        } else {
+            if (!$returnEmptyIfNotFound) {
+                $langvar = $show_special_markup ? SPECIAL_OPENING_TAG.$variable.SPECIAL_CLOSING_TAG : $variable;
+            } else {
+                return '';
+            }
         }
     }
     if (empty($langvar) || !is_string($langvar) && !$returnEmptyIfNotFound) {

+ 4 - 0
main/inc/lib/javascript/chat/video.php

@@ -40,6 +40,10 @@ $htmlHeadXtra[] = '<script type="text/javascript" src="'
 
 $navigator = api_get_navigator();
 
+Display::addFlash(
+    Display::return_message(get_lang('FeatureDisabledBecauseOfUnmaintainedThirdPartyLibraries'), 'error')
+);
+
 $template = new Template();
 $template->assign('room_name', $chatVideo->getRoomName());
 $template->assign('chat_user', $chatUser);

+ 265 - 119
main/inc/lib/message.lib.php

@@ -364,17 +364,18 @@ class MessageManager
      * @param int    $receiver_user_id
      * @param string $subject
      * @param string $content
-     * @param array  $attachments         files array($_FILES) (optional)
-     * @param array  $fileCommentList     about attachment files (optional)
-     * @param int    $group_id            (optional)
-     * @param int    $parent_id           (optional)
-     * @param int    $editMessageId       id for updating the message (optional)
-     * @param int    $topic_id            (optional) the default value is the current user_id
+     * @param array  $attachments                files array($_FILES) (optional)
+     * @param array  $fileCommentList            about attachment files (optional)
+     * @param int    $group_id                   (optional)
+     * @param int    $parent_id                  (optional)
+     * @param int    $editMessageId              id for updating the message (optional)
+     * @param int    $topic_id                   (optional) the default value is the current user_id
      * @param int    $sender_id
      * @param bool   $directMessage
      * @param int    $forwardId
      * @param array  $smsParameters
      * @param bool   $checkCurrentAudioId
+     * @param bool   $forceTitleWhenSendingEmail force the use of $title as subject instead of "You have a new message"
      *
      * @return bool
      */
@@ -392,7 +393,8 @@ class MessageManager
         $directMessage = false,
         $forwardId = 0,
         $smsParameters = [],
-        $checkCurrentAudioId = false
+        $checkCurrentAudioId = false,
+        $forceTitleWhenSendingEmail = false
     ) {
         $table = Database::get_main_table(TABLE_MESSAGE);
         $group_id = (int) $group_id;
@@ -598,7 +600,8 @@ class MessageManager
                     $content,
                     $sender_info,
                     $attachmentAddedByMail,
-                    $smsParameters
+                    $smsParameters,
+                    $forceTitleWhenSendingEmail
                 );
             } else {
                 $usergroup = new UserGroup();
@@ -1238,7 +1241,8 @@ class MessageManager
             $title = Security::remove_XSS($title);
             $userInfo = api_get_user_info($senderId);
             if ($request === true) {
-                $message[1] = '<a onclick="show_sent_message('.$messageId.')" href="javascript:void(0)">'.$userInfo['complete_name_with_username'].'</a>';
+                $message[1] = '<a onclick="show_sent_message('.$messageId.')" href="javascript:void(0)">'.
+                    $userInfo['complete_name_with_username'].'</a>';
                 $message[2] = '<a onclick="show_sent_message('.$messageId.')" href="javascript:void(0)">'.str_replace(
                         "\\",
                         "",
@@ -1628,6 +1632,16 @@ class MessageManager
         if (empty($main_message)) {
             return false;
         }
+
+        $webCodePath = api_get_path(WEB_CODE_PATH);
+
+        $iconCalendar = Display::returnFontAwesomeIcon('calendar');
+
+        $langEdit = get_lang('Edit');
+        $langReply = get_lang('Reply');
+        $langLastUpdated = get_lang('LastUpdated');
+        $langCreated = get_lang('Created');
+
         $rows = self::get_messages_by_group_by_message($groupId, $topic_id);
         $rows = self::calculate_children($rows, $topic_id);
         $current_user_id = api_get_user_id();
@@ -1645,74 +1659,70 @@ class MessageManager
         $files_attachments = self::getAttachmentLinkList($main_message['id']);
         $name = $user_sender_info['complete_name'];
 
-        $topic_page_nr = isset($_GET['topics_page_nr']) ? intval($_GET['topics_page_nr']) : null;
+        $topic_page_nr = isset($_GET['topics_page_nr']) ? (int) $_GET['topics_page_nr'] : null;
 
         $links .= '<div class="pull-right">';
         $links .= '<div class="btn-group btn-group-sm">';
 
-        if (($my_group_role == GROUP_USER_PERMISSION_ADMIN ||
-                $my_group_role == GROUP_USER_PERMISSION_MODERATOR) ||
+        if (($my_group_role == GROUP_USER_PERMISSION_ADMIN || $my_group_role == GROUP_USER_PERMISSION_MODERATOR) ||
             $main_message['user_sender_id'] == $current_user_id
         ) {
-            $urlEdit = api_get_path(WEB_CODE_PATH);
-            $urlEdit .= 'social/message_for_group_form.inc.php?';
-            $urlEdit .= http_build_query(
+            $urlEdit = $webCodePath.'social/message_for_group_form.inc.php?'
+                .http_build_query(
+                    [
+                        'user_friend' => $current_user_id,
+                        'group_id' => $groupId,
+                        'message_id' => $main_message['id'],
+                        'action' => 'edit_message_group',
+                        'anchor_topic' => 'topic_'.$main_message['id'],
+                        'topics_page_nr' => $topic_page_nr,
+                        'items_page_nr' => $items_page_nr,
+                        'topic_id' => $main_message['id'],
+                    ]
+                );
+
+            $links .= Display::toolbarButton(
+                $langEdit,
+                $urlEdit,
+                'pencil',
+                'default',
+                ['class' => 'ajax', 'data-title' => $langEdit, 'data-size' => 'lg'],
+                false
+            );
+        }
+
+        $links .= self::getLikesButton($main_message['id'], $current_user_id, $groupId);
+
+        $urlReply = $webCodePath.'social/message_for_group_form.inc.php?'
+            .http_build_query(
                 [
                     'user_friend' => $current_user_id,
                     'group_id' => $groupId,
                     'message_id' => $main_message['id'],
-                    'action' => 'edit_message_group',
+                    'action' => 'reply_message_group',
                     'anchor_topic' => 'topic_'.$main_message['id'],
                     'topics_page_nr' => $topic_page_nr,
-                    'items_page_nr' => $items_page_nr,
                     'topic_id' => $main_message['id'],
                 ]
             );
 
-            $links .= Display::url(
-                Display::returnFontAwesomeIcon('pencil'),
-                $urlEdit,
-                [
-                    'class' => 'btn btn-default ajax',
-                    'title' => get_lang('Edit'),
-                    'data-title' => get_lang('Edit'),
-                    'data-size' => 'lg',
-                ]
-            );
-        }
-
-        $urlReply = api_get_path(WEB_CODE_PATH);
-        $urlReply .= 'social/message_for_group_form.inc.php?';
-        $urlReply .= http_build_query(
-            [
-                'user_friend' => api_get_user_id(),
-                'group_id' => $groupId,
-                'message_id' => $main_message['id'],
-                'action' => 'reply_message_group',
-                'anchor_topic' => 'topic_'.$main_message['id'],
-                'topics_page_nr' => $topic_page_nr,
-                'topic_id' => $main_message['id'],
-            ]
-        );
-
-        $links .= Display::url(
-            Display::returnFontAwesomeIcon('commenting'),
+        $links .= Display::toolbarButton(
+            $langReply,
             $urlReply,
-            [
-                'class' => 'btn btn-default ajax',
-                'title' => get_lang('Reply'),
-                'data-title' => get_lang('Reply'),
-                'data-size' => 'lg',
-            ]
+            'commenting',
+            'default',
+            ['class' => 'ajax', 'data-title' => $langReply, 'data-size' => 'lg'],
+            false
         );
 
         if (api_is_platform_admin()) {
-            $links .= Display::url(
-                Display::returnFontAwesomeIcon('trash'),
+            $links .= Display::toolbarButton(
+                get_lang('Delete'),
                 'group_topics.php?action=delete&id='.$groupId.'&topic_id='.$topic_id,
-                [
-                    'class' => 'btn btn-default',
-                ]
+                'trash',
+                'default',
+                [],
+                false
             );
         }
 
@@ -1725,32 +1735,37 @@ class MessageManager
         $main_content .= '<div class="row">';
         $main_content .= '<div class="col-md-2">';
         $main_content .= '<div class="avatar-author">';
-        $main_content .= '<img width="60px" src="'.$userPicture.'" alt="'.$name.'" class="img-responsive img-circle" title="'.$name.'" />';
+        $main_content .= Display::img(
+            $userPicture,
+            $name,
+            ['width' => '60px', 'class' => 'img-responsive img-circle'],
+            false
+        );
         $main_content .= '</div>';
         $main_content .= '</div>';
 
         $date = '';
         if ($main_message['send_date'] != $main_message['update_date']) {
             if (!empty($main_message['update_date'])) {
-                $date = '<div class="date"> '.
-                    Display::returnFontAwesomeIcon('calendar').' '.get_lang('LastUpdate').' '.
-                    Display::dateToStringAgoAndLongDate($main_message['update_date']).
-                    '</div>';
+                $date = '<div class="date"> '
+                    ."$iconCalendar $langLastUpdated "
+                    .Display::dateToStringAgoAndLongDate($main_message['update_date'])
+                    .'</div>';
             }
         } else {
-            $date = '<div class="date"> '.
-                Display::returnFontAwesomeIcon('calendar').' '.get_lang('Created').' '.
-                Display::dateToStringAgoAndLongDate($main_message['send_date']).
-                '</div>';
-        }
-        $attachment = '<div class="message-attach">'.(!empty($files_attachments) ? implode(
-                '<br />',
-                $files_attachments
-            ) : '').'</div>';
+            $date = '<div class="date"> '
+                ."$iconCalendar $langCreated "
+                .Display::dateToStringAgoAndLongDate($main_message['send_date'])
+                .'</div>';
+        }
+        $attachment = '<div class="message-attach">'
+            .(!empty($files_attachments) ? implode('<br />', $files_attachments) : '')
+            .'</div>';
         $main_content .= '<div class="col-md-10">';
-        $user_link = '<a href="'.api_get_path(
-                WEB_PATH
-            ).'main/social/profile.php?u='.$main_message['user_sender_id'].'">'.$name.'</a>';
+        $user_link = Display::url(
+            $name,
+            $webCodePath.'social/profile.php?u='.$main_message['user_sender_id']
+        );
         $main_content .= '<div class="message-content"> ';
         $main_content .= '<div class="username">'.$user_link.'</div>';
         $main_content .= $date;
@@ -1771,13 +1786,14 @@ class MessageManager
         if (is_array($rows) && count($rows) > 0) {
             $topics = $rows;
             $array_html_items = [];
+
             foreach ($topics as $index => $topic) {
                 if (empty($topic['id'])) {
                     continue;
                 }
-                $items_page_nr = isset($_GET['items_'.$topic['id'].'_page_nr']) ? intval(
-                    $_GET['items_'.$topic['id'].'_page_nr']
-                ) : null;
+                $items_page_nr = isset($_GET['items_'.$topic['id'].'_page_nr'])
+                    ? (int) $_GET['items_'.$topic['id'].'_page_nr']
+                    : null;
                 $links = '';
                 $links .= '<div class="pull-right">';
                 $html_items = '';
@@ -1786,71 +1802,110 @@ class MessageManager
                 $name = $user_sender_info['complete_name'];
 
                 $links .= '<div class="btn-group btn-group-sm">';
-                if (($my_group_role == GROUP_USER_PERMISSION_ADMIN || $my_group_role == GROUP_USER_PERMISSION_MODERATOR) ||
+                if (
+                    ($my_group_role == GROUP_USER_PERMISSION_ADMIN ||
+                        $my_group_role == GROUP_USER_PERMISSION_MODERATOR
+                    ) ||
                     $topic['user_sender_id'] == $current_user_id
                 ) {
-                    $links .= '<a href="'.api_get_path(
-                            WEB_CODE_PATH
-                        ).'social/message_for_group_form.inc.php?height=400&width=800&&user_friend='.$current_user_id.'&group_id='.$groupId.'&message_id='.$topic['id'].'&action=edit_message_group&anchor_topic=topic_'.$topic_id.'&topics_page_nr='.$topic_page_nr.'&items_page_nr='.$items_page_nr.'&topic_id='.$topic_id.'" class="ajax btn btn-default" data-size="lg" data-title="'.get_lang(
-                            'Edit'
-                        ).'" title="'.get_lang('Edit').'">'.
-                        Display::returnFontAwesomeIcon('pencil').'</a>';
+                    $links .= Display::toolbarButton(
+                        $langEdit,
+                        $webCodePath.'social/message_for_group_form.inc.php?'
+                            .http_build_query(
+                                [
+                                    'user_friend' => $current_user_id,
+                                    'group_id' => $groupId,
+                                    'message_id' => $topic['id'],
+                                    'action' => 'edit_message_group',
+                                    'anchor_topic' => 'topic_'.$topic_id,
+                                    'topics_page_nr' => $topic_page_nr,
+                                    'items_page_nr' => $items_page_nr,
+                                    'topic_id' => $topic_id,
+                                ]
+                            ),
+                        'pencil',
+                        'default',
+                        ['class' => 'ajax', 'data-title' => $langEdit, 'data-size' => 'lg'],
+                        false
+                    );
                 }
-                $links .= '<a href="'.api_get_path(
-                        WEB_CODE_PATH
-                    ).'social/message_for_group_form.inc.php?height=400&width=800&&user_friend='.api_get_user_id(
-                    ).'&group_id='.$groupId.'&message_id='.$topic['id'].'&action=reply_message_group&anchor_topic=topic_'.$topic_id.'&topics_page_nr='.$topic_page_nr.'&items_page_nr='.$items_page_nr.'&topic_id='.$topic_id.'" class="ajax btn btn-default" data-size="lg" data-title="'.get_lang(
-                        'Reply'
-                    ).'" title="'.get_lang('Reply').'">';
-                $links .= Display::returnFontAwesomeIcon('commenting').'</a>';
+
+                $links .= self::getLikesButton($topic['id'], $current_user_id, $groupId);
+
+                $links .= Display::toolbarButton(
+                    $langReply,
+                    $webCodePath.'social/message_for_group_form.inc.php?'
+                        .http_build_query(
+                            [
+                                'user_friend' => $current_user_id,
+                                'group_id' => $groupId,
+                                'message_id' => $topic['id'],
+                                'action' => 'reply_message_group',
+                                'anchor_topic' => 'topic_'.$topic_id,
+                                'topics_page_nr' => $topic_page_nr,
+                                'items_page_nr' => $items_page_nr,
+                                'topic_id' => $topic_id,
+                            ]
+                        ),
+                    'commenting',
+                    'default',
+                    ['class' => 'ajax', 'data-title' => $langReply, 'data-size' => 'lg'],
+                    false
+                );
                 $links .= '</div>';
                 $links .= '</div>';
 
                 $userPicture = $user_sender_info['avatar'];
-                $user_link = '<a href="'.api_get_path(
-                        WEB_PATH
-                    ).'main/social/profile.php?u='.$topic['user_sender_id'].'">'.$name.'&nbsp</a>';
+                $user_link = Display::url(
+                    $name,
+                    $webCodePath.'social/profile.php?u='.$topic['user_sender_id']
+                );
                 $html_items .= '<div class="row">';
                 $html_items .= '<div class="col-md-2">';
                 $html_items .= '<div class="avatar-author">';
-                $html_items .= '<img width="60px" src="'.$userPicture.'" alt="'.$name.'" class="img-responsive img-circle" title="'.$name.'" /></div>';
+                $html_items .= Display::img(
+                    $userPicture,
+                    $name,
+                    ['width' => '60px', 'class' => 'img-responsive img-circle'],
+                    false
+                );
+                $html_items .= '</div>';
                 $html_items .= '</div>';
 
                 $date = '';
                 if ($topic['send_date'] != $topic['update_date']) {
                     if (!empty($topic['update_date'])) {
-                        $date = '<div class="date"> '.
-                            Display::returnFontAwesomeIcon('calendar').' '.
-                            get_lang('LastUpdate').' '.Display::dateToStringAgoAndLongDate($topic['update_date']).
-                            '</div>';
+                        $date = '<div class="date"> '
+                            ."$iconCalendar $langLastUpdated "
+                            .Display::dateToStringAgoAndLongDate($topic['update_date'])
+                            .'</div>';
                     }
                 } else {
-                    $date = '<div class="date"> '.
-                        Display::returnFontAwesomeIcon('calendar').
-                        get_lang('Created').' '.Display::dateToStringAgoAndLongDate($topic['send_date']).
-                        '</div>';
+                    $date = '<div class="date"> '
+                        ."$iconCalendar $langCreated "
+                        .Display::dateToStringAgoAndLongDate($topic['send_date'])
+                        .'</div>';
                 }
-                $attachment = '<div class="message-attach">'.(!empty($files_attachments) ? implode(
-                        '<br />',
-                        $files_attachments
-                    ) : '').'</div>';
-                $html_items .= '<div class="col-md-10">';
-                $html_items .= '<div class="message-content">';
-                $html_items .= $links;
-                $html_items .= '<div class="username">'.$user_link.'</div>';
-                $html_items .= $date;
-                $html_items .= '<div class="message">'.
-                    Security::remove_XSS($topic['content'], STUDENT, true).
-                    '</div>'.$attachment.'</div>';
-                $html_items .= '</div>';
-                $html_items .= '</div>';
+                $attachment = '<div class="message-attach">'
+                    .(!empty($files_attachments) ? implode('<br />', $files_attachments) : '')
+                    .'</div>';
+                $html_items .= '<div class="col-md-10">'
+                    .'<div class="message-content">'
+                    .$links
+                    .'<div class="username">'.$user_link.'</div>'
+                    .$date
+                    .'<div class="message">'
+                    .Security::remove_XSS($topic['content'], STUDENT, true)
+                    .'</div>'.$attachment.'</div>'
+                    .'</div>'
+                    .'</div>';
 
                 $base_padding = 20;
 
                 if ($topic['indent_cnt'] == 0) {
                     $indent = $base_padding;
                 } else {
-                    $indent = intval($topic['indent_cnt']) * $base_padding + $base_padding;
+                    $indent = (int) $topic['indent_cnt'] * $base_padding + $base_padding;
                 }
 
                 $html_items = Display::div($html_items, ['class' => 'message-post', 'id' => 'msg_'.$topic['id']]);
@@ -2677,6 +2732,94 @@ class MessageManager
         }
     }
 
+    /**
+     * @param int $messageId
+     * @param int $userId
+     *
+     * @throws \Doctrine\ORM\Query\QueryException
+     *
+     * @return array
+     */
+    public static function countLikesAndDislikes($messageId, $userId)
+    {
+        if (!api_get_configuration_value('social_enable_likes_messages')) {
+            return [];
+        }
+
+        $messageId = (int) $messageId;
+        $userId = (int) $userId;
+
+        $em = Database::getManager();
+
+        $likesCount = $em
+            ->createQuery('SELECT COUNT(l) FROM ChamiloCoreBundle:MessageLikes l
+                WHERE l.liked = true AND l.message = :message')
+            ->setParameters(['message' => $messageId])
+            ->getSingleScalarResult();
+
+        $dislikesCount = $em
+            ->createQuery('SELECT COUNT(l) FROM ChamiloCoreBundle:MessageLikes l
+                WHERE l.liked = false AND l.message = :message')
+            ->setParameters(['message' => $messageId])
+            ->getSingleScalarResult();
+
+        $userLike = $em
+            ->getRepository('ChamiloCoreBundle:MessageLikes')
+            ->findOneBy(['message' => $messageId, 'user' => $userId]);
+
+        return [
+            'likes' => $likesCount,
+            'dislikes' => $dislikesCount,
+            'user_liked' => $userLike ? $userLike->isLiked() : false,
+            'user_disliked' => $userLike ? $userLike->isDisliked() : false,
+        ];
+    }
+
+    /**
+     * @param int $messageId
+     * @param int $userId
+     * @param int $groupId   Optional.
+     *
+     * @throws \Doctrine\ORM\Query\QueryException
+     *
+     * @return string
+     */
+    public static function getLikesButton($messageId, $userId, $groupId = 0)
+    {
+        if (!api_get_configuration_value('social_enable_likes_messages')) {
+            return '';
+        }
+
+        $countLikes = self::countLikesAndDislikes($messageId, $userId);
+
+        $btnLike = Display::button(
+            'like',
+            Display::returnFontAwesomeIcon('thumbs-up', '', true)
+                .PHP_EOL.'<span>'.$countLikes['likes'].'</span>',
+            [
+                'title' => get_lang('Like'),
+                'class' => 'btn btn-default social-like '.($countLikes['user_liked'] ? 'disabled' : ''),
+                'data-status' => 'like',
+                'data-message' => $messageId,
+                'data-group' => $groupId,
+            ]
+        );
+        $btnDislike = Display::button(
+            'like',
+            Display::returnFontAwesomeIcon('thumbs-down', '', true)
+            .PHP_EOL.'<span>'.$countLikes['dislikes'].'</span>',
+            [
+                'title' => get_lang('Dislike'),
+                'class' => 'btn btn-default social-like '.($countLikes['user_disliked'] ? 'disabled' : ''),
+                'data-status' => 'dislike',
+                'data-message' => $messageId,
+                'data-group' => $groupId,
+            ]
+        );
+
+        return $btnLike.PHP_EOL.$btnDislike;
+    }
+
     /**
      * Execute the SQL necessary to know the number of messages in the database.
      *
@@ -2684,16 +2827,19 @@ class MessageManager
      *
      * @return int The number of unread messages in the database for the given user
      */
-    private static function getCountNewMessagesFromDB($userId)
+    public static function getCountNewMessagesFromDB($userId)
     {
+        $userId = (int) $userId;
+
         if (empty($userId)) {
             return 0;
         }
+
         $table = Database::get_main_table(TABLE_MESSAGE);
         $sql = "SELECT COUNT(id) as count 
                 FROM $table
                 WHERE
-                    user_receiver_id=".api_get_user_id()." AND
+                    user_receiver_id=".$userId." AND
                     msg_status = ".MESSAGE_STATUS_UNREAD;
         $result = Database::query($sql);
         $row = Database::fetch_assoc($result);

+ 18 - 9
main/inc/lib/notification.lib.php

@@ -143,10 +143,11 @@ class Notification extends Model
     /**
      * @param string $title
      * @param array  $senderInfo
+     * @param bool   $forceTitleWhenSendingEmail force the use of $title as subject instead of "You have a new message"
      *
      * @return string
      */
-    public function formatTitle($title, $senderInfo)
+    public function formatTitle($title, $senderInfo, $forceTitleWhenSendingEmail = false)
     {
         $hook = HookNotificationTitle::create();
         if (!empty($hook)) {
@@ -200,6 +201,11 @@ class Notification extends Model
                 break;
         }
 
+        // The title won't be changed, it will be used as is
+        if ($forceTitleWhenSendingEmail) {
+            $newTitle = $title;
+        }
+
         if (!empty($hook)) {
             $hook->setEventData(['title' => $newTitle]);
             $data = $hook->notifyNotificationTitle(HOOK_EVENT_TYPE_POST);
@@ -214,17 +220,18 @@ class Notification extends Model
     /**
      * Save message notification.
      *
-     * @param int    $type          message type
-     *                              NOTIFICATION_TYPE_MESSAGE,
-     *                              NOTIFICATION_TYPE_INVITATION,
-     *                              NOTIFICATION_TYPE_GROUP
+     * @param int    $type                       message type
+     *                                           NOTIFICATION_TYPE_MESSAGE,
+     *                                           NOTIFICATION_TYPE_INVITATION,
+     *                                           NOTIFICATION_TYPE_GROUP
      * @param int    $messageId
-     * @param array  $userList      recipients: user list of ids
+     * @param array  $userList                   recipients: user list of ids
      * @param string $title
      * @param string $content
-     * @param array  $senderInfo    result of api_get_user_info() or GroupPortalManager:get_group_data()
+     * @param array  $senderInfo                 result of api_get_user_info() or GroupPortalManager:get_group_data()
      * @param array  $attachments
      * @param array  $smsParameters
+     * @param bool   $forceTitleWhenSendingEmail force the use of $title as subject instead of "You have a new message"
      */
     public function saveNotification(
         $messageId,
@@ -234,12 +241,13 @@ class Notification extends Model
         $content,
         $senderInfo = [],
         $attachments = [],
-        $smsParameters = []
+        $smsParameters = [],
+        $forceTitleWhenSendingEmail = false
     ) {
         $this->type = (int) $type;
         $messageId = (int) $messageId;
         $content = $this->formatContent($messageId, $content, $senderInfo);
-        $titleToNotification = $this->formatTitle($title, $senderInfo);
+        $titleToNotification = $this->formatTitle($title, $senderInfo, $forceTitleWhenSendingEmail);
         $settingToCheck = '';
         $avoid_my_self = false;
 
@@ -303,6 +311,7 @@ class Notification extends Model
                     case self::NOTIFY_INVITATION_AT_ONCE:
                     case self::NOTIFY_GROUP_AT_ONCE:
                         $extraHeaders = [];
+                        //ofaj
                         $noReply = api_get_setting('noreply_email_address');
                         if (empty($noReply) && isset($senderInfo['email'])) {
                             $extraHeaders = [

+ 4 - 4
main/inc/lib/online.inc.php

@@ -107,13 +107,15 @@ function online_logout($user_id = null, $logout_redirect = false)
         $user_id = isset($_GET['uid']) ? intval($_GET['uid']) : 0;
     }
 
-    //Changing global chat status to offline
-    // ofaj
+    // Changing global chat status to offline
     if (api_is_global_chat_enabled()) {
         $chat = new Chat();
         $chat->setUserStatus(0);
     }
 
+    $chat = new Chat();
+    $chat->close();
+
     // selecting the last login of the user
     $sql = "SELECT login_id, login_date
     		FROM $tbl_track_login
@@ -136,8 +138,6 @@ function online_logout($user_id = null, $logout_redirect = false)
         'tool' => 'logout',
         'tool_id' => 0,
         'tool_id_detail' => 0,
-        'action' => '',
-        'info' => '',
     ];
     Event::registerLog($logInfo);
 

+ 31 - 12
main/inc/lib/pdf.lib.php

@@ -58,10 +58,6 @@ class PDF
         $this->params['pdf_date'] = isset($params['pdf_date']) ? $params['pdf_date'] : api_format_date($localTime, DATE_TIME_FORMAT_LONG);
         $this->params['pdf_date_only'] = isset($params['pdf_date']) ? $params['pdf_date'] : api_format_date($localTime, DATE_FORMAT_LONG);
 
-        // Ofaj set custom paths to load ttfonts and font configuration
-        define('_MPDF_SYSTEM_TTFONTS_CONFIG', api_get_path(LIBRARY_PATH).'pdf_config.php');
-        define('_MPDF_SYSTEM_TTFONTS', api_get_path(SYS_APP_PATH).'Resources/public/fonts/');
-
         $this->pdf = new mPDF(
             'UTF-8',
             $pageFormat,
@@ -273,7 +269,7 @@ class PDF
                 $this->pdf->WriteHTML($css, 1);
             }
 
-            //it's not a chapter but the file exists, print its title
+            // it's not a chapter but the file exists, print its title
             if ($print_title) {
                 $this->pdf->WriteHTML(
                     '<html><body><h3>'.$html_title.'</h3></body></html>'
@@ -333,7 +329,6 @@ class PDF
                 $this->pdf->WriteHTML('<html><body>'.$image.'</body></html>'.$page_break);
             }
         }
-
         if (empty($pdf_name)) {
             $output_file = 'pdf_'.date('Y-m-d-his').'.pdf';
         } else {
@@ -365,6 +360,8 @@ class PDF
      * 'F' (save to local file) or
      * 'S' (return as a string)
      *
+     * @throws MpdfException
+     *
      * @return string Web path
      */
     public function content_to_pdf(
@@ -416,9 +413,15 @@ class PDF
             //Fixing only images @todo do the same thing with other elements
             $elements = $doc->getElementsByTagName('img');
             $protocol = api_get_protocol();
+            $replaced = [];
             if (!empty($elements)) {
                 foreach ($elements as $item) {
                     $old_src = $item->getAttribute('src');
+
+                    if (in_array($old_src, $replaced)) {
+                        continue;
+                    }
+
                     if (strpos($old_src, $protocol) === false) {
                         if (strpos($old_src, '/main/default_course_document') === false) {
                             if (strpos($old_src, '/main/inc/lib/') === false &&
@@ -436,6 +439,7 @@ class PDF
                                 );
                                 $new_path = $document_path.$old_src_fixed;
                                 $document_html = str_replace($old_src, $new_path, $document_html);
+                                $replaced[] = $old_src;
                             }
                         }
                     }
@@ -459,7 +463,11 @@ class PDF
         }
 
         if (!empty($css)) {
-            $this->pdf->WriteHTML($css, 1);
+            try {
+                $this->pdf->WriteHTML($css, 1);
+            } catch (MpdfException $e) {
+                error_log($e);
+            }
         }
 
         if ($addDefaultCss) {
@@ -470,11 +478,19 @@ class PDF
             ];
             foreach ($basicStyles as $style) {
                 $cssContent = file_get_contents($style);
-                $this->pdf->WriteHTML($cssContent, 1);
+                try {
+                    $this->pdf->WriteHTML($cssContent, 1);
+                } catch (MpdfException $e) {
+                    error_log($e);
+                }
             }
         }
 
-        $this->pdf->WriteHTML($document_html);
+        try {
+            $this->pdf->WriteHTML($document_html);
+        } catch (MpdfException $e) {
+            error_log($e);
+        }
 
         if (empty($pdf_name)) {
             $output_file = 'pdf_'.date('Y-m-d-his').'.pdf';
@@ -864,6 +880,7 @@ class PDF
      */
     private static function fixImagesPaths($documentHtml, array $courseInfo, $dirName = '')
     {
+        $documentHtml = '<?xml encoding="utf-8" ?>'.$documentHtml;
         $doc = new DOMDocument();
         @$doc->loadHTML($documentHtml);
 
@@ -889,12 +906,16 @@ class PDF
                 continue;
             }
 
+            // It's a reference to a file in the system, do not change it
+            if (file_exists($src)) {
+                continue;
+            }
+
             if (strpos($src, '/main/default_course_document') === 0) {
                 $element->setAttribute(
                     'src',
                     str_replace('/main/default_course_document', $sysCodePath.'default_course_document', $src)
                 );
-
                 continue;
             }
 
@@ -903,7 +924,6 @@ class PDF
                     'src',
                     str_replace('/main/img/', $sysCodePath.'img/', $src)
                 );
-
                 continue;
             }
 
@@ -912,7 +932,6 @@ class PDF
                     'src',
                     str_replace('/app/upload/', $sysUploadPath, $src)
                 );
-
                 continue;
             }
 

+ 4 - 4
main/inc/lib/pear/HTML/Common.php

@@ -128,7 +128,7 @@ class HTML_Common
     function _getTabs()
     {
         return str_repeat($this->_getTab(), $this->_tabOffset);
-    } // end func _getTabs
+    }
 
     /**
      * Returns an HTML formatted attribute string
@@ -145,7 +145,7 @@ class HTML_Common
             foreach ($attributes as $key => $value) {
             	// Modified by Ivan Tcholakov, 16-MAR-2010
                 $value = @htmlspecialchars($value, ENT_COMPAT, $charset);
-                $strAttr .= ' ' . $key . '= "' . $value. '"';
+                $strAttr .= ' ' . $key . '="' . $value. '"';
             }
         }
 
@@ -244,7 +244,7 @@ class HTML_Common
         if (isset($attributes[$attr])) {
             unset($attributes[$attr]);
         }
-    } //end func _removeAttr
+    }
 
     /**
      * Returns the value of the given attribute
@@ -461,5 +461,5 @@ class HTML_Common
     function charset($newCharset = null)
     {
         return 'UTF-8';
-    } // end func charset
+    }
 }

+ 2 - 2
main/inc/lib/pear/HTML/QuickForm/Renderer.php

@@ -153,5 +153,5 @@ class HTML_QuickForm_Renderer
     function finishGroup(&$group)
     {
         return;
-    } // end func finishGroup
-} // end class HTML_QuickForm_Renderer
+    }
+}

+ 23 - 23
main/inc/lib/pear/HTML/QuickForm/button.php

@@ -242,29 +242,29 @@ class HTML_QuickForm_button extends HTML_QuickForm_input
                     ';
                 } else {
                     $template = '
-                        <div class="form-group {error_class}">
-                            <label {label-for} class="col-sm-'.$size[0].' control-label" >
-                                <!-- BEGIN required --><span class="form_required">*</span><!-- END required -->
-                                {label}
-                            </label>
-                            <div class="col-sm-'.$size[1].'">
-                                {icon}
-                                {element}
-        
-                                <!-- BEGIN label_2 -->
-                                    <p class="help-block">{label_2}</p>
-                                <!-- END label_2 -->
-        
-                                <!-- BEGIN error -->
-                                    <span class="help-inline help-block">{error}</span>
-                                <!-- END error -->
-                            </div>
-                            <div class="col-sm-'.$size[2].'">
-                                <!-- BEGIN label_3 -->
-                                    {label_3}
-                                <!-- END label_3 -->
-                            </div>
-                        </div>';
+                <div class="form-group {error_class}">
+                    <label {label-for} class="col-sm-'.$size[0].' control-label" >
+                        <!-- BEGIN required --><span class="form_required">*</span><!-- END required -->
+                        {label}
+                    </label>
+                    <div class="col-sm-'.$size[1].'">
+                        {icon}
+                        {element}
+
+                        <!-- BEGIN label_2 -->
+                            <p class="help-block">{label_2}</p>
+                        <!-- END label_2 -->
+
+                        <!-- BEGIN error -->
+                            <span class="help-inline help-block">{error}</span>
+                        <!-- END error -->
+                    </div>
+                    <div class="col-sm-'.$size[2].'">
+                        <!-- BEGIN label_3 -->
+                            {label_3}
+                        <!-- END label_3 -->
+                    </div>
+                </div>';
                 }
                 return $template;
                 break;

+ 4 - 3
main/inc/lib/pear/HTML/QuickForm/checkbox.php

@@ -140,10 +140,11 @@ class HTML_QuickForm_checkbox extends HTML_QuickForm_input
         } elseif ($this->_flagFrozen) {
             $label = $this->_text;
         } else {
-            $className = $this->checkboxClass;
+            $labelClass = $this->labelClass;
+            $checkClass = $this->checkboxClass;
             $name = $this->_attributes['name'];
-            $label ='<div id="'.$name.'" class="'.$className.'_'.$name.'">
-                <label>' .
+            $label ='<div id="'.$name.'" class="'.$checkClass.'">
+                <label class="'.$labelClass.'">' .
                     HTML_QuickForm_input::toHtml().' '.$this->_text.
                 '</label>
                 </div>

+ 16 - 16
main/inc/lib/pear/HTML/QuickForm/element.php

@@ -175,7 +175,7 @@ class HTML_QuickForm_element extends HTML_Common
     public function apiVersion()
     {
         return 3.2;
-    } // end func apiVersion
+    }
 
     /**
      * Returns element type
@@ -187,7 +187,7 @@ class HTML_QuickForm_element extends HTML_Common
     public function getType()
     {
         return $this->_type;
-    } // end func getType
+    }
 
     /**
      * Sets the input field name
@@ -199,7 +199,7 @@ class HTML_QuickForm_element extends HTML_Common
      */
     public function setName($name)
     {
-    } //end func setName
+    }
 
     /**
      * Returns the element name
@@ -210,7 +210,7 @@ class HTML_QuickForm_element extends HTML_Common
      */
     public function getName()
     {
-    } //end func getName
+    }
 
     /**
      * Sets the value of the form element
@@ -222,7 +222,7 @@ class HTML_QuickForm_element extends HTML_Common
      */
     public function setValue($value)
     {
-    } // end func setValue
+    }
 
     /**
      * Returns the value of the form element
@@ -234,7 +234,7 @@ class HTML_QuickForm_element extends HTML_Common
     public function getValue()
     {
         return null;
-    } // end func getValue
+    }
 
     /**
      * @return string
@@ -242,10 +242,10 @@ class HTML_QuickForm_element extends HTML_Common
     public function getCleanValue()
     {
         $value = $this->cleanValueFromParameter($this->getValue());
-    // }}}
+
         return $value;
     }
-    // {{{ freeze()
+
     /**
      * @param string $value
      *
@@ -267,7 +267,7 @@ class HTML_QuickForm_element extends HTML_Common
     public function freeze()
     {
         $this->_flagFrozen = true;
-    } //end func freeze
+    }
 
    /**
     * Unfreezes the element so that it becomes editable
@@ -303,7 +303,7 @@ class HTML_QuickForm_element extends HTML_Common
         $value .= $this->_getPersistantData();
 
         return '<span class="freeze">'.$value.'</span>';
-    } //end func getFrozenHtml
+    }
 
    /**
     * Used by getFrozenHtml() to pass the element's value if _persistantFreeze is on
@@ -335,7 +335,7 @@ class HTML_QuickForm_element extends HTML_Common
     public function isFrozen()
     {
         return $this->_flagFrozen;
-    } // end func isFrozen
+    }
 
     /**
      * Sets wether an element value should be kept in an hidden field
@@ -349,7 +349,7 @@ class HTML_QuickForm_element extends HTML_Common
     function setPersistantFreeze($persistant=false)
     {
         $this->_persistantFreeze = $persistant;
-    } //end func setPersistantFreeze
+    }
 
     /**
      * Sets display text for the element
@@ -366,7 +366,7 @@ class HTML_QuickForm_element extends HTML_Common
         if (!empty($labelFor)) {
             $this->_label_for = $labelFor;
         }
-    } //end func setLabel
+    }
 
     /**
      * Returns display text for the element
@@ -378,7 +378,7 @@ class HTML_QuickForm_element extends HTML_Common
     function getLabel()
     {
         return $this->_label;
-    } //end func getLabel
+    }
 
     /**
      * Returns "for" attribute for the element
@@ -389,7 +389,7 @@ class HTML_QuickForm_element extends HTML_Common
     function getLabelFor()
     {
         return $this->_label_for;
-    } //end func getLabelFor
+    }
 
     /**
      * Tries to find the element value from the values array
@@ -430,7 +430,7 @@ class HTML_QuickForm_element extends HTML_Common
         } else {
             return null;
         }
-    } //end func _findValue
+    }
 
     /**
      * Called by HTML_QuickForm whenever form event is made on this element

+ 32 - 32
main/inc/lib/pear/HTML/QuickForm/file.php

@@ -370,11 +370,11 @@ class HTML_QuickForm_file extends HTML_QuickForm_input
             $class = '';
             if (isset($this->_attributes['custom']) && $this->_attributes['custom']) {
                 $class = 'input-file';
-            }
+        }
 
             return $js.$this->_getTabs().
                 '<input class="'.$class.'" '.$this->_getAttrString($this->_attributes).' />';
-        }
+    }
     }
 
     /**
@@ -401,13 +401,13 @@ class HTML_QuickForm_file extends HTML_QuickForm_input
             case FormValidator::LAYOUT_HORIZONTAL:
                 if (isset($attributes['custom']) && $attributes['custom'] == true) {
                     $template = '
-                                <div class="input-file-container">  
-                                    {element}
-                                    <label tabindex="0" {label-for} class="input-file-trigger">
-                                        <i class="fa fa-picture-o fa-lg" aria-hidden="true"></i> {label}
-                                    </label>
-                                </div>
-                                <p class="file-return"></p>
+                        <div class="input-file-container">  
+                            {element}
+                            <label tabindex="0" {label-for} class="input-file-trigger">
+                                <i class="fa fa-picture-o fa-lg" aria-hidden="true"></i> {label}
+                            </label>
+                        </div>
+                        <p class="file-return"></p>                        
                         <script>
                             document.querySelector("html").classList.add(\'js\');
                             var fileInput  = document.querySelector( ".input-file" ),  
@@ -423,38 +423,38 @@ class HTML_QuickForm_file extends HTML_QuickForm_input
                                fileInput.focus();
                                return false;
                             });  
-                            fileInput.addEventListener("change", function(event) {  
+                            fileInput.addEventListener("change", function(event) {
                                 fileName = this.value;
                                 if (this.files[0]) {
                                     fileName = this.files[0].name;
                                 }
                                 the_return.innerHTML = fileName;  
-                            });
+                            });                            
                         </script>
                     ';
                 } else {
                     $template = '
-                <div  id="file_' . $name . '" class="form-group {error_class}">
-                    <label {label-for} class="col-sm-3 control-label" >
-                        <!-- BEGIN required --><span class="form_required">*</span><!-- END required -->
-                        {label}
-                    </label>
-                    <div class="col-sm-7">
-                        {icon}
-                        {element}
-                        <!-- BEGIN label_2 -->
-                            <p class="help-block">{label_2}</p>
-                        <!-- END label_2 -->
-                        <!-- BEGIN error -->
-                            <span class="help-inline help-block">{error}</span>
-                        <!-- END error -->
-                    </div>
-                    <div class="col-sm-2">
-                        <!-- BEGIN label_3 -->
-                            {label_3}
-                        <!-- END label_3 -->
-                    </div>
-                </div>';
+                    <div  id="file_' . $name . '" class="form-group {error_class}">
+                        <label {label-for} class="col-sm-3 control-label" >
+                            <!-- BEGIN required --><span class="form_required">*</span><!-- END required -->
+                            {label}
+                        </label>
+                        <div class="col-sm-7">
+                            {icon}
+                            {element}
+                            <!-- BEGIN label_2 -->
+                                <p class="help-block">{label_2}</p>
+                            <!-- END label_2 -->
+                            <!-- BEGIN error -->
+                                <span class="help-inline help-block">{error}</span>
+                            <!-- END error -->
+                        </div>
+                        <div class="col-sm-2">
+                            <!-- BEGIN label_3 -->
+                                {label_3}
+                            <!-- END label_3 -->
+                        </div>
+                    </div>';
                 }
                 return $template;
                 break;

+ 8 - 8
main/inc/lib/pear/HTML/QuickForm/input.php

@@ -46,7 +46,7 @@ class HTML_QuickForm_input extends HTML_QuickForm_element
     public function __construct($elementName=null, $elementLabel=null, $attributes=null)
     {
         parent::__construct($elementName, $elementLabel, $attributes);
-    } //end constructor
+    }
 
     /**
      * Sets the element type
@@ -60,7 +60,7 @@ class HTML_QuickForm_input extends HTML_QuickForm_element
     {
         $this->_type = $type;
         $this->updateAttributes(array('type'=>$type));
-    } // end func setType
+    }
 
     /**
      * Sets the input field name
@@ -73,7 +73,7 @@ class HTML_QuickForm_input extends HTML_QuickForm_element
     function setName($name)
     {
         $this->updateAttributes(array('name'=>$name));
-    } //end func setName
+    }
 
     /**
      * Returns the element name
@@ -85,7 +85,7 @@ class HTML_QuickForm_input extends HTML_QuickForm_element
     function getName()
     {
         return $this->getAttribute('name');
-    } //end func getName
+    }
 
     /**
      * Sets the value of the form element
@@ -97,8 +97,8 @@ class HTML_QuickForm_input extends HTML_QuickForm_element
      */
     public function setValue($value)
     {
-        $this->updateAttributes(array('value'=>$value));
-    } // end func setValue
+        $this->updateAttributes(array('value' => $value));
+    }
 
     /**
      * Returns the value of the form element
@@ -110,7 +110,7 @@ class HTML_QuickForm_input extends HTML_QuickForm_element
     public function getValue()
     {
         return $this->getAttribute('value');
-    } // end func getValue
+    }
 
     /**
      * Returns the input field in HTML
@@ -126,7 +126,7 @@ class HTML_QuickForm_input extends HTML_QuickForm_element
         } else {
             return $this->_getTabs() . '<input' . $this->_getAttrString($this->_attributes) . ' />';
         }
-    } //end func toHtml
+    }
 
     /**
      * Called by HTML_QuickForm whenever form event is made on this element

+ 2 - 2
main/inc/lib/pear/HTML/QuickForm/link.php

@@ -187,5 +187,5 @@ class HTML_QuickForm_link extends HTML_QuickForm_static
     function getFrozenHtml()
     {
         return;
-    } //end func getFrozenHtml
-} //end class HTML_QuickForm_textarea
+    }
+}

+ 2 - 2
main/inc/lib/pear/HTML/QuickForm/reset.php

@@ -18,7 +18,7 @@ class HTML_QuickForm_reset extends HTML_QuickForm_button
         parent::__construct($elementName, null, $attributes);
         $this->setValue($value);
         $this->setType('reset');
-    } //end constructor
+    }
 
     /**
      * Freeze the element so that only its value is returned
@@ -29,5 +29,5 @@ class HTML_QuickForm_reset extends HTML_QuickForm_button
     function freeze()
     {
         return false;
-    } //end func freeze
+    }
 }

+ 9 - 9
main/inc/lib/pear/HTML/QuickForm/static.php

@@ -53,7 +53,7 @@ class HTML_QuickForm_static extends HTML_QuickForm_element
         $this->_persistantFreeze = false;
         $this->_type = 'static';
         $this->_text = $text;
-    } //end constructor
+    }
 
     /**
      * Sets the element name
@@ -65,7 +65,7 @@ class HTML_QuickForm_static extends HTML_QuickForm_element
     function setName($name)
     {
         $this->updateAttributes(array('name'=>$name));
-    } //end func setName
+    }
 
     /**
      * Returns the element name
@@ -76,7 +76,7 @@ class HTML_QuickForm_static extends HTML_QuickForm_element
     function getName()
     {
         return $this->getAttribute('name');
-    } //end func getName
+    }
 
     /**
      * Sets the text
@@ -88,7 +88,7 @@ class HTML_QuickForm_static extends HTML_QuickForm_element
     function setText($text)
     {
         $this->_text = $text;
-    } // end func setText
+    }
 
     /**
      * Sets the text (uses the standard setValue call to emulate a form element.
@@ -100,7 +100,7 @@ class HTML_QuickForm_static extends HTML_QuickForm_element
     function setValue($text)
     {
         $this->setText($text);
-    } // end func setValue
+    }
 
     /**
      * Returns the static text element in HTML
@@ -111,7 +111,7 @@ class HTML_QuickForm_static extends HTML_QuickForm_element
     public function toHtml()
     {
         return $this->_getTabs() . $this->_text;
-    } //end func toHtml
+    }
 
     /**
      * Returns the value of field without HTML tags
@@ -122,7 +122,7 @@ class HTML_QuickForm_static extends HTML_QuickForm_element
     function getFrozenHtml()
     {
         return $this->toHtml();
-    } //end func getFrozenHtml
+    }
 
     /**
      * Called by HTML_QuickForm whenever form event is made on this element
@@ -152,9 +152,9 @@ class HTML_QuickForm_static extends HTML_QuickForm_element
                 parent::onQuickFormEvent($event, $arg, $caller);
         }
         return true;
-    } // end func onQuickFormEvent
+    }
 
-   /**
+    /**
     * We override this here because we don't want any values from static elements
     */
     function exportValue(&$submitValues, $assoc = false)

+ 3 - 3
main/inc/lib/pear/HTML/QuickForm/submit.php

@@ -49,7 +49,7 @@ class HTML_QuickForm_submit extends HTML_QuickForm_input
         parent::__construct($elementName, null, $attributes);
         $this->setValue($value);
         $this->setType('submit');
-    } //end constructor
+    }
 
     /**
      * Freeze the element so that only its value is returned
@@ -60,7 +60,7 @@ class HTML_QuickForm_submit extends HTML_QuickForm_input
     function freeze()
     {
         return false;
-    } //end func freeze
+    }
 
    /**
     * Only return the value if it is found within $submitValues (i.e. if
@@ -71,4 +71,4 @@ class HTML_QuickForm_submit extends HTML_QuickForm_input
         return $this->_prepareValue($this->_findValue($submitValues), $assoc);
     }
 
-} //end class HTML_QuickForm_submit
+}

+ 1 - 1
main/inc/lib/pear/HTML/QuickForm/text.php

@@ -241,7 +241,7 @@ class HTML_QuickForm_text extends HTML_QuickForm_input
         if ($this->isFrozen()) {
             return $this->getFrozenHtml();
         } else {
-            return '<input ' . $this->_getAttrString($this->_attributes) . ' />';
+            return '<input '.$this->_getAttrString($this->_attributes).' />';
         }
     }
 }

+ 8 - 8
main/inc/lib/pear/HTML/QuickForm/textarea.php

@@ -76,7 +76,7 @@ class HTML_QuickForm_textarea extends HTML_QuickForm_element
     function setName($name)
     {
         $this->updateAttributes(array('name'=>$name));
-    } //end func setName
+    }
 
     /**
      * Returns the element name
@@ -88,7 +88,7 @@ class HTML_QuickForm_textarea extends HTML_QuickForm_element
     function getName()
     {
         return $this->getAttribute('name');
-    } //end func getName
+    }
 
     /**
      * Sets value for textarea element
@@ -101,7 +101,7 @@ class HTML_QuickForm_textarea extends HTML_QuickForm_element
     function setValue($value)
     {
         $this->_value = $value;
-    } //end func setValue
+    }
 
     /**
      * Returns the value of the form element
@@ -113,7 +113,7 @@ class HTML_QuickForm_textarea extends HTML_QuickForm_element
     function getValue()
     {
         return $this->_value;
-    } // end func getValue
+    }
 
     /**
      * Sets wrap type for textarea element
@@ -126,7 +126,7 @@ class HTML_QuickForm_textarea extends HTML_QuickForm_element
     function setWrap($wrap)
     {
         $this->updateAttributes(array('wrap' => $wrap));
-    } //end func setWrap
+    }
 
     /**
      * Sets height in rows for textarea element
@@ -139,7 +139,7 @@ class HTML_QuickForm_textarea extends HTML_QuickForm_element
     function setRows($rows)
     {
         $this->updateAttributes(array('rows' => $rows));
-    } //end func setRows
+    }
 
     /**
      * Sets width in cols for textarea element
@@ -152,7 +152,7 @@ class HTML_QuickForm_textarea extends HTML_QuickForm_element
     function setCols($cols)
     {
         $this->updateAttributes(array('cols' => $cols));
-    } //end func setCols
+    }
 
     /**
      * Returns the textarea element in HTML
@@ -171,7 +171,7 @@ class HTML_QuickForm_textarea extends HTML_QuickForm_element
                    // because we wrap the form later we don't want the text indented
                    // Modified by Ivan Tcholakov, 16-MAR-2010.
                    //preg_replace("/(\r\n|\n|\r)/", '&#010;', htmlspecialchars($this->_value)) .
-                   preg_replace("/(\r\n|\n|\r)/", '&#010;', @htmlspecialchars($this->_value, ENT_COMPAT, HTML_Common::charset())) .
+                   preg_replace("/(\r\n|\n|\r)/", '&#010;', $this->getCleanValue()) .
                    //
                    '</textarea>';
         }

+ 4 - 2
main/inc/lib/plugin.class.php

@@ -322,10 +322,12 @@ class Plugin
         $settings = $this->get_settings();
         foreach ($settings as $setting) {
             if ($setting['variable'] == $this->get_name().'_'.$name) {
+                $unserialized = UnserializeApi::unserialize('not_allowed_classes', $setting['selected_value'], true);
+
                 if (!empty($setting['selected_value']) &&
-                    @unserialize($setting['selected_value']) !== false
+                    false !== $unserialized
                 ) {
-                    $setting['selected_value'] = unserialize($setting['selected_value']);
+                    $setting['selected_value'] = $unserialized;
                 }
 
                 return $setting['selected_value'];

+ 3 - 2
main/inc/lib/plugin.lib.php

@@ -437,8 +437,9 @@ class AppPlugin
             $settings_filtered = [];
             foreach ($plugin_settings as $item) {
                 if (!empty($item['selected_value'])) {
-                    if (@unserialize($item['selected_value']) !== false) {
-                        $item['selected_value'] = unserialize($item['selected_value']);
+                    $unserialized = UnserializeApi::unserialize('not_allowed_classes', $item['selected_value'], true);
+                    if (false !== $unserialized) {
+                        $item['selected_value'] = $unserialized;
                     }
                 }
                 $settings_filtered[$item['variable']] = $item['selected_value'];

+ 286 - 226
main/inc/lib/sessionmanager.lib.php

@@ -117,30 +117,31 @@ class SessionManager
      *
      * @author Carlos Vargas <carlos.vargas@beeznest.com>, from existing code
      *
-     * @param string $name
-     * @param string $startDate                    (YYYY-MM-DD hh:mm:ss)
-     * @param string $endDate                      (YYYY-MM-DD hh:mm:ss)
-     * @param string $displayStartDate             (YYYY-MM-DD hh:mm:ss)
-     * @param string $displayEndDate               (YYYY-MM-DD hh:mm:ss)
-     * @param string $coachStartDate               (YYYY-MM-DD hh:mm:ss)
-     * @param string $coachEndDate                 (YYYY-MM-DD hh:mm:ss)
-     * @param int    $sessionCategoryId            ID of the session category in which this session is registered
-     * @param mixed  $coachId                      If int, this is the session coach id,
-     *                                             if string, the coach ID will be looked for from the user table
-     * @param int    $visibility                   Visibility after end date (0 = read-only, 1 = invisible, 2 = accessible)
-     * @param bool   $fixSessionNameIfExists
-     * @param string $duration
-     * @param string $description                  Optional. The session description
-     * @param int    $showDescription              Optional. Whether show the session description
-     * @param array  $extraFields
-     * @param int    $sessionAdminId               Optional. If this sessions was created by a session admin, assign it to him
-     * @param bool   $sendSubscriptionNotification Optional.
-     *                                             Whether send a mail notification to users being subscribed
-     *
-     * @todo use an array to replace all this parameters or use the model.lib.php ...
+     * @param string   $name
+     * @param string   $startDate                    (YYYY-MM-DD hh:mm:ss)
+     * @param string   $endDate                      (YYYY-MM-DD hh:mm:ss)
+     * @param string   $displayStartDate             (YYYY-MM-DD hh:mm:ss)
+     * @param string   $displayEndDate               (YYYY-MM-DD hh:mm:ss)
+     * @param string   $coachStartDate               (YYYY-MM-DD hh:mm:ss)
+     * @param string   $coachEndDate                 (YYYY-MM-DD hh:mm:ss)
+     * @param mixed    $coachId                      If int, this is the session coach id,
+     *                                               if string, the coach ID will be looked for from the user table
+     * @param int      $sessionCategoryId            ID of the session category in which this session is registered
+     * @param int      $visibility                   Visibility after end date (0 = read-only, 1 = invisible, 2 = accessible)
+     * @param bool     $fixSessionNameIfExists
+     * @param string   $duration
+     * @param string   $description                  Optional. The session description
+     * @param int      $showDescription              Optional. Whether show the session description
+     * @param array    $extraFields
+     * @param int      $sessionAdminId               Optional. If this sessions was created by a session admin, assign it to him
+     * @param bool     $sendSubscriptionNotification Optional.
+     *                                               Whether send a mail notification to users being subscribed
+     * @param int|null $accessUrlId                  Optional.
      *
      * @return mixed Session ID on success, error message otherwise
-     * */
+     *
+     * @todo   use an array to replace all this parameters or use the model.lib.php ...
+     */
     public static function create_session(
         $name,
         $startDate,
@@ -158,23 +159,22 @@ class SessionManager
         $showDescription = 0,
         $extraFields = [],
         $sessionAdminId = 0,
-        $sendSubscriptionNotification = false
+        $sendSubscriptionNotification = false,
+        $accessUrlId = null
     ) {
         global $_configuration;
 
         // Check portal limits
-        $access_url_id = 1;
+        $accessUrlId = api_is_multiple_url_enabled()
+            ? (empty($accessUrlId) ? api_get_current_access_url_id() : (int) $accessUrlId)
+            : 1;
 
-        if (api_get_multiple_access_url()) {
-            $access_url_id = api_get_current_access_url_id();
-        }
-
-        if (is_array($_configuration[$access_url_id]) &&
-            isset($_configuration[$access_url_id]['hosting_limit_sessions']) &&
-            $_configuration[$access_url_id]['hosting_limit_sessions'] > 0
+        if (is_array($_configuration[$accessUrlId]) &&
+            isset($_configuration[$accessUrlId]['hosting_limit_sessions']) &&
+            $_configuration[$accessUrlId]['hosting_limit_sessions'] > 0
         ) {
             $num = self::count_sessions();
-            if ($num >= $_configuration[$access_url_id]['hosting_limit_sessions']) {
+            if ($num >= $_configuration[$accessUrlId]['hosting_limit_sessions']) {
                 api_warn_hosting_contact('hosting_limit_sessions');
 
                 return get_lang('PortalSessionsLimitReached');
@@ -306,9 +306,8 @@ class SessionManager
                       api_mail_html($complete_name, $user_info['email'], $subject, $message);
                      *
                      */
-                    //Adding to the correct URL
-                    $access_url_id = api_get_current_access_url_id();
-                    UrlManager::add_session_to_url($session_id, $access_url_id);
+                    // Adding to the correct URL
+                    UrlManager::add_session_to_url($session_id, $accessUrlId);
 
                     // add event to system log
                     $user_id = api_get_user_id();
@@ -1841,6 +1840,9 @@ class SessionManager
         $sql = "UPDATE $ticket SET session_id = NULL WHERE session_id IN ($id_checked)";
         Database::query($sql);
 
+        $app_plugin = new AppPlugin();
+        $app_plugin->performActionsWhenDeletingItem('session', $id_checked);
+
         $sql = "DELETE FROM $tbl_session WHERE id IN ($id_checked)";
         Database::query($sql);
 
@@ -1894,6 +1896,7 @@ class SessionManager
      * @param array $userList
      * @param int   $session_visibility
      * @param bool  $empty_users
+     * @param bool  $registerUsersToAllCourses
      *
      * @return bool
      */
@@ -1901,7 +1904,8 @@ class SessionManager
         $sessionId,
         $userList,
         $session_visibility = SESSION_VISIBLE_READ_ONLY,
-        $empty_users = true
+        $empty_users = true,
+        $registerUsersToAllCourses = true
     ) {
         if ($sessionId != strval(intval($sessionId))) {
             return false;
@@ -1982,15 +1986,9 @@ class SessionManager
                     false
                 );
                 // Variables for default template
-                $tplContent->assign(
-                    'complete_name',
-                    stripslashes($user_info['complete_name'])
-                );
+                $tplContent->assign('complete_name', stripslashes($user_info['complete_name']));
                 $tplContent->assign('session_name', $session->getName());
-                $tplContent->assign(
-                    'session_coach',
-                    $session->getGeneralCoach()->getCompleteName()
-                );
+                $tplContent->assign('session_coach', $session->getGeneralCoach()->getCompleteName());
                 $layoutContent = $tplContent->get_template(
                     'mail/content_subscription_to_session_confirmation.tpl'
                 );
@@ -2010,96 +2008,78 @@ class SessionManager
             }
         }
 
-        foreach ($course_list as $courseId) {
-            // for each course in the session
-            $nbr_users = 0;
-            $courseId = (int) $courseId;
+        if ($registerUsersToAllCourses) {
+            foreach ($course_list as $courseId) {
+                // for each course in the session
+                $nbr_users = 0;
+                $courseId = (int) $courseId;
 
-            $sql = "SELECT DISTINCT user_id
-                    FROM $tbl_session_rel_course_rel_user
-                    WHERE
-                        session_id = $sessionId AND
-                        c_id = $courseId AND
-                        status = 0
-                    ";
-            $result = Database::query($sql);
-            $existingUsers = [];
-            while ($row = Database::fetch_array($result)) {
-                $existingUsers[] = $row['user_id'];
-            }
-
-            // Delete existing users
-            if ($empty_users) {
-                foreach ($existingUsers as $existing_user) {
-                    if (!in_array($existing_user, $userList)) {
-                        $sql = "DELETE FROM $tbl_session_rel_course_rel_user
-                                WHERE
-                                    session_id = $sessionId AND
-                                    c_id = $courseId AND
-                                    user_id = $existing_user AND
-                                    status = 0 ";
-                        $result = Database::query($sql);
-
-                        Event::addEvent(
-                            LOG_SESSION_DELETE_USER_COURSE,
-                            LOG_USER_ID,
-                            $existing_user,
-                            api_get_utc_datetime(),
-                            api_get_user_id(),
-                            $courseId,
-                            $sessionId
-                        );
+                $sql = "SELECT DISTINCT user_id
+                        FROM $tbl_session_rel_course_rel_user
+                        WHERE
+                            session_id = $sessionId AND
+                            c_id = $courseId AND
+                            status = 0
+                        ";
+                $result = Database::query($sql);
+                $existingUsers = [];
+                while ($row = Database::fetch_array($result)) {
+                    $existingUsers[] = $row['user_id'];
+                }
 
-                        if (Database::affected_rows($result)) {
-                            $nbr_users--;
+                // Delete existing users
+                if ($empty_users) {
+                    foreach ($existingUsers as $existing_user) {
+                        if (!in_array($existing_user, $userList)) {
+                            self::unSubscribeUserFromCourseSession($existing_user, $courseId, $sessionId);
                         }
                     }
                 }
-            }
-
-            // Replace with this new function
-            // insert new users into session_rel_course_rel_user and ignore if they already exist
-            foreach ($userList as $enreg_user) {
-                if (!in_array($enreg_user, $existingUsers)) {
-                    $status = self::get_user_status_in_course_session(
-                        $enreg_user,
-                        $courseId,
-                        $sessionId
-                    );
-
-                    // Avoid duplicate entries.
-                    if ($status === false || ($status !== false && $status != 0)) {
-                        $enreg_user = (int) $enreg_user;
-                        $sql = "INSERT IGNORE INTO $tbl_session_rel_course_rel_user (session_id, c_id, user_id, visibility, status)
-                                VALUES($sessionId, $courseId, $enreg_user, $session_visibility, 0)";
-                        $result = Database::query($sql);
-                        if (Database::affected_rows($result)) {
-                            $nbr_users++;
-                        }
 
-                        Event::addEvent(
-                            LOG_SESSION_ADD_USER_COURSE,
-                            LOG_USER_ID,
+                // Replace with this new function
+                // insert new users into session_rel_course_rel_user and ignore if they already exist
+                foreach ($userList as $enreg_user) {
+                    if (!in_array($enreg_user, $existingUsers)) {
+                        $status = self::get_user_status_in_course_session(
                             $enreg_user,
-                            api_get_utc_datetime(),
-                            api_get_user_id(),
                             $courseId,
                             $sessionId
                         );
+
+                        // Avoid duplicate entries.
+                        if ($status === false || ($status !== false && $status != 0)) {
+                            $enreg_user = (int) $enreg_user;
+                            $sql = "INSERT IGNORE INTO $tbl_session_rel_course_rel_user (session_id, c_id, user_id, visibility, status)
+                                    VALUES($sessionId, $courseId, $enreg_user, $session_visibility, 0)";
+                            $result = Database::query($sql);
+                            if (Database::affected_rows($result)) {
+                                $nbr_users++;
+                            }
+
+                            Event::addEvent(
+                                LOG_SESSION_ADD_USER_COURSE,
+                                LOG_USER_ID,
+                                $enreg_user,
+                                api_get_utc_datetime(),
+                                api_get_user_id(),
+                                $courseId,
+                                $sessionId
+                            );
+                        }
                     }
                 }
-            }
 
-            // Count users in this session-course relation
-            $sql = "SELECT COUNT(user_id) as nbUsers
-                    FROM $tbl_session_rel_course_rel_user
-                    WHERE session_id = $sessionId AND c_id = $courseId AND status<>2";
-            $rs = Database::query($sql);
-            list($nbr_users) = Database::fetch_array($rs);
-            // update the session-course relation to add the users total
-            $sql = "UPDATE $tbl_session_rel_course SET nbr_users = $nbr_users
-                    WHERE session_id = $sessionId AND c_id = $courseId";
-            Database::query($sql);
+                // Count users in this session-course relation
+                $sql = "SELECT COUNT(user_id) as nbUsers
+                        FROM $tbl_session_rel_course_rel_user
+                        WHERE session_id = $sessionId AND c_id = $courseId AND status<>2";
+                $rs = Database::query($sql);
+                list($nbr_users) = Database::fetch_array($rs);
+                // update the session-course relation to add the users total
+                $sql = "UPDATE $tbl_session_rel_course SET nbr_users = $nbr_users
+                        WHERE session_id = $sessionId AND c_id = $courseId";
+                Database::query($sql);
+            }
         }
 
         // Delete users from the session
@@ -2146,17 +2126,10 @@ class SessionManager
         }
 
         // update number of users in the session
-        $nbr_users = count($userList);
-        if ($empty_users) {
-            // update number of users in the session
-            $sql = "UPDATE $tbl_session SET nbr_users= $nbr_users
-                    WHERE id = $sessionId ";
-            Database::query($sql);
-        } else {
-            $sql = "UPDATE $tbl_session SET nbr_users = nbr_users + $nbr_users
-                    WHERE id = $sessionId";
-            Database::query($sql);
-        }
+        $sql = "UPDATE $tbl_session 
+                SET nbr_users = (SELECT count(user_id) FROM $tbl_session_rel_user WHERE session_id = $sessionId)
+                WHERE id = $sessionId";
+        Database::query($sql);
     }
 
     /**
@@ -2278,7 +2251,7 @@ class SessionManager
     ) {
         $table = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
         $tableSessionCourse = Database::get_main_table(TABLE_MAIN_SESSION_COURSE);
-        $sessionId = intval($sessionId);
+        $sessionId = (int) $sessionId;
 
         if (empty($sessionId) || empty($userList) || empty($courseInfo)) {
             return false;
@@ -2288,12 +2261,12 @@ class SessionManager
 
         $statusCondition = null;
         if (isset($status) && !is_null($status)) {
-            $status = intval($status);
+            $status = (int) $status;
             $statusCondition = " AND status = $status";
         }
 
         foreach ($userList as $userId) {
-            $userId = intval($userId);
+            $userId = (int) $userId;
             $sql = "DELETE FROM $table
                     WHERE
                         session_id = $sessionId AND
@@ -2302,6 +2275,16 @@ class SessionManager
                         $statusCondition
                     ";
             Database::query($sql);
+
+            Event::addEvent(
+                LOG_SESSION_DELETE_USER_COURSE,
+                LOG_USER_ID,
+                $userId,
+                api_get_utc_datetime(),
+                api_get_user_id(),
+                $courseId,
+                $sessionId
+            );
         }
 
         if ($updateTotal) {
@@ -2352,11 +2335,11 @@ class SessionManager
             return false;
         }
 
-        $session_id = intval($session_id);
+        $session_id = (int) $session_id;
+        $session_visibility = (int) $session_visibility;
         $course_code = Database::escape_string($course_code);
         $courseInfo = api_get_course_info($course_code);
         $courseId = $courseInfo['real_id'];
-        $session_visibility = intval($session_visibility);
 
         if ($removeUsersNotInList) {
             $currentUsers = self::getUsersByCourseSession($session_id, $courseInfo, 0);
@@ -2380,7 +2363,7 @@ class SessionManager
 
         $nbr_users = 0;
         foreach ($user_list as $enreg_user) {
-            $enreg_user = intval($enreg_user);
+            $enreg_user = (int) $enreg_user;
             // Checking if user exists in session - course - user table.
             $sql = "SELECT count(user_id) as count
                     FROM $tbl_session_rel_course_rel_user
@@ -2455,8 +2438,6 @@ class SessionManager
         $session_id = (int) $session_id;
         $user_id = (int) $user_id;
 
-        $tbl_session_rel_course_rel_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
-        $tbl_session_rel_course = Database::get_main_table(TABLE_MAIN_SESSION_COURSE);
         $tbl_session_rel_user = Database::get_main_table(TABLE_MAIN_SESSION_USER);
         $tbl_session = Database::get_main_table(TABLE_MAIN_SESSION);
 
@@ -2464,7 +2445,7 @@ class SessionManager
                 WHERE
                     session_id = $session_id AND
                     user_id = $user_id AND
-                    relation_type <> ".SESSION_RELATION_TYPE_RRHH."";
+                    relation_type <> ".SESSION_RELATION_TYPE_RRHH;
         $result = Database::query($sql);
         $return = Database::affected_rows($result);
 
@@ -2474,39 +2455,65 @@ class SessionManager
                 WHERE id = $session_id ";
         Database::query($sql);
 
+        Event::addEvent(
+            LOG_SESSION_DELETE_USER,
+            LOG_USER_ID,
+            $user_id,
+            api_get_utc_datetime(),
+            api_get_user_id(),
+            null,
+            $session_id
+        );
+
         // Get the list of courses related to this session
         $course_list = self::get_course_list_by_session_id($session_id);
         if (!empty($course_list)) {
             foreach ($course_list as $course) {
-                $courseId = $course['id'];
-                // Delete user from course
-                $sql = "DELETE FROM $tbl_session_rel_course_rel_user
-                        WHERE session_id = $session_id AND c_id = $courseId AND user_id = $user_id";
-                $result = Database::query($sql);
-
-                Event::addEvent(
-                    LOG_SESSION_DELETE_USER_COURSE,
-                    LOG_USER_ID,
-                    $user_id,
-                    api_get_utc_datetime(),
-                    api_get_user_id(),
-                    $courseId,
-                    $session_id
-                );
-
-                if (Database::affected_rows($result)) {
-                    // Update number of users in this relation
-                    $sql = "UPDATE $tbl_session_rel_course SET 
-                            nbr_users = nbr_users - 1
-                            WHERE session_id = $session_id AND c_id = $courseId";
-                    Database::query($sql);
-                }
+                self::unSubscribeUserFromCourseSession($user_id, $course['id'], $session_id);
             }
         }
 
         return true;
     }
 
+    /**
+     * @param int $user_id
+     * @param int $courseId
+     * @param int $session_id
+     */
+    public static function unSubscribeUserFromCourseSession($user_id, $courseId, $session_id)
+    {
+        $user_id = (int) $user_id;
+        $courseId = (int) $courseId;
+        $session_id = (int) $session_id;
+
+        $tbl_session_rel_course_rel_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
+        $tbl_session_rel_course = Database::get_main_table(TABLE_MAIN_SESSION_COURSE);
+
+        // Delete user from course
+        $sql = "DELETE FROM $tbl_session_rel_course_rel_user
+                WHERE session_id = $session_id AND c_id = $courseId AND user_id = $user_id";
+        $result = Database::query($sql);
+
+        if (Database::affected_rows($result)) {
+            // Update number of users in this relation
+            $sql = "UPDATE $tbl_session_rel_course SET 
+                    nbr_users = nbr_users - 1
+                    WHERE session_id = $session_id AND c_id = $courseId";
+            Database::query($sql);
+        }
+
+        Event::addEvent(
+            LOG_SESSION_DELETE_USER_COURSE,
+            LOG_USER_ID,
+            $user_id,
+            api_get_utc_datetime(),
+            api_get_user_id(),
+            $courseId,
+            $session_id
+        );
+    }
+
     /**
      * Subscribes courses to the given session and optionally (default)
      * unsubscribe previous users.
@@ -3440,7 +3447,7 @@ class SessionManager
         $noCoach = false
     ) {
         // Definition of variables
-        $userId = intval($userId);
+        $userId = (int) $userId;
 
         $sessionId = !empty($sessionId) ? intval($sessionId) : api_get_session_id();
         $courseId = !empty($courseId) ? intval($courseId) : api_get_course_id();
@@ -4205,7 +4212,9 @@ class SessionManager
         if (empty($id)) {
             return [];
         }
-        $id = intval($id);
+        $id = (int) $id;
+        $urlId = empty($urlId) ? api_get_current_access_url_id() : (int) $urlId;
+
         $tbl_user = Database::get_main_table(TABLE_MAIN_USER);
         $tbl_session_rel_user = Database::get_main_table(TABLE_MAIN_SESSION_USER);
         $table_access_url_user = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
@@ -4228,8 +4237,6 @@ class SessionManager
                 ON (au.user_id = u.user_id)
                 ";
 
-        $urlId = empty($urlId) ? api_get_current_access_url_id() : (int) $urlId;
-
         if (is_numeric($status)) {
             $status = (int) $status;
             $sql .= " WHERE su.relation_type = $status AND (au.access_url_id = $urlId OR au.access_url_id is null)";
@@ -6887,8 +6894,8 @@ SQL;
      */
     public static function getUserSession($userId, $sessionId)
     {
-        $userId = intval($userId);
-        $sessionId = intval($sessionId);
+        $userId = (int) $userId;
+        $sessionId = (int) $sessionId;
 
         if (empty($userId) || empty($sessionId)) {
             return false;
@@ -7172,11 +7179,12 @@ SQL;
     /**
      * Get the count of user courses in session.
      *
-     * @param int $sessionId The session id
+     * @param int $sessionId
+     * @param int $courseId
      *
      * @return array
      */
-    public static function getTotalUserCoursesInSession($sessionId)
+    public static function getTotalUserCoursesInSession($sessionId, $courseId = 0)
     {
         $tableUser = Database::get_main_table(TABLE_MAIN_USER);
         $table = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
@@ -7185,6 +7193,12 @@ SQL;
             return [];
         }
 
+        $courseCondition = '';
+        if (!empty($courseId)) {
+            $courseId = (int) $courseId;
+            $courseCondition = "  c_id = $courseId AND ";
+        }
+
         $sql = "SELECT 
                     COUNT(u.id) as count, 
                     u.id, 
@@ -7193,7 +7207,9 @@ SQL;
                 FROM $table scu
                 INNER JOIN $tableUser u 
                 ON scu.user_id = u.id
-                WHERE scu.session_id = ".intval($sessionId)."
+                WHERE 
+                  $courseCondition
+                  scu.session_id = ".intval($sessionId)."
                 GROUP BY u.id";
 
         $result = Database::query($sql);
@@ -7837,7 +7853,7 @@ SQL;
                 $sql = "SELECT user_id, lastname, firstname, username
                         FROM $tbl_user
                         WHERE status = '1' ".
-                    $orderClause;
+                        $orderClause;
 
                 if (api_is_multiple_url_enabled()) {
                     $userRelAccessUrlTable = Database::get_main_table(
@@ -9160,6 +9176,102 @@ SQL;
         return $courseIds;
     }
 
+    /**
+     * @param int             $userId
+     * @param int             $sessionId
+     * @param ExtraFieldValue $extraFieldValue
+     * @param string          $collapsableLink
+     *
+     * @return array
+     */
+    public static function getCollapsableData($userId, $sessionId, $extraFieldValue, $collapsableLink)
+    {
+        $collapsed = 0;
+
+        // Get default collapsed value in extra field
+        $value = $extraFieldValue->get_values_by_handler_and_field_variable($sessionId, 'collapsed');
+        if (!empty($value) && isset($value['value'])) {
+            $collapsed = $value['value'];
+        }
+
+        $userRelSession = Sessionmanager::getUserSession($userId, $sessionId);
+        if ($userRelSession) {
+            if (isset($userRelSession['collapsed']) && $userRelSession['collapsed'] != '') {
+                $collapsed = $userRelSession['collapsed'];
+            }
+        } else {
+            return ['collapsed' => $collapsed, 'collapsable_link' => '&nbsp;'];
+        }
+
+        $link = $collapsableLink.'&session_id='.$sessionId.'&value=1';
+        $image = '<i class="fa fa-folder-open"></i>';
+        if ($collapsed == 1) {
+            $link = $collapsableLink.'&session_id='.$sessionId.'&value=0';
+            $image = '<i class="fa fa-folder"></i>';
+        }
+
+        $link = Display::url(
+            $image,
+            $link
+        );
+
+        return ['collapsed' => $collapsed, 'collapsable_link' => $link];
+    }
+
+    /**
+     * Converts "start date" and "end date" to "From start date to end date" string.
+     *
+     * @param string $startDate
+     * @param string $endDate
+     * @param bool   $showTime
+     * @param bool   $dateHuman
+     *
+     * @return string
+     */
+    public static function convertSessionDateToString($startDate, $endDate, $showTime, $dateHuman)
+    {
+        // api_get_local_time returns empty if date is invalid like 0000-00-00 00:00:00
+        $startDateToLocal = api_get_local_time(
+            $startDate,
+            null,
+            null,
+            true,
+            $showTime,
+            $dateHuman
+        );
+        $endDateToLocal = api_get_local_time(
+            $endDate,
+            null,
+            null,
+            true,
+            $showTime,
+            $dateHuman
+        );
+
+        $format = $showTime ? DATE_TIME_FORMAT_LONG_24H : DATE_FORMAT_LONG_NO_DAY;
+
+        $result = '';
+        if (!empty($startDateToLocal) && !empty($endDateToLocal)) {
+            $result = sprintf(
+                get_lang('FromDateXToDateY'),
+                api_format_date($startDateToLocal, $format),
+                api_format_date($endDateToLocal, $format)
+            );
+        } else {
+            if (!empty($startDateToLocal)) {
+                $result = get_lang('From').' '.api_format_date($startDateToLocal, $format);
+            }
+            if (!empty($endDateToLocal)) {
+                $result = get_lang('Until').' '.api_format_date($endDateToLocal, $format);
+            }
+        }
+        if (empty($result)) {
+            $result = get_lang('NoTimeLimits');
+        }
+
+        return $result;
+    }
+
     /**
      * @param int $id
      *
@@ -9226,58 +9338,6 @@ SQL;
         }
     }
 
-    /**
-     * Converts "start date" and "end date" to "From start date to end date" string.
-     *
-     * @param string $startDate
-     * @param string $endDate
-     * @param bool   $showTime
-     * @param bool   $dateHuman
-     *
-     * @return string
-     */
-    private static function convertSessionDateToString($startDate, $endDate, $showTime, $dateHuman)
-    {
-        // api_get_local_time returns empty if date is invalid like 0000-00-00 00:00:00
-        $startDateToLocal = api_get_local_time(
-            $startDate,
-            null,
-            null,
-            true,
-            $showTime,
-            $dateHuman
-        );
-        $endDateToLocal = api_get_local_time(
-            $endDate,
-            null,
-            null,
-            true,
-            $showTime,
-            $dateHuman
-        );
-
-        $result = '';
-        if (!empty($startDateToLocal) && !empty($endDateToLocal)) {
-            $result = sprintf(
-                get_lang('FromDateXToDateY'),
-                api_format_date($startDateToLocal, DATE_TIME_FORMAT_LONG_24H),
-                api_format_date($endDateToLocal, DATE_TIME_FORMAT_LONG_24H)
-            );
-        } else {
-            if (!empty($startDateToLocal)) {
-                $result = get_lang('From').' '.api_format_date($startDateToLocal, DATE_TIME_FORMAT_LONG_24H);
-            }
-            if (!empty($endDateToLocal)) {
-                $result = get_lang('Until').' '.api_format_date($endDateToLocal, DATE_TIME_FORMAT_LONG_24H);
-            }
-        }
-        if (empty($result)) {
-            $result = get_lang('NoTimeLimits');
-        }
-
-        return $result;
-    }
-
     /**
      * @param array $listA
      * @param array $listB

+ 26 - 33
main/inc/lib/skill.lib.php

@@ -597,25 +597,29 @@ class SkillRelUser extends Model
      *
      * @param int $userId    The user id
      * @param int $skillId   The skill id
-     * @param int $courseId  The course id
+     * @param int $courseId  Optional. The course id
      * @param int $sessionId Optional. The session id
      *
      * @return array The relation data. Otherwise return false
      */
-    public function getByUserAndSkill($userId, $skillId, $courseId, $sessionId = 0)
+    public function getByUserAndSkill($userId, $skillId, $courseId = 0, $sessionId = 0)
     {
-        $where = [
-            'user_id = ? AND skill_id = ? AND course_id = ? AND session_id = ?' => [
-                intval($userId),
-                intval($skillId),
-                intval($courseId),
-                $sessionId ? intval($sessionId) : null,
-            ],
-        ];
+        $sql = "SELECT * FROM {$this->table} WHERE user_id = %d AND skill_id = %d ";
 
-        return Database::select('*', $this->table, [
-            'where' => $where,
-        ], 'first');
+        if ($courseId > 0) {
+            $sql .= "AND course_id = %d ".api_get_session_condition($sessionId, true);
+        }
+
+        $sql = sprintf(
+            $sql,
+            $userId,
+            $skillId,
+            $courseId
+        );
+
+        $result = Database::query($sql);
+
+        return Database::fetch_assoc($result);
     }
 
     /**
@@ -834,10 +838,10 @@ class Skill extends Model
 
             $item = '';
             if ($showBadge) {
-                $item = $skill[$imageSize];
+                $item = '<div class="item">'.$skill[$imageSize].'</div>';
             }
 
-            $name = $skill['name'];
+            $name = '<div class="caption">'.$skill['name'].'</div>';
             if (!empty($skill['short_code'])) {
                 $name = $skill['short_code'];
             }
@@ -1300,7 +1304,7 @@ class Skill extends Model
             foreach ($skills as $skill) {
                 if ($getSkillData) {
                     $skillData = $this->get($skill['id']);
-                    $skillData['url'] = api_get_path(WEB_PATH).'badge/'.$skill['issue'].'/user/'.$userId;
+                    $skillData['url'] = api_get_path(WEB_PATH).'badge/'.$skill['id'].'/user/'.$userId;
                     $skillList[$skill['id']] = array_merge($skill, $skillData);
                 } else {
                     $skillList[$skill['id']] = $skill['id'];
@@ -1383,7 +1387,6 @@ class Skill extends Model
     public function getUserSkillsTable($userId, $courseId = 0, $sessionId = 0, $addTitle = true)
     {
         $skills = $this->getUserSkills($userId, true, $courseId, $sessionId);
-
         $courseTempList = [];
         $tableRows = [];
         $skillParents = [];
@@ -1408,11 +1411,11 @@ class Skill extends Model
                     $courseTempList[$courseId] = $courseInfo;
                 }
             }
-
             $tableRow = [
-                'skill_badge' => $resultData['img_mini'],
+                'skill_badge' => $resultData['img_small'],
                 'skill_name' => self::translateName($resultData['name']),
                 'short_code' => $resultData['short_code'],
+                'skill_url' => $resultData['url'],
                 'achieved_at' => api_get_local_time($resultData['acquired_skill_at']),
                 'course_image' => '',
                 'course_name' => '',
@@ -1434,15 +1437,9 @@ class Skill extends Model
         }
 
         if ($addTitle) {
-            $tableResult .= '
-                    <table class="table">
-                        <thead>
-                            <tr>
-                                <th>'.get_lang('AchievedSkills').'</th>
-                            </tr>
-                        </thead>
-                        <tbody>
-                        <tr><td>';
+            $tableResult .= '<div class="header-title">'.get_lang('AchievedSkills').'</div>
+                    <div class="skills-badges">
+                   ';
         }
 
         if (!empty($skillParents)) {
@@ -1542,11 +1539,7 @@ class Skill extends Model
         }
 
         if ($addTitle) {
-            $tableResult .= '</td>
-                            </tr>
-                        </tbody>
-                    </table>
-                ';
+            $tableResult .= '</div>';
         }
         $tableResult .= '</div>';
 

+ 104 - 104
main/inc/lib/social.lib.php

@@ -953,6 +953,15 @@ class SocialManager extends UserManager
         $portfolioIcon = Display::return_icon('wiki_task.png', get_lang('Portfolio'));
         $personalDataIcon = Display::return_icon('database.png', get_lang('PersonalDataReport'));
 
+        $forumCourseId = api_get_configuration_value('global_forums_course_id');
+        $groupUrl = api_get_path(WEB_CODE_PATH).'social/groups.php';
+        if (!empty($forumCourseId)) {
+            $courseInfo = api_get_course_info_by_id($forumCourseId);
+            if (!empty($courseInfo)) {
+                $groupUrl = api_get_path(WEB_CODE_PATH).'forum/index.php?cidReq='.$courseInfo['code'];
+            }
+        }
+
         $html = '';
         $active = null;
         if (!in_array(
@@ -960,7 +969,7 @@ class SocialManager extends UserManager
             ['shared_profile', 'groups', 'group_edit', 'member_list', 'waiting_list', 'invite_friends']
         )) {
             $links = '<ul class="nav nav-pills nav-stacked">';
-            $active = $show == 'home' ? 'active' : null;
+            $active = $show === 'home' ? 'active' : null;
             $links .= '
                 <li class="home-icon '.$active.'">
                     <a href="'.api_get_path(WEB_CODE_PATH).'social/home.php">
@@ -975,7 +984,7 @@ class SocialManager extends UserManager
                     </a>
                 </li>';
 
-            //Invitations
+            // Invitations
             $active = $show == 'invitations' ? 'active' : null;
             $links .= '
                 <li class="invitations-icon '.$active.'">
@@ -984,7 +993,7 @@ class SocialManager extends UserManager
                     </a>
                 </li>';
 
-            //Shared profile and groups
+            // Shared profile and groups
             $active = $show == 'shared_profile' ? 'active' : null;
             $links .= '
                 <li class="shared-profile-icon'.$active.'">
@@ -999,15 +1008,15 @@ class SocialManager extends UserManager
                         '.$friendsIcon.' '.get_lang('Friends').'
                     </a>
                 </li>';
-            $active = $show == 'browse_groups' ? 'active' : null;
+            $active = $show === 'browse_groups' ? 'active' : null;
             $links .= '
                 <li class="browse-groups-icon '.$active.'">
-                    <a href="'.api_get_path(WEB_CODE_PATH).'social/groups.php">
+                    <a href="'.$groupUrl.'">
                         '.$groupsIcon.' '.get_lang('SocialGroups').'
                     </a>
                 </li>';
 
-            //Search users
+            // Search users
             $active = $show == 'search' ? 'active' : null;
             $links .= '
                 <li class="search-icon '.$active.'">
@@ -1016,7 +1025,7 @@ class SocialManager extends UserManager
                     </a>
                 </li>';
 
-            //My files
+            // My files
             $active = $show == 'myfiles' ? 'active' : null;
 
             $myFiles = '
@@ -1070,7 +1079,7 @@ class SocialManager extends UserManager
             );
         }
 
-        if ($show == 'shared_profile') {
+        if ($show === 'shared_profile') {
             $links = '<ul class="nav nav-pills nav-stacked">';
             // My own profile
             if ($show_full_profile && $user_id == intval(api_get_user_id())) {
@@ -1085,7 +1094,7 @@ class SocialManager extends UserManager
                             '.$messagesIcon.' '.get_lang('Messages').$count_unread_message.'
                         </a>
                     </li>';
-                $active = $show == 'invitations' ? 'active' : null;
+                $active = $show === 'invitations' ? 'active' : null;
                 $links .= '
                     <li class="invitations-icon'.$active.'">
                         <a href="'.api_get_path(WEB_CODE_PATH).'social/invitations.php">
@@ -1103,12 +1112,14 @@ class SocialManager extends UserManager
                         <a href="'.api_get_path(WEB_CODE_PATH).'social/friends.php">
                             '.$friendsIcon.' '.get_lang('Friends').'
                         </a>
-                    </li>
-                    <li class="browse-groups-icon">
-                        <a href="'.api_get_path(WEB_CODE_PATH).'social/groups.php">
+                    </li>';
+
+                $links .= '<li class="browse-groups-icon">
+                        <a href="'.$groupUrl.'">
                             '.$groupsIcon.' '.get_lang('SocialGroups').'
                         </a>
-                    </li>';
+                        </li>';
+
                 $active = $show == 'search' ? 'active' : null;
                 $links .= '
                     <li class="search-icon '.$active.'">
@@ -1149,9 +1160,9 @@ class SocialManager extends UserManager
                     $sendMessageText
                 );
                 $sendMessageUrl = api_get_path(WEB_AJAX_PATH).'user_manager.ajax.php?'.http_build_query([
-                        'a' => 'get_user_popup',
-                        'user_id' => $user_id,
-                    ]);
+                    'a' => 'get_user_popup',
+                    'user_id' => $user_id,
+                ]);
 
                 $links .= '<li>';
                 $links .= Display::url(
@@ -1177,9 +1188,7 @@ class SocialManager extends UserManager
             }
 
             // Check if I already sent an invitation message
-            $invitation_sent_list = self::get_list_invitation_sent_by_user_id(
-                api_get_user_id()
-            );
+            $invitation_sent_list = self::get_list_invitation_sent_by_user_id(api_get_user_id());
 
             if (isset($invitation_sent_list[$user_id]) && is_array($invitation_sent_list[$user_id]) &&
                 count($invitation_sent_list[$user_id]) > 0
@@ -1575,7 +1584,7 @@ class SocialManager extends UserManager
 
         $extension = strtolower(substr(strrchr($safeFileName, '.'), 1));
         $allowedTypes = api_get_supported_image_extensions();
-        // Adding videos
+
         $allowedTypes[] = 'mp4';
         $allowedTypes[] = 'webm';
         $allowedTypes[] = 'ogg';
@@ -1590,13 +1599,13 @@ class SocialManager extends UserManager
     /**
      * Gets all messages from someone's wall (within specific limits).
      *
-     * @param int        $userId    id of wall shown
-     * @param int|string $parentId  id message (Post main)
+     * @param int        $userId     id of wall shown
+     * @param int|string $parentId   id message (Post main)
      * @param int|array  $groupId
      * @param int|array  $friendId
-     * @param string     $startDate Date from which we want to show the messages, in UTC time
-     * @param int        $start     Limit for the number of parent messages we want to show
-     * @param int        $length    Wall message query offset
+     * @param string     $startDate  Date from which we want to show the messages, in UTC time
+     * @param int        $start      Limit for the number of parent messages we want to show
+     * @param int        $length     Wall message query offset
      * @param bool       $getCount
      * @param array      $threadList
      *
@@ -1616,7 +1625,6 @@ class SocialManager extends UserManager
         $threadList = []
     ) {
         $tblMessage = Database::get_main_table(TABLE_MESSAGE);
-        $tblMessageAttachment = Database::get_main_table(TABLE_MESSAGE_ATTACHMENT);
 
         $parentId = (int) $parentId;
         $userId = (int) $userId;
@@ -1635,15 +1643,7 @@ class SocialManager extends UserManager
                     group_id,
                     '' as forum_id,
                     '' as thread_id,
-                    '' as c_id,                    
-                    (
-                        SELECT ma.path FROM $tblMessageAttachment ma
-                        WHERE  ma.message_id = tm.id 
-                    ) as path,
-                    (
-                        SELECT ma.filename FROM $tblMessageAttachment ma 
-                        WHERE ma.message_id = tm.id 
-                    ) as filename
+                    '' as c_id
                   ";
 
         if ($getCount) {
@@ -1651,15 +1651,10 @@ class SocialManager extends UserManager
         }
 
         $sql = "$select                    
-                FROM $tblMessage tm                
-                WHERE 
+                    FROM $tblMessage tm
+                WHERE
                     msg_status <> ".MESSAGE_STATUS_WALL_DELETE.' AND ';
 
-        // Date filter
-        if (!empty($startDate)) {
-            //$sql .= " AND send_date > '$startDate' ";
-        }
-
         // My own posts
         $userReceiverCondition = ' (
             user_receiver_id = '.$userId.' AND 
@@ -1667,18 +1662,6 @@ class SocialManager extends UserManager
             parent_id = '.$parentId.'
         )';
 
-        /*$messageStatusCondition = '';
-        if (!empty($messageStatus)) {
-            if (is_array($messageStatus)) {
-                $messageStatus = array_map('intval', $messageStatus);
-                $messageStatus = implode("','", $messageStatus);
-                $messageStatusCondition = " AND msg_status IN ('$messageStatus') ";
-            } else {
-                $messageStatus = (int) $messageStatus;
-                $messageStatusCondition = " AND msg_status = '$messageStatus') ";
-            }
-        }*/
-
         // User condition
         $sql .= $userReceiverCondition;
 
@@ -1729,10 +1712,8 @@ class SocialManager extends UserManager
                                 '' as group_id,
                                 forum_id,
                                 thread_id,
-                                c_id,
-                                '' as path,
-                                '' as filename                             
-                                ";
+                                c_id                            
+        ";
             }
 
             $threadList = array_map('intval', $threadList);
@@ -1829,7 +1810,7 @@ class SocialManager extends UserManager
     ) {
         $messageId = $messageInfo['id'];
         $messages = MessageManager::getMessagesByParent($messageInfo['id'], 0, $offset, $limit);
-        $formattedList = '<div class="sub-mediapost">';
+        $formattedList = '<div class="sub-mediapost row">';
         $users = [];
 
         // The messages are ordered by date descendant, for comments we need ascendant
@@ -1845,13 +1826,17 @@ class SocialManager extends UserManager
 
         $formattedList .= '</div>';
         $formattedList .= '<div class="mediapost-form">';
-        $formattedList .= '<form id = "form_comment_'.$messageId.'" name="post_comment" method="POST">
+        $formattedList .= '<form class="form-horizontal" id="form_comment_'.$messageId.'" name="post_comment" method="POST">
+                <div class="col-sm-9">
                 <label for="comment" class="hide">'.get_lang('SocialWriteNewComment').'</label>
                 <input type="hidden" name = "messageId" value="'.$messageId.'" />
-                <textarea placeholder="'.get_lang('SocialWriteNewComment').'" name="comment" rows="1" style="width:80%;" ></textarea>
-                <a onclick="submitComment('.$messageId.');" href="javascript:void(0);" name="social_wall_new_msg_submit" class="pull-right btn btn-default">
+                <textarea rows="3" class="form-control" placeholder="'.get_lang('SocialWriteNewComment').'" name="comment" rows="1" ></textarea>
+                </div>
+                <div class="col-sm-3">
+                <a onclick="submitComment('.$messageId.');" href="javascript:void(0);" name="social_wall_new_msg_submit" class="btn btn-default btn-post">
                     <em class="fa fa-pencil"></em> '.get_lang('Post').'
                 </a>
+                </div>
                 </form>';
         $formattedList .= '</div>';
 
@@ -1885,16 +1870,16 @@ class SocialManager extends UserManager
         $isAdmin = self::is_admin($users[$userIdLoop]['id']);
         if ($userStatus === 5) {
             if ($users[$userIdLoop]['has_certificates']) {
-                $iconStatus = '<img class="pull-left" src="'.$urlImg.'icons/svg/identifier_graduated.svg" width="22px" height="22px">';
+                $iconStatus = '<img src="'.$urlImg.'icons/svg/identifier_graduated.svg" width="22px" height="22px">';
             } else {
-                $iconStatus = '<img class="pull-left" src="'.$urlImg.'icons/svg/identifier_student.svg" width="22px" height="22px">';
+                $iconStatus = '<img src="'.$urlImg.'icons/svg/identifier_student.svg" width="22px" height="22px">';
             }
         } else {
             if ($userStatus === 1) {
                 if ($isAdmin) {
-                    $iconStatus = '<img class="pull-left" src="'.$urlImg.'icons/svg/identifier_admin.svg" width="22px" height="22px">';
+                    $iconStatus = '<img src="'.$urlImg.'icons/svg/identifier_admin.svg" width="22px" height="22px">';
                 } else {
-                    $iconStatus = '<img class="pull-left" src="'.$urlImg.'icons/svg/identifier_teacher.svg" width="22px" height="22px">';
+                    $iconStatus = '<img src="'.$urlImg.'icons/svg/identifier_teacher.svg" width="22px" height="22px">';
                 }
             }
         }
@@ -1912,34 +1897,41 @@ class SocialManager extends UserManager
                      </a>';
         $comment .= '</div>';
         $comment .= '</div>';
-        $comment .= '<div class="col-md-9 col-xs-9 social-post-answers">';
+        $comment .= '<div class="col-md-7 col-xs-7 social-post-answers">';
         $comment .= '<div class="user-data">';
         $comment .= $iconStatus;
         $comment .= '<div class="username"><a href="'.$url.'">'.$nameComplete.'</a> 
                         <span>'.Security::remove_XSS($message['content']).'</span>
-                     </div>';
+                       </div>';
         $comment .= '<div>'.$date.'</div>';
         $comment .= '<br />';
         $comment .= '</div>';
         $comment .= '</div>';
 
+        $comment .= '<div class="col-md-3 col-xs-3 social-post-answers">';
+        $comment .= '<div class="pull-right btn-group btn-group-sm">';
+
+        $comment .= MessageManager::getLikesButton(
+            $message['id'],
+            $currentUserId
+        );
+
         $isOwnWall = $currentUserId == $userIdLoop || $currentUserId == $receiverId;
         if ($isOwnWall) {
-            $comment .= '<div class="col-md-1 col-xs-1 social-post-answers">';
-            $comment .= '<div class="pull-right deleted-mgs">';
             $comment .= Display::url(
-                Display::returnFontAwesomeIcon('trash'),
+                    Display::returnFontAwesomeIcon('trash', '', true),
                 'javascript:void(0)',
                 [
                     'id' => 'message_'.$message['id'],
                     'title' => get_lang('SocialMessageDelete'),
                     'onclick' => 'deleteComment('.$message['id'].')',
+                    'class' => 'btn btn-default',
                 ]
             );
-            $comment .= '</div>';
-            $comment .= '</div>';
         }
         $comment .= '</div>';
+        $comment .= '</div>';
+        $comment .= '</div>';
 
         return $comment;
     }
@@ -1979,17 +1971,13 @@ class SocialManager extends UserManager
      */
     public static function getPostAttachment($message)
     {
-        if (isset($message['path']) && !empty($message['path'])) {
-            $previews = self::getAttachmentPreviewList($message);
+        $previews = self::getAttachmentPreviewList($message);
 
-            if (empty($previews)) {
-                return '';
-            }
-
-            return implode('', $previews);
+        if (empty($previews)) {
+            return '';
         }
 
-        return '';
+        return implode('', $previews);
     }
 
     /**
@@ -2334,7 +2322,6 @@ class SocialManager extends UserManager
     {
         //SOCIALGOODFRIEND , USER_RELATION_TYPE_FRIEND, USER_RELATION_TYPE_PARENT
         $friends = self::get_friends($user_id, USER_RELATION_TYPE_FRIEND);
-        $number_of_images = 30;
         $numberFriends = count($friends);
         $friendHtml = '';
 
@@ -2342,9 +2329,12 @@ class SocialManager extends UserManager
             $friendHtml .= '<div class="list-group contact-list">';
             $j = 1;
 
-            usort($friends, function ($a, $b) {
-                return strcmp($b['user_info']['user_is_online_in_chat'], $a['user_info']['user_is_online_in_chat']);
-            });
+            usort(
+                $friends,
+                function ($a, $b) {
+                    return strcmp($b['user_info']['user_is_online_in_chat'], $a['user_info']['user_is_online_in_chat']);
+                }
+            );
 
             foreach ($friends as $friend) {
                 if ($j > $numberFriends) {
@@ -2375,7 +2365,7 @@ class SocialManager extends UserManager
                     $friend['friend_user_id'],
                     api_get_user_id()
                 );
-                //$showLinkToChat = api_is_global_chat_enabled() && $friend['friend_user_id'] != api_get_user_id() && $relation == USER_RELATION_TYPE_FRIEND;
+
                 if ($showLinkToChat) {
                     $friendHtml .= '<a onclick="javascript:chatWith(\''.$friend['friend_user_id'].'\', \''.$name_user.'\', \''.$status.'\',\''.$friendAvatarSmall.'\')" href="javascript:void(0);" class="list-group-item">';
                     $friendHtml .= $friend_avatar.' <span class="username">'.$name_user.'</span>';
@@ -2414,7 +2404,9 @@ class SocialManager extends UserManager
             FormValidator::LAYOUT_HORIZONTAL
         );
 
-        $socialWallPlaceholder = isset($_GET['u']) ? get_lang('SocialWallWriteNewPostToFriend') : get_lang('SocialWallWhatAreYouThinkingAbout');
+        $socialWallPlaceholder = isset($_GET['u']) ? get_lang('SocialWallWriteNewPostToFriend') : get_lang(
+            'SocialWallWhatAreYouThinkingAbout'
+        );
 
         $form->addTextarea(
             'social_wall_new_msg_main',
@@ -2449,9 +2441,9 @@ class SocialManager extends UserManager
     }
 
     /**
-     * @param int $userId
-     * @param int $start
-     * @param int $length
+     * @param int   $userId
+     * @param int   $start
+     * @param int   $length
      * @param array $threadList
      *
      * @return array
@@ -2564,11 +2556,12 @@ class SocialManager extends UserManager
     /**
      * Get HTML code block for user skills.
      *
-     * @param int $userId The user ID
+     * @param int    $userId      The user ID
+     * @param string $orientation
      *
      * @return string
      */
-    public static function getSkillBlock($userId)
+    public static function getSkillBlock($userId, $orientation = 'horizontal')
     {
         if (Skill::isAllowed($userId, false) === false) {
             return '';
@@ -2579,12 +2572,10 @@ class SocialManager extends UserManager
 
         $template = new Template(null, false, false, false, false, false);
         $template->assign('ranking', $ranking);
-        $template->assign('skills', $skill->getUserSkillsTable($userId, 0, 0, false)['table']);
+        $template->assign('orientation', $orientation);
+        $template->assign('skills', $skill->getUserSkillsTable($userId, 0, 0, false)['skills']);
         $template->assign('user_id', $userId);
-        $template->assign(
-            'show_skills_report_link',
-            api_is_student() || api_is_student_boss() || api_is_drh()
-        );
+        $template->assign('show_skills_report_link', api_is_student() || api_is_student_boss() || api_is_drh());
 
         $skillBlock = $template->get_template('social/skills_block.tpl');
 
@@ -3150,21 +3141,30 @@ class SocialManager extends UserManager
         $postAttachment = self::getPostAttachment($message);
 
         $html = '';
-        $html .= '<div class="top-mediapost">';
+        $html .= '<div class="top-mediapost" >';
+        $html .= '<div class="pull-right btn-group btn-group-sm">';
+
+        $html .= MessageManager::getLikesButton(
+            $message['id'],
+            $currentUserId,
+            !empty($message['group_info']['id']) ? (int) $message['group_info']['id'] : 0
+        );
+
         if ($canEdit) {
             $htmlDelete = Display::url(
-                Display::returnFontAwesomeIcon('trash'),
+                Display::returnFontAwesomeIcon('trash', '', true),
                 'javascript:void(0)',
                 [
                     'id' => 'message_'.$message['id'],
                     'title' => get_lang('SocialMessageDelete'),
                     'onclick' => 'deleteMessage('.$message['id'].')',
+                    'class' => 'btn btn-default',
                 ]
             );
-            $html .= '<div class="pull-right deleted-mgs">';
+
             $html .= $htmlDelete;
-            $html .= '</div>';
         }
+        $html .= '</div>';
 
         $html .= '<div class="user-image" >';
         $html .= '<a href="'.$urlAuthor.'">
@@ -3173,13 +3173,13 @@ class SocialManager extends UserManager
         $html .= '<div class="user-data">';
         $html .= $iconStatus;
         $html .= '<div class="username"><a href="'.$urlAuthor.'">'.$nameCompleteAuthor.'</a>'.$htmlReceiver.'</div>';
-        $html .= '<div>'.$date.'</div>';
+        $html .= '<div class="post-date">'.$date.'</div>';
         $html .= '</div>';
         $html .= '<div class="msg-content">';
-        $html .= '<div class="post-attachment" >';
+        $html .= '<div class="post-attachment thumbnail">';
         $html .= $postAttachment;
         $html .= '</div>';
-        $html .= '<p>'.Security::remove_XSS($message['content']).'</p>';
+        $html .= '<div>'.Security::remove_XSS($message['content']).'</div>';
         $html .= '</div>';
         $html .= '</div>'; // end mediaPost
 

+ 1 - 1
main/inc/lib/sortable_table.class.php

@@ -130,7 +130,7 @@ class SortableTable extends HTML_Table
             $table_id = $table_name.uniqid();
         }
         $this->table_id = $table_id;
-        parent::__construct(['class' => 'data_table table', 'id' => $table_id]);
+        parent::__construct(['class' => 'table table-bordered data_table', 'id' => $table_id]);
         $this->table_name = $table_name;
         $this->additional_parameters = [];
         $this->param_prefix = $table_name.'_';

+ 97 - 8
main/inc/lib/statistics.lib.php

@@ -305,7 +305,7 @@ class Statistics
             } else {
                 if (!empty($row[2])) {
                     $originalData = str_replace('\\', '', $row[2]);
-                    $row[2] = unserialize($originalData);
+                    $row[2] = UnserializeApi::unserialize('not_allowed_classes', $originalData);
                     if (is_array($row[2]) && !empty($row[2])) {
                         $row[2] = implode_with_key(', ', $row[2]);
                     } else {
@@ -629,12 +629,13 @@ class Statistics
     /**
      * get the number of recent logins.
      *
-     * @param bool $distinct        Whether to only give distinct users stats, or *all* logins
+     * @param bool $distinct            Whether to only give distinct users stats, or *all* logins
      * @param int  $sessionDuration
+     * @param bool $completeMissingDays Whether to fill the daily gaps (if any) when getting a list of logins
      *
      * @return array
      */
-    public static function getRecentLoginStats($distinct = false, $sessionDuration = 0)
+    public static function getRecentLoginStats($distinct = false, $sessionDuration = 0, $completeMissingDays = true)
     {
         $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
         $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
@@ -650,6 +651,7 @@ class Statistics
         $date = new DateTime($now);
         $date->sub(new DateInterval('P15D'));
         $newDate = $date->format('Y-m-d h:i:s');
+        $totalLogin = self::buildDatesArray($newDate, $now, true);
 
         $field = 'login_id';
         if ($distinct) {
@@ -665,18 +667,18 @@ class Statistics
                 GROUP BY date(login_date)";
 
         $res = Database::query($sql);
-        $totalLogin = [];
         while ($row = Database::fetch_array($res, 'ASSOC')) {
-            $totalLogin[$row['login_date']] = $row['number'];
+            $monthAndDay = substr($row['login_date'], 5, 5);
+            $totalLogin[$monthAndDay] = $row['number'];
         }
 
         return $totalLogin;
     }
 
     /**
-     * Show some stats about the accesses to the different course tools.
+     * Get course tools usage statistics for the whole platform (by URL if multi-url).
      */
-    public static function printToolStats()
+    public static function getToolsStats()
     {
         $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ACCESS);
         $access_url_rel_course_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
@@ -724,6 +726,19 @@ class Statistics
             $result[$tool_names[$obj->access_tool]] = $obj->number_of_logins;
         }
 
+        return $result;
+    }
+
+    /**
+     * Show some stats about the accesses to the different course tools.
+     *
+     * @param array $result If defined, this serves as data. Otherwise, will get the data from getToolsStats()
+     */
+    public static function printToolStats($result = null)
+    {
+        if (empty($result)) {
+            $result = self::getToolsStats();
+        }
         self::printStats(get_lang('PlatformToolAccess'), $result, true);
     }
 
@@ -751,7 +766,8 @@ class Statistics
         while ($obj = Database::fetch_object($res)) {
             $result[$obj->course_language] = $obj->number_of_courses;
         }
-        self::printStats(get_lang('CountCourseByLanguage'), $result, true);
+
+        return $result;
     }
 
     /**
@@ -1060,4 +1076,77 @@ class Statistics
             false
         );
     }
+
+    /**
+     * Returns an array with indexes as the 'yyyy-mm-dd' format of each date
+     * within the provided range (including limits). Dates are assumed to be
+     * given in UTC.
+     *
+     * @param string $startDate  Start date, in Y-m-d or Y-m-d h:i:s format
+     * @param string $endDate    End date, in Y-m-d or Y-m-d h:i:s format
+     * @param bool   $removeYear Whether to remove the year in the results (for easier reading)
+     *
+     * @return array|bool False on error in the params, array of [date1 => 0, date2 => 0, ...] otherwise
+     */
+    public static function buildDatesArray($startDate, $endDate, $removeYear = false)
+    {
+        if (strlen($startDate) > 10) {
+            $startDate = substr($startDate, 0, 10);
+        }
+        if (strlen($endDate) > 10) {
+            $endDate = substr($endDate, 0, 10);
+        }
+        if (!preg_match('/\d\d\d\d-\d\d-\d\d/', $startDate)) {
+            return false;
+        }
+        if (!preg_match('/\d\d\d\d-\d\d-\d\d/', $startDate)) {
+            return false;
+        }
+        $startTimestamp = strtotime($startDate);
+        $endTimestamp = strtotime($endDate);
+        $list = [];
+        for ($time = $startTimestamp; $time < $endTimestamp; $time += 86400) {
+            $datetime = api_get_utc_datetime($time);
+            if ($removeYear) {
+                $datetime = substr($datetime, 5, 5);
+            } else {
+                $dateTime = substr($datetime, 0, 10);
+            }
+            $list[$datetime] = 0;
+        }
+
+        return $list;
+    }
+
+    /**
+     * Prepare the JS code to load a chart.
+     *
+     * @param string $url     URL for AJAX data generator
+     * @param string $type    bar, line, pie, etc
+     * @param string $options Additional options to the chart (see chart-specific library)
+     * @param string A JS code for loading the chart together with a call to AJAX data generator
+     */
+    public static function getJSChartTemplate($url, $type = 'pie', $options = '', $elementId = 'canvas')
+    {
+        $chartCode = '
+        <script>
+        $(document).ready(function() {
+            $.ajax({
+                url: "'.$url.'",
+                type: "POST",
+                success: function(data) {
+                    Chart.defaults.global.responsive = true;
+                    var ctx = document.getElementById("'.$elementId.'").getContext("2d");
+                    var myLoginChart = new Chart(ctx, {
+                        type: "'.$type.'",
+                        data: data,
+                        options: {'.$options.'}
+                    });
+                }
+            });
+        });
+        </script>';
+
+        return $chartCode;
+    }
 }

+ 1 - 1
main/inc/lib/system/session.class.php

@@ -66,7 +66,7 @@ class Session implements \ArrayAccess
 
     /**
      * @param string $variable
-     * @param string $value
+     * @param mixed  $value
      */
     public static function write($variable, $value)
     {

+ 10 - 43
main/inc/lib/template.lib.php

@@ -163,19 +163,19 @@ class Template
             ],
             [
                 'name' => 'icon',
-                'callable' => 'Template::get_icon_path',
+                'callable' => 'Display::get_icon_path',
             ],
             [
                 'name' => 'img',
-                'callable' => 'Template::get_image',
+                'callable' => 'Display::get_image',
             ],
             [
                 'name' => 'format_date',
-                'callable' => 'Template::format_date',
+                'callable' => 'api_format_date',
             ],
             [
                 'name' => 'get_template',
-                'callable' => 'Template::findTemplateFilePath',
+                'callable' => 'api_find_template',
             ],
             [
                 'name' => 'date_to_time_ago',
@@ -255,40 +255,6 @@ class Template
         }
     }
 
-    /**
-     * @param string $image
-     * @param int    $size
-     *
-     * @return string
-     */
-    public static function get_icon_path($image, $size = ICON_SIZE_SMALL)
-    {
-        return Display::return_icon($image, '', [], $size, false, true);
-    }
-
-    /**
-     * @param string $image
-     * @param int    $size
-     * @param string $name
-     *
-     * @return string
-     */
-    public static function get_image($image, $size = ICON_SIZE_SMALL, $name = '')
-    {
-        return Display::return_icon($image, $name, [], $size);
-    }
-
-    /**
-     * @param string $timestamp
-     * @param string $format
-     *
-     * @return string
-     */
-    public static function format_date($timestamp, $format = null)
-    {
-        return api_format_date($timestamp, $format);
-    }
-
     /**
      * Return the item's url key:.
      *
@@ -469,16 +435,16 @@ class Template
 
         $this->assign('show_toolbar', $show_toolbar);
 
-        //Only if course is available
+        // Only if course is available
         $courseToolBar = '';
         $show_course_navigation_menu = '';
         if (!empty($this->course_id) && $this->user_is_logged_in) {
             if (api_get_setting('show_toolshortcuts') != 'false') {
-                //Course toolbar
+                // Course toolbar
                 $courseToolBar = CourseHome::show_navigation_tool_shortcuts();
             }
             if (api_get_setting('show_navigation_menu') != 'false') {
-                //Course toolbar
+                // Course toolbar
                 $show_course_navigation_menu = CourseHome::show_navigation_menu();
             }
         }
@@ -531,7 +497,7 @@ class Template
      */
     public function get_template($name)
     {
-        return self::findTemplateFilePath($name);
+        return api_find_template($name);
     }
 
     /**
@@ -776,6 +742,7 @@ class Template
             'select2/dist/js/select2.min.js',
             "select2/dist/js/i18n/$isoCode.js",
             'mediaelement/plugins/vrview/vrview.js',
+            'js-cookie/src/js.cookie.js',
         ];
 
         $features = api_get_configuration_value('video_features');
@@ -1518,7 +1485,7 @@ class Template
         $this->assign('prefetch', $prefetch);
         $this->assign('text_direction', api_get_text_direction());
         $this->assign('section_name', 'section-'.$this_section);
-        $this->assignFavIcon(); //Set a 'favico' var for the template
+        $this->assignFavIcon();
         $this->setHelp();
 
         $this->assignBugNotification(); //Prepare the 'bug_notification' var for the template

+ 72 - 174
main/inc/lib/tracking.lib.php

@@ -1871,21 +1871,21 @@ class Tracking
         } else {
             $tbl_track_login = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
             $sql = 'SELECT login_course_date
-                FROM '.$tbl_track_login.'
-                WHERE
-                    user_id = '.$student_id.' AND
-                    c_id = '.$courseId.' AND
-                    session_id = '.$session_id.'
-                ORDER BY login_course_date ASC 
-                LIMIT 0,1';
+                    FROM '.$tbl_track_login.'
+                    WHERE
+                        user_id = '.$student_id.' AND
+                        c_id = '.$courseId.' AND
+                        session_id = '.$session_id.'
+                    ORDER BY login_course_date ASC 
+                    LIMIT 0,1';
             $rs = Database::query($sql);
             if (Database::num_rows($rs) > 0) {
                 if ($first_login_date = Database::result($rs, 0, 0)) {
                     if ($convert_date) {
                         return api_convert_and_format_date(
-                        $first_login_date,
-                        DATE_FORMAT_SHORT
-                    );
+                            $first_login_date,
+                            DATE_FORMAT_SHORT
+                        );
                     } else {
                         return $first_login_date;
                     }
@@ -1949,14 +1949,14 @@ class Tracking
                     //If the last connection is > than 7 days, the text is red
                     //345600 = 7 days in seconds
                     /*
-                if ($now - $last_login_date_timestamp > 604800) {
-                    if ($convert_date) {
-                        $last_login_date = api_convert_and_format_date($last_login_date, DATE_FORMAT_SHORT);
-                        $icon = api_is_allowed_to_edit() ?
-                            '<a href="'.api_get_path(WEB_CODE_PATH).'announcements/announcements.php?action=add&remind_inactive='.$student_id.'&cidReq='.$courseInfo['code'].'" title="'.get_lang('RemindInactiveUser').'">
-                              '.Display::return_icon('messagebox_warning.gif').'
-                             </a>'
-                            : null;
+                    if ($now - $last_login_date_timestamp > 604800) {
+                        if ($convert_date) {
+                            $last_login_date = api_convert_and_format_date($last_login_date, DATE_FORMAT_SHORT);
+                            $icon = api_is_allowed_to_edit() ?
+                                '<a href="'.api_get_path(WEB_CODE_PATH).'announcements/announcements.php?action=add&remind_inactive='.$student_id.'&cidReq='.$courseInfo['code'].'" title="'.get_lang('RemindInactiveUser').'">
+                                  '.Display::return_icon('messagebox_warning.gif').'
+                                 </a>'
+                                : null;
                             return $icon.Display::label($last_login_date, 'warning');
                         } else {
                             return $last_login_date;
@@ -3302,6 +3302,7 @@ class Tracking
                     WHERE c_id = $course_id $condition_lp";
             $result = Database::query($sql);
             $session_condition = api_get_session_condition($session_id);
+
             // calculates time
             if (Database::num_rows($result) > 0) {
                 while ($row = Database::fetch_array($result)) {
@@ -3767,7 +3768,7 @@ class Tracking
     /**
      * Get sessions coached by user.
      *
-     * @param $coach_id
+     * @param        $coach_id
      * @param int    $start
      * @param int    $limit
      * @param bool   $getCount
@@ -4096,7 +4097,6 @@ class Tracking
             return $row['count'];
         }
 
-        require_once api_get_path(SYS_CODE_PATH).'forum/forumconfig.inc.php';
         require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
 
         $courseInfo = api_get_course_info($courseCode);
@@ -5963,155 +5963,53 @@ class Tracking
      */
     public static function generate_session_exercise_graph($names, $my_results, $average)
     {
-        /* Create and populate the pData object */
-        $myData = new pData();
-        $myData->addPoints($names, 'Labels');
-        $myData->addPoints($my_results, 'Serie1');
-        $myData->addPoints($average, 'Serie2');
-        $myData->setSerieWeight('Serie1', 1);
-        $myData->setSerieTicks('Serie2', 4);
-        $myData->setSerieDescription('Labels', 'Months');
-        $myData->setAbscissa('Labels');
-        $myData->setSerieDescription('Serie1', get_lang('MyResults'));
-        $myData->setSerieDescription('Serie2', get_lang('AverageScore'));
-        $myData->setAxisUnit(0, '%');
-        $myData->loadPalette(api_get_path(SYS_CODE_PATH).'palettes/pchart/default.color', true);
-        // Cache definition
-        $cachePath = api_get_path(SYS_ARCHIVE_PATH);
-        $myCache = new pCache(['CacheFolder' => substr($cachePath, 0, strlen($cachePath) - 1)]);
-        $chartHash = $myCache->getHash($myData);
-
-        if ($myCache->isInCache($chartHash)) {
-            //if we already created the img
-            $imgPath = api_get_path(SYS_ARCHIVE_PATH).$chartHash;
-            $myCache->saveFromCache($chartHash, $imgPath);
-            $imgPath = api_get_path(WEB_ARCHIVE_PATH).$chartHash;
-        } else {
-            /* Define width, height and angle */
-            $mainWidth = 860;
-            $mainHeight = 500;
-            $angle = 50;
-
-            /* Create the pChart object */
-            $myPicture = new pImage($mainWidth, $mainHeight, $myData);
-
-            /* Turn of Antialiasing */
-            $myPicture->Antialias = false;
-
-            /* Draw the background */
-            $settings = ['R' => 255, 'G' => 255, 'B' => 255];
-            $myPicture->drawFilledRectangle(0, 0, $mainWidth, $mainHeight, $settings);
-
-            /* Add a border to the picture */
-            $myPicture->drawRectangle(
-                0,
-                0,
-                $mainWidth - 1,
-                $mainHeight - 1,
-                ['R' => 0, 'G' => 0, 'B' => 0]
-            );
-
-            /* Set the default font */
-            $myPicture->setFontProperties(
-                [
-                    'FontName' => api_get_path(SYS_FONTS_PATH).'opensans/OpenSans-Regular.ttf',
-                    'FontSize' => 10, ]
-            );
-            /* Write the chart title */
-            $myPicture->drawText(
-                $mainWidth / 2,
-                30,
-                get_lang('ExercisesInTimeProgressChart'),
-                [
-                    'FontSize' => 12,
-                    'Align' => TEXT_ALIGN_BOTTOMMIDDLE,
-                ]
-            );
-
-            /* Set the default font */
-            $myPicture->setFontProperties(
-                [
-                    'FontName' => api_get_path(SYS_FONTS_PATH).'opensans/OpenSans-Regular.ttf',
-                    'FontSize' => 6,
-                ]
-            );
-
-            /* Define the chart area */
-            $myPicture->setGraphArea(60, 60, $mainWidth - 60, $mainHeight - 150);
-
-            /* Draw the scale */
-            $scaleSettings = [
-                'XMargin' => 10,
-                'YMargin' => 10,
-                'Floating' => true,
-                'GridR' => 200,
-                'GridG' => 200,
-                'GridB' => 200,
-                'DrawSubTicks' => true,
-                'CycleBackground' => true,
-                'LabelRotation' => $angle,
-                'Mode' => SCALE_MODE_ADDALL_START0,
-            ];
-            $myPicture->drawScale($scaleSettings);
-
-            /* Turn on Antialiasing */
-            $myPicture->Antialias = true;
-
-            /* Enable shadow computing */
-            $myPicture->setShadow(
-                true,
-                [
-                    'X' => 1,
-                    'Y' => 1,
-                    'R' => 0,
-                    'G' => 0,
-                    'B' => 0,
-                    'Alpha' => 10,
-                ]
-            );
-
-            /* Draw the line chart */
-            $myPicture->setFontProperties(
-                [
-                    'FontName' => api_get_path(SYS_FONTS_PATH).'opensans/OpenSans-Regular.ttf',
-                    'FontSize' => 10,
-                ]
-            );
-            $myPicture->drawSplineChart();
-            $myPicture->drawPlotChart(
-                [
-                    'DisplayValues' => true,
-                    'PlotBorder' => true,
-                    'BorderSize' => 1,
-                    'Surrounding' => -60,
-                    'BorderAlpha' => 80,
-                ]
-            );
-
-            /* Write the chart legend */
-            $myPicture->drawLegend(
-                $mainWidth / 2 + 50,
-                50,
-                [
-                    'Style' => LEGEND_BOX,
-                    'Mode' => LEGEND_HORIZONTAL,
-                    'FontR' => 0,
-                    'FontG' => 0,
-                    'FontB' => 0,
-                    'R' => 220,
-                    'G' => 220,
-                    'B' => 220,
-                    'Alpha' => 100,
-                ]
-            );
-
-            $myCache->writeToCache($chartHash, $myPicture);
-            $imgPath = api_get_path(SYS_ARCHIVE_PATH).$chartHash;
-            $myCache->saveFromCache($chartHash, $imgPath);
-            $imgPath = api_get_path(WEB_ARCHIVE_PATH).$chartHash;
-        }
-
-        $html = '<img src="'.$imgPath.'">';
+        $html = api_get_js('chartjs/Chart.js');
+        $canvas = Display::tag('canvas', '', ['id' => 'session_graph_chart']);
+        $html .= Display::tag('div', $canvas, ['style' => 'width:100%']);
+        $jsStr = " var data = {
+                       labels:".json_encode($names).",
+                       datasets: [
+                       {
+                         label: '".get_lang('MyResults')."',
+                         backgroundColor: 'rgb(255, 99, 132)',
+                         stack: 'Stack1',
+                         data: ".json_encode($my_results).",
+                        },
+                        {
+                         label: '".get_lang('AverageScore')."',
+                         backgroundColor: 'rgb(75, 192, 192)',
+                         stack: 'Stack2',
+                         data: ".json_encode($average).",
+                        },
+                        ],  
+                    };
+                    var ctx = document.getElementById('session_graph_chart').getContext('2d');
+                    var myBarChart = new Chart(ctx, {
+                    type: 'bar',
+                    data: data,
+                    options: {
+                            title: {
+                                    display: true,
+                                    text: '".get_lang('ExercisesInTimeProgressChart')."'
+                            },
+                            tooltips: {
+                                    mode: 'index',
+                                    intersect: false
+                            },
+                            responsive: true,
+                            scales: {
+                                yAxes: [{
+                                    ticks: {
+                                        // Include a dollar sign in the ticks
+                                        callback: function(value, index, values) {
+                                            return value + '%';
+                                        }
+                                    }
+                                }]
+                            }
+                    }
+                });";
+        $html .= Display::tag('script', $jsStr);
 
         return $html;
     }
@@ -6933,7 +6831,7 @@ class Tracking
                             break;
                         case TOOL_LEARNPATH:
                             if ($item['tool_id'] != $beforeItem['tool_id']) {
-                                continue;
+                                break;
                             }
                             if (!isset($lpTime[$item['tool_id']])) {
                                 $lpTime[$item['tool_id']] = 0;
@@ -7760,7 +7658,7 @@ class TrackingCourseLog
             $user_ids = array_map('intval', $user_ids);
             $condition_user = " WHERE user.user_id IN (".implode(',', $user_ids).") ";
         } else {
-            $user_ids = intval($user_ids);
+            $user_ids = (int) $user_ids;
             $condition_user = " WHERE user.user_id = $user_ids ";
         }
 
@@ -7798,9 +7696,9 @@ class TrackingCourseLog
             $direction = 'ASC';
         }
 
-        $column = intval($column);
-        $from = intval($from);
-        $number_of_items = intval($number_of_items);
+        $column = (int) $column;
+        $from = (int) $from;
+        $number_of_items = (int) $number_of_items;
 
         $sql .= " ORDER BY col$column $direction ";
         $sql .= " LIMIT $from,$number_of_items";

+ 1 - 1
main/inc/lib/usergroup.lib.php

@@ -1372,7 +1372,7 @@ class UserGroup extends Model
     {
         // Validation 1.
         $group_id = (int) $group_id;
-        // Validation 1.
+
         if (empty($group_id)) {
             return false;
         }

+ 267 - 23
main/inc/lib/usermanager.lib.php

@@ -478,6 +478,9 @@ class UserManager
                 $layoutContent = $tplContent->get_template('mail/content_registration_platform.tpl');
                 $emailBody = $tplContent->fetch($layoutContent);
 
+                $userInfo = api_get_user_info($userId);
+                $mailTemplateManager = new MailTemplateManager();
+
                 /* MANAGE EVENT WITH MAIL */
                 if (EventsMail::check_if_using_class('user_registration')) {
                     $values["about_user"] = $return;
@@ -495,11 +498,33 @@ class UserManager
                         'password' => $original_password,
                     ];
 
+                    $emailBodyTemplate = '';
+                    if (!empty($emailTemplate)) {
+                        if (isset($emailTemplate['content_registration_platform.tpl']) &&
+                            !empty($emailTemplate['content_registration_platform.tpl'])
+                        ) {
+                            $emailBodyTemplate = $mailTemplateManager->parseTemplate(
+                                $emailTemplate['content_registration_platform.tpl'],
+                                $userInfo
+                            );
+                        }
+                    }
+
                     $twoEmail = api_get_configuration_value('send_two_inscription_confirmation_mail');
                     if ($twoEmail === true) {
                         $layoutContent = $tplContent->get_template('mail/new_user_first_email_confirmation.tpl');
                         $emailBody = $tplContent->fetch($layoutContent);
 
+                        if (!empty($emailBodyTemplate) &&
+                            isset($emailTemplate['new_user_first_email_confirmation.tpl']) &&
+                            !empty($emailTemplate['new_user_first_email_confirmation.tpl'])
+                        ) {
+                            $emailBody = $mailTemplateManager->parseTemplate(
+                                $emailTemplate['new_user_first_email_confirmation.tpl'],
+                                $userInfo
+                            );
+                        }
+
                         api_mail_html(
                             $recipient_name,
                             $email,
@@ -516,6 +541,16 @@ class UserManager
                         $layoutContent = $tplContent->get_template('mail/new_user_second_email_confirmation.tpl');
                         $emailBody = $tplContent->fetch($layoutContent);
 
+                        if (!empty($emailBodyTemplate) &&
+                            isset($emailTemplate['new_user_second_email_confirmation.tpl']) &&
+                            !empty($emailTemplate['new_user_second_email_confirmation.tpl'])
+                        ) {
+                            $emailBody = $mailTemplateManager->parseTemplate(
+                                $emailTemplate['new_user_second_email_confirmation.tpl'],
+                                $userInfo
+                            );
+                        }
+
                         api_mail_html(
                             $recipient_name,
                             $email,
@@ -529,6 +564,9 @@ class UserManager
                             $additionalParameters
                         );
                     } else {
+                        if (!empty($emailBodyTemplate)) {
+                            $emailBody = $emailBodyTemplate;
+                        }
                         $sendToInbox = api_get_configuration_value('send_inscription_msg_to_inbox');
                         if ($sendToInbox) {
                             $adminList = self::get_all_administrators();
@@ -604,6 +642,17 @@ class UserManager
 
                     $layoutContent = $tplContent->get_template('mail/content_registration_platform_to_admin.tpl');
                     $emailBody = $tplContent->fetch($layoutContent);
+
+                    if (!empty($emailBodyTemplate) &&
+                        isset($emailTemplate['content_registration_platform_to_admin.tpl']) &&
+                        !empty($emailTemplate['content_registration_platform_to_admin.tpl'])
+                    ) {
+                        $emailBody = $mailTemplateManager->parseTemplate(
+                            $emailTemplate['content_registration_platform_to_admin.tpl'],
+                            $userInfo
+                        );
+                    }
+
                     $subject = '['.api_get_setting('siteName').'] '.get_lang('NewStudentRegistered');
                     foreach ($adminList as $adminId => $data) {
                         MessageManager::send_message_simple(
@@ -879,6 +928,9 @@ class UserManager
             Database::query($sql);
         }
 
+        $app_plugin = new AppPlugin();
+        $app_plugin->performActionsWhenDeletingItem('user', $user_id);
+
         // Delete user from database
         $sql = "DELETE FROM $table_user WHERE id = '".$user_id."'";
         Database::query($sql);
@@ -2618,14 +2670,12 @@ class UserManager
         $splitMultiple = false,
         $fieldFilter = null
     ) {
-        // A sanity check.
+        $user_id = (int) $user_id;
+
         if (empty($user_id)) {
-            $user_id = 0;
-        } else {
-            if ($user_id != strval(intval($user_id))) {
-                return [];
-            }
+            return [];
         }
+
         $extra_data = [];
         $t_uf = Database::get_main_table(TABLE_EXTRA_FIELD);
         $t_ufv = Database::get_main_table(TABLE_EXTRA_FIELD_VALUES);
@@ -2650,7 +2700,7 @@ class UserManager
             }
         }
 
-        $sql .= " ORDER BY f.field_order";
+        $sql .= ' ORDER BY f.field_order';
 
         $res = Database::query($sql);
         if (Database::num_rows($res) > 0) {
@@ -2956,7 +3006,8 @@ class UserManager
         $user_id,
         $is_time_over = true,
         $ignore_visibility_for_admins = false,
-        $ignoreTimeLimit = false
+        $ignoreTimeLimit = false,
+        $getCount = false
     ) {
         if ($user_id != strval(intval($user_id))) {
             return [];
@@ -2974,20 +3025,27 @@ class UserManager
         // LEFT JOIN is used for session_rel_course_rel_user because an inner
         // join would not catch session-courses where the user is general
         // session coach but which do not have students nor coaches registered
-        $dql = "SELECT DISTINCT
-                    s.id,
-                    s.name,
-                    s.accessStartDate AS access_start_date,
-                    s.accessEndDate AS access_end_date,
-                    s.duration,
-                    sc.id AS session_category_id,
-                    sc.name AS session_category_name,
-                    sc.dateStart AS session_category_date_start,
-                    sc.dateEnd AS session_category_date_end,
-                    s.coachAccessStartDate AS coach_access_start_date,
-                    s.coachAccessEndDate AS coach_access_end_date,
-                    CASE WHEN s.accessEndDate IS NULL THEN 1 ELSE 0 END HIDDEN _isFieldNull
-                    $position
+        $dqlSelect = " COUNT(DISTINCT s.id) ";
+
+        if (!$getCount) {
+            $dqlSelect = " DISTINCT
+                s.id,
+                s.name,
+                s.accessStartDate AS access_start_date,
+                s.accessEndDate AS access_end_date,
+                s.duration,
+                sc.id AS session_category_id,
+                sc.name AS session_category_name,
+                sc.dateStart AS session_category_date_start,
+                sc.dateEnd AS session_category_date_end,
+                s.coachAccessStartDate AS coach_access_start_date,
+                s.coachAccessEndDate AS coach_access_end_date,
+                CASE WHEN s.accessEndDate IS NULL THEN 1 ELSE 0 END HIDDEN _isFieldNull
+                $position
+            ";
+        }
+
+        $dql = "SELECT $dqlSelect
                 FROM ChamiloCoreBundle:Session AS s
                 LEFT JOIN ChamiloCoreBundle:SessionRelCourseRelUser AS scu WITH scu.session = s
                 INNER JOIN ChamiloCoreBundle:AccessUrlRelSession AS url WITH url.sessionId = s.id
@@ -3052,6 +3110,10 @@ class UserManager
             )
         ;
 
+        if ($getCount) {
+            return $dqlStudent->getSingleScalarResult() + $dqlCoach->getSingleScalarResult();
+        }
+
         $sessionDataStudent = $dqlStudent->getResult();
         $sessionDataCoach = $dqlCoach->getResult();
 
@@ -3067,6 +3129,10 @@ class UserManager
             $sessionData[$row['id']] = $row;
         }
 
+        $collapsable = api_get_configuration_value('allow_user_session_collapsable');
+        $extraField = new ExtraFieldValue('session');
+        $collapsableLink = api_get_path(WEB_PATH).'user_portal.php?action=collapse_session';
+
         $categories = [];
         foreach ($sessionData as $row) {
             $session_id = $row['id'];
@@ -3198,6 +3264,7 @@ class UserManager
                 'courses' => $courseList,
                 'collapsed' => $collapsed,
                 'collapsable_link' => $collapsedAction,
+                'duration' => $row['duration'],
             ];
         }
 
@@ -6107,7 +6174,7 @@ SQL;
 
         Database::getManager()->persist($user);
         Database::getManager()->flush();
-
+        // ofaj
         $url = api_get_path(WEB_CODE_PATH).'auth/user_mail_confirmation.php?token='.$uniqueId;
         $mailSubject = get_lang('RegistrationConfirmation');
         // Check if the user was originally set for an automated subscription to a course or session
@@ -6405,6 +6472,183 @@ SQL;
         return (int) $row['count'];
     }
 
+    /**
+     * @param array $userInfo
+     * @param int   $searchYear
+     *
+     * @throws Exception
+     *
+     * @return array
+     */
+    public static function getSubscribedSessionsByYear(array $userInfo, $searchYear)
+    {
+        $timezone = new DateTimeZone(api_get_timezone());
+
+        $sessions = [];
+
+        if (DRH == $userInfo['status']) {
+            $sessions = SessionManager::get_sessions_followed_by_drh($userInfo['id']);
+        } elseif (SESSIONADMIN == $userInfo['status']) {
+            $sessions = SessionManager::getSessionsAdmin($userInfo['id']);
+        } else {
+            $sessionsByCategory = self::get_sessions_by_category($userInfo['id'], false, true, true);
+            $sessionsByCategory = array_column($sessionsByCategory, 'sessions');
+
+            foreach ($sessionsByCategory as $sessionsInCategory) {
+                $sessions = array_merge($sessions, $sessionsInCategory);
+            }
+        }
+
+        $sessions = array_map(
+            function ($sessionInfo) {
+                if (!isset($sessionInfo['session_id'])) {
+                    $sessionInfo['session_id'] = $sessionInfo['id'];
+                }
+                if (!isset($sessionInfo['session_name'])) {
+                    $sessionInfo['session_name'] = $sessionInfo['name'];
+                }
+
+                return $sessionInfo;
+            },
+            $sessions
+        );
+
+        $calendarSessions = [];
+
+        foreach ($sessions as $sessionInfo) {
+            if (!empty($sessionInfo['duration'])) {
+                $courseAccess = CourseManager::getFirstCourseAccessPerSessionAndUser(
+                    $sessionInfo['session_id'],
+                    $userInfo['id']
+                );
+
+                if (empty($courseAccess)) {
+                    continue;
+                }
+
+                $firstAcessDate = new DateTime(api_get_local_time($courseAccess['login_course_date']), $timezone);
+                $lastAccessDate = clone $firstAcessDate;
+                $lastAccessDate->modify("+{$sessionInfo['duration']} days");
+
+                $firstAccessYear = (int) $firstAcessDate->format('Y');
+                $lastAccessYear = (int) $lastAccessDate->format('Y');
+
+                if ($firstAccessYear <= $searchYear && $lastAccessYear >= $searchYear) {
+                    $calendarSessions[$sessionInfo['session_id']] = [
+                        'name' => $sessionInfo['session_name'],
+                        'access_start_date' => $firstAcessDate->format('Y-m-d h:i:s'),
+                        'access_end_date' => $lastAccessDate->format('Y-m-d h:i:s'),
+                    ];
+                }
+
+                continue;
+            }
+
+            $accessStartDate = !empty($sessionInfo['access_start_date'])
+                ? new DateTime(api_get_local_time($sessionInfo['access_start_date']), $timezone)
+                : null;
+            $accessEndDate = !empty($sessionInfo['access_end_date'])
+                ? new DateTime(api_get_local_time($sessionInfo['access_end_date']), $timezone)
+                : null;
+            $accessStartYear = $accessStartDate ? (int) $accessStartDate->format('Y') : 0;
+            $accessEndYear = $accessEndDate ? (int) $accessEndDate->format('Y') : 0;
+
+            $isValid = false;
+
+            if ($accessStartYear && $accessEndYear) {
+                if ($accessStartYear <= $searchYear && $accessEndYear >= $searchYear) {
+                    $isValid = true;
+                }
+            }
+
+            if ($accessStartYear && !$accessEndYear) {
+                if ($accessStartYear == $searchYear) {
+                    $isValid = true;
+                }
+            }
+
+            if (!$accessStartYear && $accessEndYear) {
+                if ($accessEndYear == $searchYear) {
+                    $isValid = true;
+                }
+            }
+
+            if ($isValid) {
+                $calendarSessions[$sessionInfo['session_id']] = [
+                    'name' => $sessionInfo['session_name'],
+                    'access_start_date' => $accessStartDate ? $accessStartDate->format('Y-m-d h:i:s') : null,
+                    'access_end_date' => $accessEndDate ? $accessEndDate->format('Y-m-d h:i:s') : null,
+                ];
+            }
+        }
+
+        return $calendarSessions;
+    }
+
+    /**
+     * Get sessions info for planification calendar.
+     *
+     * @param array $sessionsList Session list from UserManager::getSubscribedSessionsByYear
+     * @param int   $searchYear
+     *
+     * @throws Exception
+     *
+     * @return array
+     */
+    public static function getSessionsCalendarByYear(array $sessionsList, $searchYear)
+    {
+        $timezone = new DateTimeZone(api_get_timezone());
+        $calendar = [];
+
+        foreach ($sessionsList as $sessionId => $sessionInfo) {
+            $startDate = $sessionInfo['access_start_date']
+                ? new DateTime(api_get_local_time($sessionInfo['access_start_date']), $timezone)
+                : null;
+            $endDate = $sessionInfo['access_end_date']
+                ? new DateTime(api_get_local_time($sessionInfo['access_end_date']), $timezone)
+                : null;
+
+            $startYear = $startDate ? (int) $startDate->format('Y') : 0;
+            $startWeekYear = $startDate ? (int) $startDate->format('o') : 0;
+            $startWeek = $startDate ? (int) $startDate->format('W') : 0;
+            $endYear = $endDate ? (int) $endDate->format('Y') : 0;
+            $endWeekYear = $endDate ? (int) $endDate->format('o') : 0;
+            $endWeek = $endDate ? (int) $endDate->format('W') : 0;
+
+            $start = $startWeekYear < $searchYear ? 0 : $startWeek - 1;
+            $duration = $endWeekYear > $searchYear ? 52 - $start : $endWeek - $start;
+
+            $calendar[] = [
+                'id' => $sessionId,
+                'name' => $sessionInfo['name'],
+                'human_date' => SessionManager::convertSessionDateToString($startDate, $endDate, false, true),
+                'start_in_last_year' => $startYear < $searchYear,
+                'end_in_next_year' => $endYear > $searchYear,
+                'no_start' => !$startWeek,
+                'no_end' => !$endWeek,
+                'start' => $start,
+                'duration' => $duration > 0 ? $duration : 1,
+            ];
+        }
+
+        usort(
+            $calendar,
+            function ($sA, $sB) {
+                if ($sA['start'] == $sB['start']) {
+                    return 0;
+                }
+
+                if ($sA['start'] < $sB['start']) {
+                    return -1;
+                }
+
+                return 1;
+            }
+        );
+
+        return $calendar;
+    }
+
     /**
      * @return EncoderFactory
      */

+ 38 - 8
main/inc/lib/userportal.lib.php

@@ -914,11 +914,11 @@ class IndexManager
         if (api_get_configuration_value('allow_my_files_link_in_homepage')) {
             if (api_get_setting('allow_my_files') !== 'false') {
                 $items[] = [
-                        'class' => 'myfiles-social',
-                        'icon' => Display::return_icon('sn-files.png', get_lang('Files')),
-                        'link' => api_get_path(WEB_PATH).'main/social/myfiles.php',
-                        'title' => get_lang('MyFiles'),
-                    ];
+                    'class' => 'myfiles-social',
+                    'icon' => Display::return_icon('sn-files.png', get_lang('Files')),
+                    'link' => api_get_path(WEB_PATH).'main/social/myfiles.php',
+                    'title' => get_lang('MyFiles'),
+                ];
             }
         }
 
@@ -1395,6 +1395,14 @@ class IndexManager
 
         $sessions_with_category = '';
         $sessions_with_no_category = '';
+
+        $collapsable = api_get_configuration_value('allow_user_session_collapsable');
+        $collapsableLink = '';
+        if ($collapsable) {
+            $collapsableLink = api_get_path(WEB_PATH).'user_portal.php?action=collapse_session';
+        }
+
+        $extraFieldValue = new ExtraFieldValue('session');
         if ($showSessions) {
             $coursesListSessionStyle = api_get_configuration_value('courses_list_session_title_link');
             $coursesListSessionStyle = $coursesListSessionStyle === false ? 1 : $coursesListSessionStyle;
@@ -1587,9 +1595,8 @@ class IndexManager
                                 $params['category_id'] = $session_box['category_id'];
                                 $params['title'] = $session_box['title'];
                                 $params['id_coach'] = $coachId;
-                                $params['coach_url'] = api_get_path(
-                                        WEB_AJAX_PATH
-                                    ).'user_manager.ajax.php?a=get_user_popup&user_id='.$coachId;
+                                $params['coach_url'] = api_get_path(WEB_AJAX_PATH).
+                                    'user_manager.ajax.php?a=get_user_popup&user_id='.$coachId;
                                 $params['coach_name'] = !empty($session_box['coach']) ? $session_box['coach'] : null;
                                 $params['coach_avatar'] = UserManager::getUserPicture(
                                     $coachId,
@@ -1600,6 +1607,18 @@ class IndexManager
                                 $params['duration'] = isset($session_box['duration']) ? ' '.$session_box['duration'] : null;
                                 $params['edit_actions'] = $actions;
                                 $params['show_actions'] = SessionManager::cantEditSession($session_id);
+
+                                if ($collapsable) {
+                                    $collapsableData = Sessionmanager::getCollapsableData(
+                                        $user_id,
+                                        $session_id,
+                                        $extraFieldValue,
+                                        $collapsableLink
+                                    );
+                                    $params['collapsed'] = $collapsableData['collapsed'];
+                                    $params['collapsable_link'] = $collapsableData['collapsable_link'];
+                                }
+
                                 $params['show_description'] = $session_box['show_description'] == 1 && $portalShowDescription;
                                 $params['description'] = $session_box['description'];
                                 $params['visibility'] = $session_box['visibility'];
@@ -1750,6 +1769,17 @@ class IndexManager
                                     // ofaj
                                     $sessionParams[0]['is_old'] = $markAsOld;
                                     $sessionParams[0]['is_future'] = $markAsFuture;
+
+                                    if ($collapsable) {
+                                        $collapsableData = Sessionmanager::getCollapsableData(
+                                            $user_id,
+                                            $session_id,
+                                            $extraFieldValue,
+                                            $collapsableLink
+                                        );
+                                        $sessionParams[0]['collapsable_link'] = $collapsableData['collapsable_link'];
+                                        $sessionParams[0]['collapsed'] = $collapsableData['collapsed'];
+                                    }
                                     $sessionParams[0]['image'] = isset($imageField['value']) ? $imageField['value'] : null;
 
                                     $actions = api_get_path(WEB_CODE_PATH).'session/resume_session.php?id_session='.$session_id;

+ 2 - 2
main/inc/lib/webservices/Rest.php

@@ -1015,10 +1015,10 @@ class Rest extends WebService
         $sessionId = $this->session ? $this->session->getId() : 0;
         $forum = get_forums($forumId, $this->course->getCode(), true, $sessionId);
         $courseInfo = api_get_course_info($this->course->getCode());
-        $id = store_thread($forum, $values, $courseInfo, false, $this->user->getId(), $sessionId);
+        $thread = store_thread($forum, $values, $courseInfo, false, $this->user->getId(), $sessionId);
 
         return [
-            'registered' => $id,
+            'registered' => $thread->getIid(),
         ];
     }
 

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.