Explorar o código

Minor - partial merge from 1.11.x

Julio Montoya %!s(int64=6) %!d(string=hai) anos
pai
achega
62480b4413
Modificáronse 41 ficheiros con 2629 adicións e 1177 borrados
  1. 23 11
      main/inc/lib/AnnouncementEmail.php
  2. 8 7
      main/inc/lib/AnnouncementManager.php
  3. 73 18
      main/inc/lib/CourseChatUtils.php
  4. 3 3
      main/inc/lib/CoursesAndSessionsCatalog.class.php
  5. 1 1
      main/inc/lib/ScheduledAnnouncement.php
  6. 19 18
      main/inc/lib/TicketManager.php
  7. 215 256
      main/inc/lib/agenda.lib.php
  8. 161 68
      main/inc/lib/api.lib.php
  9. 21 9
      main/inc/lib/banner.lib.php
  10. 21 7
      main/inc/lib/certificate.lib.php
  11. 87 30
      main/inc/lib/course.lib.php
  12. 1 1
      main/inc/lib/course_description.lib.php
  13. 42 41
      main/inc/lib/course_home.lib.php
  14. 3 0
      main/inc/lib/database.constants.inc.php
  15. 6 1
      main/inc/lib/diagnoser.lib.php
  16. 55 31
      main/inc/lib/document.lib.php
  17. 103 63
      main/inc/lib/events.lib.php
  18. 454 97
      main/inc/lib/exercise.lib.php
  19. 89 2
      main/inc/lib/exercise_show_functions.lib.php
  20. 26 17
      main/inc/lib/extra_field.lib.php
  21. 1 1
      main/inc/lib/extra_field_option.lib.php
  22. 30 25
      main/inc/lib/extra_field_value.lib.php
  23. 2 2
      main/inc/lib/fixlinks.js
  24. 5 2
      main/inc/lib/groupmanager.lib.php
  25. 34 10
      main/inc/lib/legal.lib.php
  26. 3 3
      main/inc/lib/link.lib.php
  27. 163 18
      main/inc/lib/message.lib.php
  28. 60 25
      main/inc/lib/myspace.lib.php
  29. 2 3
      main/inc/lib/notebook.lib.php
  30. 13 11
      main/inc/lib/pdf.lib.php
  31. 36 10
      main/inc/lib/plugin.class.php
  32. 2 1
      main/inc/lib/security.lib.php
  33. 128 62
      main/inc/lib/sessionmanager.lib.php
  34. 76 5
      main/inc/lib/skill.lib.php
  35. 15 3
      main/inc/lib/social.lib.php
  36. 5 1
      main/inc/lib/sortable_table.class.php
  37. 4 0
      main/inc/lib/template.lib.php
  38. 130 84
      main/inc/lib/tracking.lib.php
  39. 225 32
      main/inc/lib/usergroup.lib.php
  40. 253 174
      main/inc/lib/usermanager.lib.php
  41. 31 24
      main/inc/lib/userportal.lib.php

+ 23 - 11
main/inc/lib/AnnouncementEmail.php

@@ -298,18 +298,30 @@ class AnnouncementEmail
         }
 
         foreach ($users as $user) {
-            if (!empty($this->logger)) {
-                $this->logger->addInfo('Announcement: #'.$this->announcement('id').'. Send email to user: #'.$user['user_id']);
-            }
             $message = $this->message($user['user_id']);
-            MessageManager::send_message_simple(
-                $user['user_id'],
-                $subject,
-                $message,
-                $senderId,
-                $sendToDrhUsers,
-                true
-            );
+            $wasSent = MessageManager::messageWasAlreadySent($senderId, $user['user_id'], $subject, $message);
+            if ($wasSent === false) {
+                if (!empty($this->logger)) {
+                    $this->logger->addInfo(
+                        'Announcement: #'.$this->announcement('id').'. Send email to user: #'.$user['user_id']
+                    );
+                }
+                MessageManager::send_message_simple(
+                    $user['user_id'],
+                    $subject,
+                    $message,
+                    $senderId,
+                    $sendToDrhUsers,
+                    true
+                );
+            } else {
+                if (!empty($this->logger)) {
+                    $this->logger->addInfo(
+                        'Message "'.$subject.'" was already sent. Announcement: #'.$this->announcement('id').'. 
+                        User: #'.$user['user_id']
+                    );
+                }
+            }
 
             if (($counter % $batchSize) === 0) {
                 $em->flush();

+ 8 - 7
main/inc/lib/AnnouncementManager.php

@@ -439,8 +439,6 @@ class AnnouncementManager
         }
 
         //$toUser = $itemProperty->getToUser();
-        //$toUserId = !empty($toUser) ? $toUser->getId() : 0;
-        // The user id is always the current one.
         $toUserId = api_get_user_id();
         $content = self::parseContent(
             $toUserId,
@@ -456,7 +454,8 @@ class AnnouncementManager
         $html .= Display::dateToStringAgoAndLongDate($lastEdit);
         $html .= "</td></tr>";
 
-        if (api_is_allowed_to_edit(false, true)) {
+        $allow = !api_get_configuration_value('hide_announcement_sent_to_users_info');
+        if (api_is_allowed_to_edit(false, true) && $allow) {
             $sent_to = self::sent_to('announcement', $id);
             $sent_to_form = self::sent_to_form($sent_to);
             $html .= Display::tag(
@@ -807,7 +806,7 @@ class AnnouncementManager
         $courseId = api_get_course_int_id();
         $tbl_item_property = Database::get_course_table(TABLE_ITEM_PROPERTY);
         $table = Database::get_course_table(TABLE_ANNOUNCEMENT);
-        $id = intval($id);
+        $id = (int) $id;
 
         $params = [
             'title' => $title,
@@ -825,7 +824,7 @@ class AnnouncementManager
 
         $id_attach = 0;
         if ($row_attach) {
-            $id_attach = intval($row_attach['id']);
+            $id_attach = (int) $row_attach['id'];
         }
 
         if (!empty($file)) {
@@ -1124,14 +1123,16 @@ class AnnouncementManager
             switch ($toGroup) {
                 // it was send to one specific user
                 case null:
-                    $to[] = "USER:".$row['to_user_id'];
+                    if (isset($row['to_user_id']) && !empty($row['to_user_id'])) {
+                        $to[] = 'USER:'.$row['to_user_id'];
+                    }
                     break;
                 // it was sent to everyone
                 case 0:
                     return 'everyone';
                     break;
                 default:
-                    $to[] = "GROUP:".$toGroup;
+                    $to[] = 'GROUP:'.$toGroup;
             }
         }
 

+ 73 - 18
main/inc/lib/CourseChatUtils.php

@@ -4,7 +4,9 @@
 use Chamilo\CoreBundle\Entity\Course;
 use Chamilo\CoreBundle\Entity\CourseRelUser;
 use Chamilo\CoreBundle\Entity\Session;
+use Chamilo\CoreBundle\Entity\SessionRelCourseRelUser;
 use Chamilo\CourseBundle\Entity\CChatConnected;
+use Chamilo\UserBundle\Entity\User;
 use Doctrine\Common\Collections\Criteria;
 use Michelf\MarkdownExtra;
 
@@ -94,7 +96,7 @@ class CourseChatUtils
         $document_path = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document';
         $basepath_chat = '/chat_files';
         $group_info = [];
-        if (!$this->groupId) {
+        if ($this->groupId) {
             $group_info = GroupManager::get_group_properties($this->groupId);
             $basepath_chat = $group_info['directory'].'/chat_files';
         }
@@ -273,8 +275,6 @@ class CourseChatUtils
 
     /**
      * Keep registered to a user as connected.
-     *
-     * @throws \Doctrine\ORM\NonUniqueResultException
      */
     public function keepUserAsConnected()
     {
@@ -707,32 +707,63 @@ class CourseChatUtils
     /**
      * Get the users online data.
      *
+     * @throws \Doctrine\ORM\ORMException
+     * @throws \Doctrine\ORM\OptimisticLockException
+     * @throws \Doctrine\ORM\TransactionRequiredException
+     *
      * @return array
      */
     public function listUsersOnline()
     {
         $subscriptions = $this->getUsersSubscriptions();
         $usersInfo = [];
-        /** @var CourseRelUser $subscription */
-        foreach ($subscriptions as $subscription) {
-            $user = $subscription->getUser();
-            $usersInfo[] = [
-                'id' => $user->getId(),
-                'firstname' => $user->getFirstname(),
-                'lastname' => $user->getLastname(),
-                'status' => !$this->sessionId ? $subscription->getStatus() : $user->getStatus(),
-                'image_url' => UserManager::getUserPicture($user->getId(), USER_IMAGE_SIZE_MEDIUM),
-                'profile_url' => api_get_path(WEB_CODE_PATH).'social/profile.php?u='.$user->getId(),
-                'complete_name' => $user->getCompleteName(),
-                'username' => $user->getUsername(),
-                'email' => $user->getEmail(),
-                'isConnected' => $this->userIsConnected($user->getId()),
-            ];
+
+        if ($this->groupId) {
+            /** @var User $groupUser */
+            foreach ($subscriptions as $groupUser) {
+                $usersInfo[] = $this->formatUser(
+                    $groupUser,
+                    $groupUser->getStatus()
+                );
+            }
+        } else {
+            /** @var CourseRelUser|SessionRelCourseRelUser $subscription */
+            foreach ($subscriptions as $subscription) {
+                $user = $subscription->getUser();
+                $usersInfo[] = $this->formatUser(
+                    $user,
+                    $this->sessionId ? $user->getStatus() : $subscription->getStatus()
+                );
+            }
         }
 
         return $usersInfo;
     }
 
+    /**
+     * Format the user data to return it in the user list.
+     *
+     * @param User $user
+     * @param int  $status
+     *
+     * @return array
+     */
+    private function formatUser(User $user, $status)
+    {
+        return [
+            'id' => $user->getId(),
+            'firstname' => $user->getFirstname(),
+            'lastname' => $user->getLastname(),
+            'status' => $status,
+            'image_url' => UserManager::getUserPicture($user->getId(), USER_IMAGE_SIZE_MEDIUM),
+            'profile_url' => api_get_path(WEB_CODE_PATH).'social/profile.php?u='.$user->getId(),
+            'complete_name' => $user->getCompleteName(),
+            'username' => $user->getUsername(),
+            'email' => $user->getEmail(),
+            'isConnected' => $this->userIsConnected($user->getId()),
+        ];
+    }
+
     /**
      * Get the users subscriptions (SessionRelCourseRelUser array or CourseRelUser array) for chat.
      *
@@ -745,6 +776,30 @@ class CourseChatUtils
     private function getUsersSubscriptions()
     {
         $em = Database::getManager();
+
+        if ($this->groupId) {
+            $students = $em
+                ->createQuery(
+                    'SELECT u FROM ChamiloUserBundle:User u
+                    INNER JOIN ChamiloCourseBundle:CGroupRelUser gru
+                        WITH u.id = gru.userId AND gru.cId = :course
+                    WHERE u.id != :user AND gru.groupId = :group'
+                )
+                ->setParameters(['course' => $this->courseId, 'user' => $this->userId, 'group' => $this->groupId])
+                ->getResult();
+            $tutors = $em
+                ->createQuery(
+                    'SELECT u FROM ChamiloUserBundle:User u
+                    INNER JOIN ChamiloCourseBundle:CGroupRelTutor grt
+                        WITH u.id = grt.userId AND grt.cId = :course
+                    WHERE u.id != :user AND grt.groupId = :group'
+                )
+                ->setParameters(['course' => $this->courseId, 'user' => $this->userId, 'group' => $this->groupId])
+                ->getResult();
+
+            return array_merge($tutors, $students);
+        }
+
         /** @var Course $course */
         $course = $em->find('ChamiloCoreBundle:Course', $this->courseId);
 

+ 3 - 3
main/inc/lib/CoursesAndSessionsCatalog.class.php

@@ -24,7 +24,7 @@ class CoursesAndSessionsCatalog
      */
     public static function is($value = CATALOG_COURSES)
     {
-        $showCoursesSessions = intval(api_get_setting('catalog_show_courses_sessions'));
+        $showCoursesSessions = (int) api_get_setting('catalog_show_courses_sessions');
         if ($showCoursesSessions == $value) {
             return true;
         }
@@ -41,7 +41,7 @@ class CoursesAndSessionsCatalog
      */
     public static function showSessions()
     {
-        $catalogShow = intval(api_get_setting('catalog_show_courses_sessions'));
+        $catalogShow = (int) api_get_setting('catalog_show_courses_sessions');
 
         if ($catalogShow == CATALOG_SESSIONS || $catalogShow == CATALOG_COURSES_SESSIONS) {
             return true;
@@ -59,7 +59,7 @@ class CoursesAndSessionsCatalog
      */
     public static function showCourses()
     {
-        $catalogShow = intval(api_get_setting('catalog_show_courses_sessions'));
+        $catalogShow = (int) api_get_setting('catalog_show_courses_sessions');
 
         if ($catalogShow == CATALOG_COURSES || $catalogShow == CATALOG_COURSES_SESSIONS) {
             return true;

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

@@ -312,7 +312,7 @@ class ScheduledAnnouncement extends Model
                     }
                     $users = SessionManager::get_users_by_session(
                         $sessionId,
-                        '0',
+                        0,
                         false,
                         $urlId
                     );

+ 19 - 18
main/inc/lib/TicketManager.php

@@ -196,7 +196,7 @@ class TicketManager
 
         $table = Database::get_main_table(TABLE_TICKET_CATEGORY_REL_USER);
         foreach ($users as $userId) {
-            if (self::userIsAssignedToCategory($userId, $categoryId) == false) {
+            if (self::userIsAssignedToCategory($userId, $categoryId) === false) {
                 $params = [
                     'category_id' => $categoryId,
                     'user_id' => $userId,
@@ -217,8 +217,8 @@ class TicketManager
     public static function userIsAssignedToCategory($userId, $categoryId)
     {
         $table = Database::get_main_table(TABLE_TICKET_CATEGORY_REL_USER);
-        $userId = intval($userId);
-        $categoryId = intval($categoryId);
+        $userId = (int) $userId;
+        $categoryId = (int) $categoryId;
         $sql = "SELECT * FROM $table 
                 WHERE category_id = $categoryId AND user_id = $userId";
         $result = Database::query($sql);
@@ -234,7 +234,7 @@ class TicketManager
     public static function getUsersInCategory($categoryId)
     {
         $table = Database::get_main_table(TABLE_TICKET_CATEGORY_REL_USER);
-        $categoryId = intval($categoryId);
+        $categoryId = (int) $categoryId;
         $sql = "SELECT * FROM $table WHERE category_id = $categoryId";
         $result = Database::query($sql);
 
@@ -247,7 +247,7 @@ class TicketManager
     public static function deleteAllUserInCategory($categoryId)
     {
         $table = Database::get_main_table(TABLE_TICKET_CATEGORY_REL_USER);
-        $categoryId = intval($categoryId);
+        $categoryId = (int) $categoryId;
         $sql = "DELETE FROM $table WHERE category_id = $categoryId";
         Database::query($sql);
     }
@@ -583,8 +583,8 @@ class TicketManager
         $ticketId,
         $userId
     ) {
-        $ticketId = intval($ticketId);
-        $userId = intval($userId);
+        $ticketId = (int) $ticketId;
+        $userId = (int) $userId;
 
         if (empty($ticketId)) {
             return false;
@@ -636,8 +636,8 @@ class TicketManager
         $status = 'NOL',
         $sendConfirmation = false
     ) {
-        $ticketId = intval($ticketId);
-        $userId = intval($userId);
+        $ticketId = (int) $ticketId;
+        $userId = (int) $userId;
         $table_support_messages = Database::get_main_table(TABLE_TICKET_MESSAGE);
         $table_support_tickets = Database::get_main_table(TABLE_TICKET_TICKET);
         if ($sendConfirmation) {
@@ -764,24 +764,26 @@ class TicketManager
      * @param int $number_of_items
      * @param $column
      * @param $direction
-     * @param int $userId
      *
      * @return array
      */
-    public static function get_tickets_by_user_id(
+    public static function getTicketsByCurrentUser(
         $from,
         $number_of_items,
         $column,
-        $direction,
-        $userId = 0
+        $direction
     ) {
         $table_support_category = Database::get_main_table(TABLE_TICKET_CATEGORY);
         $table_support_tickets = Database::get_main_table(TABLE_TICKET_TICKET);
         $table_support_priority = Database::get_main_table(TABLE_TICKET_PRIORITY);
         $table_support_status = Database::get_main_table(TABLE_TICKET_STATUS);
         $direction = !empty($direction) ? $direction : 'DESC';
-        $userId = !empty($userId) ? $userId : api_get_user_id();
+        $userId = api_get_user_id();
         $userInfo = api_get_user_info($userId);
+
+        if (empty($userInfo)) {
+            return [];
+        }
         $isAdmin = UserManager::is_admin($userId);
 
         if (!isset($_GET['project_id'])) {
@@ -995,11 +997,9 @@ class TicketManager
     }
 
     /**
-     * @param int $userId
-     *
      * @return int
      */
-    public static function get_total_tickets_by_user_id($userId = 0)
+    public static function getTotalTicketsCurrentUser()
     {
         $table_support_category = Database::get_main_table(TABLE_TICKET_CATEGORY);
         $table_support_tickets = Database::get_main_table(TABLE_TICKET_TICKET);
@@ -1106,7 +1106,7 @@ class TicketManager
     /**
      * @param int $id
      *
-     * @return MessageAttachment
+     * @return false|MessageAttachment
      */
     public static function getTicketMessageAttachment($id)
     {
@@ -2428,6 +2428,7 @@ class TicketManager
         $allowRoleList = self::getAllowedRolesFromProject($projectId);
 
         // Check if a role was set to the project
+        // Project 1 is considered the default and is accessible to all users
         if (!empty($allowRoleList) && is_array($allowRoleList)) {
             if (in_array($userInfo['status'], $allowRoleList)) {
                 return true;

+ 215 - 256
main/inc/lib/agenda.lib.php

@@ -29,7 +29,7 @@ class Agenda
      *
      * @param string $type
      * @param int    $senderId  Optional The user sender ID
-     * @param int    $courseId  Opitonal. The course ID
+     * @param int    $courseId  Optional. The course ID
      * @param int    $sessionId Optional The session ID
      */
     public function __construct(
@@ -84,10 +84,9 @@ class Agenda
                     );
 
                     $isGroupAccess = $userHasAccess || $isTutor;
+                    $isAllowToEdit = false;
                     if ($isGroupAccess) {
                         $isAllowToEdit = true;
-                    } else {
-                        $isAllowToEdit = false;
                     }
                 }
 
@@ -111,7 +110,6 @@ class Agenda
 
         $this->setIsAllowedToEdit($isAllowToEdit);
         $this->events = [];
-
         $agendaColors = array_merge(
             [
                 'platform' => 'red', //red
@@ -140,7 +138,7 @@ class Agenda
      */
     public function setSenderId($senderId)
     {
-        $this->senderId = intval($senderId);
+        $this->senderId = (int) $senderId;
     }
 
     /**
@@ -167,7 +165,7 @@ class Agenda
      */
     public function setSessionId($id)
     {
-        $this->sessionId = intval($id);
+        $this->sessionId = (int) $id;
     }
 
     /**
@@ -205,8 +203,8 @@ class Agenda
     /**
      * Adds an event to the calendar.
      *
-     * @param string $start                 datetime format: 2012-06-14 09:00:00
-     * @param string $end                   datetime format: 2012-06-14 09:00:00
+     * @param string $start                 datetime format: 2012-06-14 09:00:00 in local time
+     * @param string $end                   datetime format: 2012-06-14 09:00:00 in local time
      * @param string $allDay                (true, false)
      * @param string $title
      * @param string $content
@@ -324,12 +322,7 @@ class Agenda
                                 foreach ($sendTo['groups'] as $group) {
                                     $groupInfoItem = [];
                                     if ($group) {
-                                        $groupInfoItem = GroupManager::get_group_properties(
-                                            $group
-                                        );
-                                        if ($groupInfoItem) {
-                                            $groupIidItem = $groupInfoItem['iid'];
-                                        }
+                                        $groupInfoItem = GroupManager::get_group_properties($group);
                                     }
 
                                     api_item_property_update(
@@ -447,8 +440,8 @@ class Agenda
     public function getRepeatedInfoByEvent($eventId, $courseId)
     {
         $repeatTable = Database::get_course_table(TABLE_AGENDA_REPEAT);
-        $eventId = intval($eventId);
-        $courseId = intval($courseId);
+        $eventId = (int) $eventId;
+        $courseId = (int) $courseId;
         $sql = "SELECT * FROM $repeatTable
                 WHERE c_id = $courseId AND cal_id = $eventId";
         $res = Database::query($sql);
@@ -460,10 +453,111 @@ class Agenda
         return $repeatInfo;
     }
 
+    /**
+     * @param string $type
+     * @param string $startEvent      in UTC
+     * @param string $endEvent        in UTC
+     * @param string $repeatUntilDate in UTC
+     *
+     * @throws Exception
+     *
+     * @return array
+     */
+    public function generateDatesByType($type, $startEvent, $endEvent, $repeatUntilDate)
+    {
+        $continue = true;
+        $repeatUntilDate = new DateTime($repeatUntilDate, new DateTimeZone('UTC'));
+        $loopMax = 365;
+        $counter = 0;
+        $list = [];
+
+        switch ($type) {
+            case 'daily':
+                $interval = 'P1D';
+                break;
+            case 'weekly':
+                $interval = 'P1W';
+                break;
+            case 'monthlyByDate':
+                $interval = 'P1M';
+                break;
+            case 'monthlyByDay':
+                // not yet implemented
+                break;
+            case 'monthlyByDayR':
+                // not yet implemented
+                break;
+            case 'yearly':
+                $interval = 'P1Y';
+                break;
+        }
+
+        if (empty($interval)) {
+            return [];
+        }
+        $timeZone = api_get_timezone();
+
+        while ($continue) {
+            $startDate = new DateTime($startEvent, new DateTimeZone('UTC'));
+            $endDate = new DateTime($endEvent, new DateTimeZone('UTC'));
+
+            $startDate->add(new DateInterval($interval));
+            $endDate->add(new DateInterval($interval));
+
+            $newStartDate = $startDate->format('Y-m-d H:i:s');
+            $newEndDate = $endDate->format('Y-m-d H:i:s');
+
+            $startEvent = $newStartDate;
+            $endEvent = $newEndDate;
+
+            if ($endDate > $repeatUntilDate) {
+                break;
+            }
+
+            // @todo remove comment code
+            $startDateInLocal = new DateTime($newStartDate, new DateTimeZone($timeZone));
+            //$originalOffset = $startDate->getOffset();
+            if ($startDateInLocal->format('I') == 0) {
+                // Is saving time? Then fix UTC time to add time
+                $seconds = $startDateInLocal->getOffset();
+                $startDate->add(new DateInterval("PT".$seconds."S"));
+                $startDateFixed = $startDate->format('Y-m-d H:i:s');
+                $startDateInLocalFixed = new DateTime($startDateFixed, new DateTimeZone($timeZone));
+                $newStartDate = $startDateInLocalFixed->format('Y-m-d H:i:s');
+            } else {
+                /*$seconds = $startDateInLocal->getOffset();
+                $startDate->add(new DateInterval("PT".$seconds."S"));
+                $startDateFixed = $startDate->format('Y-m-d H:i:s');
+                $startDateInLocalFixed = new DateTime($startDateFixed, new DateTimeZone($timeZone));
+                $newStartDate = $startDateInLocalFixed->format('Y-m-d H:i:s');*/
+            }
+            //var_dump($newStartDate.' - '.$startDateInLocal->format('I'));
+            $endDateInLocal = new DateTime($newEndDate, new DateTimeZone($timeZone));
+
+            if ($endDateInLocal->format('I') == 0) {
+                // Is saving time? Then fix UTC time to add time
+                $seconds = $endDateInLocal->getOffset();
+                $endDate->add(new DateInterval("PT".$seconds."S"));
+                $endDateFixed = $endDate->format('Y-m-d H:i:s');
+                $endDateInLocalFixed = new DateTime($endDateFixed, new DateTimeZone($timeZone));
+                $newEndDate = $endDateInLocalFixed->format('Y-m-d H:i:s');
+            }
+            $list[] = ['start' => $newStartDate, 'end' => $newEndDate, 'i' => $startDateInLocal->format('I')];
+            $counter++;
+
+            // just in case stop if more than $loopMax
+            if ($counter > $loopMax) {
+                break;
+            }
+        }
+
+        return $list;
+    }
+
     /**
      * @param int    $eventId
      * @param string $type
-     * @param string $end     in local time
+     * @param string $end     in UTC
      * @param array  $sentTo
      *
      * @return bool
@@ -477,56 +571,18 @@ class Agenda
             return false;
         }
 
-        $course_id = $this->course['real_id'];
-        $eventId = intval($eventId);
+        $courseId = $this->course['real_id'];
+        $eventId = (int) $eventId;
 
         $sql = "SELECT title, content, start_date, end_date, all_day
                 FROM $t_agenda
-                WHERE c_id = $course_id AND id = $eventId";
+                WHERE c_id = $courseId AND id = $eventId";
         $res = Database::query($sql);
 
         if (Database::num_rows($res) !== 1) {
             return false;
         }
 
-        $row = Database::fetch_array($res);
-        $origStartDate = api_strtotime($row['start_date'], 'UTC');
-        $origEndDate = api_strtotime($row['end_date'], 'UTC');
-        $diff = $origEndDate - $origStartDate;
-
-        $title = $row['title'];
-        $content = $row['content'];
-        $allDay = $row['all_day'];
-
-        $now = time();
-        $type = Database::escape_string($type);
-        $end = api_strtotime($end);
-
-        if (1 <= $end && $end <= 500) {
-            // We assume that, with this type of value, the user actually gives a count of repetitions
-            //and that he wants us to calculate the end date with that (particularly in case of imports from ical)
-            switch ($type) {
-                case 'daily':
-                    $end = $origStartDate + (86400 * $end);
-                    break;
-                case 'weekly':
-                    $end = $this->addWeek($origStartDate, $end);
-                    break;
-                case 'monthlyByDate':
-                    $end = $this->addMonth($origStartDate, $end);
-                    break;
-                case 'monthlyByDay':
-                    //TODO
-                    break;
-                case 'monthlyByDayR':
-                    //TODO
-                    break;
-                case 'yearly':
-                    $end = $this->addYear($origStartDate, $end);
-                    break;
-            }
-        }
-
         $typeList = [
             'daily',
             'weekly',
@@ -536,90 +592,50 @@ class Agenda
             'yearly',
         ];
 
+        if (!in_array($type, $typeList)) {
+            return false;
+        }
+
+        $now = time();
+
         // The event has to repeat *in the future*. We don't allow repeated
         // events in the past
-        if ($end > $now && in_array($type, $typeList)) {
-            $sql = "INSERT INTO $t_agenda_r (c_id, cal_id, cal_type, cal_end)
-                    VALUES ($course_id, '$eventId', '$type', '$end')";
-            Database::query($sql);
-
-            switch ($type) {
-                // @todo improve loop.
-                case 'daily':
-                    for ($i = $origStartDate + 86400; $i <= $end; $i += 86400) {
-                        $start = date('Y-m-d H:i:s', $i);
-                        $repeatEnd = date('Y-m-d H:i:s', $i + $diff);
-                        $this->addEvent(
-                            $start,
-                            $repeatEnd,
-                            $allDay,
-                            $title,
-                            $content,
-                            $sentTo,
-                            false,
-                            $eventId
-                        );
-                    }
-                    break;
-                case 'weekly':
-                    for ($i = $origStartDate + 604800; $i <= $end; $i += 604800) {
-                        $start = date('Y-m-d H:i:s', $i);
-                        $repeatEnd = date('Y-m-d H:i:s', $i + $diff);
-                        $this->addEvent(
-                            $start,
-                            $repeatEnd,
-                            $allDay,
-                            $title,
-                            $content,
-                            $sentTo,
-                            false,
-                            $eventId
-                        );
-                    }
-                    break;
-                case 'monthlyByDate':
-                    $next_start = $this->addMonth($origStartDate);
-                    while ($next_start <= $end) {
-                        $start = date('Y-m-d H:i:s', $next_start);
-                        $repeatEnd = date('Y-m-d H:i:s', $next_start + $diff);
-                        $this->addEvent(
-                            $start,
-                            $repeatEnd,
-                            $allDay,
-                            $title,
-                            $content,
-                            $sentTo,
-                            false,
-                            $eventId
-                        );
-                        $next_start = $this->addMonth($next_start);
-                    }
-                    break;
-                case 'monthlyByDay':
-                    //not yet implemented
-                    break;
-                case 'monthlyByDayR':
-                    //not yet implemented
-                    break;
-                case 'yearly':
-                    $next_start = $this->addYear($origStartDate);
-                    while ($next_start <= $end) {
-                        $start = date('Y-m-d H:i:s', $next_start);
-                        $repeatEnd = date('Y-m-d H:i:s', $next_start + $diff);
-                        $this->addEvent(
-                            $start,
-                            $repeatEnd,
-                            $allDay,
-                            $title,
-                            $content,
-                            $sentTo,
-                            false,
-                            $eventId
-                        );
-                        $next_start = $this->addYear($next_start);
-                    }
-                    break;
-            }
+        if ($end > $now) {
+            return false;
+        }
+
+        $row = Database::fetch_array($res);
+
+        $title = $row['title'];
+        $content = $row['content'];
+        $allDay = $row['all_day'];
+
+        $type = Database::escape_string($type);
+        $end = Database::escape_string($end);
+        $endTimeStamp = api_strtotime($end, 'UTC');
+        $sql = "INSERT INTO $t_agenda_r (c_id, cal_id, cal_type, cal_end)
+                VALUES ($courseId, '$eventId', '$type', '$endTimeStamp')";
+        Database::query($sql);
+
+        $generatedDates = $this->generateDatesByType($type, $row['start_date'], $row['end_date'], $end);
+
+        if (empty($generatedDates)) {
+            return false;
+        }
+
+        foreach ($generatedDates as $dateInfo) {
+            $start = api_get_local_time($dateInfo['start']);
+            $end = api_get_local_time($dateInfo['end']);
+            $this->addEvent(
+                $start,
+                $end,
+                $allDay,
+                $title,
+                $content,
+                $sentTo,
+                false,
+                $eventId
+            );
         }
 
         return true;
@@ -634,7 +650,7 @@ class Agenda
     public function storeAgendaEventAsAnnouncement($item_id, $sentTo = [])
     {
         $table_agenda = Database::get_course_table(TABLE_AGENDA);
-        $course_id = api_get_course_int_id();
+        $courseId = api_get_course_int_id();
 
         // Check params
         if (empty($item_id) || $item_id != strval(intval($item_id))) {
@@ -644,7 +660,7 @@ class Agenda
         // Get the agenda item.
         $item_id = intval($item_id);
         $sql = "SELECT * FROM $table_agenda
-                WHERE c_id = $course_id AND id = ".$item_id;
+                WHERE c_id = $courseId AND id = ".$item_id;
         $res = Database::query($sql);
 
         if (Database::num_rows($res) > 0) {
@@ -761,9 +777,9 @@ class Agenda
                     }
                 }
 
-                $course_id = $this->course['real_id'];
+                $courseId = $this->course['real_id'];
 
-                if (empty($course_id)) {
+                if (empty($courseId)) {
                     return false;
                 }
 
@@ -790,7 +806,7 @@ class Agenda
                         [
                             'id = ? AND c_id = ? AND session_id = ? ' => [
                                 $id,
-                                $course_id,
+                                $courseId,
                                 $this->sessionId,
                             ],
                         ]
@@ -1036,7 +1052,7 @@ class Agenda
                 }
                 break;
             case 'course':
-                $course_id = api_get_course_int_id();
+                $courseId = api_get_course_int_id();
                 $sessionId = api_get_session_id();
                 $isAllowToEdit = api_is_allowed_to_edit(null, true);
 
@@ -1053,7 +1069,7 @@ class Agenda
                     }
                 }
 
-                if (!empty($course_id) && $isAllowToEdit) {
+                if (!empty($courseId) && $isAllowToEdit) {
                     // Delete
                     $eventInfo = $this->get_event($id);
                     if ($deleteAllItemsFromSerie) {
@@ -1085,7 +1101,7 @@ class Agenda
                     // Removing from events.
                     Database::delete(
                         $this->tbl_course_agenda,
-                        ['id = ? AND c_id = ?' => [$id, $course_id]]
+                        ['id = ? AND c_id = ?' => [$id, $courseId]]
                     );
 
                     api_item_property_update(
@@ -1102,7 +1118,7 @@ class Agenda
                         [
                             'cal_id = ? AND c_id = ?' => [
                                 $id,
-                                $course_id,
+                                $courseId,
                             ],
                         ]
                     );
@@ -1133,7 +1149,7 @@ class Agenda
      *
      * @param int    $start
      * @param int    $end
-     * @param int    $course_id
+     * @param int    $courseId
      * @param int    $groupId
      * @param int    $user_id
      * @param string $format
@@ -1143,7 +1159,7 @@ class Agenda
     public function getEvents(
         $start,
         $end,
-        $course_id = null,
+        $courseId = null,
         $groupId = null,
         $user_id = 0,
         $format = 'json'
@@ -1153,7 +1169,7 @@ class Agenda
                 $this->getPlatformEvents($start, $end);
                 break;
             case 'course':
-                $courseInfo = api_get_course_info_by_id($course_id);
+                $courseInfo = api_get_course_info_by_id($courseId);
 
                 // Session coach can see all events inside a session.
                 if (api_is_coach()) {
@@ -1277,8 +1293,8 @@ class Agenda
                         $courseInfo = api_get_course_info_by_id(
                             $courseInfoItem['real_id']
                         );
-                        if (isset($course_id) && !empty($course_id)) {
-                            if ($courseInfo['real_id'] == $course_id) {
+                        if (isset($courseId) && !empty($courseId)) {
+                            if ($courseInfo['real_id'] == $courseId) {
                                 $this->getCourseEvents(
                                     $start,
                                     $end,
@@ -1498,18 +1514,16 @@ class Agenda
      */
     public function getPersonalEvents($start, $end)
     {
-        $start = intval($start);
-        $end = intval($end);
+        $start = (int) $start;
+        $end = (int) $end;
         $startCondition = '';
         $endCondition = '';
 
         if ($start !== 0) {
-            $start = api_get_utc_datetime($start);
-            $startCondition = "AND date >= '".$start."'";
+            $startCondition = "AND date >= '".api_get_utc_datetime($start)."'";
         }
         if ($start !== 0) {
-            $end = api_get_utc_datetime($end);
-            $endCondition = "AND (enddate <= '".$end."' OR enddate IS NULL)";
+            $endCondition = "AND (enddate <= '".api_get_utc_datetime($end)."' OR enddate IS NULL)";
         }
         $user_id = api_get_user_id();
 
@@ -1531,16 +1545,12 @@ class Agenda
 
                 if (!empty($row['date'])) {
                     $event['start'] = $this->formatEventDate($row['date']);
-                    $event['start_date_localtime'] = api_get_local_time(
-                        $row['date']
-                    );
+                    $event['start_date_localtime'] = api_get_local_time($row['date']);
                 }
 
                 if (!empty($row['enddate'])) {
                     $event['end'] = $this->formatEventDate($row['enddate']);
-                    $event['end_date_localtime'] = api_get_local_time(
-                        $row['enddate']
-                    );
+                    $event['end_date_localtime'] = api_get_local_time($row['enddate']);
                 }
 
                 $event['description'] = $row['text'];
@@ -1553,6 +1563,21 @@ class Agenda
             }
         }
 
+        // Add plugin personal events
+
+        $this->plugin = new AppPlugin();
+        $plugins = $this->plugin->getInstalledPluginListObject();
+        /** @var Plugin $plugin */
+        foreach ($plugins as $plugin) {
+            if ($plugin->hasPersonalEvents && method_exists($plugin, 'getPersonalEvents')) {
+                $pluginEvents = $plugin->getPersonalEvents($this, $start, $end);
+
+                if (!empty($pluginEvents)) {
+                    $this->events = array_merge($this->events, $pluginEvents);
+                }
+            }
+        }
+
         return $my_events;
     }
 
@@ -1957,18 +1982,12 @@ class Agenda
                 }
 
                 if (!empty($row['start_date'])) {
-                    $event['start'] = $this->formatEventDate(
-                        $row['start_date']
-                    );
-                    $event['start_date_localtime'] = api_get_local_time(
-                        $row['start_date']
-                    );
+                    $event['start'] = $this->formatEventDate($row['start_date']);
+                    $event['start_date_localtime'] = api_get_local_time($row['start_date']);
                 }
                 if (!empty($row['end_date'])) {
                     $event['end'] = $this->formatEventDate($row['end_date']);
-                    $event['end_date_localtime'] = api_get_local_time(
-                        $row['end_date']
-                    );
+                    $event['end_date_localtime'] = api_get_local_time($row['end_date']);
                 }
 
                 $event['sent_to'] = '';
@@ -2083,7 +2102,7 @@ class Agenda
                 $event['editable'] = false;
                 $event['type'] = 'admin';
 
-                if (api_is_platform_admin() && $this->type == 'admin') {
+                if (api_is_platform_admin() && $this->type === 'admin') {
                     $event['editable'] = true;
                 }
 
@@ -2283,7 +2302,7 @@ class Agenda
     public function getForm($params = [])
     {
         $action = isset($params['action']) ? Security::remove_XSS($params['action']) : null;
-        $id = isset($params['id']) ? intval($params['id']) : null;
+        $id = isset($params['id']) ? (int) $params['id'] : 0;
 
         if ($this->type == 'course') {
             $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&id='.$id.'&type='.$this->type;
@@ -2299,18 +2318,14 @@ class Agenda
             ['enctype' => 'multipart/form-data']
         );
 
-        $idAttach = isset($params['id_attach']) ? intval(
-            $params['id_attach']
-        ) : null;
+        $idAttach = isset($params['id_attach']) ? (int) $params['id_attach'] : null;
         $groupId = api_get_group_id();
-
-        if ($id) {
-            $form_title = get_lang('ModifyCalendarItem');
-        } else {
-            $form_title = get_lang('AddCalendarItem');
+        $form_Title = get_lang('AddCalendarItem');
+        if (!empty($id)) {
+            $form_Title = get_lang('ModifyCalendarItem');
         }
 
-        $form->addElement('header', $form_title);
+        $form->addHeader($form_Title);
         $form->addElement('hidden', 'id', $id);
         $form->addElement('hidden', 'action', $action);
         $form->addElement('hidden', 'id_attach', $idAttach);
@@ -2393,11 +2408,10 @@ class Agenda
             );
 
             if ($isSubEventEdition || $isParentFromSerie) {
+                $repeatInfo = $params['repeat_info'];
                 if ($isSubEventEdition) {
                     $parentEvent = $params['parent_info'];
                     $repeatInfo = $parentEvent['repeat_info'];
-                } else {
-                    $repeatInfo = $params['repeat_info'];
                 }
                 $params['repeat'] = 1;
                 $params['repeat_type'] = $repeatInfo['cal_type'];
@@ -2423,10 +2437,9 @@ class Agenda
                 substr(api_get_local_time($params['end_date']), 0, 16);
         }
 
+        $toolbar = 'Agenda';
         if (!api_is_allowed_to_edit(null, true)) {
             $toolbar = 'AgendaStudent';
-        } else {
-            $toolbar = 'Agenda';
         }
 
         $form->addElement(
@@ -2732,12 +2745,12 @@ class Agenda
                     $fileUserUpload['tmp_name'],
                     $new_path
                 );
-                $course_id = api_get_course_int_id();
+                $courseId = api_get_course_int_id();
                 $size = intval($fileUserUpload['size']);
                 // Storing the attachments if any
                 if ($result) {
                     $params = [
-                        'c_id' => $course_id,
+                        'c_id' => $courseId,
                         'filename' => $file_name,
                         'comment' => $comment,
                         'path' => $new_file_name,
@@ -2827,61 +2840,6 @@ class Agenda
         }
     }
 
-    /**
-     * Adds x weeks to a UNIX timestamp.
-     *
-     * @param int $timestamp The timestamp
-     * @param int $num       The number of weeks to add
-     *
-     * @return int The new timestamp
-     */
-    public function addWeek($timestamp, $num = 1)
-    {
-        return $timestamp + $num * 604800;
-    }
-
-    /**
-     * Adds x months to a UNIX timestamp.
-     *
-     * @param int $timestamp The timestamp
-     * @param int $num       The number of years to add
-     *
-     * @return int The new timestamp
-     */
-    public function addMonth($timestamp, $num = 1)
-    {
-        list($y, $m, $d, $h, $n, $s) = split(
-            '/',
-            date('Y/m/d/h/i/s', $timestamp)
-        );
-        if ($m + $num > 12) {
-            $y += floor($num / 12);
-            $m += $num % 12;
-        } else {
-            $m += $num;
-        }
-
-        return mktime($h, $n, $s, $m, $d, $y);
-    }
-
-    /**
-     * Adds x years to a UNIX timestamp.
-     *
-     * @param int $timestamp The timestamp
-     * @param int $num       The number of years to add
-     *
-     * @return int The new timestamp
-     */
-    public function addYear($timestamp, $num = 1)
-    {
-        list($y, $m, $d, $h, $n, $s) = split(
-            '/',
-            date('Y/m/d/h/i/s', $timestamp)
-        );
-
-        return mktime($h, $n, $s, $m, $d, $y + $num);
-    }
-
     /**
      * @param int $eventId
      *
@@ -3202,8 +3160,7 @@ class Agenda
                             $repeat['UNTIL'],
                             new DateTimeZone($currentTimeZone)
                         );
-                        $until = $until->format('Y-m-d H:i');
-                        //$res = agenda_add_repeat_item($courseInfo, $id, $freq, $until, $attendee);
+                        $until = $until->format('Y-m-d H:i:s');
                         $this->addRepeatedItem(
                             $id,
                             $freq,
@@ -3554,7 +3511,8 @@ class Agenda
     }
 
     /**
-     * This function retrieves all the personal agenda items and add them to the agenda items found by the other functions.
+     * This function retrieves all the personal agenda items and add them to the agenda items found by the other
+     * functions.
      */
     public static function get_personal_agenda_items(
         $user_id,
@@ -3677,7 +3635,8 @@ class Agenda
      * @param    array    Agendaitems
      * @param    int    Month number
      * @param    int    Year number
-     * @param    array    Array of strings containing long week day names (deprecated, you can send an empty array instead)
+     * @param    array    Array of strings containing long week day names (deprecated, you can send an empty array
+     *                          instead)
      * @param    string    The month name
      */
     public static function display_mymonthcalendar(
@@ -4044,12 +4003,12 @@ class Agenda
                     )."main/calendar/agenda.php?cidReq=".urlencode(
                         $course["code"]
                     )."&day=$agendaday&month=$month&year=$year#$agendaday";
-                list($year, $month, $day, $hour, $min, $sec) = split(
+                list($year, $month, $day, $hour, $min, $sec) = explode(
                     '[-: ]',
                     $item['start_date']
                 );
                 $start_date = $year.$month.$day.$hour.$min;
-                list($year, $month, $day, $hour, $min, $sec) = split(
+                list($year, $month, $day, $hour, $min, $sec) = explode(
                     '[-: ]',
                     $item['end_date']
                 );
@@ -4077,11 +4036,11 @@ class Agenda
      */
     public static function get_personal_agenda_item($id)
     {
-        $tbl_personal_agenda = Database::get_main_table(TABLE_PERSONAL_AGENDA);
+        $table = Database::get_main_table(TABLE_PERSONAL_AGENDA);
         $id = intval($id);
         // make sure events of the personal agenda can only be seen by the user himself
         $user = api_get_user_id();
-        $sql = " SELECT * FROM ".$tbl_personal_agenda." WHERE id=".$id." AND user = ".$user;
+        $sql = " SELECT * FROM ".$table." WHERE id=".$id." AND user = ".$user;
         $result = Database::query($sql);
         if (Database::num_rows($result) == 1) {
             $item = Database::fetch_array($result);
@@ -4155,7 +4114,7 @@ class Agenda
      *
      * @return bool|string
      */
-    private function formatEventDate($utcTime)
+    public function formatEventDate($utcTime)
     {
         $utcTimeZone = new DateTimeZone('UTC');
         $platformTimeZone = new DateTimeZone(api_get_timezone());

+ 161 - 68
main/inc/lib/api.lib.php

@@ -196,12 +196,14 @@ define('LOG_USER_DELETE', 'user_deleted');
 define('LOG_USER_CREATE', 'user_created');
 define('LOG_USER_ENABLE', 'user_enable');
 define('LOG_USER_DISABLE', 'user_disable');
+define('LOG_USER_ANONYMIZE', 'user_anonymized');
 define('LOG_USER_FIELD_CREATE', 'user_field_created');
 define('LOG_USER_FIELD_DELETE', 'user_field_deleted');
 define('LOG_SESSION_CREATE', 'session_created');
 define('LOG_SESSION_DELETE', 'session_deleted');
 define('LOG_SESSION_ADD_USER_COURSE', 'session_add_user_course');
 define('LOG_SESSION_DELETE_USER_COURSE', 'session_delete_user_course');
+define('LOG_SESSION_ADD_USER', 'session_add_user');
 define('LOG_SESSION_DELETE_USER', 'session_delete_user');
 define('LOG_SESSION_ADD_COURSE', 'session_add_course');
 define('LOG_SESSION_DELETE_COURSE', 'session_delete_course');
@@ -270,6 +272,12 @@ define('LOG_WORK_DATA', 'work_data_array');
 define('LOG_MY_FOLDER_PATH', 'path');
 define('LOG_MY_FOLDER_NEW_PATH', 'new_path');
 
+define('LOG_TERM_CONDITION_ACCEPTED', 'term_condition_accepted');
+define('LOG_USER_CONFIRMED_EMAIL', 'user_confirmed_email');
+define('LOG_USER_REMOVED_LEGAL_ACCEPT', 'user_removed_legal_accept');
+
+define('LOG_USER_DELETE_ACCOUNT_REQUEST', 'user_delete_account_request');
+
 define('USERNAME_PURIFIER', '/[^0-9A-Za-z_\.]/');
 
 //used when login_is_email setting is true
@@ -460,7 +468,8 @@ define('RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS', 0); //show score and ex
 define('RESULT_DISABLE_NO_SCORE_AND_EXPECTED_ANSWERS', 1); //Do not show score nor answers
 define('RESULT_DISABLE_SHOW_SCORE_ONLY', 2); //Show score only
 define('RESULT_DISABLE_SHOW_FINAL_SCORE_ONLY_WITH_CATEGORIES', 3); //Show final score only with categories
-define('RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT', 4); //Show final score only with categories
+define('RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT', 4);
+// 4: Show final score only with categories and show expected answers only on the last attempt
 
 define('EXERCISE_MAX_NAME_SIZE', 80);
 
@@ -487,6 +496,7 @@ define('DRAGGABLE', 18);
 define('MATCHING_DRAGGABLE', 19);
 define('ANNOTATION', 20);
 define('READING_COMPREHENSION', 21);
+define('MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY', 22);
 
 define('EXERCISE_CATEGORY_RANDOM_SHUFFLED', 1);
 define('EXERCISE_CATEGORY_RANDOM_ORDERED', 2);
@@ -538,6 +548,7 @@ define(
     UNIQUE_ANSWER_IMAGE.':'.
     DRAGGABLE.':'.
     MATCHING_DRAGGABLE.':'.
+    MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY.':'.
     ANNOTATION
 );
 
@@ -620,6 +631,7 @@ define('RESOURCE_EVENT', 'calendar_event');
 define('RESOURCE_LINK', 'link');
 define('RESOURCE_COURSEDESCRIPTION', 'course_description');
 define('RESOURCE_LEARNPATH', 'learnpath');
+define('RESOURCE_LEARNPATH_CATEGORY', 'learnpath_category');
 define('RESOURCE_ANNOUNCEMENT', 'announcement');
 define('RESOURCE_FORUM', 'forum');
 define('RESOURCE_FORUMTOPIC', 'thread');
@@ -1493,7 +1505,7 @@ function _api_format_user($user, $add_password = false, $loadAvatars = true)
         $result[$attribute] = isset($user[$attribute]) ? $user[$attribute] : null;
     }
 
-    $user_id = intval($user['user_id']);
+    $user_id = (int) $user['user_id'];
     // Maintain the user_id index for backwards compatibility
     $result['user_id'] = $result['id'] = $user_id;
 
@@ -1549,7 +1561,7 @@ function _api_format_user($user, $add_password = false, $loadAvatars = true)
         $result['user_is_online'] = $user['user_is_online'] == true ? 1 : 0;
     }
     if (isset($user['user_is_online_in_chat'])) {
-        $result['user_is_online_in_chat'] = intval($user['user_is_online_in_chat']);
+        $result['user_is_online_in_chat'] = (int) $user['user_is_online_in_chat'];
     }
 
     if ($add_password) {
@@ -1643,7 +1655,7 @@ function api_get_user_info(
     }
 
     // Make sure user_id is safe
-    $user_id = intval($user_id);
+    $user_id = (int) $user_id;
 
     // Re-use user information if not stale and already stored in APCu
     if ($cacheAvailable === true) {
@@ -1672,7 +1684,7 @@ function api_get_user_info(
                     false,
                     true
                 );
-                if (@intval($user_status['user_chat_status']) == 1) {
+                if ((int) $user_status['user_chat_status'] == 1) {
                     $user_online_in_chat = 1;
                 }
             }
@@ -1704,10 +1716,12 @@ function api_get_user_info(
 function api_get_user_entity($userId)
 {
     $userId = (int) $userId;
-    /** @var \Chamilo\UserBundle\Repository\UserRepository $repo */
-    $repo = Database::getManager()->getRepository('ChamiloUserBundle:User');
+    $repo = UserManager::getRepository();
+
+    /** @var User $user */
+    $user = $repo->find($userId);
 
-    return $repo->find($userId);
+    return $user;
 }
 
 /**
@@ -1715,7 +1729,7 @@ function api_get_user_entity($userId)
  *
  * @param string $username
  *
- * @return array $user_info array user_id, lastname, firstname, username, email
+ * @return mixed $user_info array user_id, lastname, firstname, username, email or false on error
  *
  * @author Yannick Warnier <yannick.warnier@beeznest.com>
  */
@@ -1730,9 +1744,9 @@ function api_get_user_info_from_username($username = '')
             WHERE username='".Database::escape_string($username)."'";
     $result = Database::query($sql);
     if (Database::num_rows($result) > 0) {
-        $result_array = Database::fetch_array($result);
+        $resultArray = Database::fetch_array($result);
 
-        return _api_format_user($result_array);
+        return _api_format_user($resultArray);
     }
 
     return false;
@@ -1754,9 +1768,9 @@ function api_get_user_info_from_email($email = '')
             WHERE email ='".Database::escape_string($email)."' LIMIT 1";
     $result = Database::query($sql);
     if (Database::num_rows($result) > 0) {
-        $result_array = Database::fetch_array($result);
+        $resultArray = Database::fetch_array($result);
 
-        return _api_format_user($result_array);
+        return _api_format_user($resultArray);
     }
 
     return false;
@@ -1949,8 +1963,8 @@ function api_get_cidreq_params($courseCode, $sessionId = 0, $groupId = 0)
 function api_get_cidreq($addSessionId = true, $addGroupId = true, $origin = '')
 {
     $courseCode = api_get_course_id();
-    $url = empty($courseCode) ? '' : 'cidReq='.htmlspecialchars($courseCode, ENT_QUOTES);
-    $origin = empty($origin) ? api_get_origin() : htmlspecialchars($origin, ENT_QUOTES);
+    $url = empty($courseCode) ? '' : 'cidReq='.htmlspecialchars($courseCode);
+    $origin = empty($origin) ? api_get_origin() : Security::remove_XSS($origin);
 
     if ($addSessionId) {
         if (!empty($url)) {
@@ -2089,7 +2103,7 @@ function api_get_session_entity($id = 0)
 function api_get_course_info_by_id($id = null)
 {
     if (!empty($id)) {
-        $id = intval($id);
+        $id = (int) $id;
         $course_table = Database::get_main_table(TABLE_MAIN_COURSE);
         $course_cat_table = Database::get_main_table(TABLE_MAIN_CATEGORY);
         $sql = "SELECT
@@ -2393,10 +2407,8 @@ function get_status_from_code($status_code)
     switch ($status_code) {
         case STUDENT:
             return get_lang('Student', '');
-        case TEACHER:
-            return get_lang('Teacher', '');
         case COURSEMANAGER:
-            return get_lang('Manager', '');
+            return get_lang('Teacher', '');
         case SESSIONADMIN:
             return get_lang('SessionsAdmin', '');
         case DRH:
@@ -2531,7 +2543,7 @@ function api_get_session_visibility(
         return 0; // Means that the session is still available.
     }
 
-    $session_id = intval($session_id);
+    $session_id = (int) $session_id;
     $tbl_session = Database::get_main_table(TABLE_MAIN_SESSION);
 
     $result = Database::query("SELECT * FROM $tbl_session WHERE id = $session_id");
@@ -2575,6 +2587,7 @@ function api_get_session_visibility(
 
         return SESSION_AVAILABLE;
     }
+
     // If start date was set.
     if (!empty($row['access_start_date'])) {
         $visibility = $now > api_strtotime($row['access_start_date'], 'UTC') ? SESSION_AVAILABLE : SESSION_INVISIBLE;
@@ -2590,8 +2603,7 @@ function api_get_session_visibility(
         }
     }
 
-    /* If I'm a coach the visibility can change in my favor depending in
-     the coach dates */
+    // If I'm a coach the visibility can change in my favor depending in the coach dates.
     $isCoach = api_is_coach($session_id, $courseId);
 
     if ($isCoach) {
@@ -2657,7 +2669,7 @@ function api_get_session_condition(
     $with_base_content = false,
     $session_field = 'session_id'
 ) {
-    $session_id = intval($session_id);
+    $session_id = (int) $session_id;
 
     if (empty($session_field)) {
         $session_field = "session_id";
@@ -2938,7 +2950,7 @@ function api_is_platform_admin($allowSessionAdmins = false, $allowDrh = false)
  */
 function api_is_platform_admin_by_id($user_id = null, $url = null)
 {
-    $user_id = intval($user_id);
+    $user_id = (int) $user_id;
     if (empty($user_id)) {
         $user_id = api_get_user_id();
     }
@@ -2950,7 +2962,7 @@ function api_is_platform_admin_by_id($user_id = null, $url = null)
         return $is_admin;
     }
     // We get here only if $url is set
-    $url = intval($url);
+    $url = (int) $url;
     $url_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
     $sql = "SELECT * FROM $url_user_table
             WHERE access_url_id = $url AND user_id = $user_id";
@@ -2969,7 +2981,7 @@ function api_is_platform_admin_by_id($user_id = null, $url = null)
  */
 function api_get_user_status($user_id = null)
 {
-    $user_id = intval($user_id);
+    $user_id = (int) $user_id;
     if (empty($user_id)) {
         $user_id = api_get_user_id();
     }
@@ -3057,9 +3069,9 @@ function api_is_course_session_coach($user_id, $courseId, $session_id)
     $session_table = Database::get_main_table(TABLE_MAIN_SESSION);
     $session_rel_course_rel_user_table = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
 
-    $user_id = intval($user_id);
-    $session_id = intval($session_id);
-    $courseId = intval($courseId);
+    $user_id = (int) $user_id;
+    $session_id = (int) $session_id;
+    $courseId = (int) $courseId;
 
     $sql = "SELECT DISTINCT session.id
             FROM $session_table
@@ -3089,7 +3101,7 @@ function api_is_coach($session_id = 0, $courseId = null, $check_student_view = t
     $userId = api_get_user_id();
 
     if (!empty($session_id)) {
-        $session_id = intval($session_id);
+        $session_id = (int) $session_id;
     } else {
         $session_id = api_get_session_id();
     }
@@ -3100,7 +3112,7 @@ function api_is_coach($session_id = 0, $courseId = null, $check_student_view = t
     }
 
     if (!empty($courseId)) {
-        $courseId = intval($courseId);
+        $courseId = (int) $courseId;
     } else {
         $courseId = api_get_course_int_id();
     }
@@ -3211,7 +3223,7 @@ function api_is_invitee()
  */
 function api_is_session_in_category($session_id, $category_name)
 {
-    $session_id = intval($session_id);
+    $session_id = (int) $session_id;
     $category_name = Database::escape_string($category_name);
     $tbl_session = Database::get_main_table(TABLE_MAIN_SESSION);
     $tbl_session_category = Database::get_main_table(TABLE_MAIN_SESSION_CATEGORY);
@@ -3401,6 +3413,19 @@ function api_is_allowed_to_edit(
     }
 
     $sessionId = api_get_session_id();
+
+    if ($sessionId && api_get_configuration_value('session_courses_read_only_mode')) {
+        $efv = new ExtraFieldValue('course');
+        $lockExrafieldField = $efv->get_values_by_handler_and_field_variable(
+            api_get_course_int_id(),
+            'session_courses_read_only_mode'
+        );
+
+        if (!empty($lockExrafieldField['value'])) {
+            return false;
+        }
+    }
+
     $is_allowed_coach_to_edit = api_is_coach(null, null, $check_student_view);
     $session_visibility = api_get_session_visibility($sessionId);
     $is_courseAdmin = api_is_course_admin();
@@ -3554,10 +3579,6 @@ function api_is_allowed_to_session_edit($tutor = false, $coach = false)
             // Get the session visibility
             $session_visibility = api_get_session_visibility($sessionId);
             // if 5 the session is still available
-            //@todo We could load the session_rel_course_rel_user permission to increase the level of detail.
-            //echo api_get_user_id();
-            //echo api_get_course_id();
-
             switch ($session_visibility) {
                 case SESSION_VISIBLE_READ_ONLY: // 1
                     return false;
@@ -3990,14 +4011,14 @@ function api_get_item_visibility(
     }
 
     $tool = Database::escape_string($tool);
-    $id = intval($id);
+    $id = (int) $id;
     $session = (int) $session;
     $TABLE_ITEMPROPERTY = Database::get_course_table(TABLE_ITEM_PROPERTY);
-    $course_id = intval($_course['real_id']);
+    $course_id = (int) $_course['real_id'];
 
     $userCondition = '';
     if (!empty($user_id)) {
-        $user_id = intval($user_id);
+        $user_id = (int) $user_id;
         $userCondition = " AND to_user_id = $user_id ";
     }
 
@@ -4009,7 +4030,7 @@ function api_get_item_visibility(
 
     $groupCondition = '';
     if (!empty($group_id)) {
-        $group_id = intval($group_id);
+        $group_id = (int) $group_id;
         $groupCondition = " AND to_group_id = '$group_id' ";
     }
 
@@ -4030,7 +4051,7 @@ function api_get_item_visibility(
     }
     $row = Database::fetch_array($res);
 
-    return $row['visibility'];
+    return (int) $row['visibility'];
 }
 
 /**
@@ -4057,7 +4078,7 @@ function api_item_property_delete(
         return false;
     }
 
-    $courseId = intval($courseInfo['real_id']);
+    $courseId = (int) $courseInfo['real_id'];
 
     if (empty($courseId) || empty($tool) || empty($itemId)) {
         return false;
@@ -4171,7 +4192,7 @@ function api_item_property_update(
     $time = api_get_utc_datetime();
 
     if (!empty($session_id)) {
-        $session_id = intval($session_id);
+        $session_id = (int) $session_id;
     } else {
         $session_id = api_get_session_id();
     }
@@ -4185,7 +4206,7 @@ function api_item_property_update(
 
     if (!is_null($to_user_id)) {
         // $to_user_id has more priority than $to_group_id
-        $to_user_id = intval($to_user_id);
+        $to_user_id = (int) $to_user_id;
         $to_field = 'to_user_id';
         $to_value = $to_user_id;
     } else {
@@ -4194,11 +4215,11 @@ function api_item_property_update(
         $to_value = $to_group_id;
     }
 
-    $toValueCondition = empty($to_value) ? "NULL" : "'$to_value'";
+    $toValueCondition = empty($to_value) ? 'NULL' : "'$to_value'";
     // Set filters for $to_user_id and $to_group_id, with priority for $to_user_id
     $condition_session = " AND session_id = $session_id ";
     if (empty($session_id)) {
-        $condition_session = " AND (session_id = 0 OR session_id IS NULL) ";
+        $condition_session = ' AND (session_id = 0 OR session_id IS NULL) ';
     }
 
     $filter = " c_id = $course_id AND tool = '$tool' AND ref = $item_id $condition_session ";
@@ -4437,7 +4458,7 @@ function api_get_item_property_by_tool($tool, $course_code, $session_id = null)
 
     // Definition of tables.
     $item_property_table = Database::get_course_table(TABLE_ITEM_PROPERTY);
-    $session_id = intval($session_id);
+    $session_id = (int) $session_id;
     $session_condition = ' AND session_id = '.$session_id;
     if (empty($session_id)) {
         $session_condition = " AND (session_id = 0 OR session_id IS NULL) ";
@@ -4519,7 +4540,7 @@ function api_get_item_property_id($course_code, $tool, $ref, $sessionId = 0)
 {
     $course_info = api_get_course_info($course_code);
     $tool = Database::escape_string($tool);
-    $ref = intval($ref);
+    $ref = (int) $ref;
 
     // Definition of tables.
     $tableItemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
@@ -4527,7 +4548,7 @@ function api_get_item_property_id($course_code, $tool, $ref, $sessionId = 0)
     $sessionId = (int) $sessionId;
     $sessionCondition = " AND session_id = $sessionId ";
     if (empty($sessionId)) {
-        $sessionCondition = " AND (session_id = 0 OR session_id IS NULL) ";
+        $sessionCondition = ' AND (session_id = 0 OR session_id IS NULL) ';
     }
     $sql = "SELECT id FROM $tableItemProperty
             WHERE
@@ -4626,13 +4647,13 @@ function api_get_item_property_info($course_id, $tool, $ref, $session_id = 0, $g
     }
 
     $tool = Database::escape_string($tool);
-    $ref = intval($ref);
     $course_id = $courseInfo['real_id'];
-    $session_id = intval($session_id);
+    $ref = (int) $ref;
+    $session_id = (int) $session_id;
 
     $sessionCondition = " session_id = $session_id";
     if (empty($session_id)) {
-        $sessionCondition = " (session_id = 0 OR session_id IS NULL) ";
+        $sessionCondition = ' (session_id = 0 OR session_id IS NULL) ';
     }
 
     // Definition of tables.
@@ -4646,7 +4667,7 @@ function api_get_item_property_info($course_id, $tool, $ref, $session_id = 0, $g
                 $sessionCondition ";
 
     if (!empty($groupId)) {
-        $groupId = intval($groupId);
+        $groupId = (int) $groupId;
         $sql .= " AND to_group_id = $groupId ";
     }
 
@@ -4972,6 +4993,8 @@ function api_get_language_from_type($lang_type)
  *
  * @param int $languageId
  *
+ * @throws Exception
+ *
  * @return array
  */
 function api_get_language_info($languageId)
@@ -5962,7 +5985,7 @@ function api_get_access_urls($from = 0, $to = 1000000, $order = 'url', $directio
 function api_get_access_url($id, $returnDefault = true)
 {
     static $staticResult;
-    $id = intval($id);
+    $id = (int) $id;
 
     if (isset($staticResult[$id])) {
         $result = $staticResult[$id];
@@ -6410,7 +6433,7 @@ function api_get_current_access_url_id()
  */
 function api_get_access_url_from_user($user_id)
 {
-    $user_id = intval($user_id);
+    $user_id = (int) $user_id;
     $table_url_rel_user = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
     $table_url = Database::get_main_table(TABLE_MAIN_ACCESS_URL);
     $sql = "SELECT access_url_id
@@ -6705,7 +6728,7 @@ function api_get_tools_lists($my_tool = null)
  */
 function api_check_term_condition($userId)
 {
-    if (api_get_setting('allow_terms_conditions') == 'true') {
+    if (api_get_setting('allow_terms_conditions') === 'true') {
         // Check if exists terms and conditions
         if (LegalManager::count() == 0) {
             return true;
@@ -6721,10 +6744,10 @@ function api_check_term_condition($userId)
             $result = $data['value'];
             $user_conditions = explode(':', $result);
             $version = $user_conditions[0];
-            $lang_id = $user_conditions[1];
-            $real_version = LegalManager::get_last_version($lang_id);
+            $langId = $user_conditions[1];
+            $realVersion = LegalManager::get_last_version($langId);
 
-            return $version >= $real_version;
+            return $version >= $realVersion;
         }
 
         return false;
@@ -7656,8 +7679,8 @@ function api_is_global_chat_enabled()
 {
     return
         !api_is_anonymous() &&
-        api_get_setting('allow_global_chat') == 'true' &&
-        api_get_setting('allow_social_tool') == 'true';
+        api_get_setting('allow_global_chat') === 'true' &&
+        api_get_setting('allow_social_tool') === 'true';
 }
 
 /**
@@ -7969,6 +7992,18 @@ function api_set_settings_and_plugins()
     $_SESSION['_plugins'] = $_plugins;
 }
 
+/**
+ * Modify default memory_limit and max_execution_time limits
+ * Needed when processing long tasks.
+ */
+function api_set_more_memory_and_time_limits()
+{
+    if (function_exists('ini_set')) {
+        api_set_memory_limit('256M');
+        ini_set('max_execution_time', 1800);
+    }
+}
+
 /**
  * Tries to set memory limit, if authorized and new limit is higher than current.
  *
@@ -8447,7 +8482,7 @@ function convert_double_quote_to_single($in_text)
  */
 function api_get_origin()
 {
-    $origin = isset($_REQUEST['origin']) ? htmlspecialchars($_REQUEST['origin'], ENT_QUOTES) : '';
+    $origin = isset($_REQUEST['origin']) ? Security::remove_XSS($_REQUEST['origin']) : '';
 
     return $origin;
 }
@@ -8589,7 +8624,7 @@ function api_is_student_boss()
 function api_is_excluded_user_type($checkDB = false, $userId = 0)
 {
     if ($checkDB) {
-        $userId = empty($userId) ? api_get_user_id() : intval($userId);
+        $userId = empty($userId) ? api_get_user_id() : (int) $userId;
 
         if ($userId == 0) {
             return true;
@@ -8881,12 +8916,10 @@ function api_mail_html(
 
     // Attachment ...
     if (!empty($data_file)) {
-        $o = 0;
         foreach ($data_file as $file_attach) {
             if (!empty($file_attach['path']) && !empty($file_attach['filename'])) {
                 $mail->AddAttachment($file_attach['path'], $file_attach['filename']);
             }
-            $o++;
         }
     }
 
@@ -9038,7 +9071,7 @@ function api_unique_multidim_array($array, $key)
 }
 
 /**
- * Limit the access to Session Admins wheen the limit_session_admin_role
+ * Limit the access to Session Admins when the limit_session_admin_role
  * configuration variable is set to true.
  */
 function api_protect_limit_for_session_admin()
@@ -9086,8 +9119,8 @@ function api_upload_file($type, $file, $itemId, $cropParameters = '')
         }
 
         $pathToSave = $path.$name;
+        $result = moveUploadedFile($file, $pathToSave);
 
-        $result = move_uploaded_file($file['tmp_name'], $pathToSave);
         if ($result) {
             if (!empty($cropParameters)) {
                 $image = new Image($pathToSave);
@@ -9170,6 +9203,26 @@ function api_remove_uploaded_file($type, $file)
     }
 }
 
+/**
+ * @param string $type
+ * @param int    $itemId
+ * @param string $file
+ *
+ * @return bool
+ */
+function api_remove_uploaded_file_by_id($type, $itemId, $file)
+{
+    $file = api_get_uploaded_file($type, $itemId, $file, false);
+    $typePath = api_get_path(SYS_UPLOAD_PATH).$type;
+    if (Security::check_abs_path($file, $typePath) && file_exists($file) && is_file($file)) {
+        unlink($file);
+
+        return true;
+    }
+
+    return false;
+}
+
 /**
  * Converts string value to float value.
  *
@@ -9230,9 +9283,49 @@ function location($url, $exit = true)
  */
 function api_get_web_url()
 {
-    if (api_get_setting('server_type') == 'test') {
+    if (api_get_setting('server_type') === 'test') {
         return api_get_path(WEB_PATH).'web/app_dev.php/';
     } else {
         return api_get_path(WEB_PATH).'web/';
     }
 }
+
+/**
+ * @param string $from
+ * @param string $to
+ *
+ * @return string
+ */
+function api_get_relative_path($from, $to)
+{
+    // some compatibility fixes for Windows paths
+    $from = is_dir($from) ? rtrim($from, '\/').'/' : $from;
+    $to = is_dir($to) ? rtrim($to, '\/').'/' : $to;
+    $from = str_replace('\\', '/', $from);
+    $to = str_replace('\\', '/', $to);
+
+    $from = explode('/', $from);
+    $to = explode('/', $to);
+    $relPath = $to;
+
+    foreach ($from as $depth => $dir) {
+        // find first non-matching dir
+        if ($dir === $to[$depth]) {
+            // ignore this directory
+            array_shift($relPath);
+        } else {
+            // get number of remaining dirs to $from
+            $remaining = count($from) - $depth;
+            if ($remaining > 1) {
+                // add traversals up to first matching dir
+                $padLength = (count($relPath) + $remaining - 1) * -1;
+                $relPath = array_pad($relPath, $padLength, '..');
+                break;
+            } else {
+                $relPath[0] = './'.$relPath[0];
+            }
+        }
+    }
+
+    return implode('/', $relPath);
+}

+ 21 - 9
main/inc/lib/banner.lib.php

@@ -200,6 +200,26 @@ function return_logo($theme = '')
     );
 }
 
+/**
+ * Check if user have access to "who is online" page.
+ *
+ * @return bool
+ */
+function accessToWhoIsOnline()
+{
+    $user_id = api_get_user_id();
+    $course_id = api_get_course_int_id();
+    $access = false;
+    if ((api_get_setting('showonline', 'world') == 'true' && !$user_id) ||
+        (api_get_setting('showonline', 'users') == 'true' && $user_id) ||
+        (api_get_setting('showonline', 'course') == 'true' && $user_id && $course_id)
+    ) {
+        $access = true;
+    }
+
+    return $access;
+}
+
 /**
  * Return HTML string of a list as <li> items.
  *
@@ -208,19 +228,11 @@ function return_logo($theme = '')
 function returnNotificationMenu()
 {
     $courseInfo = api_get_course_info();
-    $course_id = 0;
-    if (!empty($courseInfo)) {
-        $course_id = $courseInfo['code'];
-    }
-
     $user_id = api_get_user_id();
     $sessionId = api_get_session_id();
     $html = '';
 
-    if ((api_get_setting('showonline', 'world') == 'true' && !$user_id) ||
-        (api_get_setting('showonline', 'users') == 'true' && $user_id) ||
-        (api_get_setting('showonline', 'course') == 'true' && $user_id && $course_id)
-    ) {
+    if (accessToWhoIsOnline()) {
         $number = getOnlineUsersCount();
         $number_online_in_course = getOnlineUsersInCourseCount($user_id, $courseInfo);
 

+ 21 - 7
main/inc/lib/certificate.lib.php

@@ -152,12 +152,12 @@ class Certificate extends Model
         if (!empty($path_info) && isset($path_info)) {
             $this->certification_user_path = $path_info.'certificate/';
             $this->certification_web_user_path = $web_path_info.'certificate/';
-
+            $mode = api_get_permissions_for_new_directories();
             if (!is_dir($path_info)) {
-                mkdir($path_info, 0777, true);
+                mkdir($path_info, $mode, true);
             }
             if (!is_dir($this->certification_user_path)) {
-                mkdir($this->certification_user_path, 0777);
+                mkdir($this->certification_user_path, $mode);
             }
         }
     }
@@ -645,8 +645,8 @@ class Certificate extends Model
 
             // Remove media=screen to be available when printing a document
             $certificateContent = str_replace(
-                api_get_path(WEB_CSS_PATH).'editor.css" media="screen"',
-                api_get_path(WEB_CSS_PATH).'editor.css" ',
+                ' media="screen"',
+                '',
                 $certificateContent
             );
 
@@ -700,16 +700,19 @@ class Certificate extends Model
         }
 
         $sessions = SessionManager::get_sessions_by_user($this->user_id, false, true);
+        $totalTimeInLearningPaths = 0;
         $sessionsApproved = [];
+        $coursesApproved = [];
         if ($sessions) {
             foreach ($sessions as $session) {
                 $allCoursesApproved = [];
                 foreach ($session['courses'] as $course) {
                     $courseInfo = api_get_course_info_by_id($course['real_id']);
+                    $courseCode = $courseInfo['code'];
                     $gradebookCategories = Category::load(
                         null,
                         null,
-                        $courseInfo['code'],
+                        $courseCode,
                         null,
                         false,
                         $session['session_id']
@@ -725,6 +728,16 @@ class Certificate extends Model
                         );
 
                         if ($result) {
+                            $coursesApproved[$course['real_id']] = $courseInfo['title'];
+
+                            // Find time spent in LP
+                            $totalTimeInLearningPaths += Tracking::get_time_spent_in_lp(
+                                $this->user_id,
+                                $courseCode,
+                                [],
+                                $session['session_id']
+                            );
+
                             $allCoursesApproved[] = true;
                         }
                     }
@@ -780,7 +793,8 @@ class Certificate extends Model
         );
         $tplContent->assign('skills', $skills);
         $tplContent->assign('sessions', $sessionsApproved);
-
+        $tplContent->assign('courses', $coursesApproved);
+        $tplContent->assign('time_spent_in_lps', api_time_to_hms($totalTimeInLearningPaths));
         $layoutContent = $tplContent->get_template('gradebook/custom_certificate.tpl');
         $content = $tplContent->fetch($layoutContent);
 

+ 87 - 30
main/inc/lib/course.lib.php

@@ -608,13 +608,12 @@ class CourseManager
             return false; //detected possible SQL injection
         }
 
-        $course_code = Database::escape_string($course_code);
         $courseInfo = api_get_course_info($course_code);
         $courseId = $courseInfo['real_id'];
         $courseCode = $courseInfo['code'];
         $userCourseCategoryId = intval($userCourseCategoryId);
 
-        if (empty($user_id) || empty($course_code)) {
+        if (empty($user_id) || empty($courseCode)) {
             return false;
         }
 
@@ -657,7 +656,7 @@ class CourseManager
             Event::addEvent(
                 LOG_SUBSCRIBE_USER_TO_COURSE,
                 LOG_COURSE_CODE,
-                $course_code,
+                $courseCode,
                 api_get_utc_datetime(),
                 api_get_user_id(),
                 $courseId,
@@ -685,7 +684,7 @@ class CourseManager
             Event::addEvent(
                 LOG_SUBSCRIBE_USER_TO_COURSE,
                 LOG_COURSE_CODE,
-                $course_code,
+                $courseCode,
                 api_get_utc_datetime(),
                 api_get_user_id(),
                 $courseId
@@ -772,11 +771,11 @@ class CourseManager
      * @author Hugues Peeters
      * @author Roan Embrechts
      *
-     * @param int    $user_id              the id of the user
-     * @param string $courseCode           the course code
-     * @param int    $status               (optional) The user's status in the course
-     * @param int    $userCourseCategoryId
-     * @param   int The user category in which this subscription will be classified
+     * @param int    $user_id                the id of the user
+     * @param string $courseCode             the course code
+     * @param int    $status                 (optional) The user's status in the course
+     * @param int    $userCourseCategoryId   The user category in which this subscription will be classified
+     * @param bool   $checkTeacherPermission
      *
      * @return false|string true if subscription succeeds, boolean false otherwise
      * @assert ('', '') === false
@@ -785,7 +784,8 @@ class CourseManager
         $user_id,
         $courseCode,
         $status = STUDENT,
-        $userCourseCategoryId = 0
+        $userCourseCategoryId = 0,
+        $checkTeacherPermission = true
     ) {
         $debug = false;
         $user_table = Database::get_main_table(TABLE_MAIN_USER);
@@ -825,7 +825,7 @@ class CourseManager
             return false; // The user has been subscribed to the course.
         }
 
-        if (!api_is_course_admin()) {
+        if ($checkTeacherPermission && !api_is_course_admin()) {
             // Check in advance whether subscription is allowed or not for this course.
             $sql = "SELECT code, visibility FROM $course_table
                     WHERE id = $courseId AND subscribe = '".SUBSCRIBE_NOT_ALLOWED."'";
@@ -1281,6 +1281,7 @@ class CourseManager
      * @param array     $userIdList
      * @param string    $filterByActive
      * @param array     $sessionIdList
+     * @param string    $searchByKeyword
      *
      * @return array|int
      */
@@ -1297,12 +1298,13 @@ class CourseManager
         $courseCodeList = [],
         $userIdList = [],
         $filterByActive = null,
-        $sessionIdList = []
+        $sessionIdList = [],
+        $searchByKeyword = ''
     ) {
         $course_table = Database::get_main_table(TABLE_MAIN_COURSE);
         $sessionTable = Database::get_main_table(TABLE_MAIN_SESSION);
 
-        $session_id = intval($session_id);
+        $session_id = (int) $session_id;
         $course_code = Database::escape_string($course_code);
         $courseInfo = api_get_course_info($course_code);
         $courseId = 0;
@@ -1399,12 +1401,13 @@ class CourseManager
                 }
             }
 
-            $sql .= ' FROM '.Database::get_main_table(TABLE_MAIN_USER).' as user '
-                  .' LEFT JOIN '.Database::get_main_table(TABLE_MAIN_COURSE_USER).' as course_rel_user
+            $sql .= " FROM ".Database::get_main_table(TABLE_MAIN_USER)." as user 
+                      LEFT JOIN ".Database::get_main_table(TABLE_MAIN_COURSE_USER)." as course_rel_user
                       ON 
                         user.id = course_rel_user.user_id AND
-                        course_rel_user.relation_type <> '.COURSE_RELATION_TYPE_RRHH.'  '
-                  ." INNER JOIN $course_table course ON course_rel_user.c_id = course.id ";
+                        course_rel_user.relation_type <> ".COURSE_RELATION_TYPE_RRHH."
+                       INNER JOIN $course_table course 
+                       ON course_rel_user.c_id = course.id ";
 
             if (!empty($course_code)) {
                 $sql .= ' AND course_rel_user.c_id = "'.$courseId.'"';
@@ -1412,7 +1415,7 @@ class CourseManager
             $where[] = ' course_rel_user.c_id IS NOT NULL ';
 
             if (isset($filter_by_status) && is_numeric($filter_by_status)) {
-                $filter_by_status = intval($filter_by_status);
+                $filter_by_status = (int) $filter_by_status;
                 $filter_by_status_condition = " course_rel_user.status = $filter_by_status AND ";
             }
         }
@@ -1465,10 +1468,19 @@ class CourseManager
         }
 
         if (isset($filterByActive)) {
-            $filterByActive = intval($filterByActive);
+            $filterByActive = (int) $filterByActive;
             $sql .= ' AND user.active = '.$filterByActive;
         }
 
+        if (!empty($searchByKeyword)) {
+            $searchByKeyword = Database::escape_string($searchByKeyword);
+            $sql .= " AND (
+                        user.firstname LIKE '$searchByKeyword' OR 
+                        user.username LIKE '$searchByKeyword' OR 
+                        user.lastname LIKE '$searchByKeyword'
+                    ) ";
+        }
+
         $sql .= ' '.$order_by.' '.$limit;
 
         $rs = Database::query($sql);
@@ -2302,6 +2314,8 @@ class CourseManager
                 rename($course_dir, $archive_dir);
             }
 
+            Category::deleteFromCourse($course['code']);
+
             // Unsubscribe all users from the course
             $sql = "DELETE FROM $table_course_user WHERE c_id = $courseId";
             Database::query($sql);
@@ -2372,8 +2386,10 @@ class CourseManager
                     WHERE course_id = $courseId";
             Database::query($sql);
 
-            $sql = "DELETE FROM skill_rel_course WHERE c_id = $courseId";
-            Database::query($sql);
+            if (api_get_configuration_value('allow_skill_rel_items')) {
+                $sql = "DELETE FROM skill_rel_course WHERE c_id = $courseId";
+                Database::query($sql);
+            }
 
             // Deletes all groups, group-users, group-tutors information
             // To prevent fK mix up on some tables
@@ -4305,7 +4321,7 @@ class CourseManager
         $course_info = api_get_course_info_by_id($course['real_id']);
         $course_visibility = $course_info['visibility'];
 
-        if ($course_visibility == COURSE_VISIBILITY_HIDDEN) {
+        if ($course_visibility === COURSE_VISIBILITY_HIDDEN) {
             return '';
         }
 
@@ -4360,9 +4376,10 @@ class CourseManager
                     $sessionCourseAvailable = true;
                 }
 
-                if ($userInCourseStatus == COURSEMANAGER || $sessionCourseAvailable) {
+                if ($userInCourseStatus === COURSEMANAGER || $sessionCourseAvailable) {
                     $session_url = $course_info['course_public_url'].'?id_session='.$course_info['id_session'];
-                    $session_title = '<a href="'.$session_url.'">'.$course_info['name'].'</a>'.$notifications;
+                    $session_title = '<a title="'.$course_info['name'].'" href="'.$session_url.'">'.
+                        $course_info['name'].'</a>'.$notifications;
                 } else {
                     $session_title = $course_info['name'];
                 }
@@ -4397,6 +4414,7 @@ class CourseManager
         $params['image'] = $image;
         $params['link'] = $session_url;
         $params['title'] = $session_title;
+        $params['name'] = $course_info['name'];
         $params['edit_actions'] = '';
         $params['document'] = '';
         $params['category'] = $course_info['categoryName'];
@@ -4407,7 +4425,10 @@ class CourseManager
             if (api_is_platform_admin()) {
                 $params['edit_actions'] .= api_get_path(WEB_CODE_PATH).'course_info/infocours.php?cidReq='.$course_info['code'];
                 if ($load_dirs) {
-                    $params['document'] .= '<a id="document_preview_'.$course_info['real_id'].'_'.$course_info['id_session'].'" class="document_preview btn btn-default btn-sm" href="javascript:void(0);">'.
+                    $params['document'] .= '<a 
+                        id="document_preview_'.$course_info['real_id'].'_'.$course_info['id_session'].'" 
+                        class="document_preview btn btn-default btn-sm" 
+                        href="javascript:void(0);">'.
                         Display::returnFontAwesomeIcon('folder-open').'</a>';
                     $params['document'] .= Display::div('', [
                         'id' => 'document_result_'.$course_info['real_id'].'_'.$course_info['id_session'],
@@ -4421,7 +4442,6 @@ class CourseManager
                 $course_info['real_id'],
                 true
             );
-
             $course_coachs = self::get_coachs_from_course(
                 $course_info['id_session'],
                 $course_info['real_id']
@@ -4437,7 +4457,9 @@ class CourseManager
         $special = isset($course['special_course']) ? true : false;
         $params['title'] = $session_title;
         $params['special'] = $special;
-        $params['code'] = $course_info['visual_code'];
+        if (api_get_setting('display_coursecode_in_courselist') === 'true') {
+            $params['visual_code'] = '('.$course_info['visual_code'].')';
+        }
         $params['extra'] = '';
         $html = $params;
 
@@ -4456,8 +4478,8 @@ class CourseManager
                 $session_category_id = self::get_session_category_id_by_session_id($course_info['id_session']);
 
                 if (
-                    $session['access_start_date'] == '0000-00-00 00:00:00' || empty($session['access_start_date']) ||
-                    $session['access_start_date'] == '0000-00-00'
+                    $session['access_start_date'] === '0000-00-00 00:00:00' || empty($session['access_start_date']) ||
+                    $session['access_start_date'] === '0000-00-00'
                 ) {
                     $session['dates'] = '';
                     if (api_get_setting('show_session_coach') === 'true') {
@@ -4465,7 +4487,9 @@ class CourseManager
                     }
                     $active = true;
                 } else {
-                    $session['dates'] = ' - '.get_lang('From').' '.$session['access_start_date'].' '.get_lang('To').' '.$session['access_end_date'];
+                    $session['dates'] = ' - '.
+                        get_lang('From').' '.$session['access_start_date'].' '.
+                        get_lang('To').' '.$session['access_end_date'];
                     if (api_get_setting('show_session_coach') === 'true') {
                         $session['coach'] = get_lang('GeneralCoach').': '.$sessionCoachName;
                     }
@@ -5587,6 +5611,7 @@ class CourseManager
             'enable_forum_auto_launch',
             'show_course_in_user_language',
             'email_to_teachers_on_new_work_feedback',
+            'student_delete_own_publication',
         ];
 
         $courseModels = ExerciseLib::getScoreModels();
@@ -5991,6 +6016,7 @@ class CourseManager
     {
         $userList = self::getCourseUsers(true);
         $groupList = self::getCourseGroups();
+
         $array = self::buildSelectOptions(
             $groupList,
             $userList,
@@ -6648,6 +6674,37 @@ class CourseManager
         return '';
     }
 
+    /**
+     * @param Course $course
+     *
+     * @return bool
+     */
+    public static function hasPicture(Course $course)
+    {
+        return file_exists(api_get_path(SYS_COURSE_PATH).$course->getDirectory().'/course-pic85x85.png');
+    }
+
+    /**
+     * Get the course picture path.
+     *
+     * @param Course $course
+     * @param bool   $fullSize
+     *
+     * @return null|string
+     */
+    public static function getPicturePath(Course $course, $fullSize = false)
+    {
+        if (!self::hasPicture($course)) {
+            return null;
+        }
+
+        if ($fullSize) {
+            return api_get_path(WEB_COURSE_PATH).$course->getDirectory().'/course-pic.png';
+        }
+
+        return api_get_path(WEB_COURSE_PATH).$course->getDirectory().'/course-pic85x85.png';
+    }
+
     /**
      * @param ToolChain $toolList
      */

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

@@ -84,7 +84,7 @@ class CourseDescription
             true,
             true
         );
-        $course_id = api_get_course_int_id();
+        $course_id = $this->course_id ?: api_get_course_int_id();
         $sql = "SELECT * FROM $table
 		        WHERE c_id = $course_id $condition_session
 		        ORDER BY id ";

+ 42 - 41
main/inc/lib/course_home.lib.php

@@ -132,10 +132,10 @@ class CourseHome
         foreach ($all_tools as &$tool) {
             if ($tool['image'] == 'scormbuilder.gif') {
                 // check if the published learnpath is visible for student
-                $published_lp_id = self::get_published_lp_id_from_link($tool['link']);
+                $lpId = self::getPublishedLpIdFromLink($tool['link']);
                 if (!api_is_allowed_to_edit(null, true) &&
                     !learnpath::is_lp_visible_for_student(
-                        $published_lp_id,
+                        $lpId,
                         api_get_user_id(),
                         api_get_course_id(),
                         api_get_session_id()
@@ -338,11 +338,11 @@ class CourseHome
             foreach ($all_tools_list as &$tool) {
                 if ($tool['image'] == 'scormbuilder.gif') {
                     // check if the published learnpath is visible for student
-                    $published_lp_id = self::get_published_lp_id_from_link($tool['link']);
+                    $lpId = self::getPublishedLpIdFromLink($tool['link']);
 
                     if (!api_is_allowed_to_edit(null, true) &&
                         !learnpath::is_lp_visible_for_student(
-                            $published_lp_id,
+                            $lpId,
                             api_get_user_id(),
                             api_get_course_id(),
                             api_get_session_id()
@@ -644,34 +644,35 @@ class CourseHome
                 }
             }
 
-            if ($temp_row['image'] == 'scormbuilder.gif') {
-                $lp_id = self::get_published_lp_id_from_link($temp_row['link']);
-                $lp = new learnpath(
-                    api_get_course_id(),
-                    $lp_id,
-                    $userId
-                );
-                $path = $lp->get_preview_image_path(ICON_SIZE_BIG);
-
-                $add = learnpath::is_lp_visible_for_student(
-                    $lp_id,
-                    $userId,
-                    api_get_course_id(),
-                    api_get_session_id()
-                );
-                if ($path) {
-                    $temp_row['custom_image'] = $path;
-                }
-            }
+            switch ($temp_row['image']) {
+                case 'scormbuilder.gif':
+                    $lpId = self::getPublishedLpIdFromLink($temp_row['link']);
+                    $lp = new learnpath(
+                        api_get_course_id(),
+                        $lpId,
+                        $userId
+                    );
+                    $path = $lp->get_preview_image_path(ICON_SIZE_BIG);
 
-            if ($temp_row['image'] === 'lp_category.gif') {
-                $lpCategory = self::getPublishedLpCategoryFromLink(
-                    $temp_row['link']
-                );
-                $add = learnpath::categoryIsVisibleForStudent(
-                    $lpCategory,
-                    $user
-                );
+                    $add = learnpath::is_lp_visible_for_student(
+                        $lpId,
+                        $userId,
+                        api_get_course_id(),
+                        api_get_session_id()
+                    );
+                    if ($path) {
+                        $temp_row['custom_image'] = $path;
+                    }
+                    break;
+                case 'lp_category.gif':
+                    $lpCategory = self::getPublishedLpCategoryFromLink(
+                        $temp_row['link']
+                    );
+                    $add = learnpath::categoryIsVisibleForStudent(
+                        $lpCategory,
+                        $user
+                    );
+                    break;
             }
 
             if ($add) {
@@ -839,13 +840,13 @@ class CourseHome
                 $tool['original_link'] = $tool['link'];
                 if ($tool['image'] == 'scormbuilder.gif') {
                     // check if the published learnpath is visible for student
-                    $published_lp_id = self::get_published_lp_id_from_link($tool['link']);
+                    $lpId = self::getPublishedLpIdFromLink($tool['link']);
                     if (api_is_allowed_to_edit(null, true)) {
                         $studentview = true;
                     }
                     if (!api_is_allowed_to_edit(null, true) &&
                         !learnpath::is_lp_visible_for_student(
-                            $published_lp_id,
+                            $lpId,
                             api_get_user_id(),
                             api_get_course_id(),
                             api_get_session_id()
@@ -1197,18 +1198,18 @@ class CourseHome
      *
      * @return int Learning path id
      */
-    public static function get_published_lp_id_from_link($published_lp_link)
+    public static function getPublishedLpIdFromLink($link)
     {
-        $lp_id = 0;
-        $param_lp_id = strstr($published_lp_link, 'lp_id=');
-        if (!empty($param_lp_id)) {
-            $a_param_lp_id = explode('=', $param_lp_id);
-            if (isset($a_param_lp_id[1])) {
-                $lp_id = intval($a_param_lp_id[1]);
+        $lpId = 0;
+        $param = strstr($link, 'lp_id=');
+        if (!empty($param)) {
+            $paramList = explode('=', $param);
+            if (isset($paramList[1])) {
+                $lpId = (int) $paramList[1];
             }
         }
 
-        return $lp_id;
+        return $lpId;
     }
 
     /**

+ 3 - 0
main/inc/lib/database.constants.inc.php

@@ -344,3 +344,6 @@ define('TABLE_CAL_DATE', 'cal_dates');
 define('TABLE_CAL_HORAIRE', 'cal_horaire');
 define('TABLE_CAL_TEMP', 'cal_temp');
 define('TABLE_STATISTIC_TRACK_E_EXERCICES_TEMP', 'track_e_exercices_temp');
+
+define('TABLE_USER_INFO_DEF', 'userinfo_def');
+define('TABLE_USER_INFO_CONTENT', 'userinfo_content');

+ 6 - 1
main/inc/lib/diagnoser.lib.php

@@ -203,7 +203,12 @@ class Diagnoser
                     $dir = api_get_path(SYS_PATH);
                     $du = exec('du -sh '.$dir, $err);
                     list($size, $none) = explode("\t", $du);
-                    $limit = $_configuration[$access_url_id]['hosting_limit_disk_space'];
+                    $limit = 0;
+                    if (isset($_configuration[$access_url_id])) {
+                        if (isset($_configuration[$access_url_id]['hosting_limit_disk_space'])) {
+                            $limit = $_configuration[$access_url_id]['hosting_limit_disk_space'];
+                        }
+                    }
                     $message2 .= sprintf(get_lang('TotalSpaceUsedByPortalXLimitIsYMB'), $size, $limit);
                 }
             }

+ 55 - 31
main/inc/lib/document.lib.php

@@ -139,7 +139,7 @@ class DocumentManager
             'movie' => 'video/x-sgi-movie',
             'mp2' => 'audio/mpeg',
             'mp3' => 'audio/mpeg',
-            'mp4' => 'video/mpeg4-generic',
+            'mp4' => 'video/mp4',
             'mpa' => 'audio/mpeg',
             'mpe' => 'video/mpeg',
             'mpeg' => 'video/mpeg',
@@ -263,7 +263,7 @@ class DocumentManager
 
         //$filename will be an array if a . was found
         if (is_array($extension)) {
-            $extension = strtolower($extension[sizeof($extension) - 1]);
+            $extension = strtolower($extension[count($extension) - 1]);
         } else {
             //file without extension
             $extension = 'empty';
@@ -273,6 +273,7 @@ class DocumentManager
         if (isset($mime_types[$extension])) {
             return $mime_types[$extension];
         }
+
         //else return octet-stream
         return 'application/octet-stream';
     }
@@ -349,7 +350,9 @@ class DocumentManager
             // Commented to avoid double caching declaration when playing with IE and HTTPS
             //header('Cache-Control: no-cache, must-revalidate');
             //header('Pragma: no-cache');
+
             $contentType = self::file_get_mime_type($filename);
+
             switch ($contentType) {
                 case 'text/html':
                     if (isset($lpFixedEncoding) && $lpFixedEncoding === 'true') {
@@ -379,11 +382,13 @@ class DocumentManager
 
             header('Content-type: '.$contentType);
             header('Content-Length: '.$len);
-            $user_agent = strtolower($_SERVER['HTTP_USER_AGENT']);
-            if (strpos($user_agent, 'msie')) {
+            $userAgent = strtolower($_SERVER['HTTP_USER_AGENT']);
+
+            if (strpos($userAgent, 'msie')) {
                 header('Content-Disposition: ; filename= '.$filename);
             } else {
-                header('Content-Disposition: inline; filename= '.$filename);
+                //header('Content-Disposition: inline');
+                header('Content-Disposition: inline;');
             }
 
             if ($fixLinksHttpToHttps) {
@@ -1294,11 +1299,12 @@ class DocumentManager
     /**
      * Gets the document data with a given id.
      *
-     * @param int    $id           Document Id (id field in c_document table)
-     * @param string $course_code  Course code
-     * @param bool   $load_parents load folder parents
-     * @param int    $session_id   The session ID,
-     *                             0 if requires context *out of* session, and null to use global context
+     * @param int    $id            Document Id (id field in c_document table)
+     * @param string $course_code   Course code
+     * @param bool   $load_parents  load folder parents
+     * @param int    $session_id    The session ID,
+     *                              0 if requires context *out of* session, and null to use global context
+     * @param bool   $ignoreDeleted
      *
      * @return array document content
      */
@@ -1306,7 +1312,8 @@ class DocumentManager
         $id,
         $course_code,
         $load_parents = false,
-        $session_id = null
+        $session_id = null,
+        $ignoreDeleted = false
     ) {
         $course_info = api_get_course_info($course_code);
         $course_id = $course_info['real_id'];
@@ -1315,15 +1322,19 @@ class DocumentManager
             return false;
         }
 
-        $session_id = empty($session_id) ? api_get_session_id() : intval($session_id);
+        $session_id = empty($session_id) ? api_get_session_id() : (int) $session_id;
         $www = api_get_path(WEB_COURSE_PATH).$course_info['path'].'/document';
         $TABLE_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
-        $id = intval($id);
+        $id = (int) $id;
         $sessionCondition = api_get_session_condition($session_id, true, true);
 
         $sql = "SELECT * FROM $TABLE_DOCUMENT
                 WHERE c_id = $course_id $sessionCondition AND id = $id";
 
+        if ($ignoreDeleted) {
+            $sql .= " AND path NOT LIKE '%_DELETED_%' ";
+        }
+
         $result = Database::query($sql);
         if ($result && Database::num_rows($result) == 1) {
             $row = Database::fetch_array($result, 'ASSOC');
@@ -1848,6 +1859,7 @@ class DocumentManager
         $user_info = api_get_user_info($user_id);
         $first_name = $user_info['firstname'];
         $last_name = $user_info['lastname'];
+        $username = $user_info['username'];
         $official_code = $user_info['official_code'];
 
         // Teacher information
@@ -1883,6 +1895,7 @@ class DocumentManager
         $info_to_replace_in_content_html = [
             $first_name,
             $last_name,
+            $username,
             $organization_name,
             $portal_name,
             $teacher_first_name,
@@ -1902,6 +1915,7 @@ class DocumentManager
         $tags = [
             '((user_firstname))',
             '((user_lastname))',
+            '((user_username))',
             '((gradebook_institution))',
             '((gradebook_sitename))',
             '((teacher_firstname))',
@@ -2990,9 +3004,10 @@ class DocumentManager
     {
         $TABLE_ITEMPROPERTY = Database::get_course_table(TABLE_ITEM_PROPERTY);
         $TABLE_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
-        $session_id = intval($session_id);
-        $group_id = intval($group_id);
-        $course_id = intval($course_id);
+
+        $session_id = (int) $session_id;
+        $group_id = (int) $group_id;
+        $course_id = (int) $course_id;
 
         if (!$course_id) {
             $course_id = api_get_course_int_id();
@@ -3013,9 +3028,9 @@ class DocumentManager
                 INNER JOIN $TABLE_DOCUMENT AS docs
                 ON (docs.id = props.ref AND props.c_id = docs.c_id)
                 WHERE
-                    props.c_id 	= $course_id AND
-                    docs.c_id 	= $course_id AND
-                    props.tool 	= '".TOOL_DOCUMENT."' AND
+                    props.c_id = $course_id AND
+                    docs.c_id = $course_id AND
+                    props.tool = '".TOOL_DOCUMENT."' AND
                     props.visibility <> 2
                     $group_condition
                     $session_condition
@@ -3025,7 +3040,7 @@ class DocumentManager
         if ($result && Database::num_rows($result) != 0) {
             $row = Database::fetch_row($result);
 
-            return $row[0];
+            return (int) $row[0];
         } else {
             return 0;
         }
@@ -3122,7 +3137,12 @@ class DocumentManager
 
         }*/
         //$type = "video/$extension";
-        $html = '<video id="myvideo"  src="'.$file.'" controls '.$type.'">';
+        //$fileInfo = parse_url($file);
+        //$type = self::file_get_mime_type(basename($fileInfo['path']));
+
+        $html = '<video id="myvideo" controls>';
+        $html .= '<source src="'.$file.'" >';
+        $html .= '</video>';
 
         return $html;
     }
@@ -3315,7 +3335,7 @@ class DocumentManager
                     api_get_user_id()
                 );
 
-                if ($showInvisibleFiles == false) {
+                if ($showInvisibleFiles === false) {
                     if (!$is_visible) {
                         continue;
                     }
@@ -3336,7 +3356,7 @@ class DocumentManager
         } else {
             if (is_array($parentData)) {
                 $documents[$parentData['title']] = [
-                    'id' => intval($folderId),
+                    'id' => (int) $folderId,
                     'files' => $newResources,
                 ];
             }
@@ -3355,15 +3375,15 @@ class DocumentManager
         );
 
         $return .= $write_result;
-        if ($lp_id == false) {
-            $url = api_get_path(WEB_AJAX_PATH).'lp.ajax.php?a=get_documents&url='.$overwrite_url.'&lp_id='.$lp_id.'&cidReq='.$course_info['code'];
+        if ($lp_id === false) {
+            $url = api_get_path(WEB_AJAX_PATH).
+                'lp.ajax.php?a=get_documents&url='.$overwrite_url.'&lp_id='.$lp_id.'&cidReq='.$course_info['code'];
             $return .= "<script>
             $('.doc_folder').click(function() {
                 var realId = this.id;
                 var my_id = this.id.split('_')[2];
                 var tempId = 'temp_'+my_id;
                 $('#res_'+my_id).show();
-
                 var tempDiv = $('#'+realId).find('#'+tempId);
                 if (tempDiv.length == 0) {
                     $.ajax({
@@ -3385,7 +3405,6 @@ class DocumentManager
                 $('.lp_resource').remove();
                 $('.document_preview_container').html('');
             });
-
             </script>";
         } else {
             //For LPs
@@ -3408,7 +3427,6 @@ class DocumentManager
                     image.attr('src', '".Display::returnIconPath('nolines_minus.gif')."');
                     $('#'+id).hide();
                     $('#'+tempId).show();
-
                     var tempDiv = $('#'+parentId).find('#'+tempId);
                     if (tempDiv.length == 0) {
                         $.ajax({
@@ -3489,7 +3507,7 @@ class DocumentManager
                     $return .= '</div>';
                     $return .= '</ul>';
                 } else {
-                    if ($resource['filetype'] == 'folder') {
+                    if ($resource['filetype'] === 'folder') {
                         $return .= self::parseFolder($folderId, $resource, $lp_id);
                     } else {
                         $return .= self::parseFile(
@@ -6774,10 +6792,16 @@ class DocumentManager
             'id' => $parentId,
             'deleteid' => $documentData['id'],
         ]);
+
         $btn = Display::url(
             $iconEn,
-            api_get_self()."?$courseParams&$urlDeleteParams",
-            ['onclick' => "return confirmation('$titleToShow');"]
+            '#',
+            [
+                'data-item-title' => $titleToShow,
+                'data-href' => api_get_self()."?$courseParams&$urlDeleteParams",
+                'data-toggle' => 'modal',
+                'data-target' => '#confirm-delete',
+            ]
         );
 
         if (

+ 103 - 63
main/inc/lib/events.lib.php

@@ -83,7 +83,7 @@ class Event
         Database::query($sql);
 
         // Auto subscribe
-        $user_status = $userInfo['status'] == SESSIONADMIN ? 'sessionadmin' : $userInfo['status'] == COURSEMANAGER ? 'teacher' : $userInfo['status'] == DRH ? 'drh' : 'student';
+        $user_status = $userInfo['status'] == SESSIONADMIN ? 'sessionadmin' : $userInfo['status'] == COURSEMANAGER ? 'teacher' : $userInfo['status'] == DRH ? 'DRH' : 'student';
         $autoSubscribe = api_get_setting($user_status.'_autosubscribe');
         if ($autoSubscribe) {
             $autoSubscribe = explode('|', $autoSubscribe);
@@ -103,6 +103,10 @@ class Event
      */
     public static function accessCourse()
     {
+        if (Session::read('login_as')) {
+            return false;
+        }
+
         $TABLETRACK_ACCESS = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ACCESS);
         //for "what's new" notification
         $TABLETRACK_LASTACCESS = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LASTACCESS);
@@ -142,7 +146,7 @@ class Event
     }
 
     /**
-     * @param string $tool name of the tool (name in mainDb.accueil table)
+     * @param string $tool name of the tool
      *
      * @author Sebastien Piraux <piraux_seb@hotmail.com>
      * @desc Record information for access event for tools
@@ -160,6 +164,10 @@ class Event
      */
     public static function event_access_tool($tool)
     {
+        if (Session::read('login_as')) {
+            return false;
+        }
+
         $tool = Database::escape_string($tool);
 
         if (empty($tool)) {
@@ -240,6 +248,10 @@ class Event
      */
     public static function event_download($documentUrl)
     {
+        if (Session::read('login_as')) {
+            return false;
+        }
+
         $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_DOWNLOADS);
         $documentUrl = Database::escape_string($documentUrl);
 
@@ -279,6 +291,10 @@ class Event
      */
     public static function event_upload($documentId)
     {
+        if (Session::read('login_as')) {
+            return false;
+        }
+
         $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_UPLOADS);
         $courseId = api_get_course_int_id();
         $reallyNow = api_get_utc_datetime();
@@ -319,6 +335,10 @@ class Event
      */
     public static function event_link($linkId)
     {
+        if (Session::read('login_as')) {
+            return false;
+        }
+
         $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LINKS);
         $reallyNow = api_get_utc_datetime();
         $userId = api_get_user_id();
@@ -376,73 +396,73 @@ class Event
         $remindList = [],
         $endDate = null
     ) {
-        if ($exeId != '') {
-            /*
-             * Code commented due BT#8423 do not change the score to 0.
-             *
-             * Validation in case of fraud with actived control time
-            if (!ExerciseLib::exercise_time_control_is_valid($exo_id, $learnpath_id, $learnpath_item_id)) {
-                $score = 0;
-            }
-            */
-            if (!isset($status) || empty($status)) {
-                $status = '';
-            } else {
-                $status = Database::escape_string($status);
-            }
-
-            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
+        if (empty($exeId)) {
+            return false;
+        }
 
-            if (!empty($questionsList)) {
-                $questionsList = array_map('intval', $questionsList);
-            }
+        /*
+         * Code commented due BT#8423 do not change the score to 0.
+         *
+         * Validation in case of fraud with actived control time
+        if (!ExerciseLib::exercise_time_control_is_valid($exo_id, $learnpath_id, $learnpath_item_id)) {
+            $score = 0;
+        }
+        */
+        if (!isset($status) || empty($status)) {
+            $status = '';
+        } else {
+            $status = Database::escape_string($status);
+        }
 
-            if (!empty($remindList)) {
-                $remindList = array_map('intval', $remindList);
-                $remindList = array_filter($remindList);
-                $remindList = implode(",", $remindList);
-            } else {
-                $remindList = '';
-            }
+        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
 
-            if (empty($endDate)) {
-                $endDate = api_get_utc_datetime();
-            }
-            $exoId = (int) $exoId;
-            $sessionId = (int) $sessionId;
-            $learnpathId = (int) $learnpathId;
-            $learnpathItemId = (int) $learnpathItemId;
-            $learnpathItemViewId = (int) $learnpathItemViewId;
-            $duration = (int) $duration;
-            $exeId = (int) $exeId;
-            $score = Database::escape_string($score);
-            $weighting = Database::escape_string($weighting);
-            $questions = implode(',', $questionsList);
-            $userIp = Database::escape_string(api_get_real_ip());
-
-            $sql = "UPDATE $table SET
-        		   exe_exo_id = $exoId,
-        		   exe_result = '$score',
-        		   exe_weighting = '$weighting',
-        		   session_id = $sessionId,
-        		   orig_lp_id = $learnpathId,
-        		   orig_lp_item_id = $learnpathItemId,
-                   orig_lp_item_view_id = $learnpathItemViewId,
-        		   exe_duration = $duration,
-        		   exe_date = '$endDate',
-        		   status = '$status',
-        		   questions_to_check = '$remindList',
-        		   data_tracking = '$questions',
-                   user_ip = '$userIp'
-        		 WHERE exe_id = $exeId";
-            Database::query($sql);
+        if (!empty($questionsList)) {
+            $questionsList = array_map('intval', $questionsList);
+        }
 
-            //Deleting control time session track
-            //ExerciseLib::exercise_time_control_delete($exo_id);
-            return true;
+        if (!empty($remindList)) {
+            $remindList = array_map('intval', $remindList);
+            $remindList = array_filter($remindList);
+            $remindList = implode(",", $remindList);
         } else {
-            return false;
+            $remindList = '';
+        }
+
+        if (empty($endDate)) {
+            $endDate = api_get_utc_datetime();
         }
+        $exoId = (int) $exoId;
+        $sessionId = (int) $sessionId;
+        $learnpathId = (int) $learnpathId;
+        $learnpathItemId = (int) $learnpathItemId;
+        $learnpathItemViewId = (int) $learnpathItemViewId;
+        $duration = (int) $duration;
+        $exeId = (int) $exeId;
+        $score = Database::escape_string($score);
+        $weighting = Database::escape_string($weighting);
+        $questions = implode(',', $questionsList);
+        $userIp = Database::escape_string(api_get_real_ip());
+
+        $sql = "UPDATE $table SET
+               exe_exo_id = $exoId,
+               exe_result = '$score',
+               exe_weighting = '$weighting',
+               session_id = $sessionId,
+               orig_lp_id = $learnpathId,
+               orig_lp_item_id = $learnpathItemId,
+               orig_lp_item_view_id = $learnpathItemViewId,
+               exe_duration = $duration,
+               exe_date = '$endDate',
+               status = '$status',
+               questions_to_check = '$remindList',
+               data_tracking = '$questions',
+               user_ip = '$userIp'
+             WHERE exe_id = $exeId";
+        Database::query($sql);
+
+        //Deleting control time session track
+        //ExerciseLib::exercise_time_control_delete($exo_id);
+        return true;
     }
 
     /**
@@ -535,6 +555,11 @@ class Event
             if (is_null($answer)) {
                 $answer = '';
             }
+
+            if (is_null($score)) {
+                $score = 0;
+            }
+
             $attempt = [
                 'user_id' => $user_id,
                 'question_id' => $question_id,
@@ -667,21 +692,32 @@ class Event
         $updateResults = false,
         $exerciseId = 0
     ) {
+        $debug = false;
         global $safe_lp_id, $safe_lp_item_id;
 
         if ($updateResults == false) {
             // Validation in case of fraud with activated control time
             if (!ExerciseLib::exercise_time_control_is_valid($exerciseId, $safe_lp_id, $safe_lp_item_id)) {
+                if ($debug) {
+                    error_log('Attempt is fraud');
+                }
                 $correct = 0;
             }
         }
 
         if (empty($exeId)) {
+            if ($debug) {
+                error_log('exe id is empty');
+            }
+
             return false;
         }
 
         $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
         if ($updateResults) {
+            if ($debug) {
+                error_log("Insert hotspot results: exeId: $exeId correct: $correct");
+            }
             $params = [
                 'hotspot_correct' => $correct,
                 'hotspot_coordinate' => $coords,
@@ -699,6 +735,10 @@ class Event
                 ]
             );
         } else {
+            if ($debug) {
+                error_log("Insert hotspot results: exeId: $exeId correct: $correct");
+            }
+
             return Database::insert(
                 $table,
                 [

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 454 - 97
main/inc/lib/exercise.lib.php


+ 89 - 2
main/inc/lib/exercise_show_functions.lib.php

@@ -265,7 +265,8 @@ class ExerciseShowFunctions
         ];
         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:'.$hotspot_colors[$orderColor].'"></span>';
+        echo '<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";
@@ -389,7 +390,8 @@ class ExerciseShowFunctions
                 if ($hide_expected_answer) {
                     $color = '';
                 }
-                echo '<span style="font-weight: bold; color: '.$color.';">'.strip_tags($answerComment).'</span>';
+                echo '<span style="font-weight: bold; color: '.$color.';">'.
+                    Security::remove_XSS($answerComment).'</span>';
             }
             echo '</td>';
             if ($ans == 1) {
@@ -496,6 +498,91 @@ class ExerciseShowFunctions
         echo '</tr>';
     }
 
+    /**
+     * Display the answers to a multiple choice question.
+     *
+     * @param int    $feedbackType
+     * @param int    $studentChoice
+     * @param int    $studentChoiceDegree
+     * @param string $answer
+     * @param string $answerComment
+     * @param int    $answerCorrect
+     * @param int    $questionId
+     * @param bool   $inResultsDisabled
+     */
+    public static function displayMultipleAnswerTrueFalseDegreeCertainty(
+        $feedbackType,
+        $studentChoice,
+        $studentChoiceDegree,
+        $answer,
+        $answerComment,
+        $answerCorrect,
+        $questionId,
+        $inResultsDisabled
+    ) {
+        $hideExpectedAnswer = false;
+        if ($feedbackType == 0 && $inResultsDisabled == 2) {
+            $hideExpectedAnswer = true;
+        }
+
+        echo '<tr><td width="5%">';
+        $question = new MultipleAnswerTrueFalseDegreeCertainty();
+        $courseId = api_get_course_int_id();
+        $newOptions = Question::readQuestionOption($questionId, $courseId);
+
+        //Your choice
+        if (isset($newOptions[$studentChoice])) {
+            echo get_lang($newOptions[$studentChoice]['name']);
+        } else {
+            echo '-';
+        }
+        echo '</td><td width="5%">';
+
+        // Expected choice
+        if (!$hideExpectedAnswer) {
+            if (isset($newOptions[$answerCorrect])) {
+                echo get_lang($newOptions[$answerCorrect]['name']);
+            } else {
+                echo '-';
+            }
+        } else {
+            echo '-';
+        }
+
+        echo '</td><td width="20%">';
+        echo $answer;
+        echo '</td><td width="5%" style="text-align:center;">';
+        echo $newOptions[$studentChoiceDegree]['name'];
+        echo '</td>';
+
+        $degreeInfo = $question->getResponseDegreeInfo(
+            $studentChoice,
+            $answerCorrect,
+            $newOptions[$studentChoiceDegree]['position']
+        );
+
+        echo '
+            <td width="15%">
+                <div style="text-align:center;color: '.$degreeInfo['color'].';
+                    background-color: '.$degreeInfo['background-color'].';
+                    line-height:30px;height:30px;width: 100%;margin:auto;"
+                    title="'.$degreeInfo['description'].'">'.
+                    nl2br($degreeInfo['label']).
+                '</div>
+            </td>';
+
+        if ($feedbackType != EXERCISE_FEEDBACK_TYPE_EXAM) {
+            echo '<td width="20%">';
+            if (isset($newOptions[$studentChoice])) {
+                echo '<span style="font-weight: bold; color: black;">'.nl2br($answerComment).'</span>';
+            }
+            echo '</td>';
+        } else {
+            echo '<td>&nbsp;</td>';
+        }
+        echo '</tr>';
+    }
+
     /**
      * Display the answers to a multiple choice question.
      *

+ 26 - 17
main/inc/lib/extra_field.lib.php

@@ -245,6 +245,9 @@ class ExtraField extends Model
     }
 
     /**
+     * Get an array of all the values from the extra_field and extra_field_options tables
+     * based on the current object's type.
+     *
      * @param array $conditions
      * @param null  $order_field_options_by
      *
@@ -467,16 +470,18 @@ class ExtraField extends Model
     /**
      * Add elements to a form.
      *
-     * @param FormValidator $form
-     * @param int           $itemId
-     * @param array         $exclude             variables of extra field to exclude
-     * @param bool          $filter
-     * @param bool          $useTagAsSelect
-     * @param array         $showOnlyTheseFields
-     * @param array         $orderFields
-     * @param bool          $adminPermissions
+     * @param FormValidator $form                The form object to which to attach this element
+     * @param int           $itemId              The item (course, user, session, etc) this extra_field is linked to
+     * @param array         $exclude             Variables of extra field to exclude
+     * @param bool          $filter              Whether to get only the fields with the "filter" flag set to 1 (true) or not (false)
+     * @param bool          $useTagAsSelect      Whether to show tag fields as select drop-down or not
+     * @param array         $showOnlyTheseFields Limit the extra fields shown to just the list given here
+     * @param array         $orderFields         An array containing the names of the fields shown, in the right order
+     * @param bool          $adminPermissions    Whether the display is considered without edition limits (true) or not (false)
      *
-     * @return array|bool
+     * @throws Exception
+     *
+     * @return array|bool If relevant, returns a one-element array with JS code to be added to the page HTML headers. Returns false if the form object was not given
      */
     public function addElements(
         $form,
@@ -536,6 +541,8 @@ class ExtraField extends Model
     }
 
     /**
+     * Return an array of all the extra fields available for this item.
+     *
      * @param int $itemId (session_id, question_id, course id)
      *
      * @return array
@@ -824,7 +831,7 @@ class ExtraField extends Model
      *
      * @return string
      */
-    public static function extrafieldSelectWithTextConvertArrayToString(array $options)
+    public static function extraFieldSelectWithTextConvertArrayToString(array $options)
     {
         $string = '';
         $parsedOptions = self::extra_field_double_select_convert_array_to_ordered_array($options);
@@ -964,15 +971,17 @@ class ExtraField extends Model
     /**
      * Add an element that matches the given extra field to the given $form object.
      *
-     * @param FormValidator $form
+     * @param FormValidator $form                The form these fields are to be attached to
      * @param array         $extraData
-     * @param bool          $adminPermissions
+     * @param bool          $adminPermissions    Whether the display is considered without edition limits (true) or not (false)
      * @param array         $extra
-     * @param int           $itemId
-     * @param array         $exclude             variables of extra field to exclude
-     * @param bool          $useTagAsSelect
-     * @param array         $showOnlyTheseFields
-     * @param array         $orderFields
+     * @param int           $itemId              The item (course, user, session, etc) this extra_field is attached to
+     * @param array         $exclude             Extra fields to be skipped, by textual ID
+     * @param bool          $useTagAsSelect      Whether to show tag fields as select drop-down or not
+     * @param array         $showOnlyTheseFields Limit the extra fields shown to just the list given here
+     * @param array         $orderFields         An array containing the names of the fields shown, in the right order
+     *
+     * @throws Exception
      *
      * @return array If relevant, returns a one-element array with JS code to be added to the page HTML headers
      */

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

@@ -647,7 +647,7 @@ class ExtraFieldOption extends Model
                     $html = ExtraField::extra_field_double_select_convert_array_to_string($options);
                     break;
                 case ExtraField::FIELD_TYPE_SELECT_WITH_TEXT_FIELD:
-                    $html = ExtraField::extrafieldSelectWithTextConvertArrayToString($options);
+                    $html = ExtraField::extraFieldSelectWithTextConvertArrayToString($options);
                     break;
                 case ExtraField::FIELD_TYPE_TRIPLE_SELECT:
                     $html = ExtraField::tripleSelectConvertArrayToString($options);

+ 30 - 25
main/inc/lib/extra_field_value.lib.php

@@ -221,6 +221,7 @@ class ExtraFieldValue extends Model
                     $em->flush();
                     break;
                 case ExtraField::FIELD_TYPE_FILE_IMAGE:
+                    $fileDir = $fileDirStored = '';
                     switch ($this->type) {
                         case 'course':
                             $fileDir = api_get_path(SYS_UPLOAD_PATH)."courses/";
@@ -246,7 +247,7 @@ class ExtraFieldValue extends Model
                         mkdir($fileDir, $dirPermissions, true);
                     }
 
-                    if (isset($value['error']) && $value['error'] == 0) {
+                    if (!empty($value['tmp_name']) && isset($value['error']) && $value['error'] == 0) {
                         //Crop the image to adjust 16:9 ratio
                         $crop = new Image($value['tmp_name']);
                         $crop->crop($params['extra_'.$field_variable.'_crop_result']);
@@ -264,6 +265,7 @@ class ExtraFieldValue extends Model
                     }
                     break;
                 case ExtraField::FIELD_TYPE_FILE:
+                    $fileDir = $fileDirStored = '';
                     switch ($this->type) {
                         case 'course':
                             $fileDir = api_get_path(SYS_UPLOAD_PATH).'courses/';
@@ -287,13 +289,13 @@ class ExtraFieldValue extends Model
                             break;
                     }
 
-                    $cleanedName = api_replace_dangerous_char($value['name']);
-                    $fileName = ExtraField::FIELD_TYPE_FILE."_{$params['item_id']}_$cleanedName";
                     if (!file_exists($fileDir)) {
                         mkdir($fileDir, $dirPermissions, true);
                     }
 
-                    if (isset($value['error']) && $value['error'] == 0) {
+                    if (!empty($value['tmp_name']) && isset($value['error']) && $value['error'] == 0) {
+                        $cleanedName = api_replace_dangerous_char($value['name']);
+                        $fileName = ExtraField::FIELD_TYPE_FILE."_{$params['item_id']}_$cleanedName";
                         moveUploadedFile($value, $fileDir.$fileName);
 
                         $new_params = [
@@ -565,7 +567,7 @@ class ExtraFieldValue extends Model
      */
     public function get_values_by_handler_and_field_id($item_id, $field_id, $transform = false)
     {
-        $field_id = intval($field_id);
+        $field_id = (int) $field_id;
         $item_id = Database::escape_string($item_id);
 
         $sql = "SELECT s.*, field_type FROM {$this->table} s
@@ -647,8 +649,8 @@ class ExtraFieldValue extends Model
      */
     public function searchValuesByField($tag, $field_id, $limit = 10)
     {
-        $field_id = intval($field_id);
-        $limit = intval($limit);
+        $field_id = (int) $field_id;
+        $limit = (int) $limit;
         $extraFieldType = $this->getExtraField()->getExtraFieldType();
 
         $tag = Database::escape_string($tag);
@@ -692,7 +694,7 @@ class ExtraFieldValue extends Model
         $filterByVisibility = false,
         $visibility = 0
     ) {
-        $item_id = intval($item_id);
+        $item_id = (int) $item_id;
         $field_variable = Database::escape_string($field_variable);
         $extraFieldType = $this->getExtraField()->getExtraFieldType();
 
@@ -706,10 +708,10 @@ class ExtraFieldValue extends Model
                     sf.extra_field_type = $extraFieldType
                 ";
         if ($filterByVisibility) {
-            $visibility = intval($visibility);
+            $visibility = (int) $visibility;
             $sql .= " AND visible_to_self = $visibility ";
         }
-        $sql .= " ORDER BY id";
+        $sql .= ' ORDER BY id';
 
         $result = Database::query($sql);
         if (Database::num_rows($result)) {
@@ -826,7 +828,7 @@ class ExtraFieldValue extends Model
      */
     public function getValuesByFieldId($fieldId)
     {
-        $fieldId = intval($fieldId);
+        $fieldId = (int) $fieldId;
         $extraFieldType = $this->getExtraField()->getExtraFieldType();
 
         $sql = "SELECT s.* FROM {$this->table} s
@@ -853,16 +855,16 @@ class ExtraFieldValue extends Model
      */
     public function getAllValuesByItemAndField($itemId, $fieldId)
     {
-        $fieldId = intval($fieldId);
-        $itemId = intval($itemId);
+        $fieldId = (int) $fieldId;
+        $itemId = (int) $itemId;
         $extraFieldType = $this->getExtraField()->getExtraFieldType();
 
         $sql = "SELECT s.* FROM {$this->table} s
                 INNER JOIN {$this->table_handler_field} sf
                 ON (s.field_id = sf.id)
                 WHERE
-                    field_id = '".$fieldId."' AND
-                    item_id = '$itemId' AND
+                    field_id = $fieldId AND
+                    item_id = $itemId AND
                     sf.extra_field_type = $extraFieldType
                 ORDER BY s.value";
         $result = Database::query($sql);
@@ -881,7 +883,7 @@ class ExtraFieldValue extends Model
      */
     public function getAllValuesByItem($itemId)
     {
-        $itemId = intval($itemId);
+        $itemId = (int) $itemId;
         $extraFieldType = $this->getExtraField()->getExtraFieldType();
 
         $sql = "SELECT s.value, sf.variable, sf.field_type, sf.id 
@@ -911,8 +913,8 @@ class ExtraFieldValue extends Model
      */
     public function getAllValuesByItemAndFieldAndValue($itemId, $fieldId, $fieldValue)
     {
-        $fieldId = intval($fieldId);
-        $itemId = intval($itemId);
+        $fieldId = (int) $fieldId;
+        $itemId = (int) $itemId;
         $extraFieldType = $this->getExtraField()->getExtraFieldType();
 
         $fieldValue = Database::escape_string($fieldValue);
@@ -943,7 +945,7 @@ class ExtraFieldValue extends Model
      */
     public function delete_all_values_by_field_id($field_id)
     {
-        $field_id = intval($field_id);
+        $field_id = (int) $field_id;
         $sql = "DELETE FROM {$this->table}
                 WHERE
                     field_id = $field_id ";
@@ -959,8 +961,8 @@ class ExtraFieldValue extends Model
      */
     public function delete_values_by_handler_and_field_id($item_id, $field_id)
     {
-        $field_id = intval($field_id);
-        $item_id = intval($item_id);
+        $field_id = (int) $field_id;
+        $item_id = (int) $item_id;
         $extraFieldType = $this->getExtraField()->getExtraFieldType();
 
         $sql = "DELETE FROM {$this->table}
@@ -979,7 +981,7 @@ class ExtraFieldValue extends Model
      */
     public function deleteValuesByItem($itemId)
     {
-        $itemId = intval($itemId);
+        $itemId = (int) $itemId;
         $extraFieldType = $this->getExtraField()->getExtraFieldType();
 
         $sql = "DELETE FROM {$this->table}
@@ -1002,8 +1004,8 @@ class ExtraFieldValue extends Model
      */
     public function deleteValuesByHandlerAndFieldAndValue($itemId, $fieldId, $fieldValue)
     {
-        $itemId = intval($itemId);
-        $fieldId = intval($fieldId);
+        $itemId = (int) $itemId;
+        $fieldId = (int) $fieldId;
 
         $fieldData = $this->getExtraField()->get($fieldId);
         if ($fieldData) {
@@ -1054,13 +1056,15 @@ class ExtraFieldValue extends Model
     public function getAllValuesForAnItem($itemId, $visibleToSelf = null, $visibleToOthers = null)
     {
         $em = Database::getManager();
-        /** @var \Doctrine\DBAL\Query\QueryBuilder $qb */
         $qb = $em->createQueryBuilder();
         $qb = $qb->select('fv')
             ->from('ChamiloCoreBundle:ExtraFieldValues', 'fv')
             ->join('fv.field', 'f')
             ->where(
                 $qb->expr()->eq('fv.itemId', ':item')
+            )
+            ->andWhere(
+                $qb->expr()->eq('f.extraFieldType', ':extra_field_type')
             );
 
         if (is_bool($visibleToSelf)) {
@@ -1077,6 +1081,7 @@ class ExtraFieldValue extends Model
 
         $fieldValues = $qb
             ->setParameter('item', $itemId)
+            ->setParameter('extra_field_type', $this->getExtraField()->getExtraFieldType())
             ->getQuery()
             ->getResult();
 

+ 2 - 2
main/inc/lib/fixlinks.js

@@ -42,7 +42,7 @@ $(document).ready(function() {
         var randLetter = String.fromCharCode(65 + Math.floor(Math.random() * 26));
         var uniqid = randLetter + Date.now();
         var openerId = uniqid +'_opener';
-        var link = '<a id="'+openerId+'" class="generated" href="#">Open website.<img src="'+iconPath+'link-external.png "/></a>';
+        var link = '<a id="'+openerId+'" class="generated" href="#">Open website.<img width="16px" src="'+iconPath+'link-external.png "/></a>';
         var embed = $(this);
         var height = embed.attr('height');
         var width = embed.attr('width');
@@ -109,7 +109,7 @@ $(document).ready(function() {
             src = url+'&type=link&src='+src;
             src = src.replace('https', 'http');
             $(this).attr('href', src);
-            var myAnchor = $('<a><img src="'+iconPath+'link-external.png "/></a>').attr("href", src).attr('target', '_blank').attr('class', 'generated');
+            var myAnchor = $('<a><img width="16px" src="'+iconPath+'link-external.png "/></a>').attr("href", src).attr('target', '_blank').attr('class', 'generated');
             $(this).after(myAnchor);
             $(this).after('-');
         }

+ 5 - 2
main/inc/lib/groupmanager.lib.php

@@ -2339,8 +2339,11 @@ class GroupManager
                 $group_name = '<a class="'.$groupNameClass.'" href="group_space.php?'.api_get_cidreq(true, false).'&gidReq='.$this_group['id'].'">'.
                     Security::remove_XSS($this_group['name']).'</a> ';
 
-                $group_name2 = '<a href="suivi_group_space.php?cidReq='.api_get_course_id().'&gidReq='.$this_group['id'].'">
-                                '.get_lang('suivi_de').''.stripslashes($this_group['name']).'</a>';
+                $group_name2 = '';
+                if (api_get_configuration_value('extra')) {
+                    $group_name2 = '<a href="group_space_tracking.php?cidReq='.api_get_course_id().'&gidReq='
+                        .$this_group['id'].'">'.get_lang('suivi_de').''.stripslashes($this_group['name']).'</a>';
+                }
 
                 if (!empty($user_id) && !empty($this_group['id_tutor']) && $user_id == $this_group['id_tutor']) {
                     $group_name .= Display::label(get_lang('OneMyGroups'), 'success');

+ 34 - 10
main/inc/lib/legal.lib.php

@@ -27,23 +27,23 @@ class LegalManager
      */
     public static function add($language, $content, $type, $changes)
     {
-        $legal_table = Database::get_main_table(TABLE_MAIN_LEGAL);
+        $legalTable = Database::get_main_table(TABLE_MAIN_LEGAL);
         $last = self::get_last_condition($language);
         $type = (int) $type;
         $time = time();
 
         if ($last['content'] != $content) {
-            $version = (int) self::get_last_condition_version($language);
+            $version = self::getLastVersion($language);
             $version++;
             $params = [
                 'language_id' => $language,
                 'content' => $content,
                 'changes' => $changes,
                 'type' => $type,
-                'version' => (int) $version,
+                'version' => $version,
                 'date' => $time,
             ];
-            Database::insert($legal_table, $params);
+            Database::insert($legalTable, $params);
 
             return true;
         } elseif ($last['type'] != $type && $language == $last['language_id']) {
@@ -54,7 +54,7 @@ class LegalManager
                 'type' => $type,
                 'date' => $time,
             ];
-            Database::update($legal_table, $params, ['id => ?' => $id]);
+            Database::update($legalTable, $params, ['id = ?' => $id]);
 
             return true;
         } else {
@@ -68,9 +68,9 @@ class LegalManager
     public static function delete($id)
     {
         /*
-        $legal_table = Database::get_main_table(TABLE_MAIN_LEGAL);
+        $legalTable = Database::get_main_table(TABLE_MAIN_LEGAL);
         $id = (int) $id;
-        $sql = "DELETE FROM $legal_table WHERE id = '".$id."'";
+        $sql = "DELETE FROM $legalTable WHERE id = '".$id."'";
         */
     }
 
@@ -79,9 +79,9 @@ class LegalManager
      *
      * @param int $language language id
      *
-     * @return array all the info of a Term and condition
+     * @return int
      */
-    public static function get_last_condition_version($language)
+    public static function getLastVersion($language)
     {
         $table = Database::get_main_table(TABLE_MAIN_LEGAL);
         $language = (int) $language;
@@ -91,7 +91,7 @@ class LegalManager
         $result = Database::query($sql);
         $row = Database::fetch_array($result);
         if (Database::num_rows($result) > 0) {
-            return $row['version'];
+            return (int) $row['version'];
         } else {
             return 0;
         }
@@ -380,4 +380,28 @@ class LegalManager
             $extraFieldValue->delete($value['id']);
         }
     }
+
+    /**
+     * @return array
+     */
+    public static function getTreatmentTypeList()
+    {
+        return  [
+            101 => 'collection',
+            102 => 'recording',
+            103 => 'organization',
+            104 => 'structure',
+            105 => 'conservation',
+            106 => 'adaptation',
+            107 => 'extraction',
+            108 => 'consultation',
+            109 => 'usage',
+            110 => 'communication',
+            111 => 'interconnection',
+            112 => 'limitation',
+            113 => 'deletion',
+            114 => 'destruction',
+            115 => 'profiling',
+        ];
+    }
 }

+ 3 - 3
main/inc/lib/link.lib.php

@@ -1722,9 +1722,9 @@ class Link extends Model
     }
 
     /**
-     * @param int $linkId
-     * @param $action
-     * @param null $token
+     * @param int    $linkId
+     * @param string $action
+     * @param null   $token
      *
      * @return FormValidator
      */

+ 163 - 18
main/inc/lib/message.lib.php

@@ -1,6 +1,7 @@
 <?php
 /* For licensing terms, see /license.txt */
 
+use Chamilo\CoreBundle\Entity\Message;
 use Chamilo\UserBundle\Entity\User;
 use ChamiloSession as Session;
 
@@ -238,6 +239,7 @@ class MessageManager
             'update_date' => $now,
         ];
         $id = Database::insert($table, $params);
+
         if ($id) {
             return true;
         }
@@ -253,12 +255,25 @@ class MessageManager
     public static function getMessagesAboutUser($aboutUserInfo)
     {
         if (!empty($aboutUserInfo)) {
-            $criteria = [
+            $table = Database::get_main_table(TABLE_MESSAGE);
+            $sql = 'SELECT id FROM '.$table.'
+                    WHERE 
+                      user_receiver_id = '.$aboutUserInfo['id'].' AND 
+                      msg_status = '.MESSAGE_STATUS_CONVERSATION.'                    
+                    ';
+            $result = Database::query($sql);
+            $messages = [];
+            $repo = Database::getManager()->getRepository('ChamiloCoreBundle:Message');
+            while ($row = Database::fetch_array($result)) {
+                $message = $repo->find($row['id']);
+                $messages[] = $message;
+            }
+            /*$criteria = [
                 'userReceiverId' => $aboutUserInfo['id'],
                 'msgStatus' => MESSAGE_STATUS_CONVERSATION,
             ];
             $repo = Database::getManager()->getRepository('ChamiloCoreBundle:Message');
-            $messages = $repo->findBy($criteria, ['sendDate' => 'DESC']);
+            $messages = $repo->findBy($criteria, ['sendDate' => 'DESC']);*/
 
             return $messages;
         }
@@ -266,22 +281,96 @@ class MessageManager
         return [];
     }
 
+    /**
+     * @param array $userInfo
+     *
+     * @return string
+     */
+    public static function getMessagesAboutUserToString($userInfo)
+    {
+        $messages = self::getMessagesAboutUser($userInfo);
+        $html = '';
+        if (!empty($messages)) {
+            /** @var Message $message */
+            foreach ($messages as $message) {
+                $tag = 'message_'.$message->getId();
+                $tagAccordion = 'accordion_'.$message->getId();
+                $tagCollapse = 'collapse_'.$message->getId();
+                $date = Display::dateToStringAgoAndLongDate(
+                    $message->getSendDate()
+                );
+                $localTime = api_get_local_time(
+                    $message->getSendDate(),
+                    null,
+                    null,
+                    false,
+                    false
+                );
+                $senderId = $message->getUserSenderId();
+                $senderInfo = api_get_user_info($senderId);
+                $html .= Display::panelCollapse(
+                    $localTime.' '.$senderInfo['complete_name'].' '.$message->getTitle(),
+                    $message->getContent().'<br />'.$date.'<br />'.get_lang(
+                        'Author'
+                    ).': '.$senderInfo['complete_name_with_message_link'],
+                    $tag,
+                    null,
+                    $tagAccordion,
+                    $tagCollapse,
+                    false
+                );
+            }
+        }
+
+        return $html;
+    }
+
+    /**
+     * @param int    $senderId
+     * @param int    $receiverId
+     * @param string $subject
+     * @param string $message
+     *
+     * @return bool
+     */
+    public static function messageWasAlreadySent($senderId, $receiverId, $subject, $message)
+    {
+        $table = Database::get_main_table(TABLE_MESSAGE);
+        $senderId = (int) $senderId;
+        $receiverId = (int) $receiverId;
+        $subject = Database::escape_string($subject);
+        $message = Database::escape_string($message);
+
+        $sql = "SELECT * FROM $table
+                WHERE 
+                    user_sender_id = $senderId AND
+                    user_receiver_id = $receiverId AND 
+                    title = '$subject' AND 
+                    content = '$message' AND
+                    (msg_status = ".MESSAGE_STATUS_UNREAD." OR msg_status = ".MESSAGE_STATUS_NEW.")                    
+                ";
+        $result = Database::query($sql);
+
+        return Database::num_rows($result) > 0;
+    }
+
     /**
      * Sends a message to a user/group.
      *
      * @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
      *
      * @return bool
      */
@@ -298,7 +387,8 @@ class MessageManager
         $sender_id = 0,
         $directMessage = false,
         $forwardId = 0,
-        $smsParameters = []
+        $smsParameters = [],
+        $checkCurrentAudioId = false
     ) {
         $table = Database::get_main_table(TABLE_MESSAGE);
         $group_id = (int) $group_id;
@@ -342,6 +432,26 @@ class MessageManager
             }
         }
 
+        if ($checkCurrentAudioId) {
+            // Add the audio file as an attachment
+            $audioId = Session::read('current_audio_id');
+            if (!empty($audioId)) {
+                $file = api_get_uploaded_file('audio_message', api_get_user_id(), $audioId);
+                if (!empty($file)) {
+                    $audioAttachment = [
+                        'name' => basename($file),
+                        'comment' => 'audio_message',
+                        'size' => filesize($file),
+                        'tmp_name' => $file,
+                        'error' => 0,
+                        'type' => DocumentManager::file_get_mime_type(basename($file)),
+                    ];
+                    // create attachment from audio message
+                    $attachmentList[] = $audioAttachment;
+                }
+            }
+        }
+
         // Validating fields
         if (empty($subject) && empty($group_id)) {
             Display::addFlash(
@@ -393,7 +503,7 @@ class MessageManager
 
             // Forward also message attachments
             if (!empty($forwardId)) {
-                $attachments = MessageManager::getAttachmentList($forwardId);
+                $attachments = self::getAttachmentList($forwardId);
                 foreach ($attachments as $attachment) {
                     if (!empty($attachment['file_source'])) {
                         $file = [
@@ -1181,14 +1291,14 @@ class MessageManager
     public static function showMessageBox($messageId, $source = 'inbox')
     {
         $table = Database::get_main_table(TABLE_MESSAGE);
-        $messageId = intval($messageId);
+        $messageId = (int) $messageId;
 
         if ($source == 'outbox') {
             if (isset($messageId) && is_numeric($messageId)) {
                 $query = "SELECT * FROM $table
                           WHERE
                             user_sender_id = ".api_get_user_id()." AND
-                            id = ".$messageId." AND
+                            id = $messageId AND
                             msg_status = ".MESSAGE_STATUS_OUTBOX;
                 $result = Database::query($query);
             }
@@ -1854,22 +1964,29 @@ class MessageManager
      *
      * @return array
      */
-    public static function get_links_message_attachment_files($messageId, $type = '')
+    public static function getAttachmentLinkList($messageId, $type = '')
     {
         $files = self::getAttachmentList($messageId);
         // get file attachments by message id
         $list = [];
         if ($files) {
-            $attach_icon = Display::return_icon('attachment.gif', '');
+            $attachIcon = Display::return_icon('attachment.gif', '');
             $archiveURL = api_get_path(WEB_CODE_PATH).'messages/download.php?type='.$type.'&file=';
             foreach ($files as $row_file) {
                 $archiveFile = $row_file['path'];
                 $filename = $row_file['filename'];
-                $filesize = format_file_size($row_file['size']);
-                $filecomment = Security::remove_XSS($row_file['comment']);
+                $size = format_file_size($row_file['size']);
+                $comment = Security::remove_XSS($row_file['comment']);
                 $filename = Security::remove_XSS($filename);
-                $list[] = $attach_icon.'&nbsp;<a href="'.$archiveURL.$archiveFile.'">'.$filename.'</a>
-                    &nbsp;('.$filesize.')'.(!empty($filecomment) ? '&nbsp;-&nbsp;<i>'.$filecomment.'</i>' : '');
+                $link = Display::url($filename, $archiveURL.$archiveFile);
+                $comment = !empty($comment) ? '&nbsp;-&nbsp;<i>'.$comment.'</i>' : '';
+
+                $attachmentLine = $attachIcon.'&nbsp;'.$link.'&nbsp;('.$size.')'.$comment;
+                if ($row_file['comment'] == 'audio_message') {
+                    $attachmentLine = '<audio src="'.$archiveURL.$archiveFile.'"/>';
+                }
+
+                $list[] = $attachmentLine;
             }
         }
 
@@ -2469,6 +2586,34 @@ class MessageManager
         }
     }
 
+    /**
+     * Clean audio messages already added in the message tool.
+     */
+    public static function cleanAudioMessage()
+    {
+        $audioId = Session::read('current_audio_id');
+        if (!empty($audioId)) {
+            api_remove_uploaded_file_by_id('audio_message', api_get_user_id(), $audioId);
+            Session::erase('current_audio_id');
+        }
+    }
+
+    /**
+     * @param int    $senderId
+     * @param string $subject
+     * @param string $message
+     */
+    public static function sendMessageToAllAdminUsers(
+        $senderId,
+        $subject,
+        $message
+    ) {
+        $admins = UserManager::get_all_administrators();
+        foreach ($admins as $admin) {
+            self::send_message_simple($admin['user_id'], $subject, $message, $senderId);
+        }
+    }
+
     /**
      * Execute the SQL necessary to know the number of messages in the database.
      *

+ 60 - 25
main/inc/lib/myspace.lib.php

@@ -2379,7 +2379,6 @@ class MySpace
                 } else {
                     $is_session_avail = self::user_available_in_session($user['UserName'], $course_list, $id_session);
                     if ($is_session_avail == 0) {
-                        // This should be the ID because the user exists.
                         $user_name = $user['UserName'];
                         $sql_select = "SELECT user_id FROM $table_user WHERE username ='$user_name' ";
                         $rs = Database::query($sql_select);
@@ -2734,29 +2733,33 @@ class MySpace
         return $users;
     }
 
+    /**
+     * @param int $courseId
+     * @param int $sessionId
+     * @param int $studentId
+     */
     public static function displayTrackingAccessOverView($courseId, $sessionId, $studentId)
     {
-        $courseId = intval($courseId);
-        $sessionId = intval($sessionId);
-        $studentId = intval($studentId);
-
-        $em = Database::getManager();
-        $sessionRepo = $em->getRepository('ChamiloCoreBundle:Session');
+        $courseId = (int) $courseId;
+        $sessionId = (int) $sessionId;
+        $studentId = (int) $studentId;
 
         $courseList = [];
         $sessionList = [];
         $studentList = [];
 
         if (!empty($courseId)) {
-            $course = $em->find('ChamiloCoreBundle:Course', $courseId);
-
-            $courseList[$course->getId()] = $course->getTitle();
+            $course = api_get_course_entity($courseId);
+            if ($course) {
+                $courseList[$course->getId()] = $course->getTitle();
+            }
         }
 
         if (!empty($sessionId)) {
-            $session = $em->find('ChamiloCoreBundle:Session', $sessionId);
-
-            $sessionList[$session->getId()] = $session->getName();
+            $session = api_get_session_entity($sessionId);
+            if ($session) {
+                $sessionList[$session->getId()] = $session->getName();
+            }
         }
 
         if (!empty($studentId)) {
@@ -2776,9 +2779,11 @@ class MySpace
                 'url' => api_get_path(WEB_AJAX_PATH).'course.ajax.php?'.http_build_query([
                     'a' => 'search_course_by_session_all',
                     'session_id' => $sessionId,
+                    'course_id' => $courseId,
                 ]),
             ]
         );
+
         $form->addElement(
             'select_ajax',
             'session_id',
@@ -2789,7 +2794,7 @@ class MySpace
                     function () {
                         var params = $.param({
                             a: 'search_session_by_course',
-                            course_id: $('#course_id').val() || 0
+                            course_id: $('#access_overview_course_id').val() || 0
                         });
 
                         return '".api_get_path(WEB_AJAX_PATH)."session.ajax.php?' + params;
@@ -2797,6 +2802,7 @@ class MySpace
                 ",
             ]
         );
+
         $form->addSelect(
             'profile',
             get_lang('Profile'),
@@ -2808,6 +2814,7 @@ class MySpace
             ],
             ['id' => 'profile']
         );
+
         $form->addElement(
             'select_ajax',
             'student_id',
@@ -2816,11 +2823,11 @@ class MySpace
             [
                 'placeholder' => get_lang('All'),
                 'url_function' => "
-                    function () {
+                    function () {                    
                         var params = $.param({
                             a: 'search_user_by_course',
-                            session_id: $('#session_id').val(),
-                            course_id: $('#course_id').val()
+                            session_id: $('#access_overview_session_id').val(),
+                            course_id: $('#access_overview_course_id').val()
                         });
 
                         return '".api_get_path(WEB_AJAX_PATH)."course.ajax.php?' + params;
@@ -2853,7 +2860,8 @@ class MySpace
                 ['MySpace', 'getUserDataAccessTrackingOverview'],
                 0
             );
-            $table->additional_parameters = $form->exportValues();
+
+            //$table->additional_parameters = $form->exportValues();
 
             $table->set_header(0, get_lang('LoginDate'), true);
             $table->set_header(1, get_lang('Username'), true);
@@ -2919,11 +2927,9 @@ class MySpace
         $track_e_course_access = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
 
         global $export_csv;
-
+        $is_western_name_order = api_is_western_name_order();
         if ($export_csv) {
             $is_western_name_order = api_is_western_name_order(PERSON_NAME_DATA_EXPORT);
-        } else {
-            $is_western_name_order = api_is_western_name_order();
         }
 
         //TODO add course name
@@ -2945,15 +2951,44 @@ class MySpace
                 u.user_id
             FROM $track_e_course_access a
             INNER JOIN $user u ON a.user_id = u.user_id
-            INNER JOIN $course c ON a.c_id = c.id";
+            INNER JOIN $course c ON a.c_id = c.id
+            WHERE 1=1 ";
+
+        if (isset($_GET['course_id']) && !empty($_GET['course_id'])) {
+            $courseId = (int) $_GET['course_id'];
+            $sql .= " AND c.id = ".$courseId;
+        }
 
         if (isset($_GET['session_id']) && !empty($_GET['session_id'])) {
-            $sessionId = intval($_GET['session_id']);
-            $sql .= " WHERE a.session_id = ".$sessionId;
+            $sessionId = (int) $_GET['session_id'];
+            $sql .= " AND a.session_id = ".$sessionId;
+        }
+
+        if (isset($_GET['student_id']) && !empty($_GET['student_id'])) {
+            $userId = (int) $_GET['student_id'];
+            $sql .= " AND u.user_id = ".$userId;
+        }
+
+        if (isset($_GET['student_id']) && !empty($_GET['student_id'])) {
+            $userId = (int) $_GET['student_id'];
+            $sql .= " AND u.user_id = ".$userId;
+        }
+
+        if (isset($_GET['date']) && !empty($_GET['date'])) {
+            $dates = DateRangePicker::parseDateRange($_GET['date']);
+            if (isset($dates['start']) && !empty($dates['start'])) {
+                $dates['start'] = Database::escape_string($dates['start']);
+                $sql .= " AND login_course_date >= '".$dates['start']."'";
+            }
+            if (isset($dates['end']) && !empty($dates['end'])) {
+                $dates['end'] = Database::escape_string($dates['end']);
+                $sql .= " AND logout_course_date <= '".$dates['end']."'";
+            }
         }
 
         $sql .= " ORDER BY col$column $orderDirection ";
         $sql .= " LIMIT $from,$numberItems";
+
         $result = Database::query($sql);
 
         $data = [];
@@ -3019,7 +3054,7 @@ class MySpace
         $end_date
     ) {
         $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
-        $user_id = intval($user_id);
+        $user_id = (int) $user_id;
         $connections = [];
         if (!empty($course_info)) {
             $courseId = intval($course_info['real_id']);

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

@@ -122,8 +122,8 @@ class NotebookManager
                 title				AS note_title,
                 description 		AS note_comment,
                 session_id			AS session_id
-               FROM $table
-               WHERE c_id = $course_id AND notebook_id = '".intval($notebook_id)."' ";
+                FROM $table
+                WHERE c_id = $course_id AND notebook_id = '".intval($notebook_id)."' ";
         $result = Database::query($sql);
         if (Database::num_rows($result) != 1) {
             return [];
@@ -200,7 +200,6 @@ class NotebookManager
 
         // Database table definition
         $table = Database::get_course_table(TABLE_NOTEBOOK);
-
         $course_id = api_get_course_int_id();
 
         $sql = "DELETE FROM $table

+ 13 - 11
main/inc/lib/pdf.lib.php

@@ -344,16 +344,17 @@ class PDF
     /**
      * Converts an html string to PDF.
      *
-     * @param string $document_html valid html
-     * @param string $css           CSS content of a CSS file
-     * @param string $pdf_name      pdf name
-     * @param string $course_code   course code
-     *                              (if you are using html that are located in the document tool you must provide this)
-     * @param string $outputMode    the MPDF output mode can be:
+     * @param string $document_html  valid html
+     * @param string $css            CSS content of a CSS file
+     * @param string $pdf_name       pdf name
+     * @param string $course_code    course code
+     *                               (if you are using html that are located in the document tool you must provide this)
+     * @param string $outputMode     the MPDF output mode can be:
      * @param bool   $saveInFile
      * @param string $fileToSave
      * @param bool   $returnHtml
      * @param bool   $addDefaultCss
+     * @param bool   $completeHeader
      *
      * 'I' (print on standard output),
      * 'D' (download file) (this is the default value),
@@ -371,7 +372,8 @@ class PDF
         $saveInFile = false,
         $fileToSave = null,
         $returnHtml = false,
-        $addDefaultCss = false
+        $addDefaultCss = false,
+        $completeHeader = true
     ) {
         $urlAppend = api_get_configuration_value('url_append');
 
@@ -387,9 +389,7 @@ class PDF
 
         // Formatting the pdf
         $course_data = api_get_course_info($course_code);
-
-        self::format_pdf($course_data);
-
+        self::format_pdf($course_data, $completeHeader);
         $document_html = preg_replace($clean_search, '', $document_html);
 
         //absolute path for frames.css //TODO: necessary?
@@ -417,7 +417,9 @@ class PDF
                     $old_src = $item->getAttribute('src');
                     if (strpos($old_src, $protocol) === false) {
                         if (strpos($old_src, '/main/default_course_document') === false) {
-                            if (strpos($old_src, '/main/inc/lib/') === false) {
+                            if (strpos($old_src, '/main/inc/lib/') === false &&
+                                strpos($old_src, '/app/upload/') === false
+                            ) {
                                 $old_src_fixed = str_replace(
                                     api_get_path(REL_COURSE_PATH).$course_data['path'].'/document/',
                                     '',

+ 36 - 10
main/inc/lib/plugin.class.php

@@ -25,6 +25,7 @@ class Plugin
     public $isMailPlugin = false;
     // Adds icon in the course home
     public $addCourseTool = true;
+    public $hasPersonalEvents = false;
 
     /**
      * When creating a new course, these settings are added to the course, in
@@ -215,6 +216,11 @@ class Plugin
             $options = null;
             if (is_array($type) && isset($type['type']) && $type['type'] === 'select') {
                 $attributes = isset($type['attributes']) ? $type['attributes'] : [];
+                if (!empty($type['options']) && isset($type['translate_options']) && $type['translate_options']) {
+                    foreach ($type['options'] as $key => &$optionName) {
+                        $optionName = $this->get_lang($optionName);
+                    }
+                }
                 $options = $type['options'];
                 $type = $type['type'];
             }
@@ -536,7 +542,7 @@ class Plugin
      */
     public function uninstall_course_fields($courseId)
     {
-        $courseId = intval($courseId);
+        $courseId = (int) $courseId;
 
         if (empty($courseId)) {
             return false;
@@ -580,8 +586,8 @@ class Plugin
     public function install_course_fields_in_all_courses($add_tool_link = true)
     {
         // Update existing courses to add plugin settings
-        $t_courses = Database::get_main_table(TABLE_MAIN_COURSE);
-        $sql = "SELECT id FROM $t_courses ORDER BY id";
+        $table = Database::get_main_table(TABLE_MAIN_COURSE);
+        $sql = "SELECT id FROM $table ORDER BY id";
         $res = Database::query($sql);
         while ($row = Database::fetch_assoc($res)) {
             $this->install_course_fields($row['id'], $add_tool_link);
@@ -594,8 +600,8 @@ class Plugin
     public function uninstall_course_fields_in_all_courses()
     {
         // Update existing courses to add conference settings
-        $t_courses = Database::get_main_table(TABLE_MAIN_COURSE);
-        $sql = "SELECT id FROM $t_courses
+        $table = Database::get_main_table(TABLE_MAIN_COURSE);
+        $sql = "SELECT id FROM $table
                 ORDER BY id";
         $res = Database::query($sql);
         while ($row = Database::fetch_assoc($res)) {
@@ -684,6 +690,7 @@ class Plugin
             }
         }
 
+        $currentUrlId = api_get_current_access_url_id();
         $attributes = [
             'variable' => 'show_tabs',
             'subkey' => $subkey,
@@ -693,7 +700,7 @@ class Plugin
             'title' => $tabName,
             'comment' => $url,
             'subkeytext' => $subkeytext,
-            'access_url' => 1,
+            'access_url' => $currentUrlId,
             'access_url_changeable' => 1,
             'access_url_locked' => 0,
         ];
@@ -877,11 +884,21 @@ class Plugin
      *
      * @return string
      */
-    public function getToolIconVisibility()
+    public function getToolIconVisibilityPerUserStatus()
     {
         return '';
     }
 
+    /**
+     * Default tool icon visibility.
+     *
+     * @return bool
+     */
+    public function isIconVisibleByDefault()
+    {
+        return true;
+    }
+
     /**
      * Get the admin URL for the plugin if Plugin::isAdminPlugin is true.
      *
@@ -908,6 +925,14 @@ class Plugin
         return '';
     }
 
+    /**
+     * @param bool $value
+     */
+    public function setHasPersonalEvents($value)
+    {
+        $this->hasPersonalEvents = $value;
+    }
+
     /**
      * Add an link for a course tool.
      *
@@ -928,7 +953,8 @@ class Plugin
             return null;
         }
 
-        $visibility = $this->getToolIconVisibility();
+        $visibilityPerStatus = $this->getToolIconVisibilityPerUserStatus();
+        $visibility = $this->isIconVisibleByDefault();
 
         $em = Database::getManager();
 
@@ -948,10 +974,10 @@ class Plugin
             $tool
                 ->setId($cToolId)
                 ->setCId($courseId)
-                ->setName($name.$visibility)
+                ->setName($name.$visibilityPerStatus)
                 ->setLink($link ?: "$pluginName/start.php")
                 ->setImage($iconName ?: "$pluginName.png")
-                ->setVisibility(true)
+                ->setVisibility($visibility)
                 ->setAdmin(0)
                 ->setAddress('squaregrey.gif')
                 ->setAddedTool(false)

+ 2 - 1
main/inc/lib/security.lib.php

@@ -317,7 +317,8 @@ class Security
         if (!isset($purifier[$user_status])) {
             $cache_dir = api_get_path(SYS_ARCHIVE_PATH).'Serializer';
             if (!file_exists($cache_dir)) {
-                mkdir($cache_dir, 0777);
+                $mode = api_get_permissions_for_new_directories();
+                mkdir($cache_dir, $mode);
             }
             $config = HTMLPurifier_Config::createDefault();
             $config->set('Cache.SerializerPath', $cache_dir);

+ 128 - 62
main/inc/lib/sessionmanager.lib.php

@@ -6,6 +6,7 @@ use Chamilo\CoreBundle\Entity\ExtraField;
 use Chamilo\CoreBundle\Repository\SequenceRepository;
 use Chamilo\CoreBundle\Entity\SequenceResource;
 use Chamilo\CoreBundle\Entity\Session;
+use Chamilo\CoreBundle\Entity\SessionRelCourse;
 use Chamilo\CoreBundle\Entity\SessionRelCourseRelUser;
 use Chamilo\CoreBundle\Entity\SessionRelUser;
 use Chamilo\UserBundle\Entity\User;
@@ -2081,6 +2082,15 @@ class SessionManager
                 $avoidDeleteThisUsers = " AND user_id NOT IN ('".implode("','", $userList)."')";
                 $sql .= $avoidDeleteThisUsers;
             }
+            Event::addEvent(
+                LOG_SESSION_DELETE_USER,
+                LOG_USER_ID,
+                'all',
+                api_get_utc_datetime(),
+                api_get_user_id(),
+                null,
+                $sessionId
+            );
             Database::query($sql);
         }
 
@@ -2092,6 +2102,15 @@ class SessionManager
                 $sql = "INSERT IGNORE INTO $tbl_session_rel_user (relation_type, session_id, user_id, registered_at)
                         VALUES (0, $sessionId, $enreg_user, '".api_get_utc_datetime()."')";
                 Database::query($sql);
+                Event::addEvent(
+                    LOG_SESSION_ADD_USER,
+                    LOG_USER_ID,
+                    $enreg_user,
+                    api_get_utc_datetime(),
+                    api_get_user_id(),
+                    null,
+                    $sessionId
+                );
             }
         }
 
@@ -2468,6 +2487,10 @@ class SessionManager
      * @param bool  $removeExistingCoursesWithUsers Whether to unsubscribe
      *                                              existing courses and users (true, default) or not (false)
      * @param bool  $copyEvaluation                 from base course to session course
+     *
+     * @throws Exception
+     *
+     * @return bool False on failure, true otherwise
      * */
     public static function add_courses_to_session(
         $sessionId,
@@ -2481,6 +2504,16 @@ class SessionManager
             return false;
         }
 
+        $em = Database::getManager();
+
+        /** @var Session $session */
+        $session = $em->find('ChamiloCoreBundle:Session', $sessionId);
+
+        if (!$session) {
+            return false;
+        }
+        $sessionVisibility = $session->getVisibility();
+
         $tbl_session_rel_course_rel_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
         $tbl_session = Database::get_main_table(TABLE_MAIN_SESSION);
         $tbl_session_rel_user = Database::get_main_table(TABLE_MAIN_SESSION_USER);
@@ -2575,7 +2608,7 @@ class SessionManager
                         if (empty($sessionCategory)) {
                             // There is no category for this course+session, so create one
                             $cat = new Category();
-                            $sessionName = api_get_session_name($sessionId);
+                            $sessionName = $session->getName();
                             $cat->set_name($courseInfo['code'].' - '.get_lang('Session').' '.$sessionName);
                             $cat->set_session_id($sessionId);
                             $cat->set_course_code($courseInfo['code']);
@@ -2683,8 +2716,8 @@ class SessionManager
                 $nbr_users = 0;
                 foreach ($user_list as $enreg_user) {
                     $enreg_user_id = intval($enreg_user['user_id']);
-                    $sql = "INSERT IGNORE INTO $tbl_session_rel_course_rel_user (session_id, c_id, user_id)
-                            VALUES ($sessionId, $courseId, $enreg_user_id)";
+                    $sql = "INSERT IGNORE INTO $tbl_session_rel_course_rel_user (session_id, c_id, user_id, visibility)
+                            VALUES ($sessionId, $courseId, $enreg_user_id, $sessionVisibility)";
                     $result = Database::query($sql);
 
                     Event::addEvent(
@@ -2712,6 +2745,8 @@ class SessionManager
                 SET nbr_courses = $nbr_courses
                 WHERE id = $sessionId";
         Database::query($sql);
+
+        return true;
     }
 
     /**
@@ -3114,18 +3149,25 @@ class SessionManager
     /**
      * Get a list of sessions of which the given conditions match with an = 'cond'.
      *
-     * @param array $conditions a list of condition example :
-     *                          array('status' => STUDENT) or
-     *                          array('s.name' => array('operator' => 'LIKE', value = '%$needle%'))
-     * @param array $order_by   a list of fields on which sort
+     * @param array $conditions          a list of condition example :
+     *                                   array('status' => STUDENT) or
+     *                                   array('s.name' => array('operator' => 'LIKE', value = '%$needle%'))
+     * @param array $order_by            a list of fields on which sort
      * @param int   $urlId
+     * @param array $onlyThisSessionList
      *
      * @return array an array with all sessions of the platform
      *
      * @todo   optional course code parameter, optional sorting parameters...
      */
-    public static function get_sessions_list($conditions = [], $order_by = [], $from = null, $to = null, $urlId = 0)
-    {
+    public static function get_sessions_list(
+        $conditions = [],
+        $order_by = [],
+        $from = null,
+        $to = null,
+        $urlId = 0,
+        $onlyThisSessionList = []
+    ) {
         $session_table = Database::get_main_table(TABLE_MAIN_SESSION);
         $session_category_table = Database::get_main_table(TABLE_MAIN_SESSION_CATEGORY);
         $user_table = Database::get_main_table(TABLE_MAIN_USER);
@@ -3177,6 +3219,12 @@ class SessionManager
             }
         }
 
+        if (!empty($onlyThisSessionList)) {
+            $onlyThisSessionList = array_map('intval', $onlyThisSessionList);
+            $onlyThisSessionList = implode("','", $onlyThisSessionList);
+            $sql_query .= " AND s.id IN ('$onlyThisSessionList') ";
+        }
+
         $orderAvailableList = ['name'];
         if (count($order_by) > 0) {
             $order = null;
@@ -3194,8 +3242,8 @@ class SessionManager
         }
 
         if (!is_null($from) && !is_null($to)) {
-            $to = intval($to);
-            $from = intval($from);
+            $to = (int) $to;
+            $from = (int) $from;
             $sql_query .= "LIMIT $from, $to";
         }
 
@@ -3865,7 +3913,7 @@ class SessionManager
     ) {
         $tbl_course = Database::get_main_table(TABLE_MAIN_COURSE);
         $tbl_session_rel_course = Database::get_main_table(TABLE_MAIN_SESSION_COURSE);
-        $session_id = intval($session_id);
+        $session_id = (int) $session_id;
         $sqlSelect = "*, c.id, c.id as real_id";
 
         if ($getCount) {
@@ -3903,7 +3951,7 @@ class SessionManager
             if ($getCount) {
                 $count = Database::fetch_assoc($result);
 
-                return intval($count['count']);
+                return (int) $count['count'];
             }
 
             while ($row = Database::fetch_array($result, 'ASSOC')) {
@@ -4151,8 +4199,8 @@ class SessionManager
 
         $urlId = empty($urlId) ? api_get_current_access_url_id() : (int) $urlId;
 
-        if ($status != '') {
-            $status = intval($status);
+        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)";
         } else {
             $sql .= " WHERE (au.access_url_id = $urlId OR au.access_url_id is null )";
@@ -4422,11 +4470,7 @@ class SessionManager
             // We will copy the current courses of the session to new courses
             if (!empty($short_courses)) {
                 if ($create_new_courses) {
-                    //Just in case
-                    if (function_exists('ini_set')) {
-                        api_set_memory_limit('256M');
-                        ini_set('max_execution_time', 0);
-                    }
+                    api_set_more_memory_and_time_limits();
                     $params = [];
                     $params['skip_lp_dates'] = true;
 
@@ -4514,12 +4558,12 @@ class SessionManager
      */
     public static function user_is_general_coach($user_id, $session_id)
     {
-        $session_id = intval($session_id);
-        $user_id = intval($user_id);
+        $session_id = (int) $session_id;
+        $user_id = (int) $user_id;
         $table = Database::get_main_table(TABLE_MAIN_SESSION);
         $sql = "SELECT DISTINCT id
 	         	FROM $table
-	         	WHERE session.id_coach =  '".$user_id."' AND id = '$session_id'";
+	         	WHERE session.id_coach = '".$user_id."' AND id = '$session_id'";
         $result = Database::query($sql);
         if ($result && Database::num_rows($result)) {
             return true;
@@ -4586,6 +4630,33 @@ SQL;
         return $count;
     }
 
+    /**
+     * @param int  $id
+     * @param bool $checkSession
+     *
+     * @return bool
+     */
+    public static function cantEditSession($id, $checkSession = true)
+    {
+        if (!self::allowToManageSessions()) {
+            return false;
+        }
+
+        if (api_is_platform_admin() && self::allowed($id)) {
+            return true;
+        }
+
+        if ($checkSession) {
+            if (self::allowed($id)) {
+                return true;
+            }
+
+            return false;
+        }
+
+        return true;
+    }
+
     /**
      * Protect a session to be edited.
      *
@@ -4596,19 +4667,7 @@ SQL;
      */
     public static function protectSession($id, $checkSession = true)
     {
-        if (self::allowToManageSessions()) {
-            if (api_is_platform_admin() && self::allowed($id)) {
-                return true;
-            }
-
-            if ($checkSession) {
-                if (self::allowed($id)) {
-                    return true;
-                } else {
-                    api_not_allowed(true);
-                }
-            }
-        } else {
+        if (!self::cantEditSession($id, $checkSession)) {
             api_not_allowed(true);
         }
     }
@@ -6546,7 +6605,8 @@ SQL;
                         }
                     }
 
-                    $message .= '<strong>'.get_lang('User').'</strong>: '.$userInfo['complete_name'].' <br />';
+                    $message .= '<strong>'.get_lang('User').'</strong>: ';
+                    $message .= $userInfo['complete_name_with_username'].' <br />';
 
                     if (!in_array($userInfo['status'], [DRH]) && !api_is_platform_admin_by_id($userInfo['user_id'])) {
                         $message .= get_lang('UserMustHaveTheDrhRole').'<br />';
@@ -7612,7 +7672,6 @@ SQL;
         }
 
         $result = Database::query($sql);
-
         while ($row = Database::fetch_assoc($result)) {
             $sessionList[] = $row;
         }
@@ -7688,7 +7747,7 @@ SQL;
         $coachInfo = [];
 
         if (!empty($sessionInfo)) {
-            $sessionId = intval($sessionInfo['id']);
+            $sessionId = (int) $sessionInfo['id'];
             $coachInfo = api_get_user_info($sessionInfo['id_coach']);
         }
 
@@ -7730,11 +7789,11 @@ SQL;
         } else {
             $sql = "SELECT COUNT(1) FROM $tbl_user WHERE status = 1";
             $rs = Database::query($sql);
-            $countUsers = Database::result($rs, 0, 0);
+            $countUsers = (int) Database::result($rs, 0, 0);
 
-            if (intval($countUsers) < 50) {
-                $orderClause = "ORDER BY ";
-                $orderClause .= api_sort_by_first_name() ? "firstname, lastname, username" : "lastname, firstname, username";
+            if ($countUsers < 50) {
+                $orderClause = 'ORDER BY ';
+                $orderClause .= api_sort_by_first_name() ? 'firstname, lastname, username' : 'lastname, firstname, username';
 
                 $sql = "SELECT user_id, lastname, firstname, username
                         FROM $tbl_user
@@ -7746,7 +7805,6 @@ SQL;
                         TABLE_MAIN_ACCESS_URL_REL_USER
                     );
                     $accessUrlId = api_get_current_access_url_id();
-
                     if ($accessUrlId != -1) {
                         $sql = "SELECT user.user_id, username, lastname, firstname
                         FROM $tbl_user user
@@ -7761,7 +7819,6 @@ SQL;
 
                 $result = Database::query($sql);
                 $coachesList = Database::store_result($result);
-
                 $coachesOptions = [];
                 foreach ($coachesList as $coachItem) {
                     $coachesOptions[$coachItem['user_id']] =
@@ -7837,11 +7894,16 @@ SQL;
         $form->addElement('checkbox', 'show_description', null, get_lang('ShowDescription'));
 
         $visibilityGroup = [];
-        $visibilityGroup[] = $form->createElement('select', 'session_visibility', null, [
-            SESSION_VISIBLE_READ_ONLY => get_lang('SessionReadOnly'),
-            SESSION_VISIBLE => get_lang('SessionAccessible'),
-            SESSION_INVISIBLE => api_ucfirst(get_lang('SessionNotAccessible')),
-        ]);
+        $visibilityGroup[] = $form->createElement(
+            'select',
+            'session_visibility',
+            null,
+            [
+                SESSION_VISIBLE_READ_ONLY => get_lang('SessionReadOnly'),
+                SESSION_VISIBLE => get_lang('SessionAccessible'),
+                SESSION_INVISIBLE => api_ucfirst(get_lang('SessionNotAccessible')),
+            ]
+        );
         $form->addGroup(
             $visibilityGroup,
             'visibility_group',
@@ -7861,7 +7923,6 @@ SQL;
         ]);
 
         $form->addHtml('<div id="duration_div" style="display:none">');
-
         $form->addElement(
             'number',
             'duration',
@@ -8021,7 +8082,11 @@ SQL;
                 $options['where'] = str_replace('AND', 'OR', $options['where']);
 
                 foreach ($options['extra'] as $extra) {
-                    $options['where'] = str_replace($extra['field'], 'fv.field_id = '.$extra['id'].' AND fvo.option_value', $options['where']);
+                    $options['where'] = str_replace(
+                        $extra['field'],
+                        'fv.field_id = '.$extra['id'].' AND fvo.option_value',
+                        $options['where']
+                    );
                     $extraFieldTables = "$tbl_session_field_values fv, $tbl_session_field_options fvo, ";
                 }
             }
@@ -8053,7 +8118,6 @@ SQL;
             $access_url_id = api_get_current_access_url_id();
             if ($access_url_id != -1) {
                 $where .= " AND ar.access_url_id = $access_url_id ";
-
                 $query_rows = "SELECT count(*) as total_rows
                                FROM $tbl_session s
                                LEFT JOIN  $tbl_session_category sc
@@ -8215,7 +8279,6 @@ SQL;
         // Inject extra session fields
         $session_field = new ExtraFieldModel('session');
         $rules = $session_field->getRules($columns, $column_model);
-
         $column_model[] = [
             'name' => 'actions',
             'index' => 'actions',
@@ -8595,7 +8658,7 @@ SQL;
      */
     public static function getAdminPath($id)
     {
-        $id = intval($id);
+        $id = (int) $id;
         $session = self::fetch($id);
         if (empty($session)) {
             return false;
@@ -8615,7 +8678,7 @@ SQL;
      */
     public static function getPath($id, $courseId = 0)
     {
-        $id = intval($id);
+        $id = (int) $id;
         $session = self::fetch($id);
         if (empty($session)) {
             return false;
@@ -8673,12 +8736,13 @@ SQL;
      * Return an associative array 'id_course' => [id_session1, id_session2...]
      * where course id_course is in sessions id_session1, id_session2.
      *
-     * @param $userId
+     * @param int $userId
      *
      * @return array
      */
     public static function getCoursesForCourseSessionCoach($userId)
     {
+        $userId = (int) $userId;
         $listResCourseSession = [];
         $tblCourse = Database::get_main_table(TABLE_MAIN_COURSE);
         $tblSessionRelCourseRelUser = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
@@ -8688,7 +8752,7 @@ SQL;
                 LEFT JOIN $tblCourse c
                 ON c.id = srcru.c_id
                 WHERE
-                    srcru.user_id =".intval($userId)." AND
+                    srcru.user_id = $userId AND
                     srcru.status = 2";
 
         $res = Database::query($sql);
@@ -8715,12 +8779,13 @@ SQL;
      */
     public static function getCoursesForMainSessionCoach($userId)
     {
+        $userId = (int) $userId;
         $listResCourseSession = [];
         $tblSession = Database::get_main_table(TABLE_MAIN_SESSION);
 
         // list of SESSION where user is session coach
         $sql = "SELECT id FROM $tblSession
-                WHERE id_coach = ".intval($userId);
+                WHERE id_coach = ".$userId;
         $res = Database::query($sql);
 
         while ($data = Database::fetch_assoc($res)) {
@@ -8824,8 +8889,8 @@ SQL;
                 }
                 $listSessionInfo = self::fetch($sessionId);
                 $listSessionIdName = [
-                    "sessionId" => $sessionId,
-                    "sessionName" => $listSessionInfo['name'],
+                    'sessionId' => $sessionId,
+                    'sessionName' => $listSessionInfo['name'],
                 ];
                 $listCat[$catId]['sessionList'][] = $listSessionIdName;
             }
@@ -8979,7 +9044,8 @@ SQL;
                 if ($response) {
                     $urlToRedirect = api_get_path(WEB_CODE_PATH).'session/index.php?session_id='.$sessionId;
                     if (!empty($onlyOneCourseSessionToRedirect)) {
-                        $urlToRedirect = api_get_path(WEB_PATH).'courses/'.$onlyOneCourseSessionToRedirect.'/index.php?id_session='.$sessionId;
+                        $urlToRedirect = api_get_path(WEB_PATH).
+                            'courses/'.$onlyOneCourseSessionToRedirect.'/index.php?id_session='.$sessionId;
                     }
 
                     header('Location: '.$urlToRedirect);

+ 76 - 5
main/inc/lib/skill.lib.php

@@ -3,9 +3,9 @@
 
 use Chamilo\CoreBundle\Component\Utils\ChamiloApi;
 use Chamilo\CoreBundle\Entity\Skill as SkillEntity;
+use Chamilo\CoreBundle\Entity\SkillRelUser as SkillRelUserEntity;
 use Chamilo\SkillBundle\Entity\SkillRelCourse;
 use Chamilo\SkillBundle\Entity\SkillRelItem;
-use Chamilo\UserBundle\Repository\UserRepository;
 use Chamilo\UserBundle\Entity\User;
 use Fhaculty\Graph\Graph;
 use Fhaculty\Graph\Vertex;
@@ -13,6 +13,8 @@ use Fhaculty\Graph\Vertex;
 /**
  * Class SkillProfile.
  *
+ * @todo break the file in different classes
+ *
  * @package chamilo.library
  */
 class SkillProfile extends Model
@@ -615,6 +617,51 @@ class SkillRelUser extends Model
             'where' => $where,
         ], 'first');
     }
+
+    /**
+     * Get the URL for the issue.
+     *
+     * @param SkillRelUserEntity $skillIssue
+     *
+     * @return string
+     */
+    public static function getIssueUrl(SkillRelUserEntity $skillIssue)
+    {
+        return api_get_path(WEB_PATH)."badge/{$skillIssue->getId()}";
+    }
+
+    /**
+     * Get the URL for the All issues page.
+     *
+     * @param SkillRelUserEntity $skillIssue
+     *
+     * @return string
+     */
+    public static function getIssueUrlAll(SkillRelUserEntity $skillIssue)
+    {
+        return api_get_path(WEB_PATH)."skill/{$skillIssue->getSkill()->getId()}/user/{$skillIssue->getUser()->getId()}";
+    }
+
+    /**
+     * Get the URL for the assertion.
+     *
+     * @param SkillRelUserEntity $skillIssue
+     *
+     * @return string
+     */
+    public static function getAssertionUrl(SkillRelUserEntity $skillIssue)
+    {
+        $url = api_get_path(WEB_CODE_PATH).'badge/assertion.php?';
+
+        $url .= http_build_query([
+            'user' => $skillIssue->getUser()->getId(),
+            'skill' => $skillIssue->getSkill()->getId(),
+            'course' => $skillIssue->getCourse() ? $skillIssue->getCourse()->getId() : 0,
+            'session' => $skillIssue->getSession() ? $skillIssue->getSession()->getId() : 0,
+        ]);
+
+        return $url;
+    }
 }
 
 /**
@@ -2095,9 +2142,7 @@ class Skill extends Model
             return true;
         }
 
-        $entityManager = Database::getManager();
-        /** @var UserRepository $userRepo */
-        $userRepo = $entityManager->getRepository('ChamiloUserBundle:User');
+        $userRepo = UserManager::getRepository();
         $fromUserStatus = $fromUser->getStatus();
 
         switch ($fromUserStatus) {
@@ -2548,7 +2593,8 @@ class Skill extends Model
         $courseId = api_get_course_int_id();
         $sessionId = api_get_session_id();
 
-        $url = api_get_path(WEB_AJAX_PATH).'skill.ajax.php?a=search_skills_in_course&course_id='.$courseId.'&session_id='.$sessionId;
+        $url = api_get_path(WEB_AJAX_PATH).
+            'skill.ajax.php?a=search_skills_in_course&course_id='.$courseId.'&session_id='.$sessionId;
         $form->addSelectAjax(
             'skills',
             get_lang('Skills'),
@@ -2877,4 +2923,29 @@ class Skill extends Model
 
         return true;
     }
+
+    /**
+     * Get the icon (badge image) URL.
+     *
+     * @param SkillEntity $skill
+     * @param bool        $getSmall Optional. Allow get the small image
+     *
+     * @return string
+     */
+    public static function getWebIconPath(SkillEntity $skill, $getSmall = false)
+    {
+        if ($getSmall) {
+            if (empty($skill->getIcon())) {
+                return \Display::return_icon('badges-default.png', null, null, ICON_SIZE_BIG, null, true);
+            }
+
+            return api_get_path(WEB_UPLOAD_PATH).'badges/'.sha1($skill->getName()).'-small.png';
+        }
+
+        if (empty($skill->getIcon())) {
+            return \Display::return_icon('badges-default.png', null, null, ICON_SIZE_HUGE, null, true);
+        }
+
+        return api_get_path(WEB_UPLOAD_PATH)."badges/{$skill->getIcon()}";
+    }
 }

+ 15 - 3
main/inc/lib/social.lib.php

@@ -925,6 +925,7 @@ class SocialManager extends UserManager
         $sharedProfileIcon = Display::return_icon('sn-profile.png', get_lang('ViewMySharedProfile'));
         $searchIcon = Display::return_icon('sn-search.png', get_lang('Search'), null, ICON_SIZE_SMALL);
         $portfolioIcon = Display::return_icon('wiki_task.png', get_lang('Portfolio'));
+        $personalDataIcon = Display::return_icon('database.png', get_lang('PersonalDataReport'));
 
         $html = '';
         $active = null;
@@ -1012,7 +1013,18 @@ class SocialManager extends UserManager
                     </li>
                 ';
             }
-            $links .= '</ul>';
+
+            if (api_get_configuration_value('enable_gdpr')) {
+                $active = $show == 'personal-data' ? 'active' : null;
+                $personalData = '
+                    <li class="personal-data-icon '.$active.'">
+                        <a href="'.api_get_path(WEB_CODE_PATH).'social/personal_data.php">
+                            '.$personalDataIcon.' '.get_lang('PersonalDataReport').'
+                        </a>
+                    </li>';
+                $links .= $personalData;
+                $links .= '</ul>';
+            }
 
             $html .= Display::panelCollapse(
                 get_lang('SocialNetwork'),
@@ -1904,7 +1916,7 @@ class SocialManager extends UserManager
         }
 
         $currentUserId = api_get_user_id();
-        $userId = intval($userId);
+        $userId = (int) $userId;
         $userRelationType = 0;
 
         $socialAvatarBlock = self::show_social_avatar_block(
@@ -2057,7 +2069,7 @@ class SocialManager extends UserManager
                     $name_user = api_get_person_name($friend['firstName'], $friend['lastName']);
                     $user_info_friend = api_get_user_info($friend['friend_user_id'], true);
 
-                    if (!empty($user_info_friend['user_is_online'])) {
+                    if (!empty($user_info_friend['user_is_online_in_chat'])) {
                         $statusIcon = Display::return_icon('statusonline.png', get_lang('Online'));
                         $status = 1;
                     } else {

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

@@ -675,7 +675,11 @@ class SortableTable extends HTML_Table
             $result[] = '<input type="hidden" name="'.$key.'" value="'.$value.'"/>';
         }
         $result[] = '<select name="'.$this->param_prefix.'per_page" onchange="javascript: this.form.submit();">';
-        for ($nr = 10; $nr <= min(50, $total_number_of_items); $nr += 10) {
+        $list = [10, 20, 50, 100, 500, 1000];
+        foreach ($list as $nr) {
+            if ($total_number_of_items <= $nr) {
+                break;
+            }
             $result[] = '<option value="'.$nr.'" '.($nr == $this->per_page ? 'selected="selected"' : '').'>'.$nr.'</option>';
         }
         // @todo no limits

+ 4 - 0
main/inc/lib/template.lib.php

@@ -177,6 +177,10 @@ class Template
                 'name' => 'get_template',
                 'callable' => 'Template::findTemplateFilePath',
             ],
+            [
+                'name' => 'date_to_time_ago',
+                'callable' => 'Display::dateToStringAgoAndLongDate',
+            ],
         ];
 
         foreach ($filters as $filter) {

+ 130 - 84
main/inc/lib/tracking.lib.php

@@ -135,7 +135,7 @@ class Tracking
      * @param string $type            classic or simple
      * @param bool   $allowExtend     Optional. Allow or not extend te results
      *
-     * @return null|string
+     * @return string
      */
     public static function getLpStats(
         $user_id,
@@ -153,15 +153,14 @@ class Tracking
         $allowExtend = true
     ) {
         if (empty($courseInfo) || empty($lp_id)) {
-            return null;
+            return '';
         }
 
         $hideTime = api_get_configuration_value('hide_lp_time');
-
-        $lp_id = intval($lp_id);
-        $lp_item_id = intval($lp_item_id);
-        $user_id = intval($user_id);
-        $session_id = intval($session_id);
+        $lp_id = (int) $lp_id;
+        $lp_item_id = (int) $lp_item_id;
+        $user_id = (int) $user_id;
+        $session_id = (int) $session_id;
         $origin = Security::remove_XSS($origin);
         $list = learnpath::get_flat_ordered_items_list($lp_id, 0, $courseInfo['real_id']);
         $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
@@ -170,7 +169,7 @@ class Tracking
         $session_condition = api_get_session_condition($session_id);
 
         // Extend all button
-        $output = null;
+        $output = '';
         $extend_all = 0;
         if ($origin == 'tracking') {
             $url_suffix = '&session_id='.$session_id.'&course='.$courseCode.'&student_id='.$user_id.'&lp_id='.$lp_id.'&origin='.$origin;
@@ -294,7 +293,7 @@ class Tracking
                     v.view_count as mycount,
                     iv.score as myscore,
                     iv.total_time as mytime,
-                    i.id as myid,
+                    i.iid as myid,
                     i.lp_id as mylpid,
                     iv.lp_view_id as mylpviewid,
                     i.title as mytitle,
@@ -306,12 +305,12 @@ class Tracking
                     path
                 FROM $TBL_LP_ITEM as i
                 INNER JOIN $TBL_LP_ITEM_VIEW as iv
-                ON (i.id = iv.lp_item_id AND i.c_id = iv.c_id)
+                ON (i.iid = iv.lp_item_id AND i.c_id = iv.c_id)
                 INNER JOIN $TBL_LP_VIEW as v
                 ON (iv.lp_view_id = v.id AND v.c_id = iv.c_id)
                 WHERE
                     v.c_id = $course_id AND
-                    i.id = $my_item_id AND
+                    i.iid = $my_item_id AND
                     i.lp_id = $lp_id  AND
                     v.user_id = $user_id AND
                     v.session_id = $session_id
@@ -329,7 +328,6 @@ class Tracking
                     if ($row['item_type'] == 'quiz') {
                         // Check results_disabled in quiz table.
                         $my_path = Database::escape_string($row['path']);
-
                         $sql = "SELECT results_disabled
                                 FROM $TBL_QUIZ
                                 WHERE
@@ -346,12 +344,10 @@ class Tracking
                     }
 
                     // If there are several attempts, and the link to extend has been clicked, show each attempt...
+                    $oddclass = 'row_even';
                     if (($counter % 2) == 0) {
                         $oddclass = 'row_odd';
-                    } else {
-                        $oddclass = 'row_even';
                     }
-
                     $extend_link = '';
                     if (!empty($inter_num)) {
                         $extend_link = Display::url(
@@ -431,10 +427,9 @@ class Tracking
                             }
                         }
 
+                        $oddclass = 'row_even';
                         if (($counter % 2) == 0) {
                             $oddclass = 'row_odd';
-                        } else {
-                            $oddclass = 'row_even';
                         }
 
                         $lesson_status = $row['mystatus'];
@@ -544,10 +539,9 @@ class Tracking
                         if ($extend_this_attempt || $extend_all) {
                             $list1 = learnpath::get_iv_interactions_array($row['iv_id']);
                             foreach ($list1 as $id => $interaction) {
+                                $oddclass = 'row_even';
                                 if (($counter % 2) == 0) {
                                     $oddclass = 'row_odd';
-                                } else {
-                                    $oddclass = 'row_even';
                                 }
                                 $student_response = urldecode($interaction['student_response']);
                                 $content_student_response = explode('__|', $student_response);
@@ -650,10 +644,9 @@ class Tracking
                         }
                     }
 
+                    $oddclass = 'row_even';
                     if (($counter % 2) == 0) {
                         $oddclass = 'row_odd';
-                    } else {
-                        $oddclass = 'row_even';
                     }
 
                     $extend_link = '';
@@ -671,6 +664,7 @@ class Tracking
                     while ($tmp_row = Database::fetch_array($result)) {
                         $subtotal_time += $tmp_row['mytime'];
                     }
+
                     $title = $row['mytitle'];
                     // Selecting the exe_id from stats attempts tables in order to look the max score value.
                     $sql = 'SELECT * FROM '.$tbl_stats_exercices.'
@@ -1001,7 +995,7 @@ class Tracking
                             $sql = "SELECT path FROM $TBL_LP_ITEM
                                     WHERE
                                         c_id = $course_id AND
-                                        id = '$lp_item_id' AND
+                                        iid = '$lp_item_id' AND
                                         lp_id = '$lp_id'";
                             $res_path = Database::query($sql);
                             $row_path = Database::fetch_array($res_path);
@@ -1170,10 +1164,9 @@ class Tracking
         }
         $progress = learnpath::getProgress($lp_id, $user_id, $course_id, $session_id);
 
+        $oddclass = 'row_even';
         if (($counter % 2) == 0) {
             $oddclass = 'row_odd';
-        } else {
-            $oddclass = 'row_even';
         }
 
         $action = null;
@@ -1625,18 +1618,18 @@ class Tracking
         $courseId,
         $session_id = 0
     ) {
-        $courseId = intval($courseId);
+        $courseId = (int) $courseId;
 
         if (empty($courseId) || empty($user_id)) {
             return 0;
         }
 
-        $session_id = intval($session_id);
+        $session_id = (int) $session_id;
         if (is_array($user_id)) {
             $user_id = array_map('intval', $user_id);
             $conditionUser = " AND user_id IN (".implode(',', $user_id).") ";
         } else {
-            $user_id = intval($user_id);
+            $user_id = (int) $user_id;
             $conditionUser = " AND user_id = $user_id ";
         }
 
@@ -1845,10 +1838,9 @@ class Tracking
         $convert_date = true
     ) {
         // protect data
-        $student_id = intval($student_id);
+        $student_id = (int) $student_id;
+        $session_id = (int) $session_id;
         $courseId = $courseInfo['real_id'];
-        $session_id = intval($session_id);
-
         $tbl_track_e_access = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ACCESS);
         $sql = 'SELECT access_date
                 FROM '.$tbl_track_e_access.'
@@ -2577,7 +2569,7 @@ class Tracking
                 if (!empty($lpList)) {
                     /** @var $lp */
                     foreach ($lpList as $lpId => $lp) {
-                        $lpIdList[] = $lpId;
+                        $lpIdList[] = $lp['lp_old_id'];
                     }
                 }
             }
@@ -2609,29 +2601,30 @@ class Tracking
         }
 
         // Fill with lp ids
+        $newProgress = [];
         if (!empty($lpIdList)) {
             foreach ($lpIdList as $lpId) {
-                if (!isset($progress[$lpId])) {
-                    $progress[$lpId] = 0;
+                if (isset($progress[$lpId])) {
+                    $newProgress[] = $progress[$lpId];
                 }
             }
+            $total = count($lpIdList);
+        } else {
+            $newProgress = $progress;
+            $total = count($newProgress);
         }
 
-        if (!empty($progress)) {
-            $sum = array_sum($progress);
-            $average = 0;
-            if (!empty($lpIdList)) {
-                $average = $sum / count($lpIdList);
-            }
-        } else {
-            $average = 0;
-            $sum = 0;
+        $average = 0;
+        $sum = 0;
+        if (!empty($newProgress)) {
+            $sum = array_sum($newProgress);
+            $average = $sum / $total;
         }
 
         if ($returnArray) {
             return [
                 $sum,
-                count($lpIdList),
+                $total,
             ];
         }
 
@@ -3226,9 +3219,9 @@ class Tracking
                             $viewCondition
                         ORDER BY iv.view_count DESC ";
 
-                        $result = Database::query($sql);
-                        if (Database::num_rows($result)) {
-                            $row = Database::fetch_array($result);
+                        $resultRow = Database::query($sql);
+                        if (Database::num_rows($resultRow)) {
+                            $row = Database::fetch_array($resultRow);
                             $totalTimeInLpItemView = $row['mytime'];
                             $lpItemViewId = $row['iid'];
 
@@ -3306,10 +3299,10 @@ class Tracking
         $session_id = 0
     ) {
         $course = api_get_course_info($course_code);
-        $student_id = intval($student_id);
-        $lp_id = intval($lp_id);
-        $last_time = 0;
-        $session_id = intval($session_id);
+        $student_id = (int) $student_id;
+        $lp_id = (int) $lp_id;
+        $session_id = (int) $session_id;
+        $lastTime = 0;
 
         if (!empty($course)) {
             $course_id = $course['real_id'];
@@ -3321,17 +3314,17 @@ class Tracking
             // database (and if no list was given, get them all)
             $sql = "SELECT id FROM $lp_table 
                     WHERE c_id = $course_id AND id = $lp_id ";
-            $res_row_lp = Database::query($sql);
-            $count_row_lp = Database::num_rows($res_row_lp);
+            $row = Database::query($sql);
+            $count = Database::num_rows($row);
 
             // calculates last connection time
-            if ($count_row_lp > 0) {
+            if ($count > 0) {
                 $sql = 'SELECT MAX(start_time)
                         FROM '.$t_lpiv.' AS item_view
                         INNER JOIN '.$t_lpv.' AS view
                         ON (item_view.lp_view_id = view.id AND item_view.c_id = view.c_id)
                         WHERE
-                            total_time > 0 AND
+                            status != "not attempted" AND
                             item_view.c_id = '.$course_id.' AND
                             view.c_id = '.$course_id.' AND
                             view.lp_id = '.$lp_id.' AND 
@@ -3339,12 +3332,12 @@ class Tracking
                             view.session_id = '.$session_id;
                 $rs = Database::query($sql);
                 if (Database::num_rows($rs) > 0) {
-                    $last_time = Database::result($rs, 0, 0);
+                    $lastTime = Database::result($rs, 0, 0);
                 }
             }
         }
 
-        return $last_time;
+        return $lastTime;
     }
 
     /**
@@ -3394,7 +3387,7 @@ class Tracking
                     INNER JOIN $tbl_session_user sru
                     ON (srcru.user_id = sru.user_id AND srcru.session_id = sru.session_id)
                     WHERE
-                        sru.relation_type<>".SESSION_RELATION_TYPE_RRHH." AND
+                        sru.relation_type <> ".SESSION_RELATION_TYPE_RRHH." AND
                         srcru.c_id = '$courseId' AND
                         srcru.session_id = '$id_session'";
 
@@ -3516,7 +3509,6 @@ class Tracking
         $tbl_session = Database::get_main_table(TABLE_MAIN_SESSION);
 
         // At first, courses where $coach_id is coach of the course //
-
         $sql = 'SELECT 1 FROM '.$tbl_session_course_user.'
                 WHERE user_id='.$coach_id.' AND status=2';
         $result = Database::query($sql);
@@ -3563,7 +3555,6 @@ class Tracking
         $tbl_course_rel_access_url = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
 
         // At first, courses where $coach_id is coach of the course.
-
         $sql = 'SELECT DISTINCT c.code
                 FROM '.$tbl_session_course_user.' sc
                 INNER JOIN '.$tbl_course.' c
@@ -3903,7 +3894,6 @@ class Tracking
 
         $conditions[] = ' pub.active <> 2 ';
         $conditionToString = implode(' AND ', $conditions);
-
         $sessionCondition = api_get_session_condition($session_id, true, false, 'pub.session_id');
         $conditionToString .= $sessionCondition;
 
@@ -3923,10 +3913,9 @@ class Tracking
     /**
      * Count messages per student inside forum tool.
      *
-     * @param    int|array        Student id
-     * @param    string    Course code
-     * @param int        Session id (optional), if param $session_id is
-     *                                                               null(default) return count of messages including sessions, 0 = session is not filtered
+     * @param int|array  Student id
+     * @param string     Course code
+     * @param int        Session id if null(default) return count of messages including sessions, 0 = session is not filtered
      *
      * @return int Count of messages
      */
@@ -3997,11 +3986,10 @@ class Tracking
     /**
      * This function counts the number of post by course.
      *
-     * @param      string     Course code
-     * @param int        Session id (optional), if param $session_id is
-     *                                                               null(default) it'll return results including sessions,
-     *                                                               0 = session is not filtered
-     * @param int                                        $groupId
+     * @param string $course_code
+     * @param int    $session_id  (optional), if is null(default) it'll return results including sessions,
+     *                            0 = session is not filtered
+     * @param int    $groupId
      *
      * @return int The number of post by course
      */
@@ -4633,15 +4621,17 @@ class Tracking
      * @param string $extra_params
      * @param bool   $show_courses
      * @param bool   $showAllSessions
+     * @param bool   $returnArray
      *
-     * @return string
+     * @return string|array
      */
     public static function show_user_progress(
         $user_id,
         $session_id = 0,
         $extra_params = '',
         $show_courses = true,
-        $showAllSessions = true
+        $showAllSessions = true,
+        $returnArray = false
     ) {
         $tbl_course = Database::get_main_table(TABLE_MAIN_COURSE);
         $tbl_session = Database::get_main_table(TABLE_MAIN_SESSION);
@@ -4671,12 +4661,12 @@ class Tracking
             $trackingColumns = $trackingColumnsConfig;
         }
 
-        $user_id = intval($user_id);
-        $session_id = intval($session_id);
+        $user_id = (int) $user_id;
+        $session_id = (int) $session_id;
         $urlId = api_get_current_access_url_id();
 
         if (api_is_multiple_url_enabled()) {
-            $sql = "SELECT c.code, title
+            $sql = "SELECT c.id, c.code, title
                     FROM $tbl_course_user cu
                     INNER JOIN $tbl_course c
                     ON (cu.c_id = c.id)
@@ -4685,29 +4675,31 @@ class Tracking
                     WHERE
                         cu.user_id = $user_id AND
                         relation_type<> ".COURSE_RELATION_TYPE_RRHH." AND
-                        access_url_id = ".$urlId."
+                        access_url_id = $urlId
                     ORDER BY title";
         } else {
-            $sql = "SELECT c.code, title
+            $sql = "SELECT c.id, c.code, title
                     FROM $tbl_course_user u
                     INNER JOIN $tbl_course c ON (c_id = c.id)
                     WHERE
                         u.user_id= $user_id AND
-                        relation_type<>".COURSE_RELATION_TYPE_RRHH."
+                        relation_type <> ".COURSE_RELATION_TYPE_RRHH."
                     ORDER BY title";
         }
 
         $rs = Database::query($sql);
         $courses = $course_in_session = $temp_course_in_session = [];
+        $courseIdList = [];
         while ($row = Database::fetch_array($rs, 'ASSOC')) {
             $courses[$row['code']] = $row['title'];
+            $courseIdList[] = $row['id'];
         }
 
-        $orderBy = " ORDER BY name ";
+        $orderBy = ' ORDER BY name ';
         $extraInnerJoin = null;
 
         if (SessionManager::orderCourseIsEnabled() && !empty($session_id)) {
-            $orderBy = " ORDER BY s.id, position ";
+            $orderBy = ' ORDER BY s.id, position ';
             $tableSessionRelCourse = Database::get_main_table(TABLE_MAIN_SESSION_COURSE);
             $extraInnerJoin = " INNER JOIN $tableSessionRelCourse src
                                 ON (cu.c_id = src.c_id AND src.session_id = $session_id) ";
@@ -4778,6 +4770,12 @@ class Tracking
             $course_in_session[$my_session_id]['name'] = $session_name;
         }
 
+        if ($returnArray) {
+            $course_in_session[0] = $courseIdList;
+
+            return $course_in_session;
+        }
+
         $html = '';
         // Course list
         if ($show_courses) {
@@ -4978,14 +4976,14 @@ class Tracking
                         // Exercise is not necessary to be visible to show results check the result_disable configuration instead
                         //$visible_return = $exercise_obj->is_visible();
                         if ($exercise_data['results_disabled'] == 0 || $exercise_data['results_disabled'] == 2) {
-                            $best_average = intval(
+                            $best_average = (int)
                                 ExerciseLib::get_best_average_score_by_exercise(
                                     $exercise_data['id'],
                                     $course_data['real_id'],
                                     $my_session_id,
                                     $user_count
                                 )
-                            );
+                            ;
 
                             $exercise_graph_list[] = $best_average;
                             $all_exercise_graph_list[] = $best_average;
@@ -5172,7 +5170,7 @@ class Tracking
 
             // Checking selected session.
             if (isset($_GET['session_id'])) {
-                $session_id_from_get = intval($_GET['session_id']);
+                $session_id_from_get = (int) $_GET['session_id'];
                 $session_data = $course_in_session[$session_id_from_get];
                 $course_list = $session_data['course_list'];
 
@@ -5410,6 +5408,13 @@ class Tracking
             }
         }
 
+        $pluginCalendar = api_get_plugin_setting('learning_calendar', 'enabled') === 'true';
+        if ($pluginCalendar) {
+            $course_in_session[0] = $courseIdList;
+            $plugin = LearningCalendarPlugin::create();
+            $html .= $plugin->getUserStatsPanel($user_id, $course_in_session);
+        }
+
         return $html;
     }
 
@@ -6085,7 +6090,6 @@ class Tracking
             );
 
             /* Do not write the chart title */
-
             /* Define the chart area */
             $myPicture->setGraphArea(5, 5, $widthSize - 5, $heightSize - 5);
 
@@ -6811,6 +6815,49 @@ class Tracking
             'html' => $courseToolInformationTotal,
         ];
     }
+
+    /**
+     * @param int $sessionId
+     *
+     * @return bool
+     */
+    public static function isAllowToTrack($sessionId)
+    {
+        $allow =
+            api_is_platform_admin(true, true) ||
+            SessionManager::user_is_general_coach(api_get_user_id(), $sessionId) ||
+            api_is_allowed_to_create_course() ||
+            api_is_course_tutor() ||
+            api_is_course_admin();
+
+        return $allow;
+    }
+
+    public function getCoursesAndSessions($userId)
+    {
+        $userId = (int) $userId;
+
+        // Get the list of sessions where the user is subscribed as student
+        $sql = 'SELECT session_id, c_id
+        FROM '.Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER).'
+        WHERE user_id='.$userId;
+        $rs = Database::query($sql);
+        $tmp_sessions = [];
+        while ($row = Database::fetch_array($rs, 'ASSOC')) {
+            $tmp_sessions[] = $row['session_id'];
+            if ($drh_can_access_all_courses) {
+                if (in_array($row['session_id'], $tmp_sessions)) {
+                    $courses_in_session[$row['session_id']][] = $row['c_id'];
+                }
+            } else {
+                if (isset($courses_in_session_by_coach[$row['session_id']])) {
+                    if (in_array($row['session_id'], $tmp_sessions)) {
+                        $courses_in_session[$row['session_id']][] = $row['c_id'];
+                    }
+                }
+            }
+        }
+    }
 }
 
 /**
@@ -7406,7 +7453,6 @@ class TrackingCourseLog
         $course_code = Database::escape_string($course_code);
         $tbl_user = Database::get_main_table(TABLE_MAIN_USER);
         $tbl_url_rel_user = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
-
         $access_url_id = api_get_current_access_url_id();
 
         // get all users data from a course for sortable with limit

+ 225 - 32
main/inc/lib/usergroup.lib.php

@@ -43,6 +43,9 @@ class UserGroup extends Model
         $this->table_course = Database::get_main_table(TABLE_MAIN_COURSE);
         $this->table_user = Database::get_main_table(TABLE_MAIN_USER);
         $this->useMultipleUrl = api_get_configuration_value('multiple_access_urls');
+        if ($this->allowTeachers()) {
+            $this->columns[] = 'author_id';
+        }
     }
 
     /**
@@ -63,6 +66,102 @@ class UserGroup extends Model
         return $row['count'];
     }
 
+    /**
+     * @param int  $id
+     * @param bool $getCount
+     *
+     * @return array|int
+     */
+    public function getUserGroupUsers($id, $getCount = false)
+    {
+        $id = (int) $id;
+
+        if ($getCount) {
+            $select = 'COUNT(u.id) count ';
+        } else {
+            $select = ' u.* ';
+        }
+
+        if ($this->useMultipleUrl) {
+            $urlId = api_get_current_access_url_id();
+            $sql = "SELECT $select
+                    FROM ".$this->usergroup_rel_user_table." u
+                    INNER JOIN ".$this->access_url_rel_usergroup." a 
+                    ON (u.user_id = a.user_id)
+                    WHERE usergroup_id = $id AND access_url_id = $urlId ";
+        } else {
+            $sql = "SELECT $select
+                    FROM ".$this->usergroup_rel_user_table." u
+                    WHERE usergroup_id = $id";
+        }
+        $result = Database::query($sql);
+        if ($getCount) {
+            if (Database::num_rows($result)) {
+                $row = Database::fetch_array($result);
+
+                return $row['count'];
+            }
+
+            return 0;
+        } else {
+            $list = [];
+            $showCalendar = api_get_plugin_setting('learning_calendar', 'enabled') === 'true';
+            $calendarPlugin = null;
+            if ($showCalendar) {
+                $calendarPlugin = LearningCalendarPlugin::create();
+            }
+            $url = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/calendar.php?';
+            while ($data = Database::fetch_array($result)) {
+                $userId = $data['user_id'];
+                $userInfo = api_get_user_info($userId);
+                $data['name'] = $userInfo['complete_name_with_username'];
+
+                if ($showCalendar) {
+                    $calendar = $calendarPlugin->getUserCalendar($userId);
+                    $data['calendar_id'] = 0;
+                    $data['calendar'] = '';
+                    if (!empty($calendar)) {
+                        $calendarInfo = $calendarPlugin->getCalendar($calendar['calendar_id']);
+                        if ($calendarInfo) {
+                            $data['calendar_id'] = $calendar['calendar_id'];
+                            $data['calendar'] = Display::url(
+                                $calendarInfo['title'],
+                                $url.'&id='.$calendar['calendar_id']
+                            );
+                        }
+                    }
+
+                    $courseAndSessionList = Tracking::show_user_progress(
+                        $userId,
+                        0,
+                        '',
+                        true,
+                        true,
+                        true
+                    );
+
+                    $stats = $calendarPlugin->getUserStats($userId, $courseAndSessionList);
+                    $evaluations = $calendarPlugin->getGradebookEvaluationListToString($userId, $courseAndSessionList);
+                    $data['gradebook_items'] = $evaluations;
+                    $totalTime = 0;
+                    foreach ($courseAndSessionList as $sessionId => $course) {
+                        foreach ($course as $courseId) {
+                            $totalTime += Tracking::get_time_spent_on_the_course($userId, $courseId, $sessionId);
+                        }
+                    }
+
+                    $data['time_spent'] = api_time_to_hms($totalTime);
+                    $data['lp_day_completed'] = $stats['completed'];
+                    $data['days_diff'] = $stats['completed'] - $stats['user_event_count'];
+                }
+                $data['id'] = $data['user_id'];
+                $list[] = $data;
+            }
+
+            return $list;
+        }
+    }
+
     /**
      * @param int $type
      *
@@ -70,13 +169,22 @@ class UserGroup extends Model
      */
     public function get_count($type = -1)
     {
+        $authorCondition = '';
+        if ($this->allowTeachers()) {
+            if (!api_is_platform_admin()) {
+                $userId = api_get_user_id();
+                $authorCondition = " AND author_id = $userId";
+            }
+        }
+
         if ($this->useMultipleUrl) {
             $urlId = api_get_current_access_url_id();
             $sql = "SELECT count(u.id) as count FROM ".$this->table." u
                     INNER JOIN ".$this->access_url_rel_usergroup." a
-                        ON (u.id = a.usergroup_id)
-                    WHERE access_url_id = $urlId
+                    ON (u.id = a.usergroup_id)
+                    WHERE access_url_id = $urlId $authorCondition
             ";
+
             $result = Database::query($sql);
             if (Database::num_rows($result)) {
                 $row = Database::fetch_array($result);
@@ -88,13 +196,15 @@ class UserGroup extends Model
         } else {
             $typeCondition = '';
             if ($type != -1) {
-                $type = intval($type);
-                $typeCondition = " WHERE group_type = $type ";
+                $type = (int) $type;
+                $typeCondition = " AND group_type = $type ";
             }
 
             $sql = "SELECT count(a.id) as count
                     FROM {$this->table} a
+                    WHERE 1 =1 
                     $typeCondition
+                    $authorCondition
             ";
             $result = Database::query($sql);
             if (Database::num_rows($result)) {
@@ -114,7 +224,7 @@ class UserGroup extends Model
     public function getUserGroupByCourseWithDataCount($course_id, $type = -1)
     {
         if ($this->useMultipleUrl) {
-            $course_id = intval($course_id);
+            $course_id = (int) $course_id;
             $urlId = api_get_current_access_url_id();
             $sql = "SELECT count(c.usergroup_id) as count
                     FROM {$this->usergroup_rel_course_table} c
@@ -133,7 +243,7 @@ class UserGroup extends Model
         } else {
             $typeCondition = '';
             if ($type != -1) {
-                $type = intval($type);
+                $type = (int) $type;
                 $typeCondition = " AND group_type = $type ";
             }
             $sql = "SELECT count(c.usergroup_id) as count
@@ -175,24 +285,48 @@ class UserGroup extends Model
     /**
      * Displays the title + grid.
      */
-    public function display()
+    public function returnGrid()
     {
         // action links
-        echo '<div class="actions">';
-        echo '<a href="../admin/index.php">'.
-            Display::return_icon('back.png', get_lang('BackTo').' '.get_lang('PlatformAdmin'), '', '32').
-            '</a>';
-        echo '<a href="'.api_get_self().'?action=add">'.
-            Display::return_icon('new_class.png', get_lang('AddClasses'), '', '32').
+        $html = '<div class="actions">';
+        if (api_is_platform_admin()) {
+            $html .= '<a href="../admin/index.php">'.
+                Display::return_icon(
+                    'back.png',
+                    get_lang('BackTo').' '.get_lang('PlatformAdmin'),
+                    '',
+                    ICON_SIZE_MEDIUM
+                ).
+                '</a>';
+        }
+
+        $html .= '<a href="'.api_get_self().'?action=add">'.
+            Display::return_icon('new_class.png', get_lang('AddClasses'), '', ICON_SIZE_MEDIUM).
             '</a>';
-        echo Display::url(
+        $html .= Display::url(
             Display::return_icon('import_csv.png', get_lang('Import'), [], ICON_SIZE_MEDIUM),
             'usergroup_import.php'
         );
-        echo Display::url(
+        $html .= Display::url(
             Display::return_icon('export_csv.png', get_lang('Export'), [], ICON_SIZE_MEDIUM),
             'usergroup_export.php'
         );
+        $html .= '</div>';
+        $html .= Display::grid_html('usergroups');
+
+        return $html;
+    }
+
+    /**
+     * Displays the title + grid.
+     */
+    public function displayToolBarUserGroupUsers()
+    {
+        // action links
+        echo '<div class="actions">';
+        echo '<a href="../admin/usergroups.php">'.
+            Display::return_icon('back.png', get_lang('BackTo').' '.get_lang('PlatformAdmin'), '', '32').
+            '</a>';
         echo '</div>';
         echo Display::grid_html('usergroups');
     }
@@ -300,7 +434,7 @@ class UserGroup extends Model
 
         $typeCondition = '';
         if ($type != -1) {
-            $type = intval($type);
+            $type = (int) $type;
             $typeCondition = " AND group_type = $type ";
         }
 
@@ -341,7 +475,7 @@ class UserGroup extends Model
     {
         $course_id = null;
         if (isset($options['course_id'])) {
-            $course_id = intval($options['course_id']);
+            $course_id = (int) $options['course_id'];
             unset($options['course_id']);
         }
 
@@ -351,7 +485,7 @@ class UserGroup extends Model
 
         $typeCondition = '';
         if ($type != -1) {
-            $type = intval($type);
+            $type = (int) $type;
             $typeCondition = " AND group_type = $type ";
         }
 
@@ -517,6 +651,7 @@ class UserGroup extends Model
             $this->usergroup_rel_user_table,
             $conditions
         );
+
         $array = [];
         if (!empty($results)) {
             foreach ($results as $row) {
@@ -620,10 +755,10 @@ class UserGroup extends Model
         if ($this->useMultipleUrl) {
             $urlId = api_get_current_access_url_id();
             $from = $this->usergroup_rel_user_table." u
-                    INNER JOIN {$this->access_url_rel_usergroup} a ON (a.usergroup_id AND u.usergroup_id)";
+                    INNER JOIN {$this->access_url_rel_usergroup} a ON (a.usergroup_id = u.usergroup_id)";
             $where = ['where' => ['user_id = ? AND access_url_id = ? ' => [$userId, $urlId]]];
         } else {
-            $from = $this->usergroup_rel_user_table." u ";
+            $from = $this->usergroup_rel_user_table.' u ';
             $where = ['where' => ['user_id = ?' => $userId]];
         }
 
@@ -926,11 +1061,20 @@ class UserGroup extends Model
         } else {
             $sql = "SELECT * FROM $this->table WHERE name = '".$name."'";
         }
+
         $res = Database::query($sql);
 
         return Database::num_rows($res) != 0;
     }
 
+    /**
+     * @return bool
+     */
+    public function allowTeachers()
+    {
+        return api_get_configuration_value('allow_teachers_to_classes') === true;
+    }
+
     /**
      * @param int $sidx
      * @param int $sord
@@ -943,17 +1087,24 @@ class UserGroup extends Model
     {
         $sord = in_array(strtolower($sord), ['asc', 'desc']) ? $sord : 'desc';
 
-        $start = intval($start);
-        $limit = intval($limit);
+        $start = (int) $start;
+        $limit = (int) $limit;
         if ($this->useMultipleUrl) {
             $urlId = api_get_current_access_url_id();
             $from = $this->table." u INNER JOIN {$this->access_url_rel_usergroup} a ON (u.id = a.usergroup_id)";
             $where = [' access_url_id = ?' => $urlId];
         } else {
-            $from = $this->table." u ";
+            $from = $this->table.' u ';
             $where = [];
         }
 
+        if ($this->allowTeachers()) {
+            if (!api_is_platform_admin()) {
+                $userId = api_get_user_id();
+                $where = [' author_id = ?' => $userId];
+            }
+        }
+
         $result = Database::select(
             'u.*',
             $from,
@@ -985,7 +1136,10 @@ class UserGroup extends Model
                         ];
                         break;
                 }
-                $group['users'] = count($this->get_users_by_usergroup($group['id'], $roles));
+                $group['users'] = Display::url(
+                    count($this->get_users_by_usergroup($group['id'], $roles)),
+                    api_get_path(WEB_CODE_PATH).'admin/usergroup_users.php?id='.$group['id']
+                );
                 $new_result[] = $group;
             }
             $result = $new_result;
@@ -1014,15 +1168,21 @@ class UserGroup extends Model
             $from = $this->table." u INNER JOIN {$this->access_url_rel_usergroup} a
                     ON (u.id = a.usergroup_id)";
             $options = ['where' => ['access_url_id = ? ' => $urlId]];
+            if ($this->allowTeachers()) {
+                $options['where'] = [' author_id = ? ' => api_get_user_id()];
+            }
             $classes = Database::select('a.id, name, description', $from, $options);
         } else {
+            if ($this->allowTeachers()) {
+                $options['where'] = [' author_id = ? ' => api_get_user_id()];
+            }
             $classes = Database::select('id, name, description', $this->table, $options);
         }
 
         $result = [];
         if (!empty($classes)) {
             foreach ($classes as $data) {
-                $users = self::getUserListByUserGroup($data['id']);
+                $users = $this->getUserListByUserGroup($data['id']);
                 $userToString = null;
                 if (!empty($users)) {
                     $userNameList = [];
@@ -1094,6 +1254,9 @@ class UserGroup extends Model
 
         $groupExists = $this->usergroup_exists(trim($params['name']));
         if ($groupExists == false) {
+            if ($this->allowTeachers()) {
+                $params['author_id'] = api_get_user_id();
+            }
             $id = parent::save($params, $show_query);
             if ($id) {
                 if ($this->useMultipleUrl) {
@@ -1616,7 +1779,7 @@ class UserGroup extends Model
             return $anonymous ? ['dir' => $base.'img/', 'file' => 'unknown.jpg'] : ['dir' => '', 'file' => ''];
         }
 
-        $id = intval($id);
+        $id = (int) $id;
         $group_table = Database::get_main_table(TABLE_USERGROUP);
         $sql = "SELECT picture FROM $group_table WHERE id = ".$id;
         $res = Database::query($sql);
@@ -1668,7 +1831,7 @@ class UserGroup extends Model
      */
     public function setGroupType($type)
     {
-        $this->groupType = intval($type);
+        $this->groupType = (int) $type;
     }
 
     /**
@@ -1845,18 +2008,25 @@ class UserGroup extends Model
      *
      * @author Julio Montoya
      *
-     * @param int $user_id
-     * @param int $group_id
+     * @param int $userId
+     * @param int $groupId
      *
      * @return bool true if success
      * */
-    public function delete_user_rel_group($user_id, $group_id)
+    public function delete_user_rel_group($userId, $groupId)
     {
+        $userId = (int) $userId;
+        $groupId = (int) $groupId;
+        if (empty($userId) || empty($groupId)) {
+            return false;
+        }
+
         $table = $this->usergroup_rel_user_table;
         $sql = "DELETE FROM $table
                 WHERE
-                    user_id = ".intval($user_id)." AND
-                    usergroup_id = ".intval($group_id)."  ";
+                    user_id = $userId AND
+                    usergroup_id = $groupId";
+
         $result = Database::query($sql);
 
         return $result;
@@ -2610,4 +2780,27 @@ class UserGroup extends Model
     {
         return $groupInfo['allow_members_leave_group'] == 1 ? true : false;
     }
+
+    /**
+     * Check permissions and blocks the page.
+     *
+     * @param array $userGroupInfo
+     */
+    public function protectScript($userGroupInfo = [])
+    {
+        api_block_anonymous_users();
+
+        if (!api_is_platform_admin()) {
+            if ($this->allowTeachers() && api_is_teacher()) {
+                if (!empty($userGroupInfo)) {
+                    if ($userGroupInfo['author_id'] != api_get_user_id()) {
+                        api_not_allowed(true);
+                    }
+                }
+            } else {
+                api_protect_admin_script(true);
+                api_protect_limit_for_session_admin();
+            }
+        }
+    }
 }

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 253 - 174
main/inc/lib/usermanager.lib.php


+ 31 - 24
main/inc/lib/userportal.lib.php

@@ -624,7 +624,7 @@ class IndexManager
                         $courses_list_string .= '<a href="'.$web_course_path.$course['directory'].'/">'.$course['title'].'</a><br />';
                         $course_details = [];
                         if (api_get_setting('display_coursecode_in_courselist') === 'true') {
-                            $course_details[] = $course['visual_code'];
+                            $course_details[] = '('.$course['visual_code'].')';
                         }
                         if (api_get_setting('display_teacher_in_courselist') === 'true') {
                             $course_details[] = CourseManager::getTeacherListFromCourseCodeToString($course['code']);
@@ -669,7 +669,7 @@ class IndexManager
                     }
                     $course_details = [];
                     if (api_get_setting('display_coursecode_in_courselist') == 'true') {
-                        $course_details[] = $course['visual_code'];
+                        $course_details[] = '('.$course['visual_code'].')';
                     }
                     if (api_get_setting('display_teacher_in_courselist') === 'true') {
                         if (!empty($course['tutor_name'])) {
@@ -783,7 +783,7 @@ class IndexManager
      *
      * @return string
      */
-    public function show_right_block(
+    public function showRightBlock(
         $title,
         $content,
         $id = '',
@@ -843,7 +843,7 @@ class IndexManager
                 <button class="btn btn-default" type="submit" name="submit" value="'.$search_btn.'" />'.
                 $search_btn.' </button>
                 </div></form>';
-            $html .= self::show_right_block(get_lang('Search'), $search_content, 'search_block');
+            $html .= $this->showRightBlock(get_lang('Search'), $search_content, 'search_block');
         }
 
         return $html;
@@ -852,15 +852,30 @@ class IndexManager
     /**
      * @return string
      */
-    public function return_classes_block()
+    public function returnClassesBlock()
     {
         if (api_get_setting('show_groups_to_users') !== 'true') {
             return '';
         }
 
+        $items = [];
+
         $usergroup = new UserGroup();
+        if (api_is_platform_admin()) {
+            $items[] = [
+                'link' => api_get_path(WEB_CODE_PATH).'admin/usergroups.php?action=add',
+                'title' => get_lang('AddClasses'),
+            ];
+        } else {
+            if (api_is_teacher() && $usergroup->allowTeachers()) {
+                $items[] = [
+                    'link' => api_get_path(WEB_CODE_PATH).'admin/usergroups.php',
+                    'title' => get_lang('ClassList'),
+                ];
+            }
+        }
+
         $usergroup_list = $usergroup->get_usergroup_by_user(api_get_user_id());
-        $items = [];
         if (!empty($usergroup_list)) {
             foreach ($usergroup_list as $group_id) {
                 $data = $usergroup->get($group_id);
@@ -870,14 +885,8 @@ class IndexManager
                 ];
             }
         }
-        if (api_is_platform_admin()) {
-            $items[] = [
-                'link' => api_get_path(WEB_CODE_PATH).'admin/usergroups.php?action=add',
-                'title' => get_lang('AddClasses'),
-            ];
-        }
 
-        $html = self::show_right_block(
+        $html = $this->showRightBlock(
             get_lang('Classes'),
             self::returnRightBlockItems($items),
             'classes_block'
@@ -904,7 +913,7 @@ class IndexManager
                 <img class="img-circle" title="'.get_lang('EditProfile').'" src="'.$userPicture.'"></a>';
             }
 
-            $html = self::show_right_block(
+            $html = $this->showRightBlock(
                 null,
                 $content,
                 'user_image_block',
@@ -929,7 +938,7 @@ class IndexManager
         $items = [];
         $userGroup = new UserGroup();
         //  @todo Add a platform setting to add the user image.
-        if (api_get_setting('allow_message_tool') == 'true') {
+        if (api_get_setting('allow_message_tool') === 'true') {
             // New messages.
             $number_of_new_messages = MessageManager::getCountNewMessages();
             // New contact invitations.
@@ -1086,7 +1095,7 @@ class IndexManager
 
             if (SessionManager::allowToManageSessions()) {
                 $items[] = [
-                    'class' => 'add-course',
+                    'class' => 'add-session',
                     'icon' => Display::return_icon('session.png', get_lang('AddSession')),
                     'link' => 'main/session/session_add.php',
                     'title' => get_lang('AddSession'),
@@ -1515,7 +1524,7 @@ class IndexManager
                                 }
 
                                 if ($showAllSessions) {
-                                    if ($allowed_time < $session_now && $allowedEndTime == false) {
+                                    if ($allowed_time < $session_now && $allowedEndTime === false) {
                                         $markAsOld = true;
                                     }
                                     if ($allowed_time > $session_now && $endSessionToTms > $session_now) {
@@ -1528,7 +1537,7 @@ class IndexManager
                                 if ($session_now >= $allowed_time && $allowedEndTime) {
                                     // Read only and accessible.
                                     $atLeastOneCourseIsVisible = true;
-                                    if (api_get_setting('hide_courses_in_sessions') == 'false') {
+                                    if (api_get_setting('hide_courses_in_sessions') === 'false') {
                                         $courseUserHtml = CourseManager::get_logged_user_course_html(
                                             $course,
                                             $session_id,
@@ -1595,7 +1604,7 @@ class IndexManager
                             }
 
                             // No courses to show.
-                            if ($atLeastOneCourseIsVisible == false) {
+                            if ($atLeastOneCourseIsVisible === false) {
                                 if (empty($html_courses_session)) {
                                     continue;
                                 }
@@ -1606,8 +1615,6 @@ class IndexManager
                                     'id' => $session_id,
                                 ];
                                 $session_box = Display::getSessionTitleBox($session_id);
-                                $actions = api_get_path(WEB_CODE_PATH).
-                                    'session/resume_session.php?id_session='.$session_id;
                                 $coachId = $session_box['id_coach'];
                                 $extraFieldValue = new ExtraFieldValue('session');
                                 $imageField = $extraFieldValue->get_values_by_handler_and_field_variable(
@@ -1629,7 +1636,7 @@ class IndexManager
                                 $params['date'] = $session_box['dates'];
                                 $params['image'] = isset($imageField['value']) ? $imageField['value'] : null;
                                 $params['duration'] = isset($session_box['duration']) ? ' '.$session_box['duration'] : null;
-                                $params['edit_actions'] = $actions;
+                                $params['show_actions'] = SessionManager::cantEditSession($session_id);
                                 $params['show_description'] = $session_box['show_description'] == 1 && $portalShowDescription;
                                 $params['description'] = $session_box['description'];
                                 $params['visibility'] = $session_box['visibility'];
@@ -1764,7 +1771,7 @@ class IndexManager
                                     $sessionParams[0]['course_list_session_style'] = $coursesListSessionStyle;
                                     $sessionParams[0]['title'] = $session_box['title'];
                                     $sessionParams[0]['subtitle'] = (!empty($session_box['coach']) ? $session_box['coach'].' | ' : '').$session_box['dates'];
-                                    $sessionParams[0]['show_actions'] = api_is_platform_admin();
+                                    $sessionParams[0]['show_actions'] = SessionManager::cantEditSession($session_id);
                                     $sessionParams[0]['courses'] = $html_courses_session;
                                     $sessionParams[0]['show_simple_session_info'] = $showSimpleSessionInfo;
                                     $sessionParams[0]['coach_name'] = !empty($session_box['coach']) ? $session_box['coach'] : null;
@@ -1834,7 +1841,7 @@ class IndexManager
 
                             $this->tpl->assign('session_category', $categoryParams);
                             $sessions_with_category .= $this->tpl->fetch(
-                                "{$this->tpl->templateFolder}/user_portal/session_category.tpl"
+                                $this->tpl->get_template('user_portal/session_category.tpl')
                             );
                         }
                     }

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio